共计 17824 个字符,预计需要花费 45 分钟才能阅读完成。
作者:张伟(谢石)
因为这篇文章真的很长,大量的篇幅在讲述内核的实现,如果你对这部分不感兴趣,那么在倡议你在看完第一局部的三个问题后,思考一下,而后间接跳转到咱们对问题的答复。
提出问题
注:本文所有的代码均为 Linux 内核源代码,版本为 5.16.2
你据说过 VLAN 么?它的全称是 Virtual Local Area Network,用于在以太网中隔离不同的播送域。它诞生的工夫很早,1995 年,IEEE 就发表了 802.1Q 规范 [ 1] 定义了在以太网数据帧中 VLAN 的格局,并且沿用至今。如果你晓得 VLAN,那么你据说过 MACVlan 和 IPVlan 么?随着容器技术的一直衰亡,IPVlan 和 MACVlan 作为 Linux 虚构网络设备,缓缓走上前台,在 2017 年 Docker Engine 的 1.13.1 的版本 [2 ] 中,就开始引入了 IPVlan 和 MACVlan 作为容器的网络解决方案。
那么你是否也有过以下的疑难呢?
1. VLAN 和 IPVlan,MACVlan 有什么关系呢?为什么名字里都有 VLAN?
2. IPVlan 和 MACVlan 为什么会有各种模式和 flag,比方 VEPA,Private,Passthrough 等等?它们的区别在哪里?
3. IPVlan 和 MACVlan 的劣势在哪里?你应该在什么状况下接触到,应用到它们呢?
我也曾有过一样的问题,明天这篇文章,咱们就针对下面三个问题一探到底。
背景常识
以下为一些背景常识,如果你对 Linux 自身很理解,能够跳过。
- 内核对网络设备的形象
在 Linux 中,咱们操作一个网络设备,不外乎应用 ip 命令或者 ifconfig 命令。对于 ip 命令的实现 iproute2 来说,它真正依赖的就是 Linux 提供的 netlink 音讯机制,内核会对每一类网络设备(无论是实在的还是虚构的)形象出一个专门响应 netlink 音讯的构造体,它们都依照 rtnl_link_ops 构造来实现,用于响应对网络设备的创立,销毁和批改。例如比拟直观的 Veth 设施:
static struct rtnl_link_ops veth_link_ops = {
.kind = DRV_NAME,
.priv_size = sizeof(struct veth_priv),
.setup = veth_setup,
.validate = veth_validate,
.newlink = veth_newlink,
.dellink = veth_dellink,
.policy = veth_policy,
.maxtype = VETH_INFO_MAX,
.get_link_net = veth_get_link_net,
.get_num_tx_queues = veth_get_num_queues,
.get_num_rx_queues = veth_get_num_queues,
};
对于一个网络设备来说,Linux 的操作和硬件设施的响应自身也须要一套标准,Linux 将其形象为 net_device_ops 这个构造体,如果你对设施驱动感兴趣,那次要就是和它打交道,仍然以 Veth 设施为例:
static const struct net_device_ops veth_netdev_ops = {
.ndo_init = veth_dev_init,
.ndo_open = veth_open,
.ndo_stop = veth_close,
.ndo_start_xmit = veth_xmit,
.ndo_get_stats64 = veth_get_stats64,
.ndo_set_rx_mode = veth_set_multicast_list,
.ndo_set_mac_address = eth_mac_addr,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = veth_poll_controller,
#endif
.ndo_get_iflink = veth_get_iflink,
.ndo_fix_features = veth_fix_features,
.ndo_set_features = veth_set_features,
.ndo_features_check = passthru_features_check,
.ndo_set_rx_headroom = veth_set_rx_headroom,
.ndo_bpf = veth_xdp,
.ndo_xdp_xmit = veth_ndo_xdp_xmit,
.ndo_get_peer_dev = veth_peer_dev,
};
从下面的定义咱们能够看到几个语义很直观的办法:ndo_start_xmit 用于发送数据包,newlink 用于创立一个新的设施。
对于接管数据包,Linux 的收包动作并不是由各个过程本人去实现的,而是由 ksoftirqd 内核线程负责了从驱动接管、网络层(ip,iptables)、传输层(tcp,udp)的解决,最终放到用户过程持有的 Socket 的 recv 缓冲区中,而后由内核 inotify 用户过程解决。对于虚构设施来说,所有的差别集中于网络层之前,在这里有一个对立的入口,即__netif_receive_skb_core。
- 801.2q 协定对 VLAN 的定义
802.1q 协定中,以太网数据帧包头中用于标记 VLAN 字段是一个 32bit 的域,构造如下:
如上所示,有 16 个 bit 用于标记 Protocol,3 个 bit 用于标记优先级,1 个 bit 用于标记格局,12 个 bit 用于寄存 VLAN id,看到这里我想你能够轻易计算出,依附 VLAN 咱们能划分出多少个播送域?没错,正是 2*12,4096 个,减去保留的全 0 和全 1,客户划分出 4094 个可用的播送域。(在 OpenFlow 衰亡之前,云计算最晚期雏形中的 vpc 的实现正是依赖 VLAN 进行网络的辨别,然而因为这个限度,很快就被淘汰了,这也催生了另一个你兴许似曾相识的名词,VxLAN,只管两者差异很大,然而仍有借鉴的缘故)。
VLAN 本来和 bridge 一样是一个交换机上的概念,不过 Linux 将它们都进行了软件的实现,Linux 在每个以太网数据帧中应用一个 16bit 的 vlan_proto 字段和 16bit 的 vlan_tci 字段实现 802.1q 协定,同时对于每一个 VLAN,都会虚构出一个子设施来解决去除 VLAN 之后的报文,没错 VLAN 也有属于本人的子设施,即 VLAN sub-interface,不同的 VLAN 子设施通过一个主设施进行物理上的报文收发,这个概念是否又有点相熟?没错,这正是 ENI-Trunking 的原理。
深刻 VLAN/MACVlan/IPVlan 的内核实现
补充了背景常识后,咱们就先从 VLAN 子设施开始,看看 Linux 内核到底是怎么做的,这里所有的内核代码都以时下较新的 5.16.2 版本为例。
VLAN 子设施
- 设施创立
VLAN 子设施起初并没有被当作一类独自的虚构设施来解决,毕竟呈现的工夫很早,代码散布比拟乱,不过外围逻辑位于 /net/8021q/ 门路下。从背景中咱们能够理解到,netlink 机制中实现了网卡设施创立的入口,对于 VLAN 子设施,它们的 netlink 音讯实现的构造体是 vlan_link_ops,而负责创立 VLAN 子设施的是 vlan_newlink 办法,内核初始化代码流程如下:
- 首先创立一个 Linux 通用的 net_device 构造体保留设施的配置信息,进入 vlan_newlink 之后,会进行 vlan_check_real_dev 查看传入的 VLAN id 是否是可用的,这其中会调用到 vlan_find_dev 办法,这个办法用于针对一个主设施查找到符合条件的子设施,前面还会用到,咱们截取一部分代码察看一下:
static int vlan_newlink(struct net *src_net, struct net_device *dev,
struct nlattr *tb[], struct nlattr *data[],
struct netlink_ext_ack *extack)
{struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
struct net_device *real_dev;
unsigned int max_mtu;
__be16 proto;
int err;
/* 这里省略掉了用于参数校验的局部 */
// 这里会设置 vlan 子设施的 vlan 信息,也就是背景常识中 vlan 相干的 protocol,vlanid,优先级和 flag 信息的默认值
vlan->vlan_proto = proto;
vlan->vlan_id = nla_get_u16(data[IFLA_VLAN_ID]);
vlan->real_dev = real_dev;
dev->priv_flags |= (real_dev->priv_flags & IFF_XMIT_DST_RELEASE);
vlan->flags = VLAN_FLAG_REORDER_HDR;
err = vlan_check_real_dev(real_dev, vlan->vlan_proto, vlan->vlan_id,
extack);
if (err < 0)
return err;
/* 这里会进行 mtu 的设置 */
err = vlan_changelink(dev, tb, data, extack);
if (!err)
err = register_vlan_dev(dev, extack);
if (err)
vlan_dev_uninit(dev);
return err;
}
- 接下来是通过 vlan_changelink 办法对设施的属性进行设置,如果你有非凡的配置,则会笼罩默认值。
- 最初进入 register_vlan_dev 办法,这个办法就是把后面曾经实现好的信息装填到 net_device 构造体,并依照 Linux 的设施治理对立接口注册到内核中。
- 接管报文
从创立过程来看,VLAN 子设施与个别设施的区别就在于它可能被主设施和 VLAN id 通过 vlan_find_dev 的形式找到,这一点很重要。
接下来咱们来看报文的接管过程,依据背景常识,物理设施接管到报文后,在进入协定栈解决之前,惯例的入口是 __netif_receive_skb_core,咱们就从这个入口开始逐步剖析,内核操作流程如下:
依据上方的示意图,咱们截取局部__netif_receive_skb_core 进行剖析:
- 首先在数据包解决流程开始的时候,会进行 skb_vlan_untag 操作,对于 VLAN 数据包来说,数据包 Protocol 字段始终是 VLAN 的 ETH_P_8021Q,skb_vlan_untag 就是将 VLAN 信息从数据包的 vlan_tci 字段中提取后,调用 vlan_set_encap_proto 将 Protocol 更新为失常的网络层协定,这时 VLAN 曾经一部分转变为失常数据包了。
- 领有 VLAN tag 的数据包会在 skb_vlan_tag_present 中进入 vlan_do_recieve 的解决流程,vlan_do_receive 的处理过程的外围就是通过 vlan_find_dev 找到子设施,将数据包中的 dev 设置为子设施,而后将 Priority 等与 VLAN 相干的信息进行清理,到了这里,VLAN 数据包曾经转变为一个发往 VLAN 子设施的一般数据包了。
- 在 vlan_do_receive 实现后,会进入 another_round,从新依照失常数据包的流程执行一次__netif_receive_skb_core,依照失常包的解决逻辑进入,进入了 rx_handler 的解决,就像一个失常的数据包一样,在子设施上通过与主设施雷同的 rx_handler 进入到网络层。
static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc,
struct packet_type **ppt_prev)
{
rx_handler_func_t *rx_handler;
struct sk_buff *skb = *pskb;
struct net_device *orig_dev;
another_round:
skb->skb_iif = skb->dev->ifindex;
/* 这是尝试对数据帧报文自身做一次 vlan 的解封装,也就从将背景中的 vlan 相干的两个字段填充 */
if (eth_type_vlan(skb->protocol)) {skb = skb_vlan_untag(skb);
if (unlikely(!skb))
goto out;
}
/* 这里就是你所熟知的 tcpdump 的抓包点了,pt_prev 记录了上一个解决报文的 handler,如你所见,一份 skb 可能被很多中央解决,包含 pcap */
list_for_each_entry_rcu(ptype, &ptype_all, list) {if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
/* 这里在存在 vlan tag 的状况下,如果有 pt_prev 曾经存在,则做一次 deliver_skb,这样其余 handler 解决的时候就会复制一份,原始报文就不会被批改 */
if (skb_vlan_tag_present(skb)) {if (pt_prev) {ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = NULL;
}
/* 这里是外围的局部,咱们看到通过 vlan_do_receive 解决之后,会变成失常包文再来一遍 */
if (vlan_do_receive(&skb))
goto another_round;
else if (unlikely(!skb))
goto out;
}
/* 这里是失常报文应该达到的中央,pt_prev 示意曾经找到了失常的 handler,而后调用 rx_handler 进入下层解决 */
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;
break;
case RX_HANDLER_PASS:
break;
}
}
if (unlikely(skb_vlan_tag_present(skb)) && !netdev_uses_dsa(skb->dev)) {
check_vlan_id:
if (skb_vlan_tag_get_id(skb)) {/* 这里是对 vlan id 并没有正确被摘除的解决,通常是因为 vlan id 不非法或者不存在在本地}
}
- 数据发送
VLAN 子设施的数据发送的入口是 vlan_dev_hard_start_xmit,相比于收包流程,其实发送的流程简略很多,内核在发送时的流程如下:
在硬件发送时,VLAN 子设施会进入 vlan_dev_hard_start_xmit 办法,这个办法实现了 ndo_start_xmit 接口,它通过__vlan_hwaccel_put_tag 办法填充 VLAN 相干的以太网信息到报文中,而后批改了报文的设施为主设施,调用主设施的 dev_queue_xmit 办法从新进入主设施的发送队列进行发送,咱们截取要害的一部分来剖析:
static netdev_tx_t vlan_dev_hard_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
/* 这里就是上文提到的 vlan_tci 的填充,这些信息都归属于子设施自身 */
if (veth->h_vlan_proto != vlan->vlan_proto ||
vlan->flags & VLAN_FLAG_REORDER_HDR) {
u16 vlan_tci;
vlan_tci = vlan->vlan_id;
vlan_tci |= vlan_dev_get_egress_qos_mask(dev, skb->priority);
__vlan_hwaccel_put_tag(skb, vlan->vlan_proto, vlan_tci);
}
/* 这里间接将设施从子设施改为了主设施,十分间接 */
skb->dev = vlan->real_dev;
len = skb->len;
if (unlikely(netpoll_tx_running(dev)))
return vlan_netpoll_send_skb(vlan, skb);
/* 这里就能够间接调用主设施进行报文发送了 */
ret = dev_queue_xmit(skb);
...
return ret;
}
MACVlan 设施
看完 VLAN 子设施之后,马上对 MACVlan 进行剖析,MACVlan 与 VLAN 子设施不一样的是,它曾经不再是以太网自身的能力了,而是一种有本人驱动的虚构网设施,这一点首先就体现在驱动代码的独立,MACVlan 相干的代码根本都位于 /drivers/net/macvlan.c 中。
MACVlan 设施有有五种 mode,其中除了 source 模式外,其余四种都呈现比拟早,定义如下:
enum macvlan_mode {
MACVLAN_MODE_PRIVATE = 1, /* don't talk to other macvlans */
MACVLAN_MODE_VEPA = 2, /* talk to other ports through ext bridge */
MACVLAN_MODE_BRIDGE = 4, /* talk to bridge ports directly */
MACVLAN_MODE_PASSTHRU = 8,/* take over the underlying device */
MACVLAN_MODE_SOURCE = 16,/* use source MAC address list to assign */
};
这里先记住这些模式的行为,对于其中的起因是咱们前面要答复的问题。
- 设施创立
对于 MACVlan 设施来说,它的 netlink 响应构造体是 macvlan_link_ops,咱们能够找到创立设施的响应办法为 macvlan_newlink,从入口开始,创立一个 MACVlan 设施的整体流程如下:
- macvlan_newlink 会调用 macvlan_common_newlink 进行理论的子设施创立操作,macvlan_common_newlink 首先会进行一个合法性的校验,这其中须要留神的就是 netif_is_MACVlan 查看,如果把一个 MACVlan 子设施作为主设施来创立的话,那么会主动采纳这个子设施的主设施作为新建网卡的主设施。
- 接下来会通过 eth_hw_addr_random 给 MACVlan 设施创立一个随机的 mac 地址,没错,MACVlan 子设施的 mac 地址是随机的,这一点很重要,前面会提到。
- 在有了 mac 地址之后,开始在主设施上初始化 MACVlan 逻辑,这里会有个查看,如果主设施从未创立过 MACVlan 设施,则会通过 macvlan_port_create 来反对 MACVlan 的初始化,而这个初始化最为外围的就是,调用 netdev_rx_handler_register 进行了 MACVlan 的 rx_handler 办法 macvlan_handle_frame 去取代了设施原来注册的 rx_handler 的动作。
- 在初始化实现后,取得一个 port,也就是子设施,而后对子设施的信息进行了设置。
- 最初通过 register_netdevice 实现了设施的创立动作。咱们截取局部外围逻辑进行剖析:
int macvlan_common_newlink(struct net *src_net, struct net_device *dev,
struct nlattr *tb[], struct nlattr *data[],
struct netlink_ext_ack *extack)
{
...
/* 这里查看了主设施是否是 macvlan 设施,如果是则间接应用他的主设施 */
if (netif_is_macvlan(lowerdev))
lowerdev = macvlan_dev_real_dev(lowerdev);
/* 这里生成了随机的 mac 地址 */
if (!tb[IFLA_ADDRESS])
eth_hw_addr_random(dev);
/* 这里进行了初始化操作,也就是替换了 rx_handler */
if (!netif_is_macvlan_port(lowerdev)) {err = macvlan_port_create(lowerdev);
if (err < 0)
return err;
create = true;
}
port = macvlan_port_get_rtnl(lowerdev);
/* 接下来一大段都是省略的对于模式的设置 */
vlan->lowerdev = lowerdev;
vlan->dev = dev;
vlan->port = port;
vlan->set_features = MACVLAN_FEATURES;
vlan->mode = MACVLAN_MODE_VEPA;
/* 最初注册了设施 */
err = register_netdevice(dev);
if (err < 0)
goto destroy_macvlan_port;
}
- 接管报文
MACVlan 设施的报文接管仍然是从__netif_receive_skb_core 入口开始,具体的代码流程如下:
- 当__netif_receive_skb_core 在主设施接管后,会进入 MACVlan 驱动注册的 macvlan_handle_frame 办法,这个办法首先会解决多播的报文,而后解决单播的报文。
- 对于多播报文,通过 is_multicast_ether_addr 后,首先通过 macvlan_hash_lookup,通过子设施上的相干信息查找到子设施,则依据网卡的 mode 进行解决,如果是 private 或者 passthrou,找到子设施并独自通过 macvlan_broadcast_one 送给它;如果是 bridge 或者没有 VEPA,则所有的子设施都会通过 macvlan_broadcast_enqueue 收到播送报文。
- 对于单播的报文,首先会将 source 模式和 passthru 模式进行解决,间接触发下层的操作,对于其余模式,依据源 mac 进行 macvlan_hash_lookup 操作,如果找到了 VLAN 信息,则将报文的 dev 设置为找到的子设施。
- 最初对报文进行 pkt_type 的设置,将其通过 RX_HANDLER_ANOTHER 的返回,再进行一次__netif_receive_skb_core 的操作,这次操作中,走到 macvlan_hash_lookup 时,因为曾经是子设施,所以会返回 RX_HANDLER_PASS 从而进入下层的解决。
- 对于 MACVlan 的数据接管过程,最为要害的就是主设施接管到报文后抉择子设施的逻辑,这部分代码如下:
static struct macvlan_dev *macvlan_hash_lookup(const struct macvlan_port *port,
const unsigned char *addr)
{
struct macvlan_dev *vlan;
u32 idx = macvlan_eth_hash(addr);
hlist_for_each_entry_rcu(vlan, &port->vlan_hash[idx], hlist,
lockdep_rtnl_is_held()) {
/* 这部分逻辑就是 macvlan 查找子设施的外围,比拟 mac 地址 */
if (ether_addr_equal_64bits(vlan->dev->dev_addr, addr))
return vlan;
}
return NULL;
}
- 发送报文
MACVlan 的发送报文过程也是从子设施接管到 ndo_start_xmit 回调函数开始,它的入口是 macvlan_start_xmit,整体的内核代码流程如下:
- 当数据包进入 macvlan_start_xmit 后,次要执行数据包发送操作的是 macvlan_queue_xmit 办法。
- macvlan_queue_xmit 首先解决 bridge 模式,咱们从 mode 的定义可知,只有 bridge 模式下才可能在主设施外部呈现不同子设施的间接通信,所有这里解决了这种非凡的状况,把多播报文和目的地为其余子设施的单播报文间接发给子设施。
- 对于其余报文,则会通过 dev_queue_xmit_accel 进行发送,dev_queue_xmit_accel 会间接调用主设施的 netdev_start_xmit 办法,从而实现报文真正的发送。
static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
{
...
/* 这里首先是 bridge 模式下的逻辑,须要思考不通子设施间的通信 */
if (vlan->mode == MACVLAN_MODE_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);
macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);
goto xmit_world;
}
/* 这里对发往同一个主设施的其余子设施进行解决,间接进行转发 */
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);
return NET_XMIT_SUCCESS;
}
}
xmit_world:
skb->dev = vlan->lowerdev;
/* 这里曾经将报文的设施设置为主设施,而后通过主设施进行发送 */
return dev_queue_xmit_accel(skb,
netdev_get_sb_channel(dev) ? dev : NULL);
}
IPVlan 设施
IPVlan 子设施相比于 MACVlan 和 VLAN 子设施来说,模型就更加简单了,不同于 MACVlan,IPVlan 将与子设施间互通行为通过 flag 来定义,同时又提供了三种 mode,定义如下:
/* 最后只有 l2 和 l3,前面 linux 有了 l3mdev,于是就呈现了 l3s,他们次要的区别还是在 rx */
enum ipvlan_mode {
IPVLAN_MODE_L2 = 0,
IPVLAN_MODE_L3,
IPVLAN_MODE_L3S,
IPVLAN_MODE_MAX
};
/* 这里其实还有个 bridge,因为默认就是 bridge,所有省略了,他们的语义和 macvlan 一样 */
#define IPVLAN_F_PRIVATE 0x01
#define IPVLAN_F_VEPA 0x02
- 设施创立
有了之前两种子设施的剖析,在 IPVlan 的剖析上,咱们也能够依照这个思路持续进行剖析,IPVlan 设施的 netlink 音讯解决构造体是 ipvlan_link_ops,而创立设施的入口办法是 ipvlan_link_new,创立 IPVlan 子设施的流程如下:
- 进入 ipvlan_link_new,进行合法性判断,与 MACVlan 相似,如果以一个 IPVlan 设施作为主设施进行新增,就会主动将 IPVlan 设施的主设施作为新设施的主设施。
- 通过 eth_hw_addr_set 设置 IPVlan 设施的 mac 地址为主设施的 mac 地址,这是 IPVlan 与 MACVlan 最显著的特色辨别。
- 进入对立网卡注册的 register_netdevice 流程,在这个流程里,如果以后没有 IPVlan 子设施存在,则会和 MACVlan 一样,进入到 ipvlan_init 的初始化过程,它会在主设施上创立 ipvl_port,并且用 IPVlan 的 rx_handler 去取代主设施原有的 rx_handler,同时也会启动一个专门的内核 worker 去解决多播报文,也就是说,对于 IPVlan,所有的多播报文其实都是对立解决的。
- 接下来持续解决以后这个新增的子设施,通过 ipvlan_set_port_mode 将以后子设施保留到主设施的信息中,同时针对 l3s 的子设施,会将它的 l3mdev 解决办法注册到 nf_hook 中,没错,这是和下面设施最大的区别,l3s 的主设施和子设施替换数据包实际上是在网络层实现的。
对于 IPVlan 网络设备,咱们截取 ipvlan_port_create 一部分代码进行剖析:
static int ipvlan_port_create(struct net_device *dev)
{
/* 从这里能够看到,port 是主设施对子设施治理的外围 */
struct ipvl_port *port;
int err, idx;
/* 子设施的各种属性,都在 port 中体现,也能够看到默认的 mode 是 l3 */
write_pnet(&port->pnet, dev_net(dev));
port->dev = dev;
port->mode = IPVLAN_MODE_L3;
/* 这里能够看到,对于 ipvlan,多播的报文都是独自解决的 */
skb_queue_head_init(&port->backlog);
INIT_WORK(&port->wq, ipvlan_process_multicast);
/* 这里就是惯例操作了,其实他是靠着这里来让主设施的收包能够顺利配合 ipvlan 的动作 */
err = netdev_rx_handler_register(dev, ipvlan_handle_frame, port);
if (err)
goto err;
}
- 接管报文
IPVlan 子设施的三种 mode 别离有不同的收包解决流程,在内核的流程如下:
- 与 MACVlan 相似,首先会通过__netif_receive_skb_core 进入到创立时注册的 ipvlan_handle_frame 的解决流程,此时数据包仍然是主设施所领有。
- 对于 mode l2 模式的报文解决,只解决多播的报文,将报文放进后面创立子设施时初始化的多播解决的队列;对于单播报文,会间接交给 ipvlan_handle_mode_l3 进行解决!
- 对于 mode l3 或者单播的 mode l2 报文,进入 ipvlan_handle_mode_l3 解决流程,首先通过 ipvlan_get_L3_hdr 获取到网络层的头信息,而后依据 ip 地址去查找到对应的子设施,最初调用 ipvlan_rcv_frame,将报文的 dev 设置为 IPVlan 子设施并返回 RX_HANDLER_ANOTHER,进行下一次收包。
- 对于 mode l3s,在 ipvlan_handle_frame 中会间接返回 RX_HANDLER_PASS,也就是说,mode l3s 的报文会在主设施就进入到网络层的解决阶段,对于 mode l3s 来说,事后注册的 nf_hook 会在 NF_INET_LOCAL_IN 时触发,执行 ipvlan_l3_rcv 操作,通过 addr 找到子设施,更换报文的网络层目标地址,而后间接进入 ip_local_deliver 进行网络层余下的操作。
- 发送报文
IPVlan 的报文发送,只管在实现上绝对简单,然而究其基本,还是各个子设施在想方法用主设施来做到发送报文的工作,IPVlan 子设施进行数据包发送时,首先进入 ipvlan_start_xmit,其外围的发送操作在 ipvlan_queue_xmit,内核代码流程如下:
- ipvlan_queue_xmit 依据子设施的模式抉择不同的发送办法,mode l2 通过 ipvlan_xmit_mode_l2 发送,mode l3 和 mode l3s 进行 ipvlan_xmit_mode_l3 发送。
- 对于 ipvlan_xmit_mode_l2,首先判断是否是本地地址或者 VEPA 模式,如果不是 VEPA 模式的本地报文,则首先通过 ipvlan_addr_lookup 查到到是否是雷同主设施下的 IPVlan 子设施,如果是,则通过 ipvlan_rcv_frame 让其余子设施进行收包解决;如果不是,则通过 dev_forward_skb 让主设施进行解决。
- 接下来 ipvlan_xmit_mode_l2 会对多播报文进行解决,在解决之前,通过 ipvlan_skb_crossing_ns 清理掉数据包的 netns 相干的信息,包含 priority 等,最初将数据包放到 ipvlan_multicast_enqueue,触发上述的多播解决流程。
- 对于非本地的数据包,通过主设施的 dev_queue_xmit 进行发送。
- ipvlan_xmit_mode_l3 的解决首先也是对 VEPA 进行判断,对与非 VEPA 模式的数据包,通过 ipvlan_addr_lookup 查找是否是其余子设施,如果是则调用 ipvlan_rcv_frame 触发其余设施进行收包解决。
- 对于非 VEPA 模式的数据包,首先进行 ipvlan_skb_crossing_ns 的解决,而后进行 ipvlan_process_outbound 的操作,此时依据数据包的网络层协定,抉择 ipvlan_process_v4_outbound 或者 ipvlan_process_v6_outbound 进行解决。
- 以 ipvlan_process_v6_outbound 为例,首先会通过 ip_route_output_flow 进行路由的查找,而后间接通过网络层的 ip_local_out,在主设施的网络层持续进行发包操作。
解决问题
经验下面的剖析和一番领会思考,我想至多第一个问题曾经能够很轻易的答复进去了:
VLAN 与 MACVlan/IPVlan 的关系
VLAN 和 IPVlan,MACVlan 有什么关系呢?为什么名字里都有 VLAN?
既然 MACVlan 和 IPVlan 抉择叫这个名字,那阐明在某些方面还是有相似之处的。咱们整体剖析下来发现,VLAN 子设施和 MACVlan,IPVlan 的外围逻辑很类似:
- 主设施负责物理上的收发包。
- 主设施将子设施治理为多个 port,而后依据肯定的规定找到 port,比方 VLAN 信息,mac 地址以及 ip 地址(macvlan_hash_lookup,vlan_find_dev,ipvlan_addr_lookup)。
- 主设施收包后,都须要通过在__netif_receive_skb_core 中走一段“回头路”.
- 子设施发包最终都是间接通过批改报文的 dev,而后让主设施去操作。
所以不难推论,MACVlan/IPVlan 的外在逻辑其实很大水平上参考了 Linux 的 VLAN 的实现。Linux 最早退出 MACVlan 是在 2007 年 6 月 18 日公布的 2.6.63 版本 [ 3],对他的形容是:
The new “MACVlan” driver allows the system administrator to create virtual interfaces mapped to and from specific MAC addresses.
而到了 2014 年 12 月 7 日 公布的 3.19 版本 [ 4] 中,第一次引入了 IPVlan,他的形容是:
The new “IPVlan” driver enable the creation of virtual network devices for container interconnection. It is designed to work well with network namespaces. IPVlan is much like the existing MACVlan driver, but it does its multiplexing at a higher level in the stack.
至于 VLAN,呈现的远比 Linux 2.4 版本还要早,很多设施的第一版驱动就曾经反对了 VLAN,不过,Linux 对于 VLAN 的 hwaccel 实现,是 2004 年的 2.6.10 [ 5],过后更新的一大批个性中,呈现了这一条:
I was poking about in the National Semi 83820 driver, and I happened to notice that the chip supports VLAN tag add/strip assist in hardware, but the driver wasn’t making use of it. This patch adds in the driver support to use the VLAN tag add/remove hardware, and enables the drivers use of the kernel VLAN hwaccel interface.
也就是说,当 Linux 开始把 VLAN 当作一个 interface 解决后,才有了前面的 MACVlan 和 IPVlan 两个 virtual interface,Linux 为了实现对于 VLAN 数据包的解决流程的减速,把不同的 VLAN 虚构成了设施,而前期 MACVlan 和 IPVlan 在这种思路之下,让虚构设施有了更大的用武之地。
这样看来,它们的关系更像是一种致敬。
对于 VEPA/passthrough/bridge/private
IPVlan 和 MACVlan 为什么会有各种模式和 flag,比方 VEPA,private,passthrough 等等?它们的区别在哪里?
其实在内核的剖析中,咱们曾经大抵理解了这几种模式的体现,如果主设施是一个钉钉群,所有的群友都能够向外发消息,那么其实几种模式就十分直观:
- private 模式,群友们相互之间都是禁言的,既不能在群内,也不能在群外。
- bridge 模式,群友们能够在群内欢快发言。
- VEPA 模式,群友们在群内禁言了,然而你们在群外间接私聊,相当于年会抢红包期间的个体禁言。
- passthrough 模式,这时候你就是群主了,除了你没人能发言。
那么为什么会有这几种模式呢?咱们从内核的体现来看,无论是 port,还是 bridge,其实都是网络的概念,也就是说,从一开始,Linux 就在致力将本人体现成一个合格的网络设备,对于主设施,Linux 致力将它做成一个交换机,对于子设施,那就是一个个网线背地的设施,这样看起来就很正当了。
实际上正是这样,无论是 VEPA 还是 private,它们最后都是网络概念。其实不止是 Linux,咱们见到很多致力于把本人伪装成物理网络的我的项目,都因循了这些行为模式,比方 OpenvSwitch [ 6]。
MACVlan 与 IPVlan 的利用
IPVlan 和 MACVlan 的劣势在哪里?你应该在什么状况下接触到,应用到它们呢?
其实到这里,才开始说到本篇文章的初衷。咱们从第二个问题发现,IPVlan 和 MACVlan 都是在做一件事:虚构网络。咱们为什么要虚构网络呢?这个问题有很多答案,然而和云计算的价值一样,虚构网络作为云计算的一项根底技术,它们最终都是为了资源利用效率的晋升。
MACVlan 和 IPVlan 就是服务了这个最终目标,土豪们一台物理机跑一个 helloworld 的时代仍然过来,从虚拟化到容器化,时代对网络密度提出了越来越高的要求,随同着容器技术的诞生,首先是 veth 走上舞台,然而密度够了,还要性能的高效,MACVlan 和 IPVlan 通过子设施晋升密度并保障高效的形式应运而生(当然还有咱们的 ENI-Trunking)。
说到这里,就要为大家举荐一下阿里云容器服务 ACK 给大家带来的高性能、高密度的网络新计划——IPVlan 计划 [ 7]!
ACK 基于 Terway 插件,实现了基于 IPVlan 的 K8s 网络解决方案。Terway 网络插件是 ACK 自研的网络插件,将原生的弹性网卡调配给 Pod 实现 Pod 网络,反对基于 Kubernetes 规范的网络策略(Network Policy)来定义容器间的拜访策略,并兼容 Calico 的网络策略。
在 Terway 网络插件中,每个 Pod 都领有本人网络栈和 IP 地址。同一台 ECS 内的 Pod 之间通信,间接通过机器外部的转发,跨 ECS 的 Pod 通信、报文通过 VPC 的弹性网卡间接转发。因为不须要应用 VxLAN 等的隧道技术封装报文,因而 Terway 模式网络具备较高的通信性能。Terway 的网络模式如下图所示:
客户在应用 ACK 创立集群时,如果抉择 Terway 网络插件,能够配置其应用 Terway IPvlan 模式。Terway IPvlan 模式采纳 IPvlan 虚拟化和 eBPF 内核技术实现高性能的 Pod 和 Service 网络。
不同于默认的 Terway 的网络模式,IPvlan 模式次要在 Pod 网络、Service、网络策略(NetworkPolicy)做了性能的优化:
- Pod 的网络间接通过 ENI 网卡的 IPvlan L2 的子接口实现,大大简化了网络在宿主机上的转发流程,让 Pod 的网络性能简直与宿主机的性能无异,提早绝对传统模式升高 30%。
- Service 的网络采纳 eBPF 替换原有的 kube-proxy 模式,不须要通过宿主机上的 iptables 或者 IPVS 转发,在大规模集群中性能简直无升高,扩展性更优。在大量新建连贯和端口复用场景申请提早比 IPVS 和 iptables 模式的大幅升高。
- Pod 的网络策略(NetworkPolicy)也采纳 eBPF 替换掉原有的 iptables 的实现,不须要在宿主机上产生大量的 iptables 规定,让网络策略对网络性能的影响降到最低。
所以,利用 IPVlan 为每个业务 pod 调配 IPVlan 网卡,既保证了网络的密度,也使传统网络的 Veth 计划实现微小的性能晋升(详见参考链接 7)。同时,Terway IPvlan 模式提供了高性能的 Service 解决方案,基于 eBPF 技术,咱们躲避了诟病已久的 Conntrack 性能问题。
置信无论是什么场景的业务,ACK with IPVlan 都是一个更为杰出的抉择。
最初感激你能浏览到这里,在这个问题背地,其实暗藏了一个问题,你晓得为什么咱们抉择 IPVlan,而没有抉择 MACVlan 么?如果你对虚构网络技术有理解,那么联合上述的内容,你应该很快就有答案了,也欢送你在评论区留言。
参考链接:
[1] 《对于 IEEE 802.1Q》**
https://zh.wikipedia.org/wiki…
[2]《Docker Engine release notes》
https://docs.docker.com/engin…
[3]《Merged for MACVlan 2.6.23》
https://lwn.net/Articles/241915/
[4]《MACVlan 3.19 Merge window part 2》
https://lwn.net/Articles/626150/
[5]《VLan 2.6.10-rc2 long-format changelog》
https://lwn.net/Articles/111033/
[6]《[ovs-dev] VEPA support in OVS》
https://mail.openvswitch.org/…
[7]《阿里云 Kubernetes 集群应用 IPVlan 减速 Pod 网络》
https://developer.aliyun.com/…
点击此处,理解基于阿里云 ACK Terway 的 IPvlan。