1. 程式人生 > >本機發送IP資料包

本機發送IP資料包

本地傳送IP資料包是指資料包包括:傳輸層產生的資料包、裸IP、SCTP、IGMP,TCP和網路層的介面函式是ip_queue_xmit,UDP和網路層介面函式是ip_push_pending_frames,資料包對外發送在核心要做以下幾件事情。

a、查詢下一個站點

IP層需要知道完成資料包輸出功能的是那個網路裝置,以及下一個站點的路由資訊,尋找路由的任務由ip_route_output_flow函式完成。

b、初始化IP頭

初始化ip協議頭,處理IP選項、資料包分片、處理校驗和。

c、呼叫網路過濾子系統做安全檢查,不合法就丟掉

d、更新統計資訊

linux在傳輸層實現了多種不同傳輸層協議,不同的協議不同組織資料包不同,向網路層傳送資料包的方式也不同,如圖是傳輸層與網路層介面函式關係。

傳輸層與網路層介面函式

                                                                        

從上圖看從傳輸層到網路層,傳送資料包的方式主要分為兩種

1)、資料包傳給網路層時已經對資料包的分片做了大量的預處理,留給IP層的工作很多少了,比如(TCP、SCTP)。

2)、UDP和裸IP將資料包分片的工作都留給了IP層。

函式說明:

ip_queue_xmit:傳輸層TCP協議呼叫,將資料包從傳輸層傳送到網路層,建立協議頭和IP選項,然後呼叫dst_output傳送。

ip_append_data:傳輸層中UDP協議呼叫,快取傳輸層到網路層的請求傳送的資料包緩衝區。

ip_append_page:傳輸層UDP協議呼叫,快取傳輸層到網路層的請求傳送資料面。

ip_push_pending_frames:將ip_append_data和ip_append_page建立的資料包傳送佇列傳送出去。

dst_output:資料包傳送函式,當資料包的目標地址是其他主機初始化為ip_output。

ip_build_and_send_pkt:用於TCP傳送同步回答訊息時建立IP協議頭和選項併發送。

ip_send_reply:使用者TCP傳送回答訊息和復位是建立IP協議頭和選項併發送。

對於裸IP和IGMP他們自己構造IP協議頭,所以直接呼叫dst_output,TCP協議頭管理連線並向對端傳送回答訊息和復位訊息時呼叫ip_send_reply,ip_send_reply又呼叫ip_append_data和ip_push_pending_frames來回答訊息資料包。

一、關鍵資料結構體

1.1.struct sock

linux核心實現支援各種協議棧,struct sock接頭體是通用套接字資料結構,這個結構體資料很龐大,定義在include/net/sock.h中

struct sock {
	/*
	 * Now struct inet_timewait_sock also uses sock_common, so please just
	 * don't add nothing before this first member (__sk_common) --acme
	 */
	 //套接字在網路中最小描述, 
	 //核心管理套接最重要的資訊結構體
	struct sock_common	__sk_common;
	//定義sock_common元素的別名方便訪問
#define sk_node			__sk_common.skc_node
#define sk_nulls_node		__sk_common.skc_nulls_node
#define sk_refcnt		__sk_common.skc_refcnt
#define sk_tx_queue_mapping	__sk_common.skc_tx_queue_mapping

#define sk_copy_start		__sk_common.skc_hash
#define sk_hash			__sk_common.skc_hash
#define sk_family		__sk_common.skc_family
#define sk_state		__sk_common.skc_state
#define sk_reuse		__sk_common.skc_reuse
#define sk_bound_dev_if		__sk_common.skc_bound_dev_if
#define sk_bind_node		__sk_common.skc_bind_node
#define sk_prot			__sk_common.skc_prot
#define sk_net			__sk_common.skc_net
	kmemcheck_bitfield_begin(flags);
	//tcp關閉套接字會發送RST資料包
...
}

1.2.struct inet_sock結構體

struct inet_sock繼承了通用套接字屬性(struct sock),是linux核心實現TCP/IP協議棧PF_INET協議族的套接字資料結構,struct inet_sock包含了傳送資料包大部分控制資訊:資料包目的ip、傳送網路裝置、ip選項。struct sock是struct inet_sock的第一個元素,所以他們的基礎地址是一樣的。

struct inet_sock {
	/* sk and pinet6 has to be the first two members of inet_sock */
	//通用套接字結構
	struct sock		sk;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	struct ipv6_pinfo	*pinet6;
#endif
	/* Socket demultiplex comparisons on incoming packets. */
	//目標地址
	__be32			inet_daddr;
	//本機繫結的ip地址
	__be32			inet_rcv_saddr;
	//目的埠
	__be16			inet_dport;
	//本地應用程式建立套接字的埠號
	__u16			inet_num;
	//傳送資料包源地址
	__be32			inet_saddr;
	//資料包存活時間ttl
	__s16			uc_ttl;
	__u16			cmsg_flags;
	//傳送資料包源埠
	__be16			inet_sport;
	//為分片資料包識別符號
	__u16			inet_id;
	//ip選項
	struct ip_options	*opt;
	//資料包服務型別tos
	__u8			tos;
	__u8			min_ttl;
	//組傳送存活時間ttl
	__u8			mc_ttl;
	//傳送路由最大mtu
	__u8			pmtudisc;
	__u8			recverr:1,
...
				mc_all:1;
	//組傳送網路裝置的索引號
	int			mc_index;
	//組傳送地址
	__be32			mc_addr;
	struct ip_mc_socklist	*mc_list;
	struct {
		//標誌位
		unsigned int		flags;
		//產生的分片資料段大小
		unsigned int		fragsize;
		//ip選項
		struct ip_options	*opt;
		//路由表緩衝去入口
		struct dst_entry	*dst;
		//ip資料包大小
		int			length; /* Total length of all frames */
		//傳送資料包目標地址
		__be32			addr;
		//存放tcp兩端連線資訊
		struct flowi		fl;
	} cork;
};

1.3、struct inet_sock struct sock關鍵函式

a、sk_dst_set 和__sk_dst_set函式

支援TCP協議的套機字,需要管理TCP連線,儲存目標地址使用的路由,

b、sk_dst_check和__sk_dst_check

檢查達到目標地址路由是否有效。

c、skb_set_owner_w

指定一個數據包所屬的套接字,也就是設定skb->sk資料域。

d、sock_alloc_send_skb和sock_wmalloc函式

分片sk_buff所需要的記憶體空間,sock_alloc_send_skb分配單個緩衝區或一系列分片資料包的第一個緩衝區,sock_wmalloc管理其餘的子分片。

二、ip_queue_ximit函式

ip_queue_xmit是傳輸層TCP協議和網路層的介面,ip_queue_xmit對資料包做一系列的IP層的初始化工作,本機產生的每個資料包都屬於某個套接字,包含套接字資料結構的指標skb->sk。ip_queue_ximit主要做以下工作

1、設定路由

先檢查skb->rtable是否為空,如果為空就要為資料包分配路由,如果沒有設定路由就呼叫ip_route_ouput_flow設定skb的路由,然後呼叫__sk_dst_check檢查目標路由是否可達。

...
	rcu_read_lock();
	//獲取路由
	rt = skb_rtable(skb);
	if (rt != NULL)
		goto packet_routed;

	/* Make sure we can route this packet. */
	//檢查路由是否可達
	rt = (struct rtable *)__sk_dst_check(sk, 0);
	if (rt == NULL) {
		__be32 daddr;

		/* Use correct destination address if we have options. */
		//設定了源路由選項,重新查詢新路由和daddr取自源路由IP地址列表
		daddr = inet->inet_daddr;
		if(opt && opt->srr)
			daddr = opt->faddr;

		{
...
			//路由不可達,重新選路由
			if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
				goto no_route;
		}
		//傳送資料包的輸出網路裝置資訊
		sk_setup_caps(sk, &rt->u.dst);
	}
...

2、構建IP協議頭

到此時skb只包含了IP資料包的常規負載資料、協議頭、從傳輸層傳來的負載資料。TCP為Socket Buffer分配記憶體是按找最大可能需要的來向系統申請記憶體,主要是考慮了下層協議頭需要空間,這樣做的可以避免IP層或更低協議在空間不夠的情況下做緩衝區複製或重分配提高效率。

...
/* OK, we know where to send it, allocate and build IP header. */
	//檢視是否有足夠空間存放IP協議頭
	skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
	skb_reset_network_header(skb);
	iph = ip_hdr(skb);
	*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
	if (ip_dont_fragment(sk, &rt->u.dst) && !skb->local_df)
		iph->frag_off = htons(IP_DF);
	else
		iph->frag_off = 0;
	//初始化IP協議頭的iph結構體各資料域
	iph->ttl      = ip_select_ttl(inet, &rt->u.dst);
	//協議
	iph->protocol = sk->sk_protocol;
	//原地址
	iph->saddr    = rt->rt_src;
	iph->daddr    = rt->rt_dst;
	/* Transport layer set skb->h.foo itself. */
	//構建IP選項
	if (opt && opt->optlen) {
		iph->ihl += opt->optlen >> 2;
		ip_options_build(skb, opt, inet->inet_daddr, rt, 0);
	}
	//設定分片資料包的表示符ID
	ip_select_ident_more(iph, &rt->u.dst, sk,
			     (skb_shinfo(skb)->gso_segs ?: 1) - 1);
	//優先順序設定
	skb->priority = sk->sk_priority;
	skb->mark = sk->sk_mark;
...

3、函式結束處理

最後呼叫ip_local_out,再呼叫__ip_local_out,然後呼叫ip_send_check計算校驗和,最後呼叫網路過濾子系統OUT鏈上的鉤子處理函式,鉤子處理函式結束呼叫dst_output接續。

int __ip_local_out(struct sk_buff *skb)
{
	struct iphdr *iph = ip_hdr(skb);

	iph->tot_len = htons(skb->len);
	//ip校驗和
	ip_send_check(iph);
	//呼叫OUT鏈上的鉤子處理函式
	return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
		       skb_dst(skb)->dev, dst_output);
}

以下是ip_queue_xmit完整程式碼:

int ip_queue_xmit(struct sk_buff *skb)
{
	struct sock *sk = skb->sk;
	struct inet_sock *inet = inet_sk(sk);
	struct ip_options *opt = inet->opt;
	struct rtable *rt;
	struct iphdr *iph;
	int res;

	/* Skip all of this if the packet is already routed,
	 * f.e. by something like SCTP.
	 */
	rcu_read_lock();
	//獲取路由
	rt = skb_rtable(skb);
	if (rt != NULL)
		goto packet_routed;

	/* Make sure we can route this packet. */
	//檢查路由是否可達
	rt = (struct rtable *)__sk_dst_check(sk, 0);
	if (rt == NULL) {
		__be32 daddr;

		/* Use correct destination address if we have options. */
		//設定了源路由選項,重新查詢新路由和daddr取自源路由IP地址列表
		daddr = inet->inet_daddr;
		if(opt && opt->srr)
			daddr = opt->faddr;

		{
			struct flowi fl = { .oif = sk->sk_bound_dev_if,
					    .mark = sk->sk_mark,
					    .nl_u = { .ip4_u =
						      { .daddr = daddr,
							.saddr = inet->inet_saddr,
							.tos = RT_CONN_FLAGS(sk) } },
					    .proto = sk->sk_protocol,
					    .flags = inet_sk_flowi_flags(sk),
					    .uli_u = { .ports =
						       { .sport = inet->inet_sport,
							 .dport = inet->inet_dport } } };

			/* If this fails, retransmit mechanism of transport layer will
			 * keep trying until route appears or the connection times
			 * itself out.
			 */
			security_sk_classify_flow(sk, &fl);
			//路由不可達,重新選路由
			if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
				goto no_route;
		}
		//傳送資料包的輸出網路裝置資訊
		sk_setup_caps(sk, &rt->u.dst);
	}
	skb_dst_set_noref(skb, &rt->u.dst);

packet_routed:
	if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
		goto no_route;

	/* OK, we know where to send it, allocate and build IP header. */
	//檢視是否有足夠空間存放IP協議頭
	skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
	skb_reset_network_header(skb);
	iph = ip_hdr(skb);
	*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
	if (ip_dont_fragment(sk, &rt->u.dst) && !skb->local_df)
		iph->frag_off = htons(IP_DF);
	else
		iph->frag_off = 0;
	//初始化IP協議頭的iph結構體各資料域
	iph->ttl      = ip_select_ttl(inet, &rt->u.dst);
	//協議
	iph->protocol = sk->sk_protocol;
	//原地址
	iph->saddr    = rt->rt_src;
	iph->daddr    = rt->rt_dst;
	/* Transport layer set skb->h.foo itself. */
	//構建IP選項
	if (opt && opt->optlen) {
		iph->ihl += opt->optlen >> 2;
		ip_options_build(skb, opt, inet->inet_daddr, rt, 0);
	}
	//設定分片資料包的表示符ID
	ip_select_ident_more(iph, &rt->u.dst, sk,
			     (skb_shinfo(skb)->gso_segs ?: 1) - 1);
	//優先順序設定
	skb->priority = sk->sk_priority;
	skb->mark = sk->sk_mark;

	res = ip_local_out(skb);
	rcu_read_unlock();
	return res;

no_route:
	rcu_read_unlock();
	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
	kfree_skb(skb);
	return -EHOSTUNREACH;
}