1. 程式人生 > >ip分片重組函式-ip_frag_queue

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;
}