1 介绍
Calico 是一个强大的 Kubernetes CNI(容器网络接口)插件,它支持多种网络模式,包括直接路由(BGP)和封装模式(IPIP 或 VXLAN)。
在云上IPIP是常见的一种使用方式。Calico 支持几种 IPIP 模式,可以通过 IP 池的配置(ipipMode)来控制:
-
Always(始终使用):所有跨主机的 Pod 通信都使用 IPIP 封装,即使主机在同一子网内。
-
CrossSubnet(跨子网使用):仅当源主机和目标主机位于不同子网时使用 IPIP 封装;同一子网内的通信直接路由,不封装。
-
Never(从不使用):禁用 IPIP,完全依赖底层网络的路由能力(通常需要 BGP 或静态路由支持)。
部署Calico+IPIP模式之后,可以看到只有一个IPIP隧道接口tun0。IPIP是如何确定其他节点的remote-ip并与其节点通信?
2 NBMA模式
在Linux的IPIP实现中,隧道分为两种:
-
点对点(Point-to-Point)隧道:通过 ip tunnel add 指定了 remote IP,数据包直接发送到该地址。
-
NBMA 隧道:没有指定 remote IP(或为 0.0.0.0),需要通过路由表决定数据包的下一跳。
Calico使用了NBMA隧道。
NBMA(Non-Broadcast Multi-Access,非广播多址访问)隧道模式是一种特殊的隧道配置方式,它不依赖广播,而是通过显式配置路由来决定数据包的发送目标。在 IPIP 中,NBMA 模式通常表现为没有指定 remote IP的隧道(默认设备为 tunl0),或者通过路由表指定多个可能的下一跳IP
配置举例:
ip tunnel add tunl0 mode ipip
ip route add 10.0.0.0/24 via 192.168.1.1 dev tunl0 onlink
在这里,tunl0 是一个 NBMA 隧道,数据包的目标由路由表中的下一跳(192.168.1.1)决定。
3 IPIP NBMA简单分析
隧道设备初始化
在 ipip.c 中,ipip_init_net 函数初始化网络命名空间中的 IPIP 设备:
static int ipip_init_net(struct net *net)
{
// 创建默认设备 tunl0
return ip_tunnel_init_net(net, ipip_net_id, &ipip_link_ops, "tunl0");
}
-
tunl0 是默认的 NBMA 设备,remote 和 local IP初始为 0.0.0.0。
-
用户可以通过 ip tunnel add 创建自定义 NBMA 隧道。
数据包发送:
void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
const struct iphdr *tnl_params, u8 protocol)
{
...
inner_iph = (const struct iphdr *)skb_inner_network_header(skb);
connected = (tunnel->parms.iph.daddr != 0);
memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
dst = tnl_params->daddr;
// remote IP为0.0.0.0的情况下,认为是NBMA隧道,从路由中取到路由下一跳地址作为remote IP
if (dst == 0) {
/* NBMA tunnel */
struct ip_tunnel_info *tun_info;
if (!skb_dst(skb)) {
dev->stats.tx_fifo_errors++;
goto tx_error;
}
tun_info = skb_tunnel_info(skb);
if (tun_info && (tun_info->mode & IP_TUNNEL_INFO_TX) &&
ip_tunnel_info_af(tun_info) == AF_INET &&
tun_info->key.u.ipv4.dst)
dst = tun_info->key.u.ipv4.dst;
else if (skb->protocol == htons(ETH_P_IP)) {
rt = skb_rtable(skb);
dst = rt_nexthop(rt, inner_iph->daddr);
}
...
NBMA 模式的运行流程
-
发送端:
-
用户发送数据包到 NBMA 隧道设备( tunl0)。
-
如果 remote 未指定,内核查询路由表获取下一跳。
-
数据包被封装,外层 IP 头使用本地地址(local)和路由表中的下一跳地址。
-
封装后的数据包通过底层网络接口发送。
-
-
接收端:
-
接收到封装数据包后,内核根据外层 IP 头查找匹配的隧道设备。
-
如果找到( tunl0),解封装内层数据包并转发到协议栈。
-