作者:张伟(谢石)

由于这篇文章真的很长,大量的篇幅在叙述内核的完结,假如你对这部分不感兴趣,那么在建议你在看完榜首部分的三个问题后,考虑一下,然后直接跳转到咱们对问题的答复。

提出问题

注:本文一切的代码均为 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 的域,结构如下:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

如上所示,有 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 办法,内核初始化代码流程如下:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

  1. 首要创立一个 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;
}
  1. 接下来是通过 vlan_changelink 办法对设备的特点进行设置,假如你有特别的装备,则会覆盖默许值。

  2. 终究进入 register_vlan_dev 办法,这个办法便是把前面现已完结好的信息装填到 net_device 结构体,并依照 Linux 的设备管理一致接口注册到内核中。

  • 接纳报文

从创立进程来看, VLAN 子设备与一般设备的差异就在于它能够被主设备和 VLAN id 通过 vlan_find_dev 的方式找到,这一点很重要。

接下来咱们来看报文的接纳进程,依据布景常识,物理设备接纳到报文后,在进入协议栈处理之前,惯例的进口是 __netif_receive_skb_core,咱们就从这个进口开端逐步剖析,内核操作流程如下:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

依据上方的示意图,咱们截取部分__netif_receive_skb_core 进行剖析:

  1. 首要在数据包处理流程开端的时分,会进行 skb_vlan_untag 操作,关于 VLAN 数据包来说,数据包 Protocol 字段一直是 VLAN 的 ETH_P_8021Q ,skb_vlan_untag 便是将 VLAN 信息从数据包的 vlan_tci 字段中提取后,调用 vlan_set_encap_proto 将 Protocol 更新为正常的网络层协议,这时 VLAN 现已一部分转变为正常数据包了。

  2. 具有 VLAN tag 的数据包会在 skb_vlan_tag_present 中进入 vlan_do_recieve 的处理流程,vlan_do_receive 的处理进程的中心便是通过 vlan_find_dev 找到子设备,将数据包中的 dev 设置为子设备,然后将 Priority 等与 VLAN 相关的信息进行整理,到了这儿,VLAN 数据包现已转变为一个发往 VLAN 子设备的普通数据包了。

  3. 在 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 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

在硬件发送时,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 设备的整体流程如下:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

  1. macvlan_newlink 会调用 macvlan_common_newlink 进行实际的子设备创立操作,macvlan_common_newlink 首要会进行一个合法性的校验,这其间需求留意的便是 netif_is_MACVlan 查看,假如把一个 MACVlan 子设备作为主设备来创立的话,那么会主动选用这个子设备的主设备作为新建网卡的主设备。

  2. 接下来会通过 eth_hw_addr_random 给 MACVlan 设备创立一个随机的 mac 地址,没错,MACVlan 子设备的 mac 地址是随机的,这一点很重要,后边会提到。

  3. 在有了 mac 地址之后,开端在主设备上初始化 MACVlan 逻辑,这儿会有个查看,假如主设备从未创立过 MACVlan 设备,则会通过 macvlan_port_create 来支持 MACVlan 的初始化,而这个初始化最为中心的便是,调用 netdev_rx_handler_register 进行了 MACVlan 的 rx_handler 办法 macvlan_handle_frame 去替代了设备本来注册的 rx_handler 的动作。

  4. 在初始化完结后,取得一个 port,也便是子设备,然后对子设备的信息进行了设置。

  5. 终究通过 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 进口开端,具体的代码流程如下:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

  1. 当__netif_receive_skb_core 在主设备接纳后,会进入 MACVlan 驱动注册的 macvlan_handle_frame 办法,这个办法首要会处理多播的报文,然后处理单播的报文。

  2. 关于多播报文,通过 is_multicast_ether_addr 后,首要通过 macvlan_hash_lookup,通过子设备上的相关信息查找到子设备,则依据网卡的 mode 进行处理,假如是 private 或许 passthrou,找到子设备并独自通过 macvlan_broadcast_one 送给它;假如是 bridge 或许没有 VEPA,则一切的子设备都会通过 macvlan_broadcast_enqueue 收到播送报文。

  3. 关于单播的报文,首要会将 source 形式和 passthru 形式进行处理,直接触发上层的操作,关于其他形式,依据源 mac 进行 macvlan_hash_lookup 操作,假如找到了 VLAN 信息,则将报文的 dev 设置为找到的子设备。

  4. 终究对报文进行 pkt_type 的设置,将其通过 RX_HANDLER_ANOTHER 的回来,再进行一次__netif_receive_skb_core 的操作,这次操作中,走到 macvlan_hash_lookup 时,由于现已是子设备,所以会回来 RX_HANDLER_PASS 从而进入上层的处理。

  5. 关于 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,整体的内核代码流程如下:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

  1. 当数据包进入 macvlan_start_xmit 后,首要履行数据包发送操作的是 macvlan_queue_xmit 办法。

  2. macvlan_queue_xmit 首要处理 bridge 形式,咱们从 mode 的界说可知,只要 bridge 形式下才或许在主设备内部呈现不同子设备的直接通讯,一切这儿处理了这种特别的状况,把多播报文和意图地为其他子设备的单播报文直接发给子设备。

  3. 关于其他报文,则会通过 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 子设备的流程如下:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

  1. 进入 ipvlan_link_new,进行合法性判别,与 MACVlan 类似,假如以一个 IPVlan 设备作为主设备进行新增,就会主动将 IPVlan 设备的主设备作为新设备的主设备。

  2. 通过 eth_hw_addr_set 设置 IPVlan 设备的 mac 地址为主设备的 mac 地址,这是 IPVlan 与 MACVlan 最明显的特征差异。

  3. 进入一致网卡注册的 register_netdevice 流程,在这个流程里,假如其时没有 IPVlan 子设备存在,则会和 MACVlan 相同,进入到 ipvlan_init 的初始化进程,它会在主设备上创立 ipvl_port,而且用 IPVlan 的 rx_handler 去替代主设备原有的 rx_handler,一起也会发动一个专门的内核 worker 去处理多播报文,也便是说,关于 IPVlan,一切的多播报文其实都是一致处理的。

  4. 接下来继续处理其时这个新增的子设备,通过 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 别离有不同的收包处理流程,在内核的流程如下:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

  1. 与 MACVlan 类似,首要会通过__netif_receive_skb_core 进入到创立时注册的 ipvlan_handle_frame 的处理流程,此刻数据包依然是主设备所具有。

  2. 关于 mode l2 形式的报文处理,只处理多播的报文,将报文放进前面创立子设备时初始化的多播处理的行列;关于单播报文,会直接交给 ipvlan_handle_mode_l3 进行处理!

  3. 关于 mode l3 或许单播的 mode l2 报文,进入 ipvlan_handle_mode_l3 处理流程,首要通过 ipvlan_get_L3_hdr 获取到网络层的头信息,然后依据 ip 地址去查找到对应的子设备,终究调用 ipvlan_rcv_frame,将报文的 dev 设置为 IPVlan 子设备并回来 RX_HANDLER_ANOTHER,进行下一次收包。

  4. 关于 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,内核代码流程如下:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

  1. ipvlan_queue_xmit 依据子设备的形式挑选不同的发送办法,mode l2 通过 ipvlan_xmit_mode_l2 发送,mode l3 和 mode l3s 进行 ipvlan_xmit_mode_l3 发送。

  2. 关于 ipvlan_xmit_mode_l2,首要判别是否是本地地址或许 VEPA 形式,假如不是 VEPA 形式的本地报文,则首要通过 ipvlan_addr_lookup 查到到是否是相同主设备下的 IPVlan 子设备,假如是,则通过 ipvlan_rcv_frame 让其他子设备进行收包处理;假如不是,则通过 dev_forward_skb 让主设备进行处理。

  3. 接下来 ipvlan_xmit_mode_l2 会对多播报文进行处理,在处理之前,通过 ipvlan_skb_crossing_ns 整理掉数据包的 netns 相关的信息,包含 priority 等,终究将数据包放到 ipvlan_multicast_enqueue,触发上述的多播处理流程。

  4. 关于非本地的数据包,通过主设备的 dev_queue_xmit 进行发送。

  5. ipvlan_xmit_mode_l3 的处理首要也是对 VEPA 进行判别,对与非 VEPA 形式的数据包,通过ipvlan_addr_lookup 查找是否是其他子设备,假如是则调用 ipvlan_rcv_frame 触发其他设备进行收包处理。

  6. 关于非 VEPA 形式的数据包,首要进行 ipvlan_skb_crossing_ns 的处理,然后进行 ipvlan_process_outbound的操作,此刻依据数据包的网络层协议,挑选 ipvlan_process_v4_outbound 或许 ipvlan_process_v6_outbound 进行处理。

  7. 以 ipvlan_process_v6_outbound 为例,首要会通过 ip_route_output_flow 进行路由的查找,然后直接通过网络层的 ip_local_out,在主设备的网络层继续进行发包操作。

解决问题

阅历上面的剖析和一番领会考虑,我想至少榜首个问题现已能够很轻易的答复出来了:

VLAN 与 MACVlan/IPVlan 的联系

VLAN 和 IPVlan,MACVlan 有什么联系呢?为什么姓名里都有 VLAN?

既然 MACVlan 和 IPVlan 挑选叫这个姓名,那说明在某些方面仍是有类似之处的。咱们整体剖析下来发现,VLAN 子设备和 MACVlan,IPVlan 的中心逻辑很类似:

  1. 主设备担任物理上的收发包。

  2. 主设备将子设备管理为多个 port,然后依据必定的规矩找到 port,比方 VLAN 信息,mac 地址以及 ip 地址(macvlan_hash_lookup,vlan_find_dev,ipvlan_addr_lookup)。

  3. 主设备收包后,都需求通过在__netif_receive_skb_core 中走一段“回头路”.

  4. 子设备发包终究都是直接通过修正报文的 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 等等?它们的差异在哪里?

其实在内核的剖析中,咱们现已大致了解了这几种形式的表现,假如主设备是一个钉钉群,一切的群友都能够向外发音讯,那么其实几种形式就十分直观:

  1. private 形式,群友们相互之间都是禁言的,既不能在群内,也不能在群外。
  2. bridge 形式,群友们能够在群内愉快发言。
  3. VEPA 形式,群友们在群内禁言了,可是你们在群外直接私聊,相当于年会抢红包时期的集体禁言。
  4. 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 的网络形式如下图所示:

从 VLAN 到 IPVLAN: 聊聊虚拟网络设备及其在云原生中的应用

客户在运用 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》

zh.wikipedia.org/wiki/IEEE_8…

[2] 《Docker Engine release notes》

docs.docker.com/engine/rele…

[3] 《Merged for MACVlan 2.6.23》

lwn.net/Articles/24…

[4] 《MACVlan 3.19 Merge window part 2》

lwn.net/Articles/62…

[5] 《VLan 2.6.10-rc2 long-format changelog》

lwn.net/Articles/11…

[6] 《[ovs-dev] VEPA support in OVS》

mail.openvswitch.org/pipermail/o…

[7] 《阿里云 Kubernetes 集群运用 IPVlan 加快 Pod 网络》

developer.aliyun.com/article/743…

点击此处,了解根据阿里云 ACK Terway 的 IPvlan。