土法炼钢兴趣小组的算法知识备份

【Linux 网络子系统深度拆解】虚拟网络设备内核实现:veth、bridge 与 macvlan

文章导航

分类入口
linuxnetworking
标签入口
#veth#bridge#macvlan#ipvlan#tun#tap#virtual-device#fdb#stp#container-networking#bpftrace

目录

物理网卡的 net_device_ops 背后是硬件寄存器和 DMA 引擎;虚拟网络设备的 net_device_ops 背后是纯软件逻辑。但对协议栈来说,它们是同一个接口——这就是 Linux 网络设备模型的抽象能力。

容器网络的绝大多数功能都建立在虚拟设备上:veth 做跨命名空间通信、bridge 做二层转发、macvlan 做轻量级隔离、tun/tap 做用户态网络。本文从内核源码拆解这四类设备的实现细节。

虚拟网络设备数据路径对比

一、veth pair:跨命名空间的零拷贝通道

veth(virtual ethernet)是成对创建的虚拟网卡,一端发送的包直接出现在另一端的接收队列中。它是容器网络最常用的跨命名空间通信机制。

1.1 创建与数据结构

veth pair 通过 netlink 创建,内核入口是 veth_newlink()。两端设备共享同一个驱动,各自持有指向对端的 peer 指针:

// drivers/net/veth.c
struct veth_priv {
    struct net_device __rcu  *peer;      // 对端设备(RCU 保护)
    atomic64_t               dropped;    // 丢包计数
    struct bpf_prog          *_xdp_prog; // XDP 程序
    struct veth_rq           *rq;        // 接收队列(NAPI 模式)
    unsigned int             requested_headroom;
};

peer 是 RCU 指针——删除 veth pair 时,一端先将对端的 peer 设为 NULL,另一端通过 rcu_dereference() 检测到 NULL 后停止转发。这比加锁高效得多,因为 veth_xmit() 在发包热路径上。

1.2 veth_xmit:发送路径

veth 的发送不涉及任何硬件操作——它只是将 skb 从一端”搬运”到另一端:

// 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)) {
        kfree_skb(skb);
        goto drop;
    }

    // 零拷贝:不复制数据,只切换 skb->dev
    if (likely(veth_forward_skb(rcv, skb, rq, use_napi) == NET_RX_SUCCESS))
        // 更新发送统计
    else
        atomic64_inc(&priv->dropped);

    rcu_read_unlock();
    return NETDEV_TX_OK;
}

veth_forward_skb() 内部调用 dev_forward_skb()__dev_forward_skb(),最终通过 netif_rx() 将 skb 注入对端设备的接收队列。关键操作:

  1. skb->dev = rcv——将包的设备指针从发送端切换为接收端
  2. skb_scrub_packet()——清除发送端的路由缓存、conntrack 关联
  3. eth_type_trans()——重新解析以太网头
  4. netif_rx()napi_gro_receive()——注入接收路径

这里没有 memcpy——skb 的数据缓冲区原封不动地从发送端传递到接收端。唯一的”代价”是接收端要走完整的收包路径,包括 netfilter 钩子。

1.3 双重 netfilter 遍历

veth 的一个重要性能特征是:一次跨命名空间通信,netfilter 钩子被遍历两次。

容器命名空间                         宿主机命名空间
┌────────────────────┐              ┌────────────────────┐
│ 应用 send()        │              │                    │
│      ↓             │              │                    │
│ OUTPUT 钩子 ①      │              │                    │
│      ↓             │              │                    │
│ POSTROUTING 钩子 ② │              │                    │
│      ↓             │              │                    │
│ veth_xmit()        │──skb 切换──→│ PREROUTING 钩子 ③  │
│                    │              │      ↓             │
└────────────────────┘              │ 路由判定           │
                                    │      ↓             │
                                    │ FORWARD 钩子 ④     │
                                    │      ↓             │
                                    │ POSTROUTING 钩子 ⑤ │
                                    │      ↓             │
                                    │ 物理网卡发送       │
                                    └────────────────────┘

五次 netfilter 钩子遍历是 veth 方案的固有开销。Cilium 通过在 veth 的 TC/XDP 钩子上挂载 BPF 程序,在 netfilter 之前就完成转发决策,绕过了大部分钩子遍历。

1.4 XDP native 模式

veth 支持 XDP native 模式。当对端挂载了 XDP 程序时,veth 切换到 NAPI 接收模式:

// drivers/net/veth.c
struct veth_rq {
    struct napi_struct  xdp_napi;     // NAPI 实例
    struct napi_struct  *napi;
    struct net_device   *dev;
    struct bpf_prog __rcu *xdp_prog;  // 该接收队列的 XDP 程序
    struct xdp_mem_info xdp_mem;
    struct xdp_rxq_info xdp_rxq;
    // ...
};

在 NAPI 模式下,veth_xmit() 不再直接调用 netif_rx(),而是将 skb 放入对端的 veth_rq 队列,然后调度 NAPI 轮询。veth_poll() 在轮询时执行 XDP 程序:

// veth_poll() → veth_xdp_rcv()(简化)
static int veth_xdp_rcv(struct veth_rq *rq, int budget, ...)
{
    // 对每个 skb 运行 XDP 程序
    act = bpf_prog_run_xdp(xdp_prog, &xdp);
    switch (act) {
    case XDP_PASS:    // 继续走协议栈
        break;
    case XDP_TX:      // 从原端口发回
        veth_xdp_tx(rq, &xdp, ...);
        break;
    case XDP_REDIRECT: // 重定向到其他设备/CPU
        xdp_do_redirect(rq->dev, &xdp, xdp_prog);
        break;
    case XDP_DROP:    // 丢弃
        break;
    }
}

XDP native 模式让 veth 可以在 netif_receive_skb() 之前就处理包,避免了分配 sk_buff 的开销。这是 Cilium 在 Kubernetes 中实现高性能网络策略的关键路径。

1.5 GRO 支持

veth 实现了 veth_gro_receive()veth_gro_complete() 回调,支持 GRO(Generic Receive Offload)聚合。当大量小包从容器发出时,GRO 在宿主机侧将它们合并为大包,减少协议栈处理次数。

二、Linux bridge:软件二层交换机

Linux bridge 是内核实现的二层交换机。Docker 的 docker0、Kubernetes 的 cni0 都是 bridge 设备。

2.1 核心数据结构

// net/bridge/br_private.h(简化)
struct net_bridge {
    struct net_device        *dev;           // bridge 设备本身
    struct list_head         port_list;      // 端口链表
    struct net_bridge_fdb_entry *fdb_hash;   // FDB 哈希表
    struct hlist_head        fdb_list;       // FDB 全量链表
    unsigned long            ageing_time;    // FDB 老化时间(默认 300s)
    u16                      group_fwd_mask; // LLDP/STP 帧转发掩码
    bridge_id                bridge_id;      // STP 网桥 ID
    bridge_id                designated_root;// STP 根网桥
    struct br_stp_info       stp_info;       // STP 状态
    // VLAN 过滤
    struct net_bridge_vlan_group __rcu *vlgrp;
    // ...
};

struct net_bridge_port {
    struct net_bridge        *br;            // 所属网桥
    struct net_device        *dev;           // 物理/虚拟端口设备
    struct list_head         list;           // 链入 br->port_list
    unsigned long            flags;          // BR_LEARNING, BR_FLOOD 等
    u8                       state;          // STP 状态
    u16                      port_no;        // 端口编号
    port_id                  port_id;        // STP 端口 ID
    // ...
};

struct net_bridge_fdb_entry {
    struct rhash_head        rhnode;         // 哈希表节点
    unsigned char            addr[ETH_ALEN]; // MAC 地址
    unsigned char            key[ETH_ALEN];  // 查找键
    struct net_bridge_port   *dst;           // 目标端口
    unsigned long            updated;        // 最后更新时间
    unsigned long            used;           // 最后使用时间
    u16                      vlan_id;        // VLAN ID
    u16                      flags;          // 本地/静态标记
};

2.2 br_handle_frame:入口处理

当一个帧到达 bridge 的端口设备时,收包路径中的 __netif_receive_skb_core() 通过 rx_handler 回调进入 bridge 处理:

// net/bridge/br_input.c(简化)
static rx_handler_result_t br_handle_frame(struct sk_buff **pskb)
{
    struct net_bridge_port *p = br_port_get_rcu(skb->dev);

    // 1. STP BPDU 处理
    if (is_link_local_ether_addr(dest)) {
        // 根据 group_fwd_mask 决定转发还是本地处理
        return br_handle_local_finish(...);
    }

    // 2. 检查端口 STP 状态
    switch (p->state) {
    case BR_STATE_FORWARDING:
    case BR_STATE_LEARNING:
        break;
    default:
        goto drop;  // DISABLED/LISTENING/BLOCKING 状态丢弃
    }

    // 3. 源 MAC 学习(如果端口启用了 BR_LEARNING)
    if (p->flags & BR_LEARNING)
        br_fdb_update(p->br, p, eth_hdr(skb)->h_source, vid, ...);

    // 4. 进入转发路径
    if (p->state == BR_STATE_FORWARDING)
        NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, ...
                br_handle_frame_finish);
}

2.3 FDB 转发决策

br_handle_frame_finish() 根据目的 MAC 在 FDB 中查找转发端口:

// net/bridge/br_input.c(简化)
static int br_handle_frame_finish(struct sk_buff *skb)
{
    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, ...);
    } else {
        // 未知单播/广播/组播:泛洪到所有端口(除入口端口)
        br_flood(br, skb, BR_PKT_UNICAST, ...);
    }
}

FDB 查找使用 rhashtable(可调整大小的哈希表),键是 (MAC, VLAN_ID) 二元组。查找在 RCU 读临界区中进行,无需加锁。

2.4 FDB 学习与老化

学习br_fdb_update() 在每个入帧时调用,记录源 MAC 与入口端口的映射。如果 MAC 已存在但端口不同(设备迁移),更新端口并刷新时间戳。

老化br_fdb_cleanup() 定期扫描 FDB,删除超过 ageing_time(默认 300 秒)未被使用的条目:

// 老化判断
if (time_after(jiffies, fdb->updated + br->ageing_time)) {
    // 删除过期条目
    fdb_delete(br, fdb, ...);
}

静态条目(通过 bridge fdb add 手动添加)和本地条目(bridge 自身 MAC)不参与老化。

2.5 STP 状态机

STP(Spanning Tree Protocol)防止二层环路。bridge 的每个端口维护一个 STP 状态:

状态 行为
DISABLED 0 端口关闭,不参与任何操作
LISTENING 1 处理 BPDU 帧,不学习、不转发
LEARNING 2 处理 BPDU,学习 MAC,不转发数据
FORWARDING 3 完全工作状态:学习 + 转发
BLOCKING 4 只处理 BPDU,阻止数据帧

状态转换由 BPDU 帧驱动。根选举完成后,冗余路径上的端口进入 BLOCKING 状态,保证无环路拓扑。

容器网络场景中通常关闭 STP(brctl stp docker0 off),因为 veth 拓扑是星型的,不存在环路。

2.6 VLAN 过滤

bridge 支持 per-port VLAN 过滤。每个端口可以配置允许通过的 VLAN ID 集合:

// include/uapi/linux/if_bridge.h
struct bridge_vlan_info {
    __u16 flags;    // BRIDGE_VLAN_INFO_MASTER, PVID, UNTAGGED
    __u16 vid;      // VLAN ID
};

启用 VLAN 过滤后(echo 1 > /sys/class/net/br0/bridge/vlan_filtering),bridge 在转发前检查帧的 VLAN 标签是否在目标端口的允许列表中。PVID(Port VLAN ID)为未标记帧添加默认 VLAN。

2.7 端口标志

bridge 端口通过 24 个二进制标志控制行为:

// include/linux/if_bridge.h
#define BR_HAIRPIN_MODE     BIT(0)   // 允许从入口端口发回(hairpin)
#define BR_BPDU_GUARD       BIT(1)   // 收到 BPDU 立即关闭端口
#define BR_ROOT_BLOCK       BIT(2)   // 阻止成为根端口
#define BR_MULTICAST_FAST_LEAVE BIT(3) // IGMP 快速离组
#define BR_LEARNING         BIT(5)   // 启用 FDB 源 MAC 学习
#define BR_FLOOD            BIT(6)   // 未知单播泛洪
#define BR_PROMISC          BIT(7)   // 混杂模式
#define BR_NEIGH_SUPPRESS   BIT(15)  // ARP/ND 代答(抑制广播)
#define BR_TX_FWD_OFFLOAD   BIT(20)  // 硬件卸载转发

BR_HAIRPIN_MODE 在容器网络中有特殊作用:当容器需要通过 bridge 访问同一宿主机上的服务时,包从 veth 端口进入 bridge 后需要从同一端口发回。默认关闭,需要显式启用。

三、macvlan:轻量级 MAC 地址虚拟化

macvlan 在一个物理网卡上创建多个虚拟设备,每个设备有独立的 MAC 地址。与 bridge 相比,macvlan 没有 FDB 学习和 STP 的开销。

3.1 数据结构

// include/linux/if_macvlan.h
struct macvlan_dev {
    struct net_device       *dev;          // 虚拟接口
    struct macvlan_port     *port;         // 父端口(管理结构)
    struct net_device       *lowerdev;     // 底层物理网卡
    enum macvlan_mode       mode;          // 工作模式
    u16                     flags;         // NOPROMISC, NODST
    struct vlan_pcpu_stats __percpu *pcpu_stats; // per-CPU 统计
    DECLARE_BITMAP(mc_filter, MACVLAN_MC_FILTER_SZ);
    unsigned int            macaddr_count;
};

struct macvlan_port {
    struct net_device       *dev;          // 底层物理网卡
    struct hlist_head       vlan_hash[MACVLAN_HASH_SIZE]; // MAC→macvlan 哈希
    struct list_head        vlans;         // 所有 macvlan 设备链表
    struct sk_buff_head     bc_queue;      // 广播队列
    // ...
};

macvlan_port 挂在物理网卡上,维护一个 MAC 地址哈希表。收到包时,通过目的 MAC 查找对应的 macvlan_dev,直接投递。

3.2 五种模式

// include/uapi/linux/if_link.h
enum macvlan_mode {
    MACVLAN_MODE_PRIVATE  = 1,   // 完全隔离
    MACVLAN_MODE_VEPA     = 2,   // 通过外部交换机转发
    MACVLAN_MODE_BRIDGE   = 4,   // 本地二层桥接
    MACVLAN_MODE_PASSTHRU = 8,   // 独占物理网卡
    MACVLAN_MODE_SOURCE   = 16,  // 基于源 MAC 过滤
};

bridge 模式:macvlan 子接口之间可以直接通信,不需要经过外部交换机。发送时检查目的 MAC 是否属于本端口的其他 macvlan,如果是则直接本地转发。

VEPA 模式:所有流量都发往物理网卡,即使目的地是同一宿主机上的另一个 macvlan。要求外部交换机支持 hairpin(802.1Qbg VEPA)。

private 模式:最严格的隔离——macvlan 子接口之间完全不能通信,即使外部交换机做 hairpin 也被内核丢弃。

passthru 模式:物理网卡只能有一个 macvlan 子接口,该接口直接使用物理网卡的 MAC。常用于需要完全控制物理网卡但不想直接迁移设备到容器的场景。

source 模式:基于源 MAC 地址列表决定哪些帧可以被接收。用于安全过滤。

3.3 收包路径

macvlan 通过 rx_handler 注册在物理网卡上(和 bridge 一样的机制):

// drivers/net/macvlan.c(简化)
static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb)
{
    struct macvlan_port *port = macvlan_port_get_rcu(skb->dev);
    struct macvlan_dev *vlan;

    if (is_multicast_ether_addr(dest)) {
        // 广播/组播:复制到所有 macvlan 子接口
        macvlan_broadcast(skb, port, ...);
    } else {
        // 单播:MAC 哈希查找
        vlan = macvlan_hash_lookup(port, dest);
        if (vlan) {
            skb->dev = vlan->dev;  // 切换到 macvlan 设备
            // 投递到该 macvlan 的接收队列
        }
    }
}

单播查找是 O(1) 哈希操作,比 bridge 的 FDB 更轻量——没有学习、没有老化、没有 STP。

3.4 macvlan 与 ipvlan 对比

ipvlan 与 macvlan 类似,但所有子接口共享同一个 MAC 地址,仅靠 IP 地址区分流量:

特性 macvlan ipvlan
MAC 地址 每个子接口独立 MAC 共享父设备 MAC
交换机影响 交换机看到多个 MAC 交换机只看到一个 MAC
广播处理 每个 MAC 独立接收 按 IP 过滤广播
模式 L2 操作 L2 模式和 L3 模式
性能 接近线速 L3 模式更快(跳过 ARP)
802.1x 兼容 不兼容(多 MAC) 兼容(单 MAC)

ipvlan L3 模式性能最优——它在三层路由级别分发包,完全跳过二层处理和 ARP 解析。

四、tun/tap:用户态网络的入口

tun 和 tap 是内核与用户态之间的数据通道。用户态程序打开 /dev/net/tun 字符设备,通过 read/write 收发网络包。

4.1 tun 与 tap 的区别

// include/uapi/linux/if_tun.h
#define IFF_TUN   0x0001    // L3 模式:用户态读写 IP 包
#define IFF_TAP   0x0002    // L2 模式:用户态读写以太网帧

4.2 内核到用户态(发送方向)

当协议栈向 tun/tap 设备发包时:

// drivers/net/tun.c(简化)
static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct tun_file *tfile;

    // 选择目标队列(多队列支持)
    tfile = rcu_dereference(tun->tfiles[txq]);

    // 将 skb 放入文件描述符的接收队列
    if (skb_queue_len(&tfile->socket.sk->sk_receive_queue) >= dev->tx_queue_len)
        goto drop;  // 队列满则丢弃

    skb_queue_tail(&tfile->socket.sk->sk_receive_queue, skb);

    // 唤醒在 read()/poll() 上等待的用户态进程
    wake_up_interruptible(&tfile->socket.wq.wait);
    return NETDEV_TX_OK;
}

用户态进程通过 read() 系统调用从文件描述符读取包。每次 read 返回一个完整的包。

4.3 用户态到内核(接收方向)

用户态程序通过 write() 系统调用注入包:

// drivers/net/tun.c(简化)
static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                            const struct iov_iter *from, ...)
{
    struct sk_buff *skb;

    // 分配 skb 并从用户态缓冲区复制数据
    skb = tun_alloc_skb(tfile, ...);
    skb_copy_datagram_from_iter(skb, 0, from, len);

    // 设置协议类型
    if (tun->flags & IFF_TUN)
        skb->protocol = pi.proto;  // tun 模式从 tun_pi 头获取
    else
        skb->protocol = eth_type_trans(skb, tun->dev); // tap 模式解析以太网头

    // 注入接收路径
    netif_rx_ni(skb);
}

注意这里有一次 copy_from_user()——这是 tun/tap 的主要性能瓶颈。每个包都要在内核态和用户态之间复制一次数据。

4.4 vhost-net 加速

QEMU/KVM 场景中,每个包经历两次上下文切换(内核→QEMU→内核)和一次数据复制。vhost-net 通过将数据路径下沉到内核态来消除这个开销:

标准路径:NIC → 内核 → QEMU(用户态) → write → 内核 → 虚拟机
vhost-net:NIC → 内核 → vhost worker(内核态) → 虚拟机

vhost-net 在内核中运行一个工作线程,直接操作 virtio 环形缓冲区,避免了用户态的上下文切换。

4.5 多队列支持

tun/tap 支持多队列(IFF_MULTI_QUEUE),允许多个用户态线程并行处理不同队列的包:

// 创建多队列 tun/tap
int fd = open("/dev/net/tun", O_RDWR);
struct ifreq ifr = { .ifr_flags = IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE };
ioctl(fd, TUNSETIFF, &ifr);

// 每个线程打开一个新的 fd 并绑定到同一设备
int fd2 = open("/dev/net/tun", O_RDWR);
ioctl(fd2, TUNSETIFF, &ifr);  // 自动分配到下一个队列

多队列配合 RSS/RPS 可以显著提高虚拟机网络吞吐量。

4.6 offload 特性

tun/tap 支持多种卸载特性,通过 virtio 网络头传递给用户态:

// include/uapi/linux/if_tun.h
#define TUN_F_CSUM     0x01    // 校验和卸载
#define TUN_F_TSO4     0x02    // TCP 分段卸载(IPv4)
#define TUN_F_TSO6     0x04    // TCP 分段卸载(IPv6)
#define TUN_F_TSO_ECN  0x08    // TSO + ECN
#define TUN_F_UFO      0x10    // UDP 分片卸载
#define TUN_F_USO4     0x20    // UDP 分段卸载(IPv4)
#define TUN_F_USO6     0x40    // UDP 分段卸载(IPv6)

启用 IFF_VNET_HDR 后,每个包前面附加 struct virtio_net_hdr,其中包含 GSO 类型和校验和偏移量。这让用户态程序(QEMU)可以处理大包而不需要内核先分段。

五、性能对比与选型指南

各类虚拟设备的吞吐量排序(从高到低):

ipvlan L3 ≥ macvlan bridge > ipvlan L2 > veth + bridge >> tun/tap
设备类型 每包开销 适用场景
ipvlan L3 最低(纯路由) 高性能容器,不需要独立 MAC
macvlan bridge 低(MAC 哈希查找) 需要独立 MAC 的容器
veth + bridge 中(双重协议栈 + FDB) Docker 默认、K8s 通用方案
veth + 路由 中(双重协议栈) Calico 方案
tun/tap 高(用户态拷贝 + 上下文切换) VPN、虚拟机

veth + bridge 是最通用的方案,牺牲一些性能换取最大的灵活性。macvlan 性能更好但不支持与宿主机通信(bridge 模式下 macvlan 无法访问父设备的 IP)。ipvlan L3 性能最优但对上层协议有限制(不支持 DHCP、不支持组播)。

六、可观测性

6.1 bpftrace 追踪虚拟设备

追踪 veth 转发:

bpftrace -e '
kprobe:veth_xmit {
    $dev = (struct net_device *)arg1;
    printf("veth_xmit: dev=%s len=%d\n", $dev->name,
           ((struct sk_buff *)arg0)->len);
}'

追踪 bridge FDB 更新:

bpftrace -e '
kprobe:br_fdb_update {
    $port = (struct net_bridge_port *)arg1;
    printf("fdb_update: port=%s\n", $port->dev->name);
}'

追踪 tun/tap 数据交换:

bpftrace -e '
kprobe:tun_net_xmit {
    $dev = (struct net_device *)arg1;
    printf("tun_xmit: dev=%s len=%d\n", $dev->name,
           ((struct sk_buff *)arg0)->len);
}
kprobe:tun_get_user {
    printf("tun_rx: from userspace, pid=%d\n", pid);
}'

6.2 bridge 状态查看

# 查看 FDB 表
bridge fdb show dev docker0

# 查看 STP 状态
brctl showstp docker0

# 查看 VLAN 过滤配置
bridge vlan show

# 查看端口统计
ip -s link show docker0

6.3 perf 性能分析

# 对比 veth 与 macvlan 的协议栈开销
perf record -g -e cycles -- iperf3 -c 10.0.0.2 -t 10
perf report --no-children

# 追踪 tun/tap 上下文切换
perf stat -e context-switches -p $(pidof qemu-system-x86_64) -- sleep 10

七、参考文献


上一篇网络命名空间:内核级网络隔离的实现

下一篇隧道协议内核实现:VXLAN、IPIP、GRE 与 WireGuard

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-24 · linux / networking

【Linux 网络子系统深度拆解】网络命名空间:内核级网络隔离的实现

容器网络的一切隔离能力,都建立在网络命名空间之上。本文从 Linux 6.6 内核源码拆解 struct net 的完整布局、possible_net_t 与 RCU 访问模式、pernet_operations 子系统注册与生命周期回调、copy_net_ns() 命名空间创建路径、per-netns 路由表/netfilter/socket 隔离机制,以及 veth pair 跨命名空间数据转发的 skb->dev 切换原理。


By .