ip分片重组函数-ip_frag_queue

在讲解此函数前 有个概念需要再次说明一下,即分片重叠的问题 

比如在没有接收到新来的分片时候 分片队列的分布如下:

当在接收到一个分片的时候,遍历链表(已经升序排序好)后找到插入位置后根据重叠与非重叠 有如下情况:

(1)不发生重叠的正常情况:
比如新分片的offset=300 len=99
则插入情况如下:

(2)发生前重叠
比如新分片的offset=280 len=119
则插入的情况如下:

(3)发送后重叠
比如新分片的offet=300 len=280
则插入的情况如下:

以上图就是对代码中覆盖部分的总结,根据图我们来看下面代码的重叠处理部分会容易理解一些

/* Add new segment to existing queue. */
static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
	struct sk_buff *prev, *next;
	struct net_device *dev;
	int flags, offset;
	int ihl, end;
	int err = -ENOENT;

	//碎片重组已经完成
	if (qp->q.last_in & INET_FRAG_COMPLETE)
		goto err;

   /*
    IPCB(skb)->flags只有在本机发送IPv4分片时被置位,那么这里的检查应该是预防收到本机自己发出的IP分片。
    
   */	
	if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&//检查skb中的分片标志 
	
        //关于ip_frag_too_far:该函数主要保证了来自同一个peer(相同的源地址)不会占用过多的IP分片队列
	    unlikely(ip_frag_too_far(qp)) &&

	   //重新初始化该队列
	    unlikely(err = ip_frag_reinit(qp))) 
    {
		ipq_kill(qp);
		goto err;
	}
	
    //计算碎片的偏移量和标志
	offset = ntohs(ip_hdr(skb)->frag_off);
	flags = offset & ~IP_OFFSET;
	offset &= IP_OFFSET;
	offset <<= 3;		/* offset is in 8-byte chunks */

	//获得ip头的长度
	ihl = ip_hdrlen(skb);

	/* Determine the position of this fragment. */
	//获得报文总的长度
	end = offset + skb->len - ihl;
	err = -EINVAL;

	//如果是最后一个分片
	if ((flags & IP_MF) == 0) 
	{
		/* If we already have some bits beyond end
		 * or have different end, the segment is corrrupted.
		 */
		//末端小于之前获得的总长度  则出现问题 
		//之前已经收到了一个最后分片,且这次判断的末端不等于之前获得的值 则出错
		if (end < qp->q.len ||
		    ((qp->q.last_in & INET_FRAG_LAST_IN) && end != qp->q.len))
			goto err;

		qp->q.last_in |= INET_FRAG_LAST_IN; //标志这是最后一个分片
		qp->q.len = end;//更新总长度
	} 
	else //不是最后一个分片
    {
        //检查是否8字节对齐
		if (end&7) 
		{
		    //除最后一个分片外 其余分片均为8字节对齐
			end &= ~7;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)//没有设置跳过校验和计算
				skb->ip_summed = CHECKSUM_NONE;//传输层自己计算校验和
		}

		//此分片尾部是否超过之前获得的报文总长度
		if (end > qp->q.len) 
		{
			/* Some bits beyond end -> corruption. */
			if (qp->q.last_in & INET_FRAG_LAST_IN)//最后一个分片已经接受到 在来的分片是错误的
				goto err;
			qp->q.len = end;//更新报文的总长度
		}
	}
	//表示空的ip分片
	if (end == offset)
		goto err;

	err = -ENOMEM;
	//将skb->data移到ip首部以后
	if (pskb_pull(skb, ihl) == NULL)
		goto err;

	err = pskb_trim_rcsum(skb, end - offset);
	if (err)
		goto err;

	/* Find out which fragments are in front and at the back of us
	 * in the chain of fragments so far.  We must know where to put
	 * this fragment, right?
	 */
	//遍历ipq中的fragments链表,每个skb的cb成员记录着当前skb进行ip重组的
    //时候所需要的偏移,fragments中的skb都是按照offset升序排好的,所以,找到
    //第一项offset大于当前IP包偏移的数据包就可以了
	prev = NULL;
	for (next = qp->q.fragments; next != NULL; next = next->next) {
		if (FRAG_CB(next)->offset >= offset)
			break;	/* bingo! */
		prev = next;
	}

	/* We found where to put this one.  Check for overlap with
	 * preceding fragment, and, if needed, align things so that
	 * any overlaps are eliminated.
	 */
	//在上面的循环中找到了插入的位置 但可能发生重叠,
	if (prev) 
	{
	    //若没发生重叠 很显然offset的大小 应该在FRAG_CB(prev)->offset + prev->len 之后也就是i值会小于0
	    /*若发生了重叠 offset的大小 在FRAG_CB(prev)->offset和FRAG_CB(prev)->offset + prev->len之间
          则i的值会大于0
	    
         从上面的循环可知,此分片应该插入到prev指向的位置后面比如 prev的offset=100 len=150 
         若此分片的offset的值 在100+150的后面  i值就会小于0 没有重叠发生
         若此分片的offset的值 在100,350之间 i的值会大于0  发生了重叠
                                             插入位置   前重叠
                                             \|/ 
       ------------------------------------------------------
         分片1   |  (与分片2发生重叠)分片2    |   分片3     |
       ------------------------------------------------------
		*/
		int i = (FRAG_CB(prev)->offset + prev->len) - offset;

		if (i > 0)//发生了重叠 
		{
			offset += i; //将偏移完后移动i 跑到prev报文区间的最右边
			err = -EINVAL;
			if (end <= offset) //此分片移动后的offset是否超过了此分片的最大长度
				goto err;//偏移超过了分片的最大长度 出错
			err = -ENOMEM;
			
			if (!pskb_pull(skb, i))//将skb的数据指针减少i个字节 改变偏移值
				goto err;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)//没有设置跳过校验和计算
				skb->ip_summed = CHECKSUM_NONE;//传输层自己计算校验和
		}
	}

	err = -ENOMEM;
    //此分片与队列中存在的分片发生后面的重叠  后重叠
    /*                      插入位置
                            \|/
       ------------------------------------------------------------------------
         分片1   |  分片2    |  (重叠与分片3甚至后面的分片发生重叠) 分片3     |
       ------------------------------------------------------------------------
       
     */
    //此分片的最大报文长度 大于后面分片的起始位置 发生后重叠
	while (next && FRAG_CB(next)->offset < end) 
	{
	    //计算重叠的字节数
		int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */
	 
		if (i < next->len)//是否只重叠了此next分片 
		{
			/* Eat head of the next overlapped fragment
			 * and leave the loop. The next ones cannot overlap.
			 */
			//将覆盖的i个字节在next分片中剔除
			//将data指针减少i个字节
			if (!pskb_pull(next, i))
				goto err;
			FRAG_CB(next)->offset += i;//更新next新的偏移 往后移动i个字节
			qp->q.meat -= i;
			if (next->ip_summed != CHECKSUM_UNNECESSARY)//没有设置跳过校验和计算
				next->ip_summed = CHECKSUM_NONE;//传输层自己计算校验和
			break;
		} 
		else 
	    {
	        //覆盖了后面的整个分片 则将覆盖的分片释放掉
			struct sk_buff *free_it = next;

			/* Old fragment is completely overridden with
			 * new one drop it.
			 */
			next = next->next;

			if (prev)
				prev->next = next;
			else
				qp->q.fragments = next;

			qp->q.meat -= free_it->len;
            //释放覆盖的整个分片
			frag_kfree_skb(qp->q.net, free_it, NULL);
		}
	}

	//设置新的偏移值
	FRAG_CB(skb)->offset = offset;

	/* Insert this fragment in the chain of fragments. */
    //将skb加入到队列中
	skb->next = next;
	if (prev)
		prev->next = skb;
	else
		qp->q.fragments = skb;

	dev = skb->dev;
	if (dev) 
	{
		qp->iif = dev->ifindex;//设置设备号
		skb->dev = NULL;
	}
	qp->q.stamp = skb->tstamp;//记录这个包接收的时间戳
	qp->q.meat += skb->len; //增加已经获得的报文的长度
	atomic_add(skb->truesize, &qp->q.net->mem);

	//说明是第一个分片 设置第一个分片的标志
	if (offset == 0)
		qp->q.last_in |= INET_FRAG_FIRST_IN;

	//如果收到了第一个分片和最后一个分片 并且报文长度也也等于原始的ip报文长度
	//则对所有的分片进行组装
	if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
	    qp->q.meat == qp->q.len)
		return ip_frag_reasm(qp, prev, dev);//根据所有的分段创建一个新的数据包

	//ip分片还未完全收齐
	write_lock(&ip4_frags.lock);
	list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
	write_unlock(&ip4_frags.lock);
	return -EINPROGRESS;

err:
	kfree_skb(skb);
	return err;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值