阅读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语言简介(二)