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

【Linux 网络子系统深度拆解】隧道协议内核实现:VXLAN、IPIP、GRE 与 WireGuard

文章导航

分类入口
linuxnetworking
标签入口
#vxlan#ipip#gre#wireguard#tunnel#overlay#ip-tunnel#metadata-dst#encapsulation#vtep#bpftrace

目录

Overlay 网络的核心操作只有两个:封装和解封装。但不同的隧道协议在封装开销、多租户隔离、加密能力、硬件卸载支持上差异巨大。IPIP 只加 20 字节外层 IP 头,VXLAN 需要 50 字节但支持 1600 万个隔离网络,WireGuard 加 60 字节但提供了 ChaCha20-Poly1305 加密。

本文从内核源码拆解这四类隧道协议的实现,以及它们共享的 ip_tunnel 通用框架。

隧道协议封装格式对比

一、ip_tunnel 通用框架

Linux 内核为 IP 隧道提供了一套通用框架,IPIP、GRE、SIT(IPv6-in-IPv4)都基于它构建。核心数据结构是 struct ip_tunnelstruct ip_tunnel_key

1.1 ip_tunnel_key:隧道元数据

// include/net/ip_tunnels.h
struct ip_tunnel_key {
    __be64      tun_id;         // 隧道标识符(VNI/GRE key)
    union {
        struct {
            __be32 src;         // 外层源 IP
            __be32 dst;         // 外层目的 IP
        } ipv4;
        struct {
            struct in6_addr src;
            struct in6_addr dst;
        } ipv6;
    } u;
    __be16      tun_flags;      // 隧道标志
    u8          tos;            // 外层 TOS/TC
    u8          ttl;            // 外层 TTL/HL
    __be32      label;          // IPv6 Flow Label
    u32         nhid;           // Nexthop ID
    __be16      tp_src;         // 传输层源端口(UDP 隧道)
    __be16      tp_dst;         // 传输层目的端口
    __u8        flow_flags;
};

ip_tunnel_key 抽象了所有 IP 隧道的公共参数。VXLAN 的 VNI 映射到 tun_id,GRE 的 key 也映射到 tun_id,UDP 隧道(VXLAN、Geneve)使用 tp_src/tp_dst 字段。

1.2 ip_tunnel_info:元数据隧道

// include/net/ip_tunnels.h
struct ip_tunnel_info {
    struct ip_tunnel_key    key;
    struct ip_tunnel_encap  encap;
#ifdef CONFIG_DST_CACHE
    struct dst_cache        dst_cache;   // 路由缓存
#endif
    u8                      options_len; // 可选头长度
    u8                      mode;        // TX/IPv6/Bridge 标志
};

#define IP_TUNNEL_INFO_TX      0x01  // 发送方向的隧道参数
#define IP_TUNNEL_INFO_IPV6    0x02  // key 中包含 IPv6 地址
#define IP_TUNNEL_INFO_BRIDGE  0x04  // 桥接隧道 ID

ip_tunnel_info 是 metadata mode(流式隧道)的核心。当隧道设备配置为 external 模式时,OVS 或 BPF 程序通过 metadata_dstip_tunnel_info 附加到 skb 上,隧道设备直接使用这些参数进行封装,而不是查自己的静态配置。

1.3 struct ip_tunnel

// include/net/ip_tunnels.h
struct ip_tunnel {
    struct ip_tunnel __rcu  *next;      // 哈希链表
    struct hlist_node       hash_node;

    struct net_device       *dev;       // 隧道网络设备
    struct net              *net;       // 所属命名空间

    unsigned long           err_time;   // 最后一次 ICMP 错误时间
    int                     err_count;  // ICMP 错误计数

    /* GRE 专用 */
    u32                     i_seqno;    // 最后收到的序列号
    atomic_t                o_seqno;    // 最后发送的序列号
    int                     tun_hlen;   // 隧道头长度

    /* ERSPAN 专用 */
    u32                     index;      // ERSPAN type II 索引
    u8                      erspan_ver; // ERSPAN 版本
    u8                      dir;        // 方向(入/出)
    u16                     hwid;       // 硬件 ID

    struct dst_cache        dst_cache;  // 路由缓存
    struct ip_tunnel_parm   parms;      // 用户态配置参数
    int                     hlen;       // tun_hlen + encap_hlen
    struct ip_tunnel_encap  encap;      // FOU/GUE 封装参数
};

struct ip_tunnel 是 IPIP、GRE、SIT 隧道设备的私有数据(通过 netdev_priv(dev) 获取)。dst_cache 缓存外层路由查找结果,避免每包都做 FIB 查找。

1.4 通用发送路径

所有基于 ip_tunnel 框架的隧道共享 ip_tunnel_xmit() 发送函数:

// net/ipv4/ip_tunnel.c(简化)
void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
                    const struct iphdr *tnl_params, u8 protocol)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);

    // 1. 查找外层路由(优先使用 dst_cache)
    rt = ip_tunnel_get_route(tunnel, skb, ...);

    // 2. 检查 MTU,必要时发送 ICMP need-frag
    if (skb->len > mtu) {
        icmp_ndo_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
        goto tx_error;
    }

    // 3. 构建外层 IP 头
    skb_push(skb, tunnel->hlen);
    iptunnel_xmit(skb, dev, rt, ...);
}

iptunnel_xmit() 填充外层 IP 头字段(src、dst、TTL、TOS、protocol),然后调用 ip_local_out() 将封装后的包送入外层 IP 栈。

二、IPIP:最小开销的 IP 隧道

IPIP(IP-in-IP)是最简单的隧道协议——在原始 IP 包外面再套一层 IP 头,开销只有 20 字节。

2.1 封装格式

┌─────────────────────────────┐
│ 外层 IP 头(20 字节)       │ protocol = 4 (IPIP)
│ src = 本端 VTEP IP          │
│ dst = 对端 VTEP IP          │
├─────────────────────────────┤
│ 内层 IP 头(20 字节)       │
│ src = 容器 IP               │
│ dst = 目的容器 IP           │
├─────────────────────────────┤
│ TCP/UDP + 载荷              │
└─────────────────────────────┘

外层 IP 头的 protocol 字段设为 4(IPPROTO_IPIP),表示载荷是另一个 IPv4 包。

2.2 发送路径

// net/ipv4/ipip.c(简化)
static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb,
                                    struct net_device *dev)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);

    if (unlikely(skb->protocol != htons(ETH_P_IP)))
        goto tx_error;  // IPIP 只能封装 IPv4

    // 使用通用框架发送
    ip_tunnel_xmit(skb, dev, &tunnel->parms.iph, IPPROTO_IPIP);
    return NETDEV_TX_OK;
}

IPIP 的发送几乎是直接调用通用框架。没有额外的隧道头部、没有序列号、没有校验和——极致简单。

2.3 接收路径

// net/ipv4/ipip.c(简化)
static int ipip_rcv(struct sk_buff *skb)
{
    struct ip_tunnel *tunnel;

    // 通过外层 IP 的 src/dst 查找对应的隧道设备
    tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex,
                              TUNNEL_NO_KEY, iph->saddr, iph->daddr, 0);
    if (tunnel) {
        // 剥离外层 IP 头,将 skb->dev 设为隧道设备
        ip_tunnel_rcv(tunnel, skb, ...);
    }
}

接收端通过 protocol 字段(值为 4)匹配到 IPIP 处理函数。ip_tunnel_lookup() 根据外层 IP 的源/目的地址找到对应的隧道设备。

2.4 限制

IPIP 的限制明显:

Calico 默认使用 IPIP(IPIPMode: Always),因为在同一数据中心内不存在中间设备过滤的问题,而 20 字节的最小开销带来了最优的吞吐量。

三、GRE:通用路由封装

GRE(Generic Routing Encapsulation)在外层 IP 头和内层包之间插入一个 GRE 头部,提供可选的 key、序列号和校验和。

3.1 封装格式

┌─────────────────────────────┐
│ 外层 IP 头(20 字节)       │ protocol = 47 (GRE)
├─────────────────────────────┤
│ GRE 头部(4-16 字节)       │
│ ┌─ C K S ─── Protocol ────┐ │
│ │ 校验和(可选,4B)       │ │
│ │ Key(可选,4B)          │ │
│ │ 序列号(可选,4B)       │ │
│ └─────────────────────────┘ │
├─────────────────────────────┤
│ 内层包(IP/Ethernet/...)   │
└─────────────────────────────┘

GRE 头部最小 4 字节(只有标志位和协议字段),最大 16 字节(校验和 + key + 序列号都启用)。

3.2 Key 字段

GRE key 是 32 位标识符,允许同一对 IP 之间建立多条逻辑隧道:

// struct ip_tunnel 中 GRE 专用字段
u32     i_seqno;     // 接收序列号(可选)
atomic_t o_seqno;    // 发送序列号(可选)
int     tun_hlen;    // GRE 头部长度(根据选项变化)

不同于 IPIP,GRE 的 key 字段映射到 ip_tunnel_key.tun_id,在 metadata mode 中可以由 BPF 或 OVS 动态设置。

3.3 ERSPAN

ERSPAN(Encapsulated Remote Switched Port Analyzer)是 GRE 的扩展,用于远程端口镜像:

// struct ip_tunnel 中 ERSPAN 专用字段
u32     index;       // ERSPAN type II 会话索引
u8      erspan_ver;  // 版本(1 或 2)
u8      dir;         // 镜像方向(入/出)
u16     hwid;        // 硬件 ID

ERSPAN 在 GRE 封装内再加一层 ERSPAN 头,携带镜像元数据。Linux 内核从 4.14 开始支持 ERSPAN type I/II,从 4.16 开始支持 type III(带时间戳)。常用于跨数据中心的流量镜像和分析。

四、VXLAN:大规模 overlay 的标准方案

VXLAN(Virtual Extensible LAN)将完整的二层以太网帧封装在 UDP 数据报中,通过 24 位 VNI 支持最多 1600 万个隔离网络。

4.1 封装格式

┌──────────────────────────────────┐
│ 外层以太网头(14B)              │ 源/目的 MAC: VTEP 设备 MAC
├──────────────────────────────────┤
│ 外层 IP 头(20B)               │ 源/目的 IP: VTEP 节点 IP
├──────────────────────────────────┤
│ UDP 头(8B)                     │ 目的端口: 4789
│                                  │ 源端口: 内层包哈希(用于 ECMP)
├──────────────────────────────────┤
│ VXLAN 头(8B)                   │ VNI (24-bit): 网络隔离标识
├──────────────────────────────────┤
│ 内层以太网帧                     │ 原始 L2 帧(包含 VLAN 标签)
└──────────────────────────────────┘

总封装开销: 14 + 20 + 8 + 8 = 50 字节

4.2 VXLAN 头部

// include/net/vxlan.h
struct vxlanhdr {
    __be32 vx_flags;    // 标志位,I 位表示 VNI 有效
    __be32 vx_vni;      // VNI(高 24 位)+ Reserved(低 8 位)
};

#define VXLAN_HF_VNI    cpu_to_be32(BIT(27))  // I 标志位
#define VXLAN_N_VID     (1u << 24)            // 最大 VNI 数量
#define VXLAN_VID_MASK  (VXLAN_N_VID - 1)

#define IANA_VXLAN_UDP_PORT  4789   // 标准端口

4.3 struct vxlan_dev

// include/net/vxlan.h
struct vxlan_dev {
    struct vxlan_dev_node hlist4;       // VNI 哈希表(IPv4)
    struct vxlan_dev_node hlist6;       // VNI 哈希表(IPv6)
    struct list_head      next;         // per-netns 链表

    struct vxlan_sock __rcu *vn4_sock;  // IPv4 UDP socket
    struct vxlan_sock __rcu *vn6_sock;  // IPv6 UDP socket

    struct net_device     *dev;         // VXLAN 网络设备
    struct net            *net;         // 所属命名空间
    struct vxlan_rdst     default_dst;  // 默认远端目的

    struct timer_list     age_timer;    // FDB 老化定时器
    spinlock_t            hash_lock[FDB_HASH_SIZE];
    unsigned int          addrcnt;      // FDB 条目计数
    struct gro_cells      gro_cells;    // GRO 支持

    struct vxlan_config   cfg;          // 用户态配置
    struct hlist_head     fdb_head[FDB_HASH_SIZE]; // FDB 哈希表
};

vxlan_dev 的核心是 FDB 哈希表(fdb_head)——它把内层 MAC 地址映射到远端 VTEP 的 IP 地址。收到内层帧时,查 FDB 确定应该把封装后的 UDP 包发到哪个远端节点。

4.4 FDB 转发表

// drivers/net/vxlan/vxlan_core.c(简化)
struct vxlan_fdb {
    struct hlist_node    hlist;        // 哈希链表节点
    struct rcu_head      rcu;
    unsigned long        updated;     // 最后更新时间
    unsigned long        used;        // 最后使用时间
    struct list_head     remotes;     // 远端目的列表(可多个)
    u8                   eth_addr[ETH_ALEN]; // 内层 MAC 地址
    u16                  state;       // NUD_REACHABLE 等
    __be32               vni;         // VNI
    u16                  flags;       // 静态/本地等标记
};

struct vxlan_rdst {
    __be32              remote_ip;    // 远端 VTEP IP
    __be16              remote_port;  // 远端 UDP 端口
    __be32              remote_vni;   // 远端 VNI
    u32                 remote_ifindex; // 出口设备索引
    struct list_head    list;
    struct rcu_head     rcu;
};

FDB 学习有三种模式:

组播学习:VTEP 加入组播组,未知单播/广播帧通过组播泛洪,收到应答后学习 MAC→VTEP 映射。

头端复制(Head-End Replication):手动配置 bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst <remote_ip>,未知帧被复制到所有已知 VTEP。

控制面注入:CNI 插件(Flannel、Calico)或 BGP EVPN 直接通过 netlink 写入 FDB 条目。这是生产环境最常用的模式——避免了组播和泛洪的开销。

4.5 VXLAN 发送路径

// drivers/net/vxlan/vxlan_core.c(简化)
static netdev_tx_t vxlan_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct vxlan_dev *vxlan = netdev_priv(dev);
    struct vxlan_fdb *f;

    // 1. 内层目的 MAC 查 FDB
    f = vxlan_find_mac(vxlan, eth_hdr(skb)->h_dest, vni);

    if (f) {
        // 单播:遍历该 MAC 的远端列表
        list_for_each_entry_rcu(rdst, &f->remotes, list)
            vxlan_xmit_one(skb, dev, vni, rdst, ...);
    } else {
        // 未知单播:遍历 default_dst(泛洪)
        vxlan_xmit_one(skb, dev, vni, &vxlan->default_dst, ...);
    }
}

vxlan_xmit_one() 执行实际的封装:

  1. 查找外层路由(ip_route_output()
  2. 构建 VXLAN 头(设置 VNI、I 标志位)
  3. 构建 UDP 头(目的端口 4789,源端口 = 内层帧哈希)
  4. 通过 udp_tunnel_xmit_skb() 发送

源端口使用内层帧的哈希值是关键优化——不同的内层流量产生不同的外层 UDP 源端口,让中间交换机的 ECMP 哈希能区分不同的隧道流量。

4.6 VXLAN 接收路径

VXLAN 设备在创建时注册一个 UDP socket 监听端口 4789。收到 UDP 包后:

udp_rcv() → vxlan_rcv()
  → 解析 VXLAN 头,提取 VNI
  → 通过 VNI 哈希表查找 vxlan_dev
  → 剥离外层头部
  → skb->dev = vxlan->dev
  → 源 MAC 学习(更新 FDB)
  → netif_rx() → 进入内层协议栈

4.7 Metadata mode(流式隧道)

传统 VXLAN 设备绑定一个固定 VNI,每个 VNI 需要一个独立的 VXLAN 设备。Metadata mode(也叫 collect_mdexternal)允许单个 VXLAN 设备处理所有 VNI:

# 创建 metadata mode VXLAN 设备
ip link add vxlan0 type vxlan external dstport 4789

在 metadata mode 下: - 发送时,上层(OVS/BPF)将 ip_tunnel_info 附加到 skb 的 metadata_dst 中,包含 VNI、远端 IP 等参数 - VXLAN 设备从 metadata_dst 读取参数进行封装,而不是查自己的 FDB - 接收时,VXLAN 设备将解封装得到的元数据(VNI、远端 IP)写入 metadata_dst,供上层读取

这是 OVS 和 Cilium 使用的隧道模式。一个 VXLAN 设备服务整个节点的所有隧道流量,由 BPF 程序或 OVS 流表决定每个包的 VNI 和目的地。

4.8 GBP 扩展

VXLAN Group Based Policy(GBP)在 VXLAN 头的保留位中携带 16 位的 Group Policy ID:

// include/net/vxlan.h(GBP 头部布局)
// |G|R|R|R|I|R|R|R|R|D|R|R|A|R|R|R|  Group Policy ID (16-bit)  |
// |                VXLAN Network Identifier (VNI) |   Reserved   |

#define VXLAN_GBP_USED_BITS  (VXLAN_HF_GBP | VXLAN_GBP_DONT_LEARN | \
                              VXLAN_GBP_POLICY_APPLIED | VXLAN_GBP_ID_MASK)

GBP 让 VXLAN 承载安全策略标签——Cilium 用它在 VXLAN 包中传递身份信息,接收端根据 Policy ID 执行网络策略,不需要查询目的 Pod 的标签。

五、WireGuard:加密隧道

WireGuard 是 Linux 5.6 内核引入的现代加密隧道协议。与 IPsec 的上万行代码相比,WireGuard 只有约 4000 行。

5.1 封装格式

┌──────────────────────────────────┐
│ 外层 IP 头(20B)               │
├──────────────────────────────────┤
│ UDP 头(8B)                     │ 端口: 可配置(默认 51820)
├──────────────────────────────────┤
│ WireGuard 头(32B)              │
│ ├─ Type (1B)                     │ 消息类型
│ ├─ Reserved (3B)                 │
│ ├─ Receiver Index (4B)           │ 对端分配的会话索引
│ ├─ Counter (8B)                  │ 包序列号(防重放)
│ └─ Encrypted Payload + MAC (16B) │ ChaCha20-Poly1305 AEAD
├──────────────────────────────────┤
│ 加密的内层 IP 包                 │
└──────────────────────────────────┘

总封装开销: 20 + 8 + 32 = 60 字节

5.2 加密路由(Cryptokey Routing)

WireGuard 的核心设计概念是加密路由——每个对端(peer)关联一个公钥和一组允许的 IP 段(AllowedIPs):

# WireGuard 配置示意
[Interface]
PrivateKey = <base64_private_key>
ListenPort = 51820

[Peer]
PublicKey = <base64_public_key>
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
Endpoint = 203.0.113.1:51820

发送时:

  1. 查找内层 IP 的目的地址匹配哪个 peer 的 AllowedIPs
  2. 使用该 peer 的会话密钥加密
  3. 发送到该 peer 的 Endpoint

接收时:

  1. 根据 Receiver Index 找到对应的会话
  2. 用会话密钥解密
  3. 验证解密后的内层源 IP 是否在该 peer 的 AllowedIPs 范围内
  4. 如果不在范围内,丢弃(防止 peer 伪造源 IP)

AllowedIPs 既是路由表又是 ACL——它同时决定”这个包发给谁加密”和”这个 peer 允许声称自己是谁”。

5.3 Noise 协议握手

WireGuard 使用 Noise_IKpsk2 协议进行密钥交换:

  1. Initiator → Responder:发送 Handshake Initiation(148 字节),包含加密的发起方公钥和时间戳
  2. Responder → Initiator:发送 Handshake Response(92 字节),包含加密的响应方公钥
  3. 双方各自推导出对称会话密钥

密钥每 2 分钟或传输 2^64 个包后自动轮换。握手使用 Curve25519 做 ECDH,会话使用 ChaCha20-Poly1305 做 AEAD 加密。

5.4 内核模块架构

drivers/net/wireguard/
├── main.c            // 模块初始化
├── device.c          // net_device_ops 实现
├── peer.c            // peer 管理
├── noise.c           // Noise 协议状态机
├── cookie.c          // Cookie 机制(DoS 防护)
├── allowedips.c      // AllowedIPs trie 查找
├── ratelimiter.c     // 握手速率限制
├── send.c            // 加密 + 发送
├── receive.c         // 接收 + 解密
└── queueing.c        // 多核并行加密队列

allowedips.c 实现了一个 radix trie,将 IP 前缀映射到 peer。发送时 O(32)(IPv4)或 O(128)(IPv6)比特匹配找到目标 peer。

queueing.c 实现了多核并行加密——加密操作分发到多个 CPU 的工作队列上,然后按序重组,充分利用现代 CPU 的 SIMD 指令集(AVX2/AVX-512 加速 ChaCha20)。

5.5 Kubernetes 集成

Calico 3.14+ 支持 WireGuard 节点间加密:

apiVersion: projectcalico.org/v3
kind: FelixConfiguration
metadata:
  name: default
spec:
  wireguardEnabled: true

启用后,Calico 自动在每个节点创建 WireGuard 接口,生成密钥对,通过 Calico 的控制面分发公钥。Pod 间跨节点流量自动加密,延迟增加约几微秒,吞吐量在 10+ Gbps 水平(取决于 CPU 的 SIMD 能力)。

六、协议对比与选型

特性 IPIP GRE VXLAN Geneve WireGuard
层级 L3 L3 L2 over L3 L2 over L3 L3
封装开销 20B 24-28B 50B 50+B 60B
多租户 32-bit key 24-bit VNI 24-bit VNI
加密 ChaCha20
NAT 穿透 好(UDP) 好(UDP) 好(UDP)
ECMP 不支持 不支持 支持 支持 支持
硬件卸载 有限 有限 广泛 部分
可扩展性 GBP TLV
内核版本 2.x+ 2.x+ 3.7+ 3.18+ 5.6+

选型建议

七、硬件卸载

现代网卡对 VXLAN 的卸载支持最为广泛:

# 检查 VXLAN 卸载能力
ethtool -k eth0 | grep -i vxlan
tx-udp_tnl-segmentation: on        # 外层 TSO
tx-udp_tnl-csum-segmentation: on   # 外层校验和
rx-vxlan-port-offload: on          # 接收端卸载

卸载的含义:

IPIP 和 GRE 的卸载支持取决于网卡型号。WireGuard 由于加密的特性,目前没有通用硬件卸载。

八、可观测性

8.1 bpftrace 追踪隧道封装

# 追踪 VXLAN 封装
bpftrace -e '
kprobe:vxlan_xmit_one {
    $skb = (struct sk_buff *)arg0;
    printf("vxlan_encap: len=%d\n", $skb->len);
}
kprobe:vxlan_rcv {
    printf("vxlan_decap: pid=%d\n", pid);
}'

追踪 ip_tunnel 通用路径:

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

8.2 MTU 问题诊断

# 监控 ICMP need-frag 消息
bpftrace -e '
kprobe:icmp_send {
    $type = arg1;
    $code = arg2;
    if ($type == 3 && $code == 4) {
        printf("ICMP need-frag: mtu=%d\n", arg3);
    }
}'

8.3 VXLAN FDB 检查

# 查看 VXLAN FDB 表
bridge fdb show dev vxlan100

# 查看 VXLAN 设备参数
ip -d link show vxlan100

# 抓取 VXLAN 封装包
tcpdump -i eth0 -nn 'udp port 4789' -e

九、参考文献


上一篇虚拟网络设备内核实现:veth、bridge 与 macvlan

下一篇多队列与流量分发:RSS/RPS/RFS/XPS

同主题继续阅读

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

2026-04-20 · linux / networking

【Linux 网络子系统深度拆解】UDP 内核实现与 socket lookup 优化

UDP 简单?在内核中它一点都不简单。双哈希表 socket 查找、SO_REUSEPORT 多核分发、Early Demux 路由缓存、UDP GRO 聚合、reader_queue 无锁读、forward allocation 内存管理、UDP 封装(ESP/L2TP/VXLAN)——本文从 Linux 6.6 内核源码拆解 UDP 的每一个优化细节。


By .