文章目录
主要探讨使用 ipvlan 为 cni 通过虚拟网卡的实现。
简介
ipvlan 和 macvlan 类似,都是从一个主机接口虚拟出多个虚拟网络接口。唯一比较大的区别就是 ipvlan 虚拟出的子接口都有相同的 mac地址(与物理接口共用同个 mac 地址),但可配置不同的 ip 地址。由于所有的虚拟接口共享同个mac地址,因此有些地方需要注意:当使用 DHCP 协议分配 ip 时,一般会用 mac 地址作为机器的标识,因此需要配置唯一的 ClientID 字段作为机器的标识, DHCP server 配置 ip 时需使用该字段作为机器标识,而不是使用 mac 地址。
两种工作模式
ipvlan 有两种不同的工作模式:L2 和 L3。一个父接口只能选择其中一种模式(不能采用混用模式),依附于它的所有虚拟接口都会运行在这个模式下。
1、L2 模式
Ipvlan 的 L2 模式和 macvlan 的 bridge 模式工作原理很相似,父接口作为交换机来转发子接口的数据。同一个网络的子接口可以通过父接口来转发数据,而如果想发送到其他网络,报文则会通过父接口的路由转发出去。
2、L3 模式
L3 模式下,ipvlan 有点像路由器的功能,它在各个虚拟网络和主机网络之间进行不同网络报文的路由转发工作。只要父接口相同,即使虚拟机/容器不在同一个网络,也可以互相 ping 通对方,因为 ipvlan 会在中间做报文的转发工作。该模式把宿主接口当成一个路由器,完全不支持广播,这个模式下的接口也比l2模式下的ipvlan接口多了一个 NOARP属性,也不会发送广播报文
Ipvlan2
同节点 Ns 互通
// 创建 netns, 创建 ipvlan,netns 内配置 ip,link up,配置路由
ip netns add node1ns
ip link add ipvl_10_1 link ens10 type ipvlan mode l2
ip link set ipvl_10_1 netns node1ns
ip netns exec node1ns ip link set ipvl_10_1 name eth0
ip netns exec node1ns ip a a 192.168.100.101/24 dev eth0
ip netns exec node1ns ip link set lo up
ip netns exec node1ns ip link set eth0 up
ip netns add node1ns2
ip link add ipvl_10_1 link ens10 type ipvlan mode l2
ip link set ipvl_10_1 netns node1ns2
ip netns exec node1ns2 ip link set ipvl_10_1 name eth0
ip netns exec node1ns2 ip a a 192.168.100.102/24 dev eth0
ip netns exec node1ns2 ip link set lo up
ip netns exec node1ns2 ip link set eth0 up
// 到此两个 ns 的 ip 是互通的
192.168.100.101 <--> 192.168.100.102 OK
Ns 内与宿主机 通信
因为要跟其他网段通信,配置 ns 内的路由
//配置 ns 内部下一跳 192.168.100.1,将 192.168.100.1/24 配置在 ens10 网卡上
ip netns exec node1ns ip r add default via 192.168.100.1
ip netns exec node1ns2 ip r add default via 192.168.100.1
第一种方法
将 192.168.100.1/24 配置在 ens10 网卡上
//在 宿主机上配置到 ns ip 的 路由
ip link add ipvl_10 link ens10 type ipvlan mode l2
ip link set ipvl_10 up
ip a a 192.168.100.1/32 dev ipvl_10
ip route add 192.168.100.101/32 dev ipvl_10
ip route add 192.168.100.102/32 dev ipvl_10
第二种方法
// 在 ns 中配置宿主机 ip 的路由
ip netns exec node1ns ip r add 172.18.22.0/24 dev eth0
ip netns exec node1ns2 ip r add 172.18.22.0/24 dev eth0
ens10 不配置 ip,在 ipvl_10 配置 宿主机的 ip
ip a a 172.18.22.114/32 dev ipvl_10
Ns 到节点外部
ns 内部下一跳 ip 在宿主机外部的情况,会直接从 ens10 出。
结论
保证业务流量走业务网卡,使用第三种方法实现 host 到 ns 的通信,将 需要通信的 host IP 配置到 业务网卡上,其他流量默认从业务网卡出。
Ipvlan3
1. 同节点 Ns 互通
// 创建 netns, 创建 ipvlan,netns 内配置 ip,link up,配置路由
ip netns add node2ns
ip link add ipvl_10_1 link ens10 type ipvlan mode l3
ip link set ipvl_10_1 netns node2ns
ip netns exec node2ns ip link set ipvl_10_1 name eth0
ip netns exec node2ns ip a a 192.168.100.201/24 dev eth0
ip netns exec node2ns ip link set lo up
ip netns exec node2ns ip link set eth0 up
ip netns exec node2ns ip r add default dev eth0
ip netns add node2ns2
ip link add ipvl_10_1 link ens10 type ipvlan mode l3
ip link set ipvl_10_1 netns node2ns2
ip netns exec node2ns2 ip link set ipvl_10_1 name eth0
ip netns exec node2ns2 ip a a 192.168.200.201/24 dev eth0
ip netns exec node2ns2 ip link set lo up
ip netns exec node2ns2 ip link set eth0 up
ip netns exec node2ns2 ip r add default dev eth0
// 到此两个 ns 的 ip 是互通的
192.168.100.201 <--> 192.168.200.201 OK
Ns 内与宿主机 通信
// 宿主机起 ipvl_10,配置需要访问的宿主机 ip,设置到 ns 的路由
ip link add ipvl_10 link ens10 type ipvlan mode l3
ip a a 172.18.22.115/32 dev ipvl_10
ip r add 192.168.100.201 dev ipvl_10
ip r add 192.168.200.201 dev ipvl_10
192.168.100.201 <--> 172.18.22.115
Ns 内到外部网络
发包时,通过宿主机路由选择对应网卡
总结
l3 mode 的 ipvlan 网卡 NOARP,不回复 arp 报文,如果外部流量访问 ipvlan 上的 ip,需要在 ens10 配置 ip,然后外部配置到 ipvlan ip 的流量下一跳到 ens10 的 ip。
源码分析
ipvlan 收包流程
ipvlan_netdev_ops 中 收包函数 ipvlan_start_xmit
int ipvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ipvl_port *port = ipvlan_port_get_rcu_bh(ipvlan->phy_dev);
if (!port)
goto out;
...
// 获取 端口模式,L2,L3 或 L3S;进行对应处理
switch(port->mode) {
case IPVLAN_MODE_L2:
return ipvlan_xmit_mode_l2(skb, dev);
case IPVLAN_MODE_L3:
#ifdef CONFIG_IPVLAN_L3S
case IPVLAN_MODE_L3S:
#endif
return ipvlan_xmit_mode_l3(skb, dev);
}
...
}
L2 mode 收包
static int ipvlan_xmit_mode_l2(struct sk_buff *skb, struct net_device *dev)
{
const struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ethhdr *eth = skb_eth_hdr(skb);
struct ipvl_addr *addr;
void *lyr3h;
int addr_type;
// 非 vepa 且源 mac 目的 mac 相等,获取三层头指针,和类型(ipv4,arp)
if (!ipvlan_is_vepa(ipvlan->port) &&
ether_addr_equal(eth->h_dest, eth->h_source)) {
lyr3h = ipvlan_get_L3_hdr(ipvlan->port, skb, &addr_type);
if (lyr3h) {
// 找到目的地址
addr = ipvlan_addr_lookup(ipvlan->port, lyr3h, addr_type, true);
if (addr) {
// ipvlan 如果是 private 则 drop。
if (ipvlan_is_private(ipvlan->port)) {
consume_skb(skb);
return NET_XMIT_DROP;
}
// ipvlan 虚拟接口收包,改 skb type 为 local,使用该目的地址的接口收包。如 1 中 netns 互通和 2 中 第三种方法。
return ipvlan_rcv_frame(addr, &skb, true);
}
}
...
/* Packet definitely does not belong to any of the
* virtual devices, but the dest is local. So forward
* the skb for the main-dev. At the RX side we just return
* RX_PASS for it to be processed further on the stack.
*/
// 目的地址不是虚拟接口里的,直接物理口收包,接下来会进协议栈。如 2 中 netns 到 host 的第一,二种方法。
return dev_forward_skb(ipvlan->phy_dev, skb);
// 多播包,虚拟接口处理
} else if (is_multicast_ether_addr(eth->h_dest)) {
skb_reset_mac_header(skb);
ipvlan_skb_crossing_ns(skb, NULL);
ipvlan_multicast_enqueue(ipvlan->port, skb, true);
return NET_XMIT_SUCCESS;
}
// 源目的 mac 不同,且不是多播包,直接物理网卡发出。
skb->dev = ipvlan->phy_dev;
return dev_queue_xmit(skb);
}
L3 mode 收包,L3 和 L3S 相同,和 L2 Mode 很像
static int ipvlan_xmit_mode_l3(struct sk_buff *skb, struct net_device *dev)
{
const struct ipvl_dev *ipvlan = netdev_priv(dev);
void *lyr3h;
struct ipvl_addr *addr;
int addr_type;
lyr3h = ipvlan_get_L3_hdr(ipvlan->port, skb, &addr_type);
if (!lyr3h)
goto out;
if (!ipvlan_is_vepa(ipvlan->port)) {
addr = ipvlan_addr_lookup(ipvlan->port, lyr3h, addr_type, true);
if (addr) {
// ip 或 arp 三层包,且目的地址是虚拟接口的,目的地址的虚拟接口收包、
if (ipvlan_is_private(ipvlan->port)) {
consume_skb(skb);
return NET_XMIT_DROP;
}
return ipvlan_rcv_frame(addr, &skb, true);
}
}
out:
// 目的地址不是本网卡接口,交给物理口,ip_route_output_flow查找路由表,走三层转发。
ipvlan_skb_crossing_ns(skb, ipvlan->phy_dev);
return ipvlan_process_outbound(skb);
}
收包流程
ipvlan 收包流程
物理卡收包后的处理,分配区分 L2,L3,L3S mode
rx_handler_result_t ipvlan_handle_frame(struct sk_buff **pskb)
{
struct sk_buff *skb = *pskb;
struct ipvl_port *port = ipvlan_port_get_rcu(skb->dev);
if (!port)
return RX_HANDLER_PASS;
switch (port->mode) {
case IPVLAN_MODE_L2:
return ipvlan_handle_mode_l2(pskb, port);
case IPVLAN_MODE_L3:
return ipvlan_handle_mode_l3(pskb, port);
#ifdef CONFIG_IPVLAN_L3S
case IPVLAN_MODE_L3S:
return RX_HANDLER_PASS;
#endif
}
/* Should not reach here */
WARN_ONCE(true, "%s called for mode = [%x]\n", __func__, port->mode);
kfree_skb(skb);
return RX_HANDLER_CONSUMED;
}
L2 mode 收包
static rx_handler_result_t ipvlan_handle_mode_l2(struct sk_buff **pskb,
struct ipvl_port *port)
{
struct sk_buff *skb = *pskb;
struct ethhdr *eth = eth_hdr(skb);
rx_handler_result_t ret = RX_HANDLER_PASS;
// 多播包,ipvlan_external_frame 判断如果是本地子接口发的包,且目的 ip 不是本物理网卡和子接口的 ip,复制一份,直接物理网卡的多播队列处理。
if (is_multicast_ether_addr(eth->h_dest)) {
if (ipvlan_external_frame(skb, port)) {
struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC);
/* External frames are queued for device local
* distribution, but a copy is given to master
* straight away to avoid sending duplicates later
* when work-queue processes this frame. This is
* achieved by returning RX_HANDLER_PASS.
*/
if (nskb) {
ipvlan_skb_crossing_ns(nskb, NULL);
ipvlan_multicast_enqueue(port, nskb, false);
}
}
} else {
// 单播报文,和 l3 mode 处理方法一致
/* Perform like l3 mode for non-multicast packet */
ret = ipvlan_handle_mode_l3(pskb, port);
}
return ret;
}
L3 mode 收包,该模式同样适合 l2 mode 的单播包
static rx_handler_result_t ipvlan_handle_mode_l3(struct sk_buff **pskb,
struct ipvl_port *port)
{
void *lyr3h;
int addr_type;
struct ipvl_addr *addr;
struct sk_buff *skb = *pskb;
rx_handler_result_t ret = RX_HANDLER_PASS;
lyr3h = ipvlan_get_L3_hdr(port, skb, &addr_type);
if (!lyr3h)
goto out;
addr = ipvlan_addr_lookup(port, lyr3h, addr_type, true);
if (addr)
// arp 或 ip 包,且目的地址是本网卡和子接口的,对应接口收包
ret = ipvlan_rcv_frame(addr, pskb, false);
out:
// 其他报文走物理网卡路由
return ret;
}
L3S mode
case IPVLAN_MODE_L3S:
return RX_HANDLER_PASS;
直接走物理卡主机路由