macvlan是什么

macvlan 是 Linux 内核 3.0 版本引入的一种虚拟网卡,它让用户可以在一块物理网卡上,再创建若干个依附于它的虚拟网卡,这些网卡拥有不同的 MAC 地址,如此就完成了网卡的虚拟化。

macvlan的模式

bridge模式

bridge模式下,物理网卡可以作为桥供macvlan虚拟网卡通信。但需要注意,macvlan虚拟网卡与物理网卡不能通信( macvlan 所有模式均不支持),不能通信意味着网卡上方的协议栈不能互通,即你不能从容器ping通宿主机,反之也一样。

VEPA模式

VEPA模式与bridge模式相比,macvlan虚拟网卡之间也不能在宿主机内部互通了!但它们还可以借助外部工作在 Hairpin 模式(发夹弯模式)的交换机完成通信, 工作在发夹弯模式的交换机在收到广播报文或者未知单播报文时,会向所有端口(不会排除报文入接口)发送报文。不仅是 macvlan 虚拟网卡之间, macvlan 虚拟网卡与物理网卡之间也能通过外部 Hairpin 交换机通信。

private模式

private 模式正如其名字,意味着该模式是”私密”的,在这种模式下,即使外部有工作在 Hairpin模式的交换机,macvlan虚拟网卡之间也不能通信。此时,从macvlan 虚拟网卡发出的广播报文经过外部 Hairpin模式的交换机echo回来,只是会送回自己。

passthrough模式

passthrough即直通,此模式下,某一个 macvlan 虚拟网卡会接管物理网卡,它将独占物理网卡。

macvlan的实现

作为一种虚拟网卡实现, macvlan以驱动模块的形式存在于内核源码中,具体请看/driver/net/macvlan.c。

创建macvlan虚拟网卡

用户通过输入ip link add link eth0 name macv1 type macvlan mode bridge 添加 macvlan 虚拟网卡。iproute 将名字、模式等信息打包成Netlink消息下发到内核,内核调用macvlan_common_newlink()创建。

static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh)
{
    ......
    ops = rtnl_link_ops_get(kind); // 会得到 macvlan_link_ops
    // 创建 macvlan 虚拟网卡的 net_device, 私有部分为 struct macvlan_dev
    dev = rtnl_create_link(link_net ? : dest_net, ifname, name_assign_type, ops, tb);
     |
     |- dev = alloc_netdev_mqs(ops->priv_size, ifname, name_assign_type,
			       ops->setup, num_tx_queues, num_rx_queues);
    ops->newlink(link_net ? : net, dev, tb, data);
      |
      |- macvlan_common_newlink(src_net, dev, tb, data);
         |
         |  // lowerdev 是依附的物理网卡 eth0
         |- lowerdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
         |
         |  // 随机分配 macvlan 网卡的 MAC 地址
         |- if (...) eth_hw_addr_random(dev);
         |
         |  // 如果 eth 还没有创建过 macvlan 网卡, 就创建
         |- if (...) macvlan_port_create(lowerdev)
            |
            | // 初始化保存 macvlan 网卡MAC地址的 HASH 表
            |- INIT_HLIST_HEAD(&port->vlan_hash[i]);
            |
            |  // 设置物理网卡 eth0 的 rx_handler 为 macvlan_handle_frame
            |- netdev_rx_handler_register(dev, macvlan_handle_frame, port);
         |
         |   // 将 macvlan 设备注册到系统
         |-  register_netdevice(dev);
}

macvlan虚拟网卡接收报文

从宿主机外发给macvlan虚拟网卡的网卡的报文总是先经过物理网卡eth0,在eth0的接收过程中,会调用到创建macvlan虚拟网卡时注册的macvlan_handle_frame,这里面将报文分为了多播(广播)和单播报文分别处理,先来看多播报文的处理:

static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb)
{
    port = macvlan_port_get_rcu(skb->dev);
	if (is_multicast_ether_addr(eth->h_dest)) {		
		*pskb = skb;
		eth = eth_hdr(skb);

        // 根据报文的 源MAC 查找是否是某个虚拟网卡的 MAC 地址
		src = macvlan_hash_lookup(port, eth->h_source);
		if (src && src->mode != MACVLAN_MODE_VEPA &&
		    src->mode != MACVLAN_MODE_BRIDGE) {
            // 如果找到且它是 private 或者 passthrou 模式,则将其送给该端口 
			vlan = src;
			ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?: netif_rx(skb);
            // 返回 CONSUMED 是告诉外面该报文已经"处理过"了
			handle_res = RX_HANDLER_CONSUMED;
			goto out;
		}

        // 如果没有找到对应的虚拟网卡 或者 其模式为 bridge 或者 vepa,则将该报文广播给所有虚拟网卡
		MACVLAN_SKB_CB(skb)->src = src;
		macvlan_broadcast_enqueue(port, skb);

        // 返回 PASS 是告诉外面 这个包还可以继续处理,也就是还可以给 eth0 上方的协议栈
		return RX_HANDLER_PASS;
	}
    ......
}

单播的情况:

static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb)
{
    ...
    // 单播报文的处理
    if (port->passthru)
		vlan = list_first_or_null_rcu(&port->vlans,
					      struct macvlan_dev, list);
	else
        // 根据报文的 目的MAC 看是否是应该某个 macvlan 虚拟网卡接收
		vlan = macvlan_hash_lookup(port, eth->h_dest);
	if (vlan == NULL)
        // 不是的话 返回 PASS,让 eth0 的协议栈处理
		return RX_HANDLER_PASS;

    // 如果是,则修改报文的入设备为 macvlan 虚拟网卡
    dev = vlan->dev;
    skb->dev = dev;

    // 返回 ANOTHER 让调用者重新走一次接收匹配过程
    handle_res = RX_HANDLER_ANOTHER;
    return handle_res;
}

macvlan虚拟网卡发送报文

macvlan虚拟网卡的发送比较简单,只有bridge模式有一个特别处理:广播报文需要发送给其他“兄弟”虚拟网卡,当然它们也必须是 bridge模式,单播报文如果能找到确定的目标,就直接回路到接收过程。只要不是单播并且找到了目标的情况,该报文最终都会修改报文出设备为物理网卡eth0后,由物理网卡发送出去。

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;

	if (vlan->mode == MACVLAN_MODE_BRIDGE) {
		const struct ethhdr *eth = (void *)skb->data;

		// 广播报文: 广播给其他同样依附于 eth0 且是 bridge 模式的 macvlan 虚拟网卡 
		if (is_multicast_ether_addr(eth->h_dest)) {
			macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);
			goto xmit_world;
		}

        // 单播报文如果能找到确切的 macvlan 虚拟网卡 就直接走接收过程
		dest = macvlan_hash_lookup(port, eth->h_dest);
		if (dest && dest->mode == MACVLAN_MODE_BRIDGE) {
			dev_forward_skb(vlan->lowerdev, skb);
			return NET_XMIT_SUCCESS;
		}
	}

xmit_world:
	skb->dev = vlan->lowerdev;
	return dev_queue_xmit(skb);
}