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);
}