天天看點

Linux網絡協定棧學習(1):進入本地封包處理流程1. NF_IP_LOCAL_IN這部分的作用2. ip_local_deliver接口3. ip_local_deliver_finish接口

Linux網絡協定棧學習(1):進入本地封包處理流程1. NF_IP_LOCAL_IN這部分的作用2. ip_local_deliver接口3. ip_local_deliver_finish接口
聲明:未經本人同意,嚴禁一切形式轉載!!!

文章目錄

  • 1. NF_IP_LOCAL_IN這部分的作用
  • 2. ip_local_deliver接口
    • 2.1 分片重組子產品~整體架構
    • 2.2 分片重組時資料組織結構
    • 2.3 分片封包重組完畢後的資料結構
    • 2.4 相關函數
      • 2.4.1 ip_local_deliver()函數
      • 2.4.2 ip_defrag()函數
      • 2.4.3 ip_find()函數
      • 2.4.4 ip_frag_queue()函數
      • 2.4.5 ip_frag_reasm()函數
  • 3. ip_local_deliver_finish接口
    • 3.1 實作邏輯
    • 3.1 相關函數
      • 3.1.1 ip_local_deliver_finish()

本篇文章主要介紹的内容為NF_IP_LOCAL_IN鈎子前後的處理邏輯。

1. NF_IP_LOCAL_IN這部分的作用

Linux網絡協定棧學習(1):進入本地封包處理流程1. NF_IP_LOCAL_IN這部分的作用2. ip_local_deliver接口3. ip_local_deliver_finish接口

這部分的主要作用是:接收并處理發往本地的IP封包,由于這裡依然屬于IP層,處理的邏輯比較簡單:

  • IP封包分片重組
  • 原始套接字相關處理
  • 将封包去除頭部後,傳遞給傳輸層處理

下面會這幾個功能做一個簡單的介紹。

2. ip_local_deliver接口

該函數的作用:

  • IP封包的分片重組,函數接口為:

    ip_defrag()

  • 進入NF_IP_LOCAL_IN HOOK點
  • 在HOOK點之後進入

    ip_local_deliver_finish()

此函數的核心功能便是:傳說中的IP封包分片重組。

2.1 分片重組子產品~整體架構

Linux網絡協定棧學習(1):進入本地封包處理流程1. NF_IP_LOCAL_IN這部分的作用2. ip_local_deliver接口3. ip_local_deliver_finish接口

2.2 分片重組時資料組織結構

Linux網絡協定棧學習(1):進入本地封包處理流程1. NF_IP_LOCAL_IN這部分的作用2. ip_local_deliver接口3. ip_local_deliver_finish接口

2.3 分片封包重組完畢後的資料結構

Linux網絡協定棧學習(1):進入本地封包處理流程1. NF_IP_LOCAL_IN這部分的作用2. ip_local_deliver接口3. ip_local_deliver_finish接口

2.4 相關函數

分片重組功能涉及的函數有:

函數名稱 作用
ip_defrag 分片重組封包核心處理函數,其他相關函數皆在此函數中直接或者間接調用
ip_find 查找分片封包對應的隊列頭,如果沒有找到,則建立一個分片封包隊列頭部節點(描述資訊)
ip_frag_create 根據(sip,dip,id,proto)建立一個分片封包頭部節點并插入到隊列中
ip_frag_intern 将建立的分片封包頭部節點插入到全局分片封包哈希表中
ip_frag_queue 将分片放入插入相應分片隊列的合适位置(根據封包中的偏移量進行排序)
ip_frag_reasm 分片封包重組。(不容易了解!!!)
ipq_put 無人引用時,釋放分片封包頭部節點以及緩存的所有分片封包
ip_frag_destroy 釋放分片封包頭部節點以及緩存的所有分片封包
ip_evictor 當緩存的分片封包總大小超過設定的門檻值時,根據LRU釋放一部分緩存的分片封包空間
ipq_kill 設定标記位,聲明此節點即将被釋放

下面對上表中的幾個重要函數做一個說明。可以結合上面的處理邏輯和數組組織結構一起更容易了解。

2.4.1 ip_local_deliver()函數

/*
 * 	Deliver IP Packets to the higher protocol layers.
 */ 
int ip_local_deliver(struct sk_buff *skb)
{
	/*
	 *	Reassemble IP fragments.
	 */

	if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
		skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER);/*分片重組之後的封包*/
		if (!skb)
			return 0;
	}

	return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
		       ip_local_deliver_finish);
}
           

2.4.2 ip_defrag()函數

此函數是分片重組的核心函數接口.

struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user)
{
	struct iphdr *iph = skb->nh.iph;
	struct ipq *qp;
	struct net_device *dev;
	
	IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);

	/* Start by cleaning up the memory. */
	if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)/*緩存的封包超過系統分片門檻值時釋放一部分緩存*/
		ip_evictor();

	dev = skb->dev;

	/* Lookup (or create) queue header */
	if ((qp = ip_find(iph, user)) != NULL) {/*查找分片節點,隻有出錯時才可能為null*/
		struct sk_buff *ret = NULL;

		spin_lock(&qp->lock);

		ip_frag_queue(qp, skb);/*将此分片封包根據序号插入到分片隊列中*/

		if (qp->last_in == (FIRST_IN|LAST_IN) &&
		    qp->meat == qp->len)
			ret = ip_frag_reasm(qp, dev);/*分片封包重組*/

		spin_unlock(&qp->lock);
		ipq_put(qp, NULL);/**/
		return ret;
	}

	IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
	kfree_skb(skb);
	return NULL;
}
           

2.4.3 ip_find()函數

/* Find the correct entry in the "incomplete datagrams" queue for
 * this IP datagram, and create new one, if nothing is found.
 */
static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
{
	__u16 id = iph->id;
	__u32 saddr = iph->saddr;
	__u32 daddr = iph->daddr;
	__u8 protocol = iph->protocol;
	unsigned int hash = ipqhashfn(id, saddr, daddr, protocol);
	struct ipq *qp;

	read_lock(&ipfrag_lock);
	for(qp = ipq_hash[hash]; qp; qp = qp->next) {
		if(qp->id == id		&&
		   qp->saddr == saddr	&&
		   qp->daddr == daddr	&&
		   qp->protocol == protocol &&
		   qp->user == user) {/*已經緩存過此五元組的分片封包,直接傳回*/
			atomic_inc(&qp->refcnt);
			read_unlock(&ipfrag_lock);
			return qp;
		}
	}
	read_unlock(&ipfrag_lock);

	return ip_frag_create(hash, iph, user);/*未緩存過此類分片封包,則建立分片緩存*/
}
           

2.4.4 ip_frag_queue()函數

/* Add new segment to existing queue. */
static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)/*每一個分片封包一個隊列,在隊列中根據偏移進行排序*/
{
	struct sk_buff *prev, *next;
	int flags, offset;
	int ihl, end;

	if (qp->last_in & COMPLETE)/*此qp即将被釋放*/
		goto err;

 	offset = ntohs(skb->nh.iph->frag_off);
	flags = offset & ~IP_OFFSET;
	offset &= IP_OFFSET;
	offset <<= 3;		/* offset is in 8-byte chunks */
 	ihl = skb->nh.iph->ihl * 4;

	/* Determine the position of this fragment. */
 	end = offset + skb->len - ihl;/*該分片封包在原封包中的起始位置*/

	/* 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 corrrupted.此段已損壞
		 */
		if (end < qp->len ||
		    ((qp->last_in & LAST_IN) && end != qp->len))/*格式檢查*/
			goto err;
		qp->last_in |= LAST_IN;/*分片全部收到标記位*/
		qp->len = end;/*所有的分片封包總長度*/
	} else {
		if (end&7) {/*必須是8的倍數??? why*/
			end &= ~7;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
				skb->ip_summed = CHECKSUM_NONE;
		}
		if (end > qp->len) {/*qp->len可以當做是一個滑動視窗*/
			/* Some bits beyond end -> corruption. */
			if (qp->last_in & LAST_IN)/*不是最後一個封包*/
				goto err;
			qp->len = end;
		}
	}
	if (end == offset)
		goto err;

	if (pskb_pull(skb, ihl) == NULL)
		goto err;
	if (pskb_trim(skb, end-offset))
		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 = NULL;
	for(next = qp->fragments; next != NULL; next = next->next) {/*為分片封包在隊列中找到合适的位置*/
		if (FRAG_CB(next)->offset >= offset)/*1、2、3、4、5、6、8、9*/
			break;	/* bingo! */		/*------------------7-----*/
		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) {
		int i = (FRAG_CB(prev)->offset + prev->len) - offset;
		/*i<0時說明存在空洞,如何處理呢?*/
		
		if (i > 0) {/*兩個分片封包存在重疊部分,則略過偏移部分*/
			offset += i;
			if (end <= offset)
				goto err;
			if (!pskb_pull(skb, i))
				goto err;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
				skb->ip_summed = CHECKSUM_NONE;
		}
	}

	while (next && FRAG_CB(next)->offset < end) {/*目前封包與後一個分片封包有重疊部分*/
		int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes *//*重疊部分大小*/

		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->meat -= i;
			if (next->ip_summed != CHECKSUM_UNNECESSARY)
				next->ip_summed = CHECKSUM_NONE;
			break;
		} else {/*重複收到同一個分片封包/或者比已收到的封包大,使用新封包替代舊封包,釋放舊封包空間*/
			struct sk_buff *free_it = next;

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

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

			qp->meat -= free_it->len;
			frag_kfree_skb(free_it, NULL);
		}
	}

	FRAG_CB(skb)->offset = offset;

	/* Insert this fragment in the chain of fragments. */
	skb->next = next;
	if (prev)
		prev->next = skb;
	else
		qp->fragments = skb;

 	if (skb->dev)
 		qp->iif = skb->dev->ifindex;
	skb->dev = NULL;
	qp->stamp = skb->stamp;/*更新時間戳*/
	qp->meat += skb->len;
	atomic_add(skb->truesize, &ip_frag_mem);
	if (offset == 0)
		qp->last_in |= FIRST_IN;

	write_lock(&ipfrag_lock);
	list_move_tail(&qp->lru_list, &ipq_lru_list);/*維護LRU隊列,将該節點移到末尾*/
	write_unlock(&ipfrag_lock);

	return;

err:
	kfree_skb(skb);
}
           

2.4.5 ip_frag_reasm()函數

/* Build a new IP datagram from all its fragments. */
/*分片封包重組*/
/*
*需要特别說明的是:這個函數并沒有直接将多個分片封包重組成一個大封包
*而是利用了
*/
static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)
{
	struct iphdr *iph;
	struct sk_buff *fp, *head = qp->fragments;
	int len;
	int ihlen;

	ipq_kill(qp);/*設定标記位,表明此分片封包空間即将被釋放(無需區分空間不足還是接收完畢)*/

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

	/* Allocate a new buffer for the datagram. */
	ihlen = head->nh.iph->ihl*4;
	len = ihlen + qp->len;/*分片封包總長度+一個IP頭部長度*/

	if(len > 65535)/*封包最長為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. */
	if (skb_shinfo(head)->frag_list) {/*如果第一個分片封包的skb不是連續的,需要特别處理*/
		struct sk_buff *clone;
		int i, plen = 0;

		if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
			goto out_nomem;
		clone->next = head->next;
		head->next = clone;
		skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
		skb_shinfo(head)->frag_list = NULL;
		for (i=0; i<skb_shinfo(head)->nr_frags; i++)
			plen += skb_shinfo(head)->frags[i].size;
		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, &ip_frag_mem);
	}

	skb_shinfo(head)->frag_list = head->next;
	skb_push(head, head->data - head->nh.raw);
	atomic_sub(head->truesize, &ip_frag_mem);

	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_HW)
			head->csum = csum_add(head->csum, fp->csum);
		head->truesize += fp->truesize;
		atomic_sub(fp->truesize, &ip_frag_mem);
	}

	head->next = NULL;
	head->dev = dev;
	head->stamp = qp->stamp;/*時間戳,為最後一個封包的時間戳*/

	iph = head->nh.iph;
	iph->frag_off = 0;
	iph->tot_len = htons(len);
	IP_INC_STATS_BH(IPSTATS_MIB_REASMOKS);
	qp->fragments = NULL;/*這裡指針已經釋放了!!!!!!也就是說分片重組的封包在這裡并沒有被釋放,而是在其他地方釋放*/
	return head;

out_nomem:
 	NETDEBUG(if (net_ratelimit())
	         printk(KERN_ERR 
			"IP: queue_glue: no memory for gluing queue %p\n",
			qp));
	goto out_fail;
out_oversize:
	if (net_ratelimit())
		printk(KERN_INFO
			"Oversized IP packet from %d.%d.%d.%d.\n",
			NIPQUAD(qp->saddr));
out_fail:
	IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
	return NULL;
}
           

3. ip_local_deliver_finish接口

此函數的主要作用包括:

  • IP層封包即将交由傳輸層進行處理,是以先将IP頭部去除;
  • 檢查是否存在原始套接字監控此協定封包,如果有,則複制一份skb,交由所有監控此協定的原始套接字(交給原始套接字處理前需要再添加上IP頭部);
  • 傳遞給相應的傳輸層協定進行後續處理;

如果不考慮傳輸層封包解析處理,則涉及的函數比較少;其中比較多的就是原始套接字相關的函數接口,這部分尚未整理學習,這裡不再詳細說明。我們隻需要知道的是:在這裡有一個原始套接字處理的過程即可。

3.1 實作邏輯

Linux網絡協定棧學習(1):進入本地封包處理流程1. NF_IP_LOCAL_IN這部分的作用2. ip_local_deliver接口3. ip_local_deliver_finish接口
  • 關于“為什麼在傳輸層不支援此協定時,需要再次判斷是否有原始套接字監控??而隻有在沒有被監聽的情況下才能提示“協定不可達”呢???”

就目前我所知道的而言,有一部分封包不需要無法通過監聽固定端口來接收封包,如OSPF協定的hello封包,沒有對應的傳輸層端口。處理這類封包的方式通常是:在應用層通過原始套接字進行擷取,是以這裡在處理時有這麼一個邏輯也就在情理之中了;畢竟該封包已經被正确的接收,隻不過接收的對象比較特殊而已,是以這種情況也不能提示“協定不可達”。

3.1 相關函數

這裡隻介紹

ip_local_deliver_finish()

函數。

3.1.1 ip_local_deliver_finish()

static inline int ip_local_deliver_finish(struct sk_buff *skb)/*分片重組後的封包*/
{
	int ihl = skb->nh.iph->ihl*4;

#ifdef CONFIG_NETFILTER_DEBUG
	nf_debug_ip_local_deliver(skb);
#endif /*CONFIG_NETFILTER_DEBUG*/

	__skb_pull(skb, ihl);/*去ip頭*/

	/* Free reference early: we don't need it any more, and it may
           hold ip_conntrack module loaded indefinitely. */
	nf_reset(skb);

        /* Point into the IP datagram, just past the header. */
        skb->h.raw = skb->data;/*IP資料部分*/

	rcu_read_lock();
	{
		/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
		int protocol = skb->nh.iph->protocol;/*擷取四層協定*/
		int hash;
		struct sock *raw_sk;
		struct net_protocol *ipprot;

	resubmit:
		hash = protocol & (MAX_INET_PROTOS - 1);/*以協定作為hash檢索原始套接字和傳輸層處理接口*/
		raw_sk = sk_head(&raw_v4_htable[hash]);/*擷取原始套接字連結清單中的第一個sock結構*/

		/* If there maybe a raw socket we must check - if not we
		 * don't care less
		 */
		if (raw_sk)/*有原始套接字在監控此協定封包,則複制一份skb給原始套接字*/
			raw_v4_input(skb, skb->nh.iph, hash);

		
		if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {/*擷取傳輸層協定操作結構體*/
			int ret;

			if (!ipprot->no_policy &&
			    !xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
				kfree_skb(skb);
				goto out;
			}
			/*交由傳輸層繼續處理此封包,注意這裡ip頭部已經去除*/
			ret = ipprot->handler(skb);/*常見的有UDP,TCP, ICMP,ESP, AH, IGMP, ... ...*/
			if (ret < 0) {
				protocol = -ret;
				goto resubmit;
			}
			IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
		} else {
			/*這裡為何在沒有原始套接字時才提示不可達呢?
			*因為有一部分封包隻能通過原始套接字接收,如ospf協定的hello封包,沒有接收接口資訊
			*有的直接通過原始套接字将hello封包抓取到應用層進行處理。這裡應該處理的是這種情況!!!
			**/
			if (!raw_sk) {/*此協定不識别,又無原始套接字在監控,傳回協定不可達(好像是協定不可達)*/
				if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
					icmp_send(skb, ICMP_DEST_UNREACH,
						  ICMP_PROT_UNREACH, 0);
				}
			} else
				IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
			kfree_skb(skb);
		}
	}
 out:
	rcu_read_unlock();

	return 0;
}
           

繼續閱讀