每个容器都有独立的 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 -n
10.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
同主题继续阅读 把当前热点继续串成多页阅读,而不是停在单篇消费。
2026-04-25 · linux / networking
容器网络不能没有虚拟设备。本文从 Linux 6.6 内核源码拆解四类核心虚拟网络设备的实现:veth pair 的 veth_xmit 零拷贝转发与 XDP native 模式、Linux bridge 的 br_handle_frame 转发路径与 FDB 学习/老化机制、macvlan 五种模式的内核实现差异、tun/tap 的内核态与用户态数据交换路径,以及各类设备的性能特征对比。
2026-04-11 · linux / containers
容器网络为什么比裸机慢?veth + bridge 每个包经过两次 netfilter,macvlan 跳过了 bridge,Cilium 用 eBPF 替掉了 iptables。到底慢多少?我们用 iperf3、wrk 和自定义 echo server 实测。
2026-04-03 · linux / networking
五种 Linux 虚拟网络设备的内核实现原理、数据流路径、性能代价与适用场景,附手工实验验证。
2025-07-24 · linux / networking
从内核源码拆解 Linux 网络丢包追踪的完整体系:kfree_skb tracepoint 与 80+ 种 drop_reason 枚举、drop_monitor netlink 子系统、dropwatch 工具、perf 丢包记录、bpftrace 丢包聚合脚本,以及生产环境常见丢包点速查表。