网络收包流程-收包函数__netif_receive_skb的核心函数__netif_receive_skb_core(三)

25 篇文章 7 订阅
12 篇文章 9 订阅

调用关系: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;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值