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


網絡子係統51_ip協議報文分片

//ip分片
//	快速路徑的條件:
//		1.skb
//			1.skb的數據長度(主緩存區+frags緩存區)小於輸出路徑的mtu
//			2.skb的數據長度對齊到8字節的邊界
//			3.skb沒有被分片
//			4.skb沒有被共享
//		2.skb->frag_list
//			1.長度小於(mtu-ip報頭-選項)
//			2.除最後一個分片外,長度都需要對齊到8字節邊界
//			3.head-data之間的空間,可以容納ip報頭
//	注:skb->frag_list的skb,沒有填充ip頭,skb填充有ip頭
//
//	慢速路徑條件:
//		隻要不滿足快速路徑其中的一條,使用慢速路徑

//	快速路徑處理過程:
//		1.第一個分片使用完整的ip選項
//		2.其餘分片使用部分ip選項
//		3.除最後一個分片外,設置MF標誌
//		4.設置offset
//		5.使用相同的路由信息,向下層傳遞

//	慢速路徑處理過程:
//		1.分配新的skb,長度為mtu,或者剩餘數據量,對齊到8字節邊界
//		2.預留l2幀頭空間
//		3.拷貝l3報頭,以及數據到新skb中
//		4.除第一個分片使用完整的ip選項,其餘分片使用部分ip選項
//		5.設置offset
//		5.使用相同的路由信息,向下層傳遞


//	對比快速路徑與慢速路徑:
//		1.慢速路徑的慢主要表現在分配新的緩存區,從舊緩存區中拷貝數據

//	注:ip報頭的offset字段,隻針對有效載荷(ip頭,ip選項不包括在內)

//調用路徑ip_output/ip_mc_output->ip_fragment
1.1 int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))
{
	struct iphdr *iph;
	int raw = 0;
	int ptr;
	struct net_device *dev;
	struct sk_buff *skb2;
	unsigned int mtu, hlen, left, len, ll_rs;
	int offset;
	int not_last_frag;
	struct rtable *rt = (struct rtable*)skb->dst;
	int err = 0;
	//出口設備
	dev = rt->u.dst.dev;
	//ip頭
	iph = skb->nh.iph;
	//ip報頭設置有DF標誌,禁止分片
	////skb->local_df如果被設置,則在需要分片,但是設置DF標誌,不向發送方傳送ICMP消息
	if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) {
		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,//需要分片,但是設置DF標誌,通知發送方不可達,原因是需要分片,並告知對方mtu
			  htonl(dst_pmtu(&rt->u.dst)));
		kfree_skb(skb);
		return -EMSGSIZE;
	}

	//可以快速分片的條件:
	//	skb
	//		1.skb的數據長度(主緩存區+frags緩存區)小於輸出路徑的mtu
	//		2.skb的數據長度對齊到8字節的邊界
	//		3.skb沒有被分片
	//		4.skb沒有被共享
	//	skb->frag_list
	//		1.長度小於(mtu-ip報頭)
	//		2.除最後一個分片外,長度都需要對齊到8字節邊界
	//		3.head-data之間的空間,可以容納ip報頭
	//	注:skb->frag_list的skb,沒有填充ip頭,skb填充有ip頭
	hlen = iph->ihl * 4;//ip頭長度
	mtu = dst_pmtu(&rt->u.dst) - hlen;	//數據空間的大小

	if (skb_shinfo(skb)->frag_list) {//frag_list存在skb
		struct sk_buff *frag;
		int first_len = skb_pagelen(skb);//skb主緩存區,frags片段中的數據長度,不包括frag_list中的skb

		if (first_len - hlen > mtu ||//超過允許的最大數據量
		    ((first_len - hlen) & 7) ||//數據長度沒有對齊到8字節
		    (iph->frag_off & htons(IP_MF|IP_OFFSET)) ||//此skb是一個分片
		    skb_cloned(skb))//克隆一份skb,慢速分片
			goto slow_path;
		//檢查frag_list中的skb是否可以快速分片
		for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {
			//frag_list中的skb沒有ip頭
			if (frag->len > mtu ||
			    ((frag->len & 7) && frag->next) ||//除最後一個分片外,其他分片的長度必須對其到8字節
			    skb_headroom(frag) < hlen)//頭空間不夠容納ip報頭(ip頭+選項)
			    goto slow_path;

			if (skb_shared(frag))//skb被共享
				goto slow_path;
		}
	//快速路徑:
		err = 0;
		offset = 0;
		frag = skb_shinfo(skb)->frag_list;
		skb_shinfo(skb)->frag_list = NULL;
		//更新skb->data_len為skb->frags中數據的大小,原始skb->data_len包括frags,frag_list中所有數據的長度
		skb->data_len = first_len - skb_headlen(skb);
		skb->len = first_len;//更新總長度為主緩存區,frags中數據的大小,原始skb->len包括主緩存區,frags,frag_list中所有數據的長度
		iph->tot_len = htons(first_len);//ip報頭的數據包長度
		iph->frag_off |= htons(IP_MF);//表示有更多的分片,第一個分片,offset=0
		ip_send_check(iph);//計算ip校驗和

		for (;;) {
			if (frag) {//處理skb->frag_list中的skb,為其準備分片的ip報頭
				frag->ip_summed = CHECKSUM_NONE;//表示校驗和沒有計算
				frag->h.raw = frag->data;
				frag->nh.raw = __skb_push(frag, hlen);//移動skb->data指針,填充ip報頭和選項
				memcpy(frag->nh.raw, iph, hlen);
				iph = frag->nh.iph;
				iph->tot_len = htons(frag->len);//總長度
				ip_copy_metadata(frag, skb);//使分片skb與頭skb有一樣的出口設備,路由信息
				if (offset == 0)//第一個分片具有完整的選項,其他分片將所有非copied的選項,均設置為NOOP
					ip_options_fragment(frag);
				offset += skb->len - hlen;//計算本分片的偏移量
				iph->frag_off = htons(offset>>3);//偏移量對齊在8字節
				if (frag->next != NULL)
					iph->frag_off |= htons(IP_MF);//設置還有更多skb
				ip_send_check(iph);//計算ip校驗和
			}

			err = output(skb);//向下傳遞前一個skb,調用ip_finish_output

			if (err || !frag)
				break;

			skb = frag;//保留指向前一個skb的指針
			frag = skb->next;//frag為下一個待處理的skb
			skb->next = NULL;//
		}

		if (err == 0) {
			IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
			return 0;
		}
		//快速路徑分片出現錯誤,釋放所有skb
		while (frag) {
			skb = frag->next;
			kfree_skb(frag);
			frag = skb;
		}
		IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
		return err;
	}

	//慢速路徑
slow_path:
	left = skb->len - hlen;	//(主緩存區,frags,frag_list)數據的總大小(不包括ip頭,選項)	
	ptr = raw + hlen;		//新分片的數據在原skb中的起始位置

	offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;//當前分片的偏移量
	not_last_frag = iph->frag_off & htons(IP_MF);//判斷是否為最後一個分片

	//開始進行分片
	while(left > 0)	{
		len = left;
		if (len > mtu)//使用mtu(此處的mtu去掉ip報頭和選項長度)
			len = mtu;
		
		if (len < left)	{
			len &= ~7;//長度對齊到8字節邊界
		}
		//分配新的skb,長度包括l2幀頭,l3報頭,l3有效載荷
		if ((skb2 = alloc_skb(len+hlen+ll_rs, GFP_ATOMIC)) == NULL) {
			NETDEBUG(printk(KERN_INFO "IP: frag: no memory for new fragment!\n"));
			err = -ENOMEM;
			goto fail;
		}
		//使所有分片都使用相同的路由信息,出口設備
		ip_copy_metadata(skb2, skb);
		skb_reserve(skb2, ll_rs);//預留l2幀頭
		skb_put(skb2, len + hlen);//data-tail之間空間大小為(ip報頭+ip選項+ip有效載荷)
		skb2->nh.raw = skb2->data;//設置l3報頭起始地址
		skb2->h.raw = skb2->data + hlen;//l3有效載荷
		//設置新創建的skb所屬的sock
		if (skb->sk)
			skb_set_owner_w(skb2, skb->sk);
		//拷貝ip頭,選項
		memcpy(skb2->nh.raw, skb->data, hlen);
		//將skb起始地址為ptr的len個字節拷貝到skb2中
		//由skb_copy_bits處理frag,frag_list
		if (skb_copy_bits(skb, ptr, skb2->h.raw, len))
			BUG();
		left -= len;//更新剩餘待分片的數據量

		iph = skb2->nh.iph;
		iph->frag_off = htons((offset >> 3));//偏移量

		//1.隻有第一個分片需要完整的選項,其他分片將非copied的選項設置為NOOP
		//2.由於第一個分片已經拷貝完整的ip報頭以及選項到其分片中
		//3.優化效率,在第一個分片拷貝完整的選項後,更新選項,非第一個分片都使用相同的ip選項
		if (offset == 0)
			ip_options_fragment(skb);

		if (left > 0 || not_last_frag)
			iph->frag_off |= htons(IP_MF);//還有跟多的分片
		ptr += len;//下一個分片的數據在原skb中的起始位置
		offset += len;//偏移量

		IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);

		iph->tot_len = htons(len + hlen);

		ip_send_check(iph);

		err = output(skb2);
		if (err)
			goto fail;
	}
	kfree_skb(skb);
	IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
	return err;

fail:
	kfree_skb(skb); 
	IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
	return err;
}

//調用路徑 ip_fragment->ip_options_fragment
//	修改ip_option(skb->cb),將非copied類型的選項,均設置為NOOP類型
2.1 void ip_options_fragment(struct sk_buff * skb) 
{
	unsigned char * optptr = skb->nh.raw;
	struct ip_options * opt = &(IPCB(skb)->opt);
	int  l = opt->optlen;
	int  optlen;

	while (l > 0) {
		switch (*optptr) {
		case IPOPT_END:
			return;
		case IPOPT_NOOP:
			l--;
			optptr++;
			continue;
		}
		optlen = optptr[1];
		if (optlen<2 || optlen>l)
		  return;
		if (!IPOPT_COPIED(*optptr))//在選項type的第8個比特,指出該選項是否應該複製到ip分片中
			memset(optptr, IPOPT_NOOP, optlen);//對不需要拷貝到除第一個分片外的選項,設置為NOOP
		l -= optlen;
		optptr += optlen;
	}
	opt->ts = 0;//均設置為0,表明不需要time stamp,record route
	opt->rr = 0;
	opt->rr_needaddr = 0;
	opt->ts_needaddr = 0;
	opt->ts_needtime = 0;
	return;
}

最後更新:2017-04-03 14:53:40

  上一篇:go SystemVerilog語言簡介(三)
  下一篇:go SystemVerilog語言簡介(二)