每个容器都有独立的 IP 地址、独立的路由表、独立的 iptables 规则——这不是虚拟机,而是同一个内核里运行的进程。实现这一切的基础设施,是网络命名空间(network namespace)。
ip netns add test
一条命令就能创建一个全新的网络栈,但内核为此做了什么?一个
struct net 有多少字段?路由表、netfilter
钩子、socket 哈希表如何做到 per-namespace
隔离?veth
是怎么把一个包从容器命名空间”传送”到宿主机命名空间的?
本文从内核源码回答这些问题。
一、struct net:网络命名空间的内核表示
网络命名空间在内核中对应 struct net,定义在
include/net/net_namespace.h。这个结构体是整个网络栈的”根节点”——路由表、netfilter
钩子、接口列表、socket 哈希表、sysctl
参数,全部挂在它下面。
关键字段分组:
// include/net/net_namespace.h
struct net {
/* 生命周期管理 */
refcount_t passive; // 销毁引用计数
struct ns_common ns; // ns.count = 活跃引用计数
struct list_head list; // 链入全局 net_namespace_list
struct llist_node cleanup_list; // "死亡队列"——等待销毁
/* 设备管理 */
struct list_head dev_base_head; // 该命名空间所有网络设备链表
struct hlist_head *dev_name_head; // 按名称哈希查找设备
struct hlist_head *dev_index_head;// 按 ifindex 哈希查找设备
struct xarray dev_by_index; // XArray 快速索引查找
u32 ifindex; // 下一个待分配的接口编号
struct net_device *loopback_dev; // 每个命名空间都有自己的 lo
/* 协议栈状态 */
struct netns_ipv4 ipv4; // 路由表、sysctl、frag 队列
struct netns_ipv6 ipv6; // IPv6 路由与配置
struct netns_nf nf; // netfilter 钩子数组
struct netns_ct ct; // conntrack 哈希表
struct netns_bpf bpf; // BPF 程序绑定
/* 归属与标识 */
struct user_namespace *user_ns; // 所属用户命名空间
struct idr netns_ids; // 对端命名空间 ID 映射
u64 net_cookie; // 唯一标识符(写一次)
u32 hash_mix; // 哈希混淆因子
/* 策略路由 */
struct list_head rules_ops; // fib_rules 操作链
spinlock_t rules_mod_lock; // 路由规则修改锁
/* ... 更多子系统:XFRM、IPVS、MPLS 等 */
};几个设计要点:
双重引用计数。ns.count
跟踪活跃用户(进程、socket、设备),当它归零时触发销毁流程;passive
跟踪销毁过程中的异步清理任务,当它也归零时才释放内存。这是因为网络子系统的销毁涉及大量异步回调,不能在
ns.count 归零时立即 kfree()。
per-netns
loopback。每个命名空间创建时都会自动生成一个
lo 设备。loopback_dev
字段直接指向它,协议栈中大量路径需要快速访问
loopback——比如发给本机的包需要重定向到 lo。
hash_mix
缓存行对齐。hash_mix 字段被标记为
____cacheline_aligned_in_smp,因为它在每次路由查找、socket
查找中都会被读取,对齐到缓存行避免 false sharing。
二、possible_net_t 与 RCU 访问模式
内核中大量对象需要知道自己”属于哪个命名空间”:net_device
需要知道、sock 需要知道、sk_buff
的处理路径需要知道。问题是:如果内核编译时关闭了
CONFIG_NET_NS,这些字段就是浪费空间。
解决方案是
possible_net_t——一个条件编译的指针包装:
// include/net/net_namespace.h
typedef struct {
#ifdef CONFIG_NET_NS
struct net __rcu *net;
#endif
} possible_net_t;当 CONFIG_NET_NS
关闭时,possible_net_t
是空结构体,占零字节。对应的访问函数也变成返回全局
init_net 的常量:
static inline struct net *read_pnet(const possible_net_t *pnet)
{
#ifdef CONFIG_NET_NS
return rcu_dereference_protected(pnet->net, true);
#else
return &init_net; // 无命名空间支持时,只有一个全局命名空间
#endif
}
static inline void write_pnet(possible_net_t *pnet, struct net *net)
{
#ifdef CONFIG_NET_NS
rcu_assign_pointer(pnet->net, net);
#endif
}read_pnet() 使用
rcu_dereference_protected(),要求调用者持有适当的锁或处于
RCU 读临界区。还有一个 read_pnet_rcu() 变体使用
rcu_dereference(),适用于纯 RCU 读路径。
基于此,内核为网络设备和 socket 提供了便捷访问器:
// include/linux/netdevice.h
static inline struct net *dev_net(const struct net_device *dev)
{
return read_pnet(&dev->nd_net);
}
// include/net/sock.h
static inline struct net *sock_net(const struct sock *sk)
{
return read_pnet(&sk->sk_net);
}收包路径中,dev_net(skb->dev)
一次间接寻址就能拿到当前包所在的命名空间,然后查该命名空间的路由表、netfilter
钩子、socket 哈希表。
三、nsproxy:进程与命名空间的绑定
一个进程不只有网络命名空间,还有
PID、mount、UTS、IPC、cgroup、time 命名空间。内核用
struct nsproxy
把所有命名空间指针聚合在一起:
// include/linux/nsproxy.h
struct nsproxy {
refcount_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns_for_children;
struct net *net_ns; // 网络命名空间
struct time_namespace *time_ns;
struct time_namespace *time_ns_for_children;
struct cgroup_namespace *cgroup_ns;
};每个 task_struct 持有一个
nsproxy 指针。同一个 nsproxy
可以被多个进程共享(通过引用计数),这在 fork 时不指定
CLONE_NEW* 标志的情况下发生。
当进程调用 clone(CLONE_NEWNET, ...) 或
unshare(CLONE_NEWNET) 时:
- 内核分配新的
nsproxy(或修改当前的) - 调用
copy_net_ns()创建新的struct net - 新
nsproxy->net_ns指向新创建的命名空间 - 进程后续的所有网络操作都在新命名空间中进行
四、命名空间创建:copy_net_ns() 与 pernet_operations
4.1 创建路径
当 clone(CLONE_NEWNET)
触发命名空间创建时,调用链为:
clone() / unshare()
→ create_new_namespaces()
→ copy_net_ns(flags, user_ns, old_net)
→ net_alloc() // 分配 struct net
→ setup_net(net, user_ns)
→ 遍历 pernet_list,调用每个 ops->init(net)
→ 链入 net_namespace_list
setup_net() 是核心:它遍历所有通过
register_pernet_subsys() 注册的子系统,依次调用
init()
回调。每个子系统负责在新命名空间中分配自己的状态。
4.2 pernet_operations:子系统注册机制
每个需要 per-namespace
状态的子系统(路由、netfilter、conntrack、socket 等)都通过
pernet_operations 注册初始化和销毁回调:
// include/net/net_namespace.h
struct pernet_operations {
struct list_head list;
int (*init)(struct net *net); // 命名空间创建时调用
void (*pre_exit)(struct net *net); // 销毁前准备
void (*exit)(struct net *net); // 销毁时清理
void (*exit_batch)(struct list_head *net_exit_list); // 批量销毁
unsigned int *id; // 子系统 ID
size_t size; // 自动分配的 per-ns 数据大小
};以 IPv4 路由为例,简化的注册流程:
// net/ipv4/fib_frontend.c(示意)
static struct pernet_operations fib_net_ops = {
.init = fib_net_init, // 创建 per-ns 路由表
.exit = fib_net_exit, // 销毁 per-ns 路由表
};
static int __init ip_fib_init(void)
{
register_pernet_subsys(&fib_net_ops);
...
}当新命名空间创建时,fib_net_init()
被调用,它会:
- 分配该命名空间的
fib_table_hash - 创建默认的 local 表和 main 表
- 添加默认路由规则
注册顺序即初始化顺序。子系统注册的先后决定了创建新命名空间时
init() 回调的调用顺序,也决定了销毁时
exit()
回调的逆序调用。这意味着依赖关系必须通过注册顺序隐式表达。
4.3 两种注册函数的区别
int register_pernet_subsys(struct pernet_operations *); // 子系统级
int register_pernet_device(struct pernet_operations *); // 设备级register_pernet_subsys() 的回调在
register_pernet_device()
之前执行。这保证了底层子系统(路由表、netfilter)在设备初始化之前就绑定好。销毁顺序相反:先销毁设备,再销毁子系统。
五、Per-Namespace 资源隔离详解
5.1 路由表隔离
每个命名空间有独立的 FIB(Forwarding Information Base):
// include/net/netns/ipv4.h
struct netns_ipv4 {
struct hlist_head *fib_table_hash; // 路由表哈希
struct fib_table __rcu *fib_main; // main 表快速指针
struct fib_table __rcu *fib_default; // default 表快速指针
struct fib_rules_ops *rules_ops; // 策略路由规则引擎
struct inet_peer_base *peers; // per-ns 对端缓存
struct fqdir *fqdir; // per-ns IP 分片队列
// ... 100+ sysctl 参数
};路由查找的入口 fib_lookup() 通过
net->ipv4.fib_table_hash
定位到当前命名空间的路由表。这意味着:
- 容器 A 的默认路由指向
10.0.0.1 - 容器 B 的默认路由指向
172.16.0.1 - 宿主机的默认路由指向
192.168.1.1
三者互不干扰,因为它们查的是不同的
fib_table_hash。
5.2 Netfilter 钩子隔离
每个命名空间有独立的 netfilter 钩子数组:
// include/net/netns/netfilter.h
struct netns_nf {
struct nf_hook_entries __rcu *hooks_ipv4[NF_INET_NUMHOOKS];
struct nf_hook_entries __rcu *hooks_ipv6[NF_INET_NUMHOOKS];
struct nf_hook_entries __rcu *hooks_arp[NF_ARP_NUMHOOKS];
struct nf_hook_entries __rcu *hooks_bridge[NF_INET_NUMHOOKS];
// ...
};五个钩子点(PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING)每个协议族都是独立数组。在容器内执行
iptables -A INPUT -j DROP
只影响该容器的命名空间,宿主机的 INPUT 链完全不受影响。
nf_hook() 的第一件事就是通过
dev_net(skb->dev)
拿到命名空间,然后查该命名空间的钩子数组:
// 简化的钩子遍历路径
static inline int nf_hook(u_int8_t pf, unsigned int hook,
struct net *net, ...)
{
struct nf_hook_entries *e = rcu_dereference(net->nf.hooks_ipv4[hook]);
// 遍历 e->hooks[] 中注册的回调
}5.3 Socket 隔离
socket 通过 sk->sk_net
绑定到命名空间。端口绑定(bind)、连接查找(socket
lookup)都在命名空间范围内进行:
// include/net/sock.h
static inline struct net *sock_net(const struct sock *sk)
{
return read_pnet(&sk->sk_net);
}容器 A 和容器 B 可以同时监听 80 端口,因为它们的 socket
哈希表是不同的。收包路径中的 __inet_lookup() 在
sock_net(sk) 匹配的前提下才会返回结果。
5.4 网络设备隔离
每个 net_device 通过 nd_net
字段绑定到一个命名空间:
// include/linux/netdevice.h
static inline struct net *dev_net(const struct net_device *dev)
{
return read_pnet(&dev->nd_net);
}
static inline void dev_net_set(struct net_device *dev, struct net *net)
{
write_pnet(&dev->nd_net, net);
}设备可以跨命名空间迁移——ip link set eth0 netns container_ns
的内核实现就是调用
dev_change_net_namespace(),它会:
- 从旧命名空间的设备链表中摘除
- 调用
dev_net_set()更新nd_net - 插入新命名空间的设备链表
- 重新注册 sysfs 和 netlink 通知
物理网卡也可以迁移到容器命名空间(SR-IOV VF 常这么做),但 loopback 设备不能迁移——它是命名空间的”内置设备”。
六、init_net:默认命名空间
系统启动时,所有网络资源都在 init_net
这个全局命名空间中:
// include/net/net_namespace.h
extern struct net init_net;init_net
是所有命名空间链表(net_namespace_list)的起点。所有宿主机进程默认使用
init_net,物理网卡初始注册在
init_net 中。
内核中有大量代码通过 &init_net
直接引用默认命名空间。当 CONFIG_NET_NS
未启用时,所有 read_pnet() 调用返回
&init_net,整个命名空间机制退化为零开销。
全局命名空间链表的遍历:
// 需要持有 net_rwsem 或 rtnl_lock
#define for_each_net(VAR) \
list_for_each_entry(VAR, &net_namespace_list, list)
// RCU 保护的遍历
#define for_each_net_rcu(VAR) \
list_for_each_entry_rcu(VAR, &net_namespace_list, list)ip netns list 命令就是通过 netlink
遍历这个链表实现的。/proc/[pid]/ns/net
符号链接指向进程的网络命名空间文件描述符。
七、veth pair:跨命名空间的数据通道
veth(virtual ethernet)是一对虚拟网卡,一端在容器命名空间,另一端在宿主机命名空间。它是容器网络最常用的跨命名空间通信机制。
7.1 veth 的数据结构
// drivers/net/veth.c
struct veth_priv {
struct net_device __rcu *peer; // 对端设备指针
atomic64_t dropped;
struct bpf_prog *_xdp_prog; // XDP 程序
struct veth_rq *rq; // 接收队列(XDP 模式)
unsigned int requested_headroom;
};veth 的核心就是 peer 指针——它指向另一端的
net_device。两端设备可以在不同的命名空间中。
7.2 veth_xmit:命名空间切换的魔法
// drivers/net/veth.c(简化)
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;
rcu_read_lock();
rcv = rcu_dereference(priv->peer); // 获取对端设备
if (unlikely(!rcv) || !pskb_may_pull(skb, ETH_HLEN)) {
kfree_skb(skb);
goto drop;
}
// 关键:skb->dev 从当前设备切换为对端设备
// 对端设备在另一个命名空间 → 包进入另一个命名空间的协议栈
if (likely(veth_forward_skb(rcv, skb, ...) == NET_RX_SUCCESS))
...
rcu_read_unlock();
return NETDEV_TX_OK;
}veth_forward_skb() 内部调用
netif_rx() 或
dev_forward_skb(),将 skb
注入对端设备的接收队列。由于 rcv->nd_net
指向对端命名空间,后续的协议栈处理(路由查找、netfilter、socket
lookup)全部在对端命名空间中进行。
数据流示意:
容器命名空间 宿主机命名空间
┌──────────────┐ ┌──────────────┐
│ 应用 send() │ │ │
│ ↓ │ │ │
│ TCP/IP 协议栈│ │ │
│ ↓ │ │ │
│ eth0 (veth) │──veth_xmit──→│ vethXXX │
│ nd_net=容器ns│ skb->dev切换 │ nd_net=宿主ns│
└──────────────┘ │ ↓ │
│ netif_rx() │
│ ↓ │
│ 宿主协议栈 │
└──────────────┘
7.3 性能特征
veth 的 veth_xmit() 本质是内存操作——没有
DMA、没有中断、没有 ring
buffer。但它仍然需要经过完整的协议栈处理,包括 netfilter
钩子。
性能关键点:
- 零拷贝:
veth_xmit()不复制 skb 数据,只是修改skb->dev指针并重新注入接收路径 - GRO 支持:veth 支持 GRO 聚合,通过
veth_gro_receive()减少协议栈处理次数 - XDP 支持:veth 的对端可以挂载 XDP
程序,在
netif_receive_skb()之前就处理包,这是 Cilium 用 veth + XDP 加速容器网络的基础 - NAPI 模式:当对端挂载了 XDP
程序时,veth 切换到 NAPI 模式接收,通过
veth_poll()批量处理,减少锁竞争
八、命名空间的销毁与清理
命名空间的销毁比创建复杂得多——网络子系统中有大量异步资源(conntrack 条目、定时器、延迟工作队列),不能在最后一个引用释放时立即销毁。
8.1 销毁流程
最后一个 put_net(net) 使 ns.count → 0
→ __put_net(net)
→ 将 net 链入 cleanup_list(死亡队列)
→ 调度 net_cleanup_work(工作队列)
→ cleanup_net()
→ 遍历 pernet_list(逆序)
→ ops->pre_exit(net) // 停止新请求
→ 遍历 pernet_list(逆序)
→ ops->exit(net) // 释放资源
→ ops->exit_batch(list) // 批量释放
→ 等待 passive 引用归零
→ net_free(net) // 释放 struct net 内存
pre_exit 与 exit
分离。pre_exit()
先标记”正在关闭”,阻止新的资源分配;exit()
再实际释放资源。这避免了在释放过程中有新的对象创建导致
use-after-free。
exit_batch
优化。当多个命名空间同时销毁时(比如一批容器被删除),exit_batch()
接收整个命名空间列表,可以批量处理,减少锁获取次数。
8.2 引用计数管理
static inline struct net *get_net(struct net *net)
{
refcount_inc(&net->ns.count);
return net;
}
static inline void put_net(struct net *net)
{
if (refcount_dec_and_test(&net->ns.count))
__put_net(net); // 触发销毁
}
static inline struct net *maybe_get_net(struct net *net)
{
// 仅当 refcount > 0 时才成功获取引用
if (!refcount_inc_not_zero(&net->ns.count))
return NULL;
return net;
}maybe_get_net()
是命名空间正在销毁时的安全获取方式——如果命名空间已经进入销毁流程(refcount
= 0),返回 NULL 而不是递增一个已经归零的计数器。
谁持有引用:
- 每个属于该命名空间的
net_device:get_net()在register_netdevice()中 - 每个属于该命名空间的
sock:get_net()在 socket 创建时 - 持有该命名空间的进程:通过
nsproxy->net_ns /proc/[pid]/ns/net文件描述符:bind mount 保持命名空间存活- netlink socket:
get_net()在netlink_kernel_create()中
九、命名空间标识与互操作
9.1 netns_ids:跨命名空间引用
内核需要在不同命名空间之间建立引用关系(比如 veth
的两端),使用 struct idr netns_ids
来管理:
// struct net 中的字段
struct idr netns_ids; // 映射:本地 ID → 对端 struct net *ip link set veth0 netns <ns>
时,内核在两个命名空间的 netns_ids
中互相注册对方的 ID。netlink 消息中使用这个 ID
来引用其他命名空间,避免传递内核指针到用户态。
9.2 net_cookie:全局唯一标识
u64 net_cookie; // 在 struct net 中net_cookie
是命名空间的全局唯一标识符,在命名空间创建时写入一次,永不改变。BPF
程序通过 bpf_get_netns_cookie()
助手函数获取当前命名空间的 cookie,用于策略匹配和统计。
十、可观测性
10.1 查看命名空间信息
# 列出所有命名命名空间
ip netns list
# 查看进程的命名空间
ls -la /proc/$PID/ns/net
# 在指定命名空间中执行命令
ip netns exec test ip addr show
# 查看命名空间中的路由表
ip netns exec test ip route show table all
# 查看命名空间中的 iptables 规则
ip netns exec test iptables -L -v -n10.2 bpftrace 追踪命名空间操作
追踪命名空间创建与销毁:
# 追踪网络命名空间创建
bpftrace -e '
kprobe:copy_net_ns {
printf("netns create: pid=%d comm=%s\n", pid, comm);
}
kprobe:cleanup_net {
printf("netns destroy batch\n");
}'追踪设备跨命名空间迁移:
# 追踪 dev_change_net_namespace
bpftrace -e '
kprobe:dev_change_net_namespace {
$dev = (struct net_device *)arg0;
printf("dev %s moving to new netns, pid=%d comm=%s\n",
$dev->name, pid, comm);
}'追踪 veth 跨命名空间转发:
# veth_xmit 追踪
bpftrace -e '
kprobe:veth_xmit {
$skb = (struct sk_buff *)arg0;
$dev = (struct net_device *)arg1;
printf("veth_xmit: dev=%s len=%d\n", $dev->name, $skb->len);
}'10.3 perf 分析命名空间开销
# 统计命名空间创建耗时
perf trace -e 'syscalls:sys_enter_unshare' -p $PID
# 火焰图分析命名空间创建路径
perf record -g -e cycles -p $PID -- sleep 10
perf script | stackcollapse-perf.pl | flamegraph.pl > netns-create.svg十一、关键参数与调优
| 参数 | 默认值 | 说明 |
|---|---|---|
/proc/sys/net/netns/max |
无限制 | 系统最大命名空间数量(部分内核版本) |
/proc/sys/net/core/somaxconn |
4096 | per-netns socket 监听队列深度 |
/proc/sys/net/ipv4/ip_forward |
0 | per-netns IP 转发开关 |
/proc/sys/net/ipv4/conf/all/rp_filter |
0 | per-netns 反向路径过滤 |
/proc/sys/net/netfilter/nf_conntrack_max |
262144 | per-netns conntrack 表大小 |
几乎所有 /proc/sys/net/ 下的参数都是
per-namespace 的。这意味着容器内
sysctl -w net.ipv4.tcp_fin_timeout=10
只影响该容器的 TCP 栈,不影响宿主机。
但有一个陷阱:容器默认没有权限修改
sysctl。需要在容器运行时配置 --sysctl
选项,或者授予 CAP_NET_ADMIN 能力。在
Kubernetes 中,可以通过 Pod 的
securityContext.sysctls 配置安全的 sysctl
参数。
十二、容器网络架构中的命名空间
12.1 Docker 默认网络模型
宿主机命名空间 (init_net)
┌─────────────────────────────────────┐
│ eth0 (物理网卡) │
│ ↑ │
│ 路由表 + iptables NAT │
│ ↑ │
│ docker0 (bridge) │
│ ↑ ↑ │
│ vethAAA vethBBB │
└────┼──────────┼─────────────────────┘
│ │
│veth pair │veth pair
↓ ↓
┌────────┐ ┌────────┐
│容器 A │ │容器 B │
│eth0 │ │eth0 │
│10.0.0.2│ │10.0.0.3│
└────────┘ └────────┘
每个容器运行在独立的网络命名空间中。docker0
网桥在宿主机命名空间中,连接所有容器的 veth
对端。容器到外网的流量通过 iptables MASQUERADE 做 SNAT。
12.2 Kubernetes CNI 模式
Kubernetes 通过 CNI(Container Network Interface)插件管理命名空间网络:
- Flannel VXLAN:每个 Pod 一个 netns → veth → cni0 bridge → flannel.1 (VXLAN) → 物理网卡
- Calico:每个 Pod 一个 netns → veth → 宿主机路由表(无 bridge),BGP 通告路由
- Cilium:每个 Pod 一个 netns → veth + XDP/TC BPF → 宿主机,eBPF 直接做策略和负载均衡
无论哪种 CNI,第一步都是 ip netns add +
ip link add veth
创建隔离环境和跨命名空间通道。
12.3 性能考量
命名空间本身的开销很小——struct net
的分配和子系统初始化在微秒级完成。真正的开销在于跨命名空间通信:
- veth 路径长度:每次跨命名空间通信都要经过完整的发送路径 + 接收路径,包括两次 netfilter 遍历
- conntrack 开销:每个命名空间独立的 conntrack 表意味着宿主机和容器的 conntrack 条目不共享,总内存占用翻倍
- 路由查找重复:从容器发出的包要查容器的路由表,到达宿主机后还要查宿主机的路由表
Cilium 通过 BPF 直接在 veth 的 TC/XDP 钩子上做转发决策,绕过了宿主机侧的完整协议栈,将跨命名空间的延迟降低了约 30-50%。
十三、参考文献
- Linux 6.6 内核源码
include/net/net_namespace.h、include/linux/nsproxy.h - Linux 6.6 内核源码
include/net/netns/ipv4.h、include/net/netns/netfilter.h - Linux 6.6 内核源码
drivers/net/veth.c - Linux 6.6 内核源码
net/core/net_namespace.c - Rami Rosen,《Linux Kernel Networking: Implementation and Theory》
- man 7 network_namespaces
上一篇:Traffic Control 深度拆解:qdisc、class 与 filter
下一篇:虚拟网络设备内核实现:veth、bridge 与 macvlan
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Linux 网络子系统深度拆解】虚拟网络设备内核实现:veth、bridge 与 macvlan
容器网络不能没有虚拟设备。本文从 Linux 6.6 内核源码拆解四类核心虚拟网络设备的实现:veth pair 的 veth_xmit 零拷贝转发与 XDP native 模式、Linux bridge 的 br_handle_frame 转发路径与 FDB 学习/老化机制、macvlan 五种模式的内核实现差异、tun/tap 的内核态与用户态数据交换路径,以及各类设备的性能特征对比。
【从零造容器】容器网络性能真相:veth vs macvlan vs eBPF 数据面
容器网络为什么比裸机慢?veth + bridge 每个包经过两次 netfilter,macvlan 跳过了 bridge,Cilium 用 eBPF 替掉了 iptables。到底慢多少?我们用 iperf3、wrk 和自定义 echo server 实测。
【Kubernetes 网络深度系列】虚拟网络设备:veth / bridge / tun/tap / macvlan / ipvlan
五种 Linux 虚拟网络设备的内核实现原理、数据流路径、性能代价与适用场景,附手工实验验证。
【Linux 网络子系统深度拆解】网络丢包定位:从 drop_monitor 到 kfree_skb 追踪
从内核源码拆解 Linux 网络丢包追踪的完整体系:kfree_skb tracepoint 与 80+ 种 drop_reason 枚举、drop_monitor netlink 子系统、dropwatch 工具、perf 丢包记录、bpftrace 丢包聚合脚本,以及生产环境常见丢包点速查表。