美文网首页
kernel网络之协议栈入口

kernel网络之协议栈入口

作者: 分享放大价值 | 来源:发表于2020-08-07 22:22 被阅读0次

    报文从网卡接收经过软中断的处理,最终是要进协议栈的,__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网络之协议栈入口

          本文链接:https://www.haomeiwen.com/subject/etiqdktx.html