tcp/ip 協議棧Linux源碼分析三 IPv4分片報文重組分析三

繼續上篇,上次講到了分片隊列的查找操作,剩下的就是分片隊列插入和重組兩個部分了,這個也是分片重組的關鍵部分。

將收到的分片插入到分片隊列是由函數inet_frag_queue()函數完成,這個函數比較長,多看幾遍就好了 :-)

/* 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;
	u8 ecn;

    /* last_in標誌位已經置位,這時候再收到報文就不用處理了,
     * 一種情況是重組已經完成,這時候又收到了報文,可能是重傳
     * 當然,分片隊列被垃圾回收定時器回收的時候也會設置這個標誌位,
     * 表示已廢棄。
     */
	if (qp->q.last_in & INET_FRAG_COMPLETE)
		goto err;

    /* 下面這段描述摘自 http://blog.chinaunix.net/uid-23629988-id-3047513.html
     * 關於ip_frag_too_far這個函數我還沒有分析清楚,日後搞明白了補上,:-)
     * 歡迎懂得大神講一下
     *    1. IPCB(skb)->flags只有在本機發送IPv4分片時被置位,那麼這裏的檢查應該是
     *    預防收到本機自己發出的IP分片。
     *    2. 關於ip_frag_too_far:該函數主要保證了來自同一個peer(相同的源地址)不
     *    會佔用過多的IP分片隊列。
     *    3. 前面兩個條件爲真時,調用ip_frag_reinit,重新初始化該隊列。出錯,那麼只
     *       好kill掉這個隊列了。
     * 
     */
	if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
	    unlikely(ip_frag_too_far(qp)) &&
	    unlikely(err = ip_frag_reinit(qp))) {
		ipq_kill(qp);
		goto err;
	}

    /* 獲取ip頭裏面的ecn標誌位 */
	ecn = ip4_frag_ecn(ip_hdr(skb)->tos);
	offset = ntohs(ip_hdr(skb)->frag_off);
	
	/* 分片標誌位 */
	flags = offset & ~IP_OFFSET;
	offset &= IP_OFFSET;

	/* 得到片偏移位置,相對於原始未分片報文,單位爲8字節 */
	offset <<= 3;		/* offset is in 8-byte chunks */
	ihl = ip_hdrlen(skb);

	/* Determine the position of this fragment. */
	/* skb的長度減去IP頭就剩下數據部分長度,這個長度加上片偏移的長度
	 * 就得到了這段報文相對於原始報文的尾偏移
	 */
	end = offset + skb->len - ihl;
	err = -EINVAL;

	/* Is this the final fragment? */
	/* 如果是最後的一片 */
	if ((flags & IP_MF) == 0) {
		/* If we already have some bits beyond end
		 * or have different end, the segment is corrupted.
		 */
		 /* 
		  * 既然是最後一片,尾偏移肯定要大於或者等於當前分片隊列的長度,不是的話就錯了
		  * 
		  * 如果已經收到過最後分片(分片重傳)並且長度和當前skb所指向的尾偏移不一致,
		  * 出錯了
		  */
		if (end < qp->q.len ||
		    ((qp->q.last_in & INET_FRAG_LAST_IN) && end != qp->q.len))
			goto err;

        /* 一切正常,設置last_in 標誌位,同時將分片隊列長度設置上 
         * 只有收到了最後一個分片報文才能夠得知完整的報文長度
         */
		qp->q.last_in |= INET_FRAG_LAST_IN;
		qp->q.len = end;
	} else {
	    /* 如果不是最後一片,並且長度不是的倍數,就截取數據到的8倍數,
	     * 因爲數據被截取了,校驗和也失效了,這裏重置校驗和
	     */
		if (end&7) {
			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;
		}
	}
	/* 說明長度爲空,丟棄 */
	if (end == offset)
		goto err;

	err = -ENOMEM;
	
	/* 去掉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?
	 */
	/* 如果是第一個分片報文則直接插入 
     * 如果上一個報文的偏移值小於當前偏移值則放在該報文後面即可
	 */ 
	prev = qp->q.fragments_tail;
	if (!prev || FRAG_CB(prev)->offset < offset) {
		next = NULL;
		goto found;
	}

	/* 亂序到達的話找到它下面一個報文即可
	 * 這裏是遍歷分片列表,找到當前報文的後一個
	 */
	prev = NULL;
	for (next = qp->q.fragments; next != NULL; next = next->next) {
		if (FRAG_CB(next)->offset >= offset)
			break;	/* bingo! */
		prev = next;
	}

found:
	/* 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) {
	    /* i等於與上個報文重疊部分數據長度,如果完全落在上個報文內部則報錯 */
		int i = (FRAG_CB(prev)->offset + prev->len) - offset;

		if (i > 0) {
		    /* 重疊的部分直接丟棄,end <= offset說明完全重疊 */
			offset += i;
			err = -EINVAL;
			if (end <= offset)
				goto err;
			err = -ENOMEM;
            /* 去掉重疊部分 */
			if (!pskb_pull(skb, i))
				goto err;
			
		    /* 數據有變更,重置校驗和 */
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
				skb->ip_summed = CHECKSUM_NONE;
		}
	}

	err = -ENOMEM;

	while (next && FRAG_CB(next)->offset < end) {
	    /* 與後面緊鄰的報文重疊部分數據長度 */
		int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */

        /* 如果重疊長度小於後面skb的長度,那麼只需要將next skb
         * 的長度減去重疊部分即可,同時更新偏移值和校驗和
         */
		if (i < next->len) {
			/* Eat head of the next overlapped fragment
			 * and leave the loop. The next ones cannot overlap.
			 */
			if (!pskb_pull(next, i))
				goto err;
			FRAG_CB(next)->offset += i;
			qp->q.meat -= i;
			if (next->ip_summed != CHECKSUM_UNNECESSARY)
				next->ip_summed = CHECKSUM_NONE;
			break;
		} else {
		    /* 走到這說明重疊長度大於next的長度,這時候next可以直接從隊列中
		     * 摘掉了。
		     */
			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;

			/* 從分片隊列釋放該skb */
			frag_kfree_skb(qp->q.net, free_it);
		}
	}

    /* 設置該skb的控制信息,即偏移值 */
	FRAG_CB(skb)->offset = offset;

	/* Insert this fragment in the chain of fragments. */
	/* 插入報文,如果是最後一片則設置fragments_tail指針指向最後一片 */
	skb->next = next;
	if (!next)
		qp->q.fragments_tail = skb;
	if (prev)
		prev->next = skb;
	else
		qp->q.fragments = skb;

	dev = skb->dev;
	if (dev) {
        /* 記錄設備的索引同時清空skb的dev指針 */	
		qp->iif = dev->ifindex;
		skb->dev = NULL;
	}
	/* 更新隊列的接收時間戳 
	 * 更新隊列當前收到長度和,注意meat和len區別,前者保存當前已接受部分數據長度,
	 * 後者表示目前已知分片最大長度,當收到最後一個分片MF=0,就能夠得到原始報文長度
	 */
	qp->q.stamp = skb->tstamp;
	qp->q.meat += skb->len;
	qp->ecn |= ecn;

	/* 增加分片內存所佔空間 */
	atomic_add(skb->truesize, &qp->q.net->mem);

	/* 設置標誌位 */
	if (offset == 0)
		qp->q.last_in |= INET_FRAG_FIRST_IN;

	if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
	    qp->q.meat == qp->q.len)

	    /* 如果報文已經收集齊,則調用ip_frag_reasm() 進行重組操作 */    
		return ip_frag_reasm(qp, prev, dev);

	write_lock(&ip4_frags.lock);

    /* 移到lru末尾 */
	list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
	write_unlock(&ip4_frags.lock);

	/* 分片還在繼續,返回EINPROGRESS */
	return -EINPROGRESS;

err:
	kfree_skb(skb);
	return err;
}

如果分片報文的集齊了就會調用ip_frag_rasm來重組,來看下:

/* 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;
	u8 ecn;

    /* last_in標誌位已經置位,這時候再收到報文就不用處理了,
     * 一種情況是重組已經完成,這時候又收到了報文,可能是重傳
     * 當然,分片隊列被垃圾回收定時器回收的時候也會設置這個標誌位,
     * 表示已廢棄。
     */
	if (qp->q.last_in & INET_FRAG_COMPLETE)
		goto err;

    /* 下面這段描述摘自 http://blog.chinaunix.net/uid-23629988-id-3047513.html
     * 關於ip_frag_too_far這個函數我還沒有分析清楚,日後搞明白了補上,:-)
     * 歡迎懂得大神講一下
     *    1. IPCB(skb)->flags只有在本機發送IPv4分片時被置位,那麼這裏的檢查應該是
     *    預防收到本機自己發出的IP分片。
     *    2. 關於ip_frag_too_far:該函數主要保證了來自同一個peer(相同的源地址)不
     *    會佔用過多的IP分片隊列。
     *    3. 前面兩個條件爲真時,調用ip_frag_reinit,重新初始化該隊列。出錯,那麼只
     *       好kill掉這個隊列了。
     * 
     */
	if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
	    unlikely(ip_frag_too_far(qp)) &&
	    unlikely(err = ip_frag_reinit(qp))) {
		ipq_kill(qp);
		goto err;
	}

    /* 獲取ip頭裏面的ecn標誌位 */
	ecn = ip4_frag_ecn(ip_hdr(skb)->tos);
	offset = ntohs(ip_hdr(skb)->frag_off);
	
	/* 分片標誌位 */
	flags = offset & ~IP_OFFSET;
	offset &= IP_OFFSET;

	/* 得到片偏移位置,相對於原始未分片報文,單位爲8字節 */
	offset <<= 3;		/* offset is in 8-byte chunks */
	ihl = ip_hdrlen(skb);

	/* Determine the position of this fragment. */
	/* skb的長度減去IP頭就剩下數據部分長度,這個長度加上片偏移的長度
	 * 就得到了這段報文相對於原始報文的尾偏移
	 */
	end = offset + skb->len - ihl;
	err = -EINVAL;

	/* Is this the final fragment? */
	/* 如果是最後的一片 */
	if ((flags & IP_MF) == 0) {
		/* If we already have some bits beyond end
		 * or have different end, the segment is corrupted.
		 */
		 /* 
		  * 既然是最後一片,尾偏移肯定要大於或者等於當前分片隊列的長度,不是的話就錯了
		  * 
		  * 如果已經收到過最後分片(分片重傳)並且長度和當前skb所指向的尾偏移不一致,
		  * 出錯了
		  */
		if (end < qp->q.len ||
		    ((qp->q.last_in & INET_FRAG_LAST_IN) && end != qp->q.len))
			goto err;

        /* 一切正常,設置last_in 標誌位,同時將分片隊列長度設置上 
         * 只有收到了最後一個分片報文才能夠得知完整的報文長度
         */
		qp->q.last_in |= INET_FRAG_LAST_IN;
		qp->q.len = end;
	} else {
	    /* 如果不是最後一片,並且長度不是的倍數,就截取數據到的8倍數,
	     * 因爲數據被截取了,校驗和也失效了,這裏重置校驗和
	     */
		if (end&7) {
			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;
		}
	}
	/* 說明長度爲空,丟棄 */
	if (end == offset)
		goto err;

	err = -ENOMEM;
	
	/* 去掉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?
	 */
	/* 如果是第一個分片報文則直接插入 
     * 如果上一個報文的偏移值小於當前偏移值則放在該報文後面即可
	 */ 
	prev = qp->q.fragments_tail;
	if (!prev || FRAG_CB(prev)->offset < offset) {
		next = NULL;
		goto found;
	}

	/* 亂序到達的話找到它下面一個報文即可
	 * 這裏是遍歷分片列表,找到當前報文的後一個
	 */
	prev = NULL;
	for (next = qp->q.fragments; next != NULL; next = next->next) {
		if (FRAG_CB(next)->offset >= offset)
			break;	/* bingo! */
		prev = next;
	}

found:
	/* 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) {
	    /* i等於與上個報文重疊部分數據長度,如果完全落在上個報文內部則報錯 */
		int i = (FRAG_CB(prev)->offset + prev->len) - offset;

		if (i > 0) {
		    /* 重疊的部分直接丟棄,end <= offset說明完全重疊 */
			offset += i;
			err = -EINVAL;
			if (end <= offset)
				goto err;
			err = -ENOMEM;
            /* 去掉重疊部分 */
			if (!pskb_pull(skb, i))
				goto err;
			
		    /* 數據有變更,重置校驗和 */
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
				skb->ip_summed = CHECKSUM_NONE;
		}
	}

	err = -ENOMEM;

	while (next && FRAG_CB(next)->offset < end) {
	    /* 與後面緊鄰的報文重疊部分數據長度 */
		int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */

        /* 如果重疊長度小於後面skb的長度,那麼只需要將next skb
         * 的長度減去重疊部分即可,同時更新偏移值和校驗和
         */
		if (i < next->len) {
			/* Eat head of the next overlapped fragment
			 * and leave the loop. The next ones cannot overlap.
			 */
			if (!pskb_pull(next, i))
				goto err;
			FRAG_CB(next)->offset += i;
			qp->q.meat -= i;
			if (next->ip_summed != CHECKSUM_UNNECESSARY)
				next->ip_summed = CHECKSUM_NONE;
			break;
		} else {
		    /* 走到這說明重疊長度大於next的長度,這時候next可以直接從隊列中
		     * 摘掉了。
		     */
			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;

			/* 從分片隊列釋放該skb */
			frag_kfree_skb(qp->q.net, free_it);
		}
	}

    /* 設置該skb的控制信息,即偏移值 */
	FRAG_CB(skb)->offset = offset;

	/* Insert this fragment in the chain of fragments. */
	/* 插入報文,如果是最後一片則設置fragments_tail指針指向最後一片 */
	skb->next = next;
	if (!next)
		qp->q.fragments_tail = skb;
	if (prev)
		prev->next = skb;
	else
		qp->q.fragments = skb;

	dev = skb->dev;
	if (dev) {
        /* 記錄設備的索引同時清空skb的dev指針 */	
		qp->iif = dev->ifindex;
		skb->dev = NULL;
	}
	/* 更新隊列的接收時間戳 
	 * 更新隊列當前收到長度和,注意meat和len區別,前者保存當前已接受部分數據長度,
	 * 後者表示目前已知分片最大長度,當收到最後一個分片MF=0,就能夠得到原始報文長度
	 */
	qp->q.stamp = skb->tstamp;
	qp->q.meat += skb->len;
	qp->ecn |= ecn;

	/* 增加分片內存所佔空間 */
	atomic_add(skb->truesize, &qp->q.net->mem);

	/* 設置標誌位 */
	if (offset == 0)
		qp->q.last_in |= INET_FRAG_FIRST_IN;

	if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
	    qp->q.meat == qp->q.len)

	    /* 如果報文已經收集齊,則調用ip_frag_reasm() 進行重組操作 */    
		return ip_frag_reasm(qp, prev, dev);

	write_lock(&ip4_frags.lock);

    /* 移到lru末尾 */
	list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
	write_unlock(&ip4_frags.lock);

	/* 分片還在繼續,返回EINPROGRESS */
	return -EINPROGRESS;

err:
	kfree_skb(skb);
	return err;
}


/* Build a new IP datagram from all its fragments. */
/* 分片重組 */
static int ip_frag_reasm(struct ipq *qp, struct sk_buff *prev,
			 struct net_device *dev)
{
	struct net *net = container_of(qp->q.net, struct net, ipv4.frags);
	struct iphdr *iph;
	struct sk_buff *fp, *head = qp->q.fragments;
	int len;
	int ihlen;
	int err;
	u8 ecn;

    /* 重組之前首先將分片隊列從分片子系統隔離開 */
	ipq_kill(qp);

    /* 檢查ecn標誌位,0xff則丟棄該報文,之所以這麼做是因爲rfc文檔建議這麼做 */
	ecn = ip4_frag_ecn_table[qp->ecn];
	if (unlikely(ecn == 0xff)) {
		err = -EINVAL;
		goto out_fail;
	}
	/* Make the one we just received the head. */
	/* 這一步是將最後接收到的skb指針指向分片隊列的首部接收完最後一片後重組完成
	 * 是要將skb傳遞給上層處理的。這一段代碼貌似複雜,多看幾遍就懂了,下面放上一個
	 * 簡要的圖。
	 */
	if (prev) {
		head = prev->next;
		fp = skb_clone(head, GFP_ATOMIC);
		if (!fp)
			goto out_nomem;

		fp->next = head->next;
		if (!fp->next)
			qp->q.fragments_tail = fp;
		prev->next = fp;

        /* skb_morph 作用基本和skb_clone一致,這裏的作用是
         * 將剛收到的指針指向分片隊列首部,fragments就是分片
         * 首部。
         */
		skb_morph(head, qp->q.fragments);
		head->next = qp->q.fragments->next;

		kfree_skb(qp->q.fragments);
		qp->q.fragments = head;
	}

	WARN_ON(head == NULL);
	WARN_ON(FRAG_CB(head)->offset != 0);

	/* Allocate a new buffer for the datagram. */
	ihlen = ip_hdrlen(head);
	len = ihlen + qp->q.len;

    /* ip報文最大65535字節,超過這個長度就報錯 */
	err = -E2BIG;
	if (len > 65535)
		goto out_oversize;

	/* Head of list must not be cloned. */
	if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
		goto out_nomem;

	/* If the first fragment is fragmented itself, we split
	 * it to two chunks: the first with data and paged part
	 * and the second, holding only fragments. */
	 /*
	  *通常SKB數據區會由線性緩存和非線性緩存組成,超過MTU大小就要使用
	  * 另外的skb來存儲,這個部分放在skb_shinfo(head)->frag_list裏。
	  * 分片隊列重組完成後也是把原來的一個個分片放到skb_shinfo(head)->frag_list裏,
	  * 所以這裏爲了避免和head原有的frag_list弄混(如果head存在frag_list),將head的數據分爲
	  * 兩個部分,head存儲線性和非線性數據區,clone指向head的原有frag_list,同時再將分片隊列
	  * 裏的skb掛到clone後,這樣後續的上層處理就非常簡單。
	  */
	if (skb_has_frag_list(head)) {
		struct sk_buff *clone;
		int i, plen = 0;

        /* 創建一個線性數據區長度爲0的skb */
		if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
			goto out_nomem;
		clone->next = head->next;
		head->next = clone;
		/* 繼承head的frag_list */
		skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;

		/* 將head的frag_list指針 重置 */
		skb_frag_list_init(head);
		for (i = 0; i < skb_shinfo(head)->nr_frags; i++)
			plen += skb_frag_size(&skb_shinfo(head)->frags[i]);
		clone->len = clone->data_len = head->data_len - plen;
		head->data_len -= clone->len;
		head->len -= clone->len;
		clone->csum = 0;
		clone->ip_summed = head->ip_summed;
		atomic_add(clone->truesize, &qp->q.net->mem);
	}

    /* 再將所有的分片掛到frag_list隊列上 */
	skb_shinfo(head)->frag_list = head->next;
	/* 指針指向傳輸層首部 */
	skb_push(head, head->data - skb_network_header(head));

    /* 處理校驗和 */
	for (fp=head->next; fp; fp = fp->next) {
		head->data_len += fp->len;
		head->len += fp->len;
		if (head->ip_summed != fp->ip_summed)
			head->ip_summed = CHECKSUM_NONE;
		else if (head->ip_summed == CHECKSUM_COMPLETE)
			head->csum = csum_add(head->csum, fp->csum);
		head->truesize += fp->truesize;
	}

	/* 重組完成,從分片佔用的系統內存中減去重組後大小 */
	atomic_sub(head->truesize, &qp->q.net->mem);

	head->next = NULL;
	head->dev = dev;
	head->tstamp = qp->q.stamp;

    /* 重置IP頭 */
	iph = ip_hdr(head);
	iph->frag_off = 0;
	iph->tot_len = htons(len);
	iph->tos |= ecn;
	IP_INC_STATS_BH(net, IPSTATS_MIB_REASMOKS);
	qp->q.fragments = NULL;
	qp->q.fragments_tail = NULL;
	return 0;

out_nomem:
	LIMIT_NETDEBUG(KERN_ERR pr_fmt("queue_glue: no memory for gluing queue %p\n"),
		       qp);
	err = -ENOMEM;
	goto out_fail;
out_oversize:
	if (net_ratelimit())
		pr_info("Oversized IP packet from %pI4\n", &qp->saddr);
out_fail:
	IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
	return err;
}

下面貼一張圖,主要處理是將收到的分片指針指向分片隊列首部,因爲重組完成後就會把重組好的報文還給協議棧繼續處理,這時候分片skb指針將由原先的skb分片指向重組skb首部

IPv4分片重組就是以上這些內容,代碼雖然很多但是邏輯不是很複雜,只要理解了分片隊列、垃圾回收隊列(lru隊列)的組織結構再結合具體的代碼分析就能夠搞清了。第一篇博客給的那張關於分片隊列、哈希表的、lru表的邏輯圖其實就是整個重組子系統的縮影,多看看那個。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章