網絡收包流程-收包函數__netif_receive_skb的核心函數__netif_receive_skb_core(三)

調用關係:netif_receive_skb-->netif_receive_skb-->netif_receive_skb_internal(->__netif_receive_skb)-->__netif_receive_skb_core
1.netif_receive_skb_internal的實現

static int netif_receive_skb_internal(struct sk_buff *skb)
{
	int ret;

	net_timestamp_check(netdev_tstamp_prequeue, skb);//記錄收包時間

	if (skb_defer_rx_timestamp(skb))
		return NET_RX_SUCCESS;

	rcu_read_lock();
/*RPS邏輯處理,現在內核中使用了RPS機制, 將報文分散到各個cpu的接收隊列中進行負載均衡處理*/
#ifdef CONFIG_RPS
	if (static_key_false(&rps_needed)) {
		struct rps_dev_flow voidflow, *rflow = &voidflow;
		int cpu = get_rps_cpu(skb->dev, skb, &rflow);//從一個給定skb的rx隊列的rps map中返回一個目的cpu

		if (cpu >= 0) {
			ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);//將此skb放到獲取的cpu的接收隊列上
			rcu_read_unlock();
			return ret;
		}
	}
#endif
	ret = __netif_receive_skb(skb);//最終調用__netif_receive_skb_core
	rcu_read_unlock();
	return ret;
}

2.__netif_receive_skb_core的實現
    瞭解此函數,首先需要知道ptype_base和ptype_all變量,二個結變量的定義如下:"net/core/dev.c"
struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly; //PTYPE_HASH_SIZE路的hash鏈表
struct list_head ptype_all __read_mostly;    /* Taps 雙向鏈表 */
這二個都是list_head變量,list_head 鏈表上掛了很多packet_type數據結構,此結構體是對應於具體協議的實例,packet_type數據結構如下:

struct packet_type {
    __be16          type;   /*  type指定了協議的標識符,處理程序func會使用該標識符 ,保存了三層協議類型,ETH_P_IP、ETH_P_ARP等等 */
    struct net_device   *dev;   /* NULL指針表示該處理程序對系統中所有網絡設備都有效      */
/* func是該結構的主要成員。它是一個指向網絡層函數的指針,如果分組的類型適當,將其傳遞給該函數。其中可能的處理程序就是ip_rcv */
    int         (*func) (struct sk_buff *,
                     struct net_device *,
                     struct packet_type *,
                     struct net_device *);
    bool            (*id_match)(struct packet_type *ptype,
                        struct sock *sk);
    void            *af_packet_priv;
    struct list_head    list;
    RH_KABI_RESERVE(1)
    RH_KABI_RESERVE(2)
    RH_KABI_RESERVE(3)
    RH_KABI_RESERVE(4)
};

         註冊packet_type的api主要在相應的協議初始化時,通過dev_add_pack函數實現,把packet_type結構掛在對應協議的的list_head上面,移除則採用dev_remove_pack。
        dev_add_pack函數除了將packet_type結構掛在對應協議的的list_head上面,還有個重要的功能就是通過函數ptype_head獲取對應協議的list_head,實現如下:

static inline struct list_head *ptype_head(const struct packet_type *pt)
{
        if (pt->type == htons(ETH_P_ALL)) //type爲ETH_P_ALL時,則掛在ptype_all上面
                return &ptype_all;
        else
                return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK]; //否則,掛在ptype_base[type&15]上面,即對應協議的list_head上面。
}

        好了,上面的可以一帶而過了,下面主要看看__netif_receive_skb_core的實現,其實就是對上面註冊的結構體packet_type的處理。
        此函數主要以下四個功能:
        1)處理 ptype_all 上所有的 packet_type->func()   ,典型場景就是抓包
        2)  處理vlan報文
        3)rx_handler函數處理,例如網橋
        4)處理ptype_base上所有的 packet_type->func() ,數據包傳遞給上層協議層處理,例如ip_rcv函數;

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc) // 將skb傳遞到上層 
{
	struct packet_type *ptype, *pt_prev;
	rx_handler_func_t *rx_handler;
	struct net_device *orig_dev;
	struct net_device *null_or_dev;
	bool deliver_exact = false;//默認不精確傳遞
	int ret = NET_RX_DROP;//默認收報失敗
	__be16 type;

	net_timestamp_check(!netdev_tstamp_prequeue, skb);//記錄收包時間,netdev_tstamp_prequeue爲0,表示可能有包延遲 

	trace_netif_receive_skb(skb);

	orig_dev = skb->dev;//記錄收包設備 

	skb_reset_network_header(skb);//重置network header,此時skb指向IP頭(沒有vlan的情況下)
	if (!skb_transport_header_was_set(skb))
		skb_reset_transport_header(skb);
	skb_reset_mac_len(skb);
  
     // 留下一個節點,最後一次向上層傳遞時,不需要再inc引用,回調中會free
 這樣相當於少調用了一次free

     
	pt_prev = NULL;

another_round:
	skb->skb_iif = skb->dev->ifindex;//設置接收設備索引號 

	__this_cpu_inc(softnet_data.processed);//處理包數統計 

	if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
	    skb->protocol == cpu_to_be16(ETH_P_8021AD)) {//vxlan報文處理,剝除vxlan頭
		skb = skb_vlan_untag(skb);//剝除vxlan頭
		if (unlikely(!skb))
			goto out;
	}

#ifdef CONFIG_NET_CLS_ACT
	if (skb->tc_verd & TC_NCLS) {
		skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
		goto ncls;
	}
#endif

	if (pfmemalloc)////此類報文不允許ptype_all處理,即tcpdump也抓不到
		goto skip_taps;
       //先處理 ptype_all 上所有的 packet_type->func()           
       //所有包都會調func,對性能影響嚴重!所有有的鉤子是隨模塊加載掛上的。
	list_for_each_entry_rcu(ptype, &ptype_all, list) {//遍歷ptye_all鏈表
		if (!ptype->dev || ptype->dev == skb->dev) {//上面的paket_type.type 爲 ETH_P_ALL,典型場景就是tcpdump抓包所使用的協議
			if (pt_prev)//pt_prev提高效率
				ret = deliver_skb(skb, pt_prev, orig_dev);//此函數最終調用paket_type.func()
			pt_prev = ptype;
		}	
	}

skip_taps:
#ifdef CONFIG_NET_CLS_ACT
	if (static_key_false(&ingress_needed)) {
		skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
		if (!skb)
			goto out;
	}

	skb->tc_verd = 0;
ncls:
#endif
	if (pfmemalloc && !skb_pfmemalloc_protocol(skb))//不支持使用pfmemalloc 
		goto drop;

	if (skb_vlan_tag_present(skb)) {// 如果是vlan包 
		if (pt_prev) {/* 處理pt_prev */
			ret = deliver_skb(skb, pt_prev, orig_dev);
			pt_prev = NULL;
		}
		if (vlan_do_receive(&skb))/* 根據實際的vlan設備調整信息,再走一遍 */
			goto another_round;
		else if (unlikely(!skb))
			goto out;
	}
/*如果一個dev被添加到一個bridge(做爲bridge的一個接口),這個接口設備的rx_handler將被設置爲br_handle_frame函數,這是在br_add_if函數中設置的,而br_add_if (net/bridge/br_if.c)是在向網橋設備上添加接口時設置的。進入br_handle_frame也就進入了bridge的邏輯代碼。*/
	rx_handler = rcu_dereference(skb->dev->rx_handler);/* 如果有註冊handler,那麼調用,比如網橋模塊 */
	if (rx_handler) {
		if (pt_prev) {
			ret = deliver_skb(skb, pt_prev, orig_dev);
			pt_prev = NULL;
		}
		switch (rx_handler(&skb)) {
		case RX_HANDLER_CONSUMED:/* 已處理,無需進一步處理 */
			ret = NET_RX_SUCCESS;
			goto out;
		case RX_HANDLER_ANOTHER:/* 修改了skb->dev,在處理一次 */
			goto another_round;
		case RX_HANDLER_EXACT:/* 精確傳遞到ptype->dev == skb->dev */
			deliver_exact = true;
		case RX_HANDLER_PASS:
			break;
		default:
			BUG();
		}
	}

	if (unlikely(skb_vlan_tag_present(skb))) {/* 還有vlan標記,說明找不到vlanid對應的設備 */
		if (skb_vlan_tag_get_id(skb))/* 存在vlanid,則判定是到其他設備的包 */
			skb->pkt_type = PACKET_OTHERHOST;
		/* Note: we might in the future use prio bits
		 * and set skb->priority like in vlan_do_receive()
		 * For the time being, just ignore Priority Code Point
		 */
		skb->vlan_tci = 0;
	}

	/* deliver only exact match when indicated */
	null_or_dev = deliver_exact ? skb->dev : NULL;//指定精確傳遞的話,就精確傳遞,否則向未指定設備的指定協議全局發送一份

	type = skb->protocol;/* 設置三層協議,下面提交都是按照三層協議提交的 */
	list_for_each_entry_rcu(ptype,&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
		if (ptype->type == type &&
		    (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
		     ptype->dev == orig_dev)) {
			if (pt_prev)
				ret = deliver_skb(skb, pt_prev, orig_dev);//上層傳遞
			pt_prev = ptype;
		}
	}
	if (pt_prev) {
		if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
			goto drop;
		else
                //使用pt_prev這裏就不需要deliver_skb來inc應用數了,  func執行內部會free,減少了一次skb_free

        
			ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);/* 傳遞到上層*/
	} else {
drop:
		if (!deliver_exact)
			atomic_long_inc(&skb->dev->rx_dropped);//網卡丟包計數
		else
			atomic_long_inc(&skb->dev->rx_nohandler);
		kfree_skb(skb);
		/* Jamal, now you will not able to escape explaining
		 * me how you were going to use this. :-)
		 */
		ret = NET_RX_DROP;
	}
out:
	return ret;
}

 

發佈了85 篇原創文章 · 獲贊 22 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章