kernel网络之协议栈入口

报文从网卡接收经过软中断的处理,最终是要进协议栈的,__netif_receive_skb_core就是这个入口,这个函数中做了vlan的处理,抓包处理,ovs/bridge等二层转发处理和分发报文(arp_rcv, ip_rcv等)处理。

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
    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);

    trace_netif_receive_skb(skb);

    orig_dev = skb->dev;

    skb_reset_network_header(skb);
    if (!skb_transport_header_was_set(skb))
        skb_reset_transport_header(skb);
    skb_reset_mac_len(skb);

    pt_prev = NULL;
    比如对于vlan报文处理,从父设备接收报文,经过vlan_do_receive找到vlan子接  
    口后,需要重新从此开始
another_round:
    skb->skb_iif = skb->dev->ifindex;

    __this_cpu_inc(softnet_data.processed);
    vlan报文或者QinQ报文,调用skb_vlan_untag剥掉vlan头
    if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
        skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
        skb = skb_vlan_untag(skb);
        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)
        goto skip_taps;
  处理抓包程序,比如tcpdump。以下两种情况才会抓包:
  a. ptype->dev 为空,即抓取所有设备的数据, 比如 tcpdump -i any
  b. ptype->dev == skb->dev 指定了抓包设备,只有从这个设备收到的报文才会抓取,比如 tcpdump -i eth0。
 如果是vlan报文的话,可以在vlan子接口的父设备 skb->dev 上抓到带vlan的报文。
 后面经过vlan处理,找到真正的vlan子接口 vlan_dev,会重新走此,就可以在vlan
 子接口上抓到不带vlan的报文。
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }

skip_taps:
#ifdef CONFIG_NET_CLS_ACT
    skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
    if (!skb)
        goto out;
ncls:
#endif

    if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
        goto drop;
    处理vlan报文
    if (vlan_tx_tag_present(skb)) {
        if (pt_prev) {
            ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = NULL;
        }
       此时 skb->dev 中的dev是vlan子接口的父设备,根据vlanid到dev查找
       vlan_dev,并赋值给skb->dev,从 another_round 重新开始协议栈处理
        if (vlan_do_receive(&skb))
            goto another_round;
        else if (unlikely(!skb))
            goto out;
    }
   linux bridge, ovs, bond, macvlan等虚拟设备的处理
    rx_handler = rcu_dereference(skb->dev->rx_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:
            goto another_round;
        case RX_HANDLER_EXACT:
            deliver_exact = true;
        case RX_HANDLER_PASS:
            break;
        default:
            BUG();
        }
    }

    if (unlikely(vlan_tx_tag_present(skb))) {
        if (vlan_tx_tag_get_id(skb))
            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;

    根据 protocol 开始调用不同的hook函数,比如arp_rcv, ip_rcv等。
    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
            ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
    } else {
drop:
        atomic_long_inc(&skb->dev->rx_dropped);
        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;
}

在__netif_receive_skb_core函数中,在for循环时会用到pt_prev,为什么要使用它呢?代码如下:

pt_prev=NULL;   
list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
}

想要理解pt_prev的存在,需要知道下面知识:
a. 分配skb时,users置为1

alloc_skb
  atomic_set(&skb->users, 1);

b. 释放skb时,如果有多个模块引用skb,则users减一,如果users为1了,则真正释放skb

void kfree_skb(struct sk_buff *skb)
{
    if (unlikely(!skb))
        return;
      //如果users为1说明只有一个模块引用这个skb,可以调用__kfree_skb安全释放skb
    if (likely(atomic_read(&skb->users) == 1))
        smp_rmb();
    //否则,调用atomic_dec_and_test将users减一并判断新值是否为0.
    //users为1已经在上面判断过,所以这个判断时users肯定是大于1的,即有多个模块引用skb
    else if (likely(!atomic_dec_and_test(&skb->users)))
        return;
    trace_kfree_skb(skb, __builtin_return_address(0));
    __kfree_skb(skb);
}

c. 调用deliver_skb上送时,users增加1,pt_prev->func执行完会调用kfree_skb,将users减1.

deliver_skb
    if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
        return -ENOMEM;
    atomic_inc(&skb->users);
    return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);

netif_receive_skb函数执行结束后,必须保证真正释放skb,否则就会造成内存泄漏
如果没有pt_prev的存在,循环结束后,得显试调用kfree_skb,代码如下:

list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (!ptype->dev || ptype->dev == skb->dev) {
   //deliver_skb中会对users加1,在func函数中会调用kfree_skb再减1,
                                //所以deliver_skb执行完,users还是1
                ret = deliver_skb(skb, pt_prev, orig_dev);
kfree_skb(skb); //显试调用,此时users为1,只有当前模块引用skb,所以就可以真正释放skb

假如ptype_all只有一个元素,则需要调用两次kfree_skb,第一次是在pt_prev->func中,第二次是上面的显试调用。

如果使用pt_prev,

pt_prev=NULL;
list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
}
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
} else {
kfree_skb(skb);

假如ptype_all只有一个元素,则只需要调用一次kfree_skb。
在for循环中,因为pt_prev为NULL,所以不会调用deliver_skb,
退出for循环后,if判断pt_prev不为空,再调用func,在func函数中调用kfree_skb时,就可以真正释放了。
假如ptype_all一个元素都没有,则会直接调用kfree_skb进行释放。
这种情况下,就是因为少调了一次deliver_skb。

也可参考:kernel网络之协议栈入口 - 简书 (jianshu.com)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值