record of kmesh project
关于Linux内核网络协议栈可以参考 计算机网络基础 — Linux 内核网络协议栈。
在我的理解中,每一个network namespace 都需要一个网卡,所以之前在docker的实现中,为容器设置namespace的时候都配置了一个虚拟网卡。而且根据kmesh的代码,似乎内核在处理network namespace的时候,也就是进出network namespace的时候,在虚拟网卡上会触发和物理网卡一样的系统调用。
参考:你知道吗?Linux中的iptables到底是什么?netfilter又是什么? 白话iptables工作原理 |097
核心:五链四表。
五链: Linux Netfilter中的 prerouting, input, forward, output, 和 postrouting 链。作用在网络层和传输层。
四表: filter, nat, mangle, 和 raw 表。每个表包含多个链。
PREROUTING
INPUT
FORWARD
OUTPUT
POSTROUTING
参阅 IPsec。Linux XFRM Reference Guide for IPsec。 kmesh 中的 IPsec,见项目的kmesh/docs/proposal/kmesh_support_encrypt.md文件。
IPsec 是一种网络安全协议,用于提供IP层的安全性。IPsec提供了两种安全机制认证和加密。 IPsec还需要有密钥的管理和交换功能。 IPsec的两种工作模式:
IPSec的的设置是单向的。
IPsec协议组包括:
IPsec的工作模式有两种:传输模式和隧道模式。
ipsec的传输流程可以参考Linux XFRM Reference Guide for IPsec中的图片如下

使用隧道模式的时候,新的ip头部的ip是什么? 新的IP头部的IP地址是如何确定是根据IPsec的Policy来确定的。Policy中会指定源IP和目标IP。 样例如下,假设容器的地址为10.xx, 节点ip为192.xx,那么使用的就是容器虚拟网卡地址,如果tmpl后指定的是节点的ip地址,那么就是节点的IP地址。
ip xfrm policy add src 10.244.1.10 dst 10.244.2.10 \
dir out tmpl src 10.244.1.10 dst 10.244.2.10 \
proto esp mode tunnel
ip xfrm policy add src 10.244.1.10 dst 10.244.2.10 \
dir out tmpl src 192.168.1.10 dst 192.168.1.20 \
proto esp mode tunnel
数据包解密的过程不依赖于Policy,首先是找到对应的State进行解密。
关于Linux tc流量控制参考 Linux tc流量控制。
Linux 内核中用于管理网络流量的核心机制。它控制数据包在网络接口上的排队、调度和发送行为。
网络接口 (eth0)
│
├── Root Qdisc (根队列规则)
│ ├── Class 1 (流量类别1)
│ │ └── Leaf Qdisc (叶子队列规则)
│ └── Class 2 (流量类别2)
│ └── Leaf Qdisc
└── Ingress Qdisc (入站队列规则)
eBPF程序被加载到内核之后,内核会为其分配一个唯一的文件描述符,本质上是一个指向内核中eBPF程序对象的引用。
如何动态加载eBPF程序
BPF_PROG_LOAD 系统调用可以用来加载eBPF程序。这个调用会将eBPF程序加载到内核中,并返回一个文件描述符(FD)。
在Kubernetes等容器编排系统中,节点内的网络架构通常如下:
Pod1 (ns1) Pod2 (ns2) Pod3 (ns3)
| | |
veth1 veth2 veth3
| | |
veth1-peer veth2-peer veth3-peer
| | |
└──────────────┴──────────────┘
|
br0 (网桥)
|
eth0 (物理网卡)
每个Pod都有自己的网络命名空间,通过veth对连接到主机的网桥,网桥再连接到物理网卡。
场景:Pod1向Pod2发送数据包
1. 为什么节点内通信不经过物理网卡?
2. veth对的工作原理
// 内核中veth的核心实现概念
struct veth_priv {
struct net_device *peer; // 指向对端设备
atomic64_t dropped;
};
// 当数据包从一端发送时,直接转发到对端
static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct veth_priv *priv = netdev_priv(dev);
struct net_device *rcv = priv->peer;
// 直接将数据包发送到对端设备
if (likely(veth_forward_skb(rcv, skb, false) == NET_RX_SUCCESS)) {
// 统计更新
}
return NETDEV_TX_OK;
}
3. Linux网桥的转发逻辑
// 网桥转发决策的核心逻辑
int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct net_bridge_port *p = br_port_get_rcu(skb->dev);
struct net_bridge *br = p->br;
struct net_bridge_fdb_entry *dst;
// 查找目标MAC地址
dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);
if (dst) {
// 找到目标端口,直接转发
br_forward(dst->dst, skb, false, true);
} else {
// 未找到则广播
br_flood(br, skb, BR_PKT_UNICAST, false, true);
}
}
节点内通信路径:
Pod1 → veth1 → veth1-peer → br0 → veth2-peer → veth2 → Pod2
跨节点通信路径:
Pod1 → veth1 → veth1-peer → br0 → eth0 → 物理网络 → 目标节点eth0 → 目标节点br0 → 目标veth-peer → 目标veth → 目标Pod
1. 内核态处理
2. CPU缓存友好
3. 延迟特征
基于以上虚拟网络原理,我们可以更深入理解Kmesh的IPsec设计:
1. 为什么节点内不需要加密?
2. 跨节点加密的必要性
3. Kmesh的KNIMap设计合理性
// KNIMap只包含跨节点的Pod CIDR
// 节点内的Pod IP不会出现在KNIMap中
// 因此tc_mark_encrypt程序不会对节点内流量进行标记
if (bpf_map_lookup_elem(&kni_map, &key)) {
// 只有跨节点的目标IP才会被标记为需要加密
mark_encrypt(ctx);
}
这种设计既保证了跨节点通信的安全性,又避免了节点内通信的不必要加密开销。
什么是Boot ID? Boot ID 是Linux系统中的一个唯一标识符,用于标识系统的每次启动。每当系统重新启动时,内核会生成一个新的、全局唯一的Boot ID。
cat /proc/sys/kernel/random/boot_id
# 输出示例:7b271d9b-6642-4ca7-bb9f-2649b28a90cf
Boot ID的特征
struct sock (Socket) 代表: 网络连接或套接字 作用: 管理连接状态、缓冲区、协议信息 位置: 内核网络栈的核心数据结构 struct sk_buff (Socket Buffer) 代表: 网络数据包 作用: 承载网络数据在协议栈间的传递 位置: 网络数据包的核心载体