閱讀663 返回首頁    go 阿裏雲 go 技術社區[雲棲]


網絡子係統86_inet協議族-l4向下(一)

//	l4數據向下l3傳遞
//	步驟:
//		1.如果sock->sk_write_queue為空,初始化corking
//			1.1 corking信息用於幫助ip層對數據進行分片
1.1 int ip_append_data(struct sock *sk, struct flowi4 *fl4,
		   int getfrag(void *from, char *to, int offset, int len,
			       int odd, struct sk_buff *skb),
		   void *from, int length, int transhdrlen,
		   struct ipcm_cookie *ipc, struct rtable **rtp,
		   unsigned int flags)
{
	struct inet_sock *inet = inet_sk(sk);
	int err;

	//sk_write_queue為空
	if (skb_queue_empty(&sk->sk_write_queue)) {
		//初始化ip用於聚合,分片數據的信息
		err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
		if (err)
			return err;
	} else {
		transhdrlen = 0;
	}
	//添加數據到sk->sk_wirte_queue
	return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
				sk_page_frag(sk), getfrag,
				from, length, transhdrlen, flags);
}

//	添加數據到隊列
//		注:skb->frags數組裏的數據時主緩存區中數據的擴展,
//			而frags_list裏的數據代表的是獨立緩存區(也就是必須作為單獨ip片段而獨立傳輸)。
//	步驟:
//		1.如果length大於mtu,或大於前一個skb剩餘的空間
//			1.1 分配新skb,通過getfrag將數據從from拷貝到新skb
//		2.在拷貝過程中,需要考慮設備是否支持分散/聚集
//			2.1 有更多數據並且出口設備不支持分散/聚集IO,以最大尺寸分配skb
//			2.2 否則,以分片尺寸分配skb
1.2 static int __ip_append_data(struct sock *sk,
			    struct flowi4 *fl4,
			    struct sk_buff_head *queue,
			    struct inet_cork *cork,
			    struct page_frag *pfrag,
			    int getfrag(void *from, char *to, int offset,
					int len, int odd, struct sk_buff *skb),
			    void *from, int length, int transhdrlen,
			    unsigned int flags)
{

	//transhdrlen L4首部長度,用以區分第一個片段和後續片段
	//	transhdrlen !=0 表示ip_append_data工作在第一個片段
	//	transhdrlen ==0 表示ip_append_data未工作在第一個片段

	struct inet_sock *inet = inet_sk(sk);
	struct sk_buff *skb;

	//ip選項
	struct ip_options *opt = cork->opt;
	int hh_len;
	int exthdrlen;
	int mtu;
	int copy;
	int err;
	int offset = 0;
	unsigned int maxfraglen, fragheaderlen;
	int csummode = CHECKSUM_NONE;
	//路由項
	struct rtable *rt = (struct rtable *)cork->dst;

	//最後一個skb
	skb = skb_peek_tail(queue);

	exthdrlen = !skb ? rt->dst.header_len : 0;
	//鏈路允許的最大報文長度
	mtu = cork->fragsize;

	//l2首部長度
	hh_len = LL_RESERVED_SPACE(rt->dst.dev);

	//分片ip首部長度
	fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);

	//分片最大長度
	//	ip報文的長度為8字節的倍數
	maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;

	//所有分片總長不超過64k
	if (cork->length + length > 0xFFFF - fragheaderlen) {
		ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
			       mtu-exthdrlen);
		return -EMSGSIZE;
	}

	//length為剩餘要處理的數據量
	while (length > 0) {
		//copy為當前ip片段中剩餘的空間量
		copy = mtu - skb->len;

		//要加入的片段大於剩餘的空間量
		if (copy < length)
		{
			//通過使用maxfraglen是長度縮減到8字節邊界
			copy = maxfraglen - skb->len;
		}
			
		//copy=0表示該分配一個新的sk_buff,因為最後一個已被完全填滿
		//copy<0表明有些數據必須從當前ip片段中刪除,並移至新片段,前一種情況的特例
		if (copy <= 0) {
			char *data;
			unsigned int datalen;
			unsigned int fraglen;
			unsigned int fraggap;
			unsigned int alloclen;
			struct sk_buff *skb_prev;
alloc_new_skb:
			skb_prev = skb;
			//保證ip數據報對齊到8字節,fraggap=已滿skb超出maxfraglen的部分,將移動到新分配的skb中取
			if (skb_prev)
				fraggap = skb_prev->len - maxfraglen;
			else
				fraggap = 0;

			//剩餘數據長度
			datalen = length + fraggap;
			//剩餘數據長度大於mtu
			if (datalen > mtu - fragheaderlen)
				datalen = maxfraglen - fragheaderlen;
			//分片長度(數據長度+首部長度)
			fraglen = datalen + fragheaderlen;

			//有更多數據並且出口設備不支持分散/聚集IO
			if ((flags & MSG_MORE) &&
			    !(rt->dst.dev->features&NETIF_F_SG))
			{
				//以最大尺寸分配內存
				alloclen = mtu;
			}
			else
			{	//以分片長度分配內存
				alloclen = fraglen;
			}
			
			...

			//分配新skb
			if (transhdrlen) {
				skb = sock_alloc_send_skb(sk,
						alloclen + hh_len + 15,
						(flags & MSG_DONTWAIT), &err);
			} else {
				skb = NULL;
				if (atomic_read(&sk->sk_wmem_alloc) <=
				    2 * sk->sk_sndbuf)
					skb = sock_wmalloc(sk,
							   alloclen + hh_len + 15, 1,
							   sk->sk_allocation);
					cork->tx_flags = 0;
			}

			skb->ip_summed = csummode;
			skb->csum = 0;
			//預留l2首部
			skb_reserve(skb, hh_len);
			skb_shinfo(skb)->tx_flags = cork->tx_flags;

			//移動skb->tail到分片尾部,返回skb->data
			data = skb_put(skb, fraglen + exthdrlen);
			skb_set_network_header(skb, exthdrlen);
			skb->transport_header = (skb->network_header +
						 fragheaderlen);
			data += fragheaderlen + exthdrlen;

			//從上一個skb拷貝未對其到8字節邊界的剩餘字節到新skb
			if (fraggap) {
				//計算剩餘字節的校驗和
				skb->csum = skb_copy_and_csum_bits(
					skb_prev, maxfraglen,
					data + transhdrlen, fraggap, 0);
				//更新上一個skb的校驗和
				skb_prev->csum = csum_sub(skb_prev->csum,
							  skb->csum);
				data += fraggap;
				pskb_trim_unique(skb_prev, maxfraglen);
			}

			//拷貝剩餘數據到緩存
			copy = datalen - transhdrlen - fraggap;
			if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
				err = -EFAULT;
				kfree_skb(skb);
				goto error;
			}

			offset += copy;
			length -= datalen - fraggap;
			transhdrlen = 0;
			exthdrlen = 0;
			csummode = CHECKSUM_NONE;

			//將skb添加到隊列上
			__skb_queue_tail(queue, skb);
			continue;
		}
		//copy>length意味著skb有足夠的空間
		if (copy > length)
			copy = length;

		//出口設備不支持分散/聚集IO
		if (!(rt->dst.dev->features&NETIF_F_SG)) {
			unsigned int off;

			off = skb->len;
			//將數據拷貝到主緩存
			if (getfrag(from, skb_put(skb, copy),
					offset, copy, off, skb) < 0) {
				__skb_trim(skb, off);
				err = -EFAULT;
				goto error;
			}
		} else {
			...
			//將數據拷貝到skb->frags中
		}
		offset += copy;
		length -= copy;
	}

	return 0;

error_efault:
	err = -EFAULT;
error:
	cork->length -= length;
	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
	return err;
}


//	L4校驗和
//		TCP,UDP協議計算的校驗和會包括其報頭,有效載荷以及一個偽裝報頭。

//TCP,UDP偽報頭
                                                

                                                  

//	硬件計算L4校驗和
//	1.使用硬件計算L4校驗和的條件:
//		1.1 由ip_append_data所構建的ip封包不會被分段(及送給ip_append_data的總數不會超過PMTU)
//		1.2 出口設備支持硬件校驗和計算
//		1.3 沒有轉換報頭(也就是IPSec集組協議)
//	2.計算方法:
//		2.1 L4協議把skb->csum初始化成正確的偏移量,並用偽報頭驗證和設定L4報頭的校驗字段

//	軟件計算L4校驗和
//		如果出口設備不支持硬件設備校驗和,或者雖然支持,但是因為sk_write_queue有一個以上的ip片段而
//		無法使用,則L4校驗和必須在軟件中計算,在這種情況,getfrag把數據拷貝到緩存區時,會對L4有效載荷
//		計算部分校驗和,然後,L4協議稍後會將這些值結合起來得到放在L4報頭內的值。





最後更新:2017-04-03 12:55:21

  上一篇:go C# DataTable.NewRow 方法
  下一篇:go 存儲那些事兒(二): 下一代Linux文件係統BTRFS簡介