美文网首页
Linux网络协议栈7--macvlan

Linux网络协议栈7--macvlan

作者: 苏苏林 | 来源:发表于2020-10-25 20:52 被阅读0次

    macvlan是linux的一种虚拟网络接口,macvlan 允许你在主机的一个网络接口上配置多个虚拟的网络接口,这些网络 interface 有自己独立的 mac 地址,也可以配置上 ip 地址进行通信。macvlan 下的虚拟机或者容器网络和主机在同一个网段中,共享同一个广播域。
    macvlan 和 bridge 比较相似,但因为它省去了 bridge 的存在,所以配置和调试起来比较简单,而且效率也相对高。除此之外,macvlan 自身也完美支持 VLAN。

    macvlan 虚拟网卡设备包括5种模式:
    private 模式:在这种模式下,macvlan设备不能接受寄生在同一个物理网卡的其他macvlan设备的数据包,即使是其他macvlan设备通过物理网卡发送出去并通过hairpin设备返回的包。
    vepa 模式:在这种模式下,macvlan设备不能直接接受寄生在同一个物理网卡的其他macvlan设备的数据包,但是其他macvlan设备可以将数据包通过物理网卡发送出去,然后通过hairpin设备返回的给其他macvlan设备。
    passthru 模式:在这种模式下,每一个物理设备只能寄生一个macvlan设备
    bridge 模式:在这种模式下,寄生在同一个物理设备的macvlan设备可以直接通讯,不需要外接的hairpin设备帮助。
    source 模式: 在这种模式下,寄生在物理设备的这类macvlan设备,只能接受指定的源 mac source的数据包,其他数据包都不接受。

    macvlan在协议栈中两个重要的数据结构:
    创建macvlan接口的时候,struct macvlan_dev 会作为macvlan接口设备数据结构net_device的私有数据结构创建(netdev_priv(dev)获取)保存单个macvlan接口的信息。
    同时会为其宿主接口(如果是在其上创建的第一个macvlan接口)的net_device挂载特殊设备接收处理函数rx_handler=macvlan_handle_frame,以及这个函数需要用到的参数rx_handler_data,就是macvlan_port。保存的是这个宿主接口以及其下的macvlan接口的整体信息,最重要的当然是查找报文dmac所对应的macvlan接口。
    所有相关处理都在macvlan_common_newlink函数中。

    struct macvlan_port {
        struct net_device   *dev;
        struct hlist_head   vlan_hash[MACVLAN_HASH_SIZE]; // macvlan设备macvlan_dev结构hash表,用于查找
        struct list_head    vlans; // macvlan设备macvlan_dev结构链表,用于遍历
        struct rcu_head     rcu;
        struct sk_buff_head bc_queue;   // 广播报文队列
    
        struct work_struct  bc_work;    // 广播报文处理任务进程
        bool            passthru;    
        int         count;
        struct hlist_head   vlan_source_hash[MACVLAN_HASH_SIZE]; // source模式用到
        DECLARE_BITMAP(mc_filter, MACVLAN_MC_FILTER_SZ);
    };
    
    
    struct macvlan_dev {
        struct net_device   *dev;      //macvlan网卡设备回指
        struct list_head    list;
        struct hlist_node   hlist;     
        struct macvlan_port *port;    // struct macvlan_port 回指
        struct net_device   *lowerdev;  // 宿主结构设备 回指
        void            *fwd_priv;           // 物理网卡支持硬件加速时用到
        struct vlan_pcpu_stats __percpu *pcpu_stats;
    
        DECLARE_BITMAP(mc_filter, MACVLAN_MC_FILTER_SZ);
    
        netdev_features_t   set_features;
        enum macvlan_mode   mode;
        u16         flags;
        /* This array tracks active taps. */
        struct macvtap_queue    __rcu *taps[MAX_MACVTAP_QUEUES];
        /* This list tracks all taps (both enabled and disabled) */
        struct list_head    queue_list;
        int         numvtaps;
        int         numqueues;
        netdev_features_t   tap_features;
        int         minor;
        int         nest_level;
    #ifdef CONFIG_NET_POLL_CONTROLLER
        struct netpoll      *netpoll;
    #endif
        unsigned int        macaddr_count;
    };
    

    宿主接口接收函数,macvlan_handle_frame。

    
    /* called under rcu_read_lock() from netif_receive_skb */
    static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb)
    {
        struct macvlan_port *port;
        struct sk_buff *skb = *pskb;
        const struct ethhdr *eth = eth_hdr(skb);
        const struct macvlan_dev *vlan;
        const struct macvlan_dev *src;
        struct net_device *dev;
        unsigned int len = 0;
        int ret;
        rx_handler_result_t handle_res;
    
        port = macvlan_port_get_rcu(skb->dev);
        // 宿主接口接收到的广播报文,有可能是外部设备发送的,也可能是内部设备发送的,如外部设备hairpin模式返回的
        if (is_multicast_ether_addr(eth->h_dest)) {
            unsigned int hash;
    
            skb = ip_check_defrag(dev_net(skb->dev), skb, IP_DEFRAG_MACVLAN);
            if (!skb)
                return RX_HANDLER_CONSUMED;
            *pskb = skb;
            eth = eth_hdr(skb);
            // source模式,不管是广播还是单播,无脑根据smac匹配接收
            macvlan_forward_source(skb, port, eth->h_source);
            src = macvlan_hash_lookup(port, eth->h_source);
            // private 模式不允许接收本地的接口(同一宿主接口派生出来的)发出的报文,passthru 模式只有一个派生接口,
            // 这两种类型等同于只向自己发,不需要入队列了。
            if (src && src->mode != MACVLAN_MODE_VEPA &&
                src->mode != MACVLAN_MODE_BRIDGE) {
                /* forward to original port. */
                vlan = src;
                ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?:
                      netif_rx(skb);
                handle_res = RX_HANDLER_CONSUMED;
                goto out;
            }
    
            hash = mc_hash(NULL, eth->h_dest);
            if (test_bit(hash, port->mc_filter))
                macvlan_broadcast_enqueue(port, src, skb);
    
            return RX_HANDLER_PASS;
        }
        /*source模式,不管是广播还是单播,无脑根据smac匹配接收。
          而且不影响正常根据mac地址匹配做转发的流程。
          所以这里如果smac 和 dmac同时和一个source类型的接口匹配了,岂不是接收了两份??但实际上没有,需要仔细看看什么原因。
        */ 
        macvlan_forward_source(skb, port, eth->h_source);
        if (port->passthru)
            // passthru 模式只有一个派生接口,直接取链表中第一个数据
            vlan = list_first_or_null_rcu(&port->vlans,
                              struct macvlan_dev, list);
        else
            // 其它在hash表中查
            vlan = macvlan_hash_lookup(port, eth->h_dest);
        if (vlan == NULL)
            return RX_HANDLER_PASS;
    
        dev = vlan->dev;
        if (unlikely(!(dev->flags & IFF_UP))) {
            kfree_skb(skb);
            return RX_HANDLER_CONSUMED;
        }
        len = skb->len + ETH_HLEN;
        skb = skb_share_check(skb, GFP_ATOMIC);
        if (!skb) {
            ret = NET_RX_DROP;
            handle_res = RX_HANDLER_CONSUMED;
            goto out;
        }
    
        *pskb = skb;
        skb->dev = dev;
        skb->pkt_type = PACKET_HOST;
    
        ret = NET_RX_SUCCESS;
        // 单播,修改了后skb->dev为macvlan接口,返回RX_HANDLER_ANOTHER,
        // __netif_receive_skb_core 会重走自己的流程,等于在macvlan接口上再走一遍协议栈。
        handle_res = RX_HANDLER_ANOTHER;
    out:
        macvlan_count_rx(vlan, len, ret == NET_RX_SUCCESS, false);
        return handle_res;
    }
    

    macvlan接口发包流程

    
    static netdev_tx_t macvlan_start_xmit(struct sk_buff *skb,
                          struct net_device *dev)
    {
        unsigned int len = skb->len;
        int ret;
        struct macvlan_dev *vlan = netdev_priv(dev);
    
        if (unlikely(netpoll_tx_running(dev)))
            return macvlan_netpoll_send_skb(vlan, skb);
        // 硬件优化的代码,不用管
        if (vlan->fwd_priv) {
            skb->dev = vlan->lowerdev;
            ret = dev_queue_xmit_accel(skb, vlan->fwd_priv);
        } else {
            // 实际调用 macvlan_queue_xmit
            ret = macvlan_queue_xmit(skb, dev);
        }
    
        if (likely(ret == NET_XMIT_SUCCESS || ret == NET_XMIT_CN)) {
            struct vlan_pcpu_stats *pcpu_stats;
    
            pcpu_stats = this_cpu_ptr(vlan->pcpu_stats);
            u64_stats_update_begin(&pcpu_stats->syncp);
            pcpu_stats->tx_packets++;
            pcpu_stats->tx_bytes += len;
            u64_stats_update_end(&pcpu_stats->syncp);
        } else {
            this_cpu_inc(vlan->pcpu_stats->tx_dropped);
        }
        return ret;
    }
    
    
    static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
    {
        const struct macvlan_dev *vlan = netdev_priv(dev);
        const struct macvlan_port *port = vlan->port;
        const struct macvlan_dev *dest;
        // bridge模式,如果报文是发向同一宿主接口派生的bridge模式的macvlan接口,则转发,其它都被禁止。
        if (vlan->mode == MACVLAN_MODE_BRIDGE) {
            const struct ethhdr *eth = (void *)skb->data;
    
            /* send to other bridge ports directly */
            // 可以看到,只有bridge模式能向 bridge模式的派生口发广播报文
            if (is_multicast_ether_addr(eth->h_dest)) {
                macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);
                goto xmit_world;
            }
    
            dest = macvlan_hash_lookup(port, eth->h_dest);
            // 可以看到,只有bridge模式能向 bridge模式的派生口发单播报文
            if (dest && dest->mode == MACVLAN_MODE_BRIDGE) {
                /* send to lowerdev first for its network taps */
                dev_forward_skb(vlan->lowerdev, skb);
    
                return NET_XMIT_SUCCESS;
            }
        }
    
    xmit_world:
        // 其它情况,直接走宿主物理口发送,所以macvlan接口的性能还是很高的,除了上面的简单的查表,不会做额外的封装,很接近物理口。
        skb->dev = vlan->lowerdev;
        return dev_queue_xmit(skb);
    }
    

    相关文章

      网友评论

          本文标题:Linux网络协议栈7--macvlan

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