macvlan分析

  在 Macvlan 出现之前,一块以太网卡添加多个 IP 地址,却不能添加多个 MAC 地址,即使使用了创建 ethx:y 这样的方式,这些“网卡”的 MAC 地址和 ethx 都是一样的,本质上,它们还是一块网卡,这将限制很多二层的操作。

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

Macvlan vs Bridge

维度BridgeMacvlan
网络层级 工作于二层(数据链路层),需通过主机协议栈转发。 工作于二层,虚拟接口直接关联物理网卡,绕过主机协议栈(默认)。
IP 分配 虚拟设备共享主机的 IP 地址,或通过 Bridge 分配独立 IP。 每个 Macvlan 接口可配置独立 IP,直接属于物理网络子网。
MAC 地址 虚拟设备共享主机物理网卡的 MAC 地址(或由 Bridge 统一管理)。 每个 Macvlan 接口有独立 MAC 地址,可被远程设备视为独立节点。
通信方式 - 虚拟设备之间通过 Bridge 转发数据。
- 与外部通信需通过主机的物理网卡路由或 NAT。

- 虚拟设备直接通过物理网卡与外部通信,无需经过主机协议栈(类似物理网卡直连)。容器流量直接通过物理网卡发送到物理网络,完全绕过宿主机的网络协议栈。

- 支持直接路由到外部网络。

性能 数据需经主机协议栈处理,存在一定性能损耗。 绕过主机协议栈,直接在二层传输,性能更接近物理网卡(尤其适合高吞吐量场景)。
隔离性 虚拟设备属于同一广播域,隔离性依赖三层路由或防火墙。 虚拟设备可通过不同子接口(如不同 Macvlan 类型)实现二层隔离(如 private 模式)。
适用场景 - 容器 / 虚拟机共享主机网络(如 Docker bridge)。
- 需要主机参与数据包处理(如 NAT、过滤)。
- 容器 / 虚拟机需要独立 MAC 地址,直接接入物理网络。
- 高性能网络场景(如裸金属容器、低延迟需求)。
配置复杂度 配置相对简单,支持图形化工具(如 nmclibrctl)。 需手动指定 MAC 地址或模式,配置更依赖命令行(如 ip link add type macvlan)。

选 Bridge 的情况:

  • 需要主机对虚拟设备的流量进行 NAT、防火墙过滤或路由。
  • 虚拟设备需共享主机的网络命名空间(如 Docker 容器的默认网络)。
  • 追求简单配置,使用成熟的工具链(如 Docker、KVM 的默认网络方案)。
  • 需要灵活组网、容器/虚拟机间直接通信,或无法依赖物理网络时(如 Docker 默认网络)。

选 Macvlan 的情况:

  • 虚拟设备需要独立的 MAC 地址,直接加入物理网络(如容器需要与物理机平级的 IP)。
  • 高性能需求,需绕过主机协议栈以减少延迟(如网络密集型应用)。
  • 需要模拟多物理机接入同一网络(如 Kubernetes 节点直接路由场景

 

  • 需要高性能、独立 MAC 地址,且物理网络支持时(如云服务器、物理机部署)。

 

 

 

macvlan 模式  

 

Macvlan 支持多种子接口模式,影响其通信行为:
  1. bridge 模式(默认):
    • 所有 Macvlan 接口共享物理网卡的同一子网,可直接二层通信(类似连接到同一交换机)。
    • 适用于同一子网内的多节点通信。
  2. private 模式:
    • 同一物理网卡上的 Macvlan 接口之间无法二层通信,但均可与外部通信。
    • 提供更强的隔离性,类似不同 VLAN 的接口。
  3. vepa 模式:
    • 所有流量必须通过物理网卡的上游交换机转发(即使是同一主机的接口间通信),也就是需要eth0 发出的报文,被交换机发回来。需要外部交换机配置 Hairpin 支持。
    • 适用于需要交换机参与流量控制的场景。
  4. passthru 模式:
    • 仅允许创建一个 Macvlan 接口,直接接管物理网卡的所有功能(类似单接口直通)。
    • 适用于需要将物理网卡完全分配给虚拟设备的场景。

 macvlan发包逻辑

static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
{
    const struct macvlan_dev *vlan = netdev_priv(dev);
    //作用:指向关联的 macvlan_port 结构体,表示当前 macvlan 设备所属的“端口”。
    //macvlan_port 是管理多个 macvlan 设备的逻辑实体,负责协调它们共享同一个底层物理设备
    const struct macvlan_port *port = vlan->port;
    const struct macvlan_dev *dest;

    if (vlan->mode == MACVLAN_MODE_BRIDGE) {//如果是bridge模式,
        const struct ethhdr *eth = skb_eth_hdr(skb);

        /* send to other bridge ports directly */
        if (is_multicast_ether_addr(eth->h_dest)) {
            skb_reset_mac_header(skb);
            //如果目的地址是组播广播则,
            //将port上所有macvlan接口,遍历一遍然后每个vlan接口转发
            macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);
            goto xmit_world;// 同时 将报文通过port 宿主parent接口发送出去
        }
        //单播数据 根据目的mac查找目标macvlan设备
        dest = macvlan_hash_lookup(port, eth->h_dest);
        if (dest && dest->mode == MACVLAN_MODE_BRIDGE) {
            /* send to lowerdev first for its network taps */
            dev_forward_skb(vlan->lowerdev, skb);
            // 如果找到了,且目标也是bridge模式; 说明是两个子接口macvlan1 和macvlan2 通信
      //macvlan1 转发到macvlan2接口实际上是发给底层eth0中转,
      //也就是eth0收到此包,然后再次macvlan_handle_frame然后转发到macvlan2 接口上
            return NET_XMIT_SUCCESS;
        }
        //否者转发到 底层物理设置eth0
    }
    //上述bridge说明了:bridge模式想子接口可以互通!  应为arp 令居表项能建立起来,其余的模式,arp报文直接转发到底层eth0 接口。
xmit_world:
    skb->dev = vlan->lowerdev;//指向底层物理网络设备(如 eth0)的 net_device 结构体
    return dev_queue_xmit_accel(skb,
                    netdev_get_sb_channel(dev) ? dev : NULL);
}
macvlan_queue_xmit()
 ├── 如果目标是同一个 port 上的其他 macvlan(macvlan2):
 │     ├── dev_forward_skb() 把包交给 eth0;包的设备是 eth0,将通过 eth0 的接收路径进入内核。// eth0 接收到 macvlan1 发给 macvlan2 的包
 │     └── return NET_XMIT_SUCCESS
 └── 否则 skb->dev = lowerdev (eth0), dev_queue_xmit()

 

也就是:VEPA /PRIVATE/PASSTHRU模式下,所有Macvlan 接口发出流量,不管目的地全部发送接口,即使流量目的地共享一个接口其它 Macvlan 接口。

eth0 接收路径:macvlan 接口

数据中的 eth0 驱动接收到,然后交给协议栈:

netif_receive_skb()
 └── __netif_receive_skb()
     └── __netif_receive_skb_core()
         └── rx_handler

 

/* 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;

    /* Packets from dev_loopback_xmit() do not have L2 header, bail out 
    即本机自发自收的包, 自己发给eth0的报文,也属于loopback,报文转到eth0 此时应该直接到协议栈;
    不应到macvlan上来,所以返回pass到协议栈处理*/
    if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
        return RX_HANDLER_PASS;

    port = macvlan_port_get_rcu(skb->dev); // 找到 eth0 上注册的 macvlan port
    if (is_multicast_ether_addr(eth->h_dest)) {//组播广播
        unsigned int hash;
        //对收到的 IP 数据报进行重组(如果被分片)。
        //如果重组失败,释放 skb 并结束处理。
        skb = ip_check_defrag(dev_net(skb->dev), skb, IP_DEFRAG_MACVLAN);
        if (!skb)
            return RX_HANDLER_CONSUMED;
        *pskb = skb;//重新获取 eth(因为 skb 地址可能变了,报文重组的skb)。
        eth = eth_hdr(skb);
        //更新源 MAC 的 macvlan 映射(用于反向查找)
        macvlan_forward_source(skb, port, eth->h_source);
        //查找源 MAC 地址绑定的 macvlan_dev(发出者)
        src = macvlan_hash_lookup(port, eth->h_source);
        if (src && src->mode != MACVLAN_MODE_VEPA &&
            src->mode != MACVLAN_MODE_BRIDGE) {
            //如果发出者的模式不是 VEPA/BRIDGE,说明不能互通(如 PRIVATE),则只转发给自己
            /* forward to original port. */
            vlan = src;//设置的是 MACVLAN_MODE_PRIVATE,macvlan 之间是不能互相通信的(即使接收到也会过滤掉 arp 会失效
            ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?:
                  netif_rx(skb);
        //macvlan_broadcast_one 向 vlan 投递 skb。此时参数local=0,返回0;则是调用netif_rx
        //如果它处理失败,则交给内核默认路径 netif_rx。
        /*其完全阻止共享同一父接口的 Macvlan 虚拟网卡之间的通讯,
        即使配置了 Hairpin 让从父接口发出的流量返回到宿主机,相应的通讯流量依然被丢弃。
        具体实现方式是丢弃广播/多播数据,这就意味着以太网地址解析 arp 将不可运行,
        除非手工探测 MAC 地址,否则通信将无法在同一宿主机下的多个 Macvlan 网卡间展开。
        */
            handle_res = RX_HANDLER_CONSUMED;
            goto out;
        }//也就是private模式子接口虚拟网卡肯定不能通信,
        //但是MACVLAN_MODE_VEPA模式,发包到eth0 然后走到此处调用

        hash = mc_hash(NULL, eth->h_dest);
        if (test_bit(hash, port->mc_filter))//如果广播目标在监听列表中(使用 hash 位图快速判断):
            macvlan_broadcast_enqueue(port, src, skb);
            //调用 macvlan_process_broadcast 广播出去,
    //也就是vepa模式下,子接口arp报文是从eth0发送出去了!一般情况下是收不到的
        return RX_HANDLER_PASS;
    }
    //如果是单播报文,
    macvlan_forward_source(skb, port, eth->h_source);
    //如果是 PASSTHRU 模式,直接取出第一个虚拟接口(只能绑定一个容器);
    if (macvlan_passthru(port))
        vlan = list_first_or_null_rcu(&port->vlans,
                          struct macvlan_dev, list);
    else {
        // 查找目的 MAC 地址对应的 macvlan 接口
        vlan = macvlan_hash_lookup(port, eth->h_dest);
    }
    //如果没找到目标 macvlan,或者目标接口处于 SOURCE 模式(只发不收),
    //则交由其他 handler 内核协议栈 处理。
    if (!vlan || vlan->mode == MACVLAN_MODE_SOURCE)
        return RX_HANDLER_PASS;
    //也就是单播 VEPA模式下:
    //macvlan 发往外部,则macvlan1--->eth0--->netif_rcv---rc_hander-->hander_pass-->router--->外网
    //macvlan发往macvlan2,则macvlan1--->eth0--->netif_rcv---rc_hander-->RX_HANDLER_ANOTHER-->maclan2
    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;
    handle_res = RX_HANDLER_ANOTHER;
out:
    macvlan_count_rx(vlan, len, ret == NET_RX_SUCCESS, false);
    return handle_res;
}

 

 

 

 

 
 
 
 

posted @ 2025-05-17 23:38  codestacklinuxer  阅读(188)  评论(0)    收藏  举报