坑边闲话:许多读者可能用了好几年 sing-box 和 Clash,却一直没搞清楚 TUN 模式和 TPROXY 模式到底差在哪里。本文结合笔者的个人理解,尝试把这几套机制从头捋了一遍。

1. 背景:代理软件为什么要分这么多模式·

使用过 sing-box、Clash 或 shadowsocks 的人都知道,这类工具往往提供多种工作模式:SOCKS 代理、TUN 模式、透明代理(REDIRECT / TPROXY)。这几种模式在文档里往往被并列列出,却鲜有人讲清楚它们的本质差异。

表面上,这些模式都是"让流量经过代理程序",但它们拦截流量的位置、使用的内核机制、对原始目标地址的处理方式,以及适用场景,都有根本性的不同。

本文的目标是建立一个统一的系统级 mental model,把这四种机制:SOCKS、REDIRECT、TUN、TPROXY,放到同一个框架下,讲清楚每种机制的工作原理、设计取舍,以及为什么现代代理软件在不同场景下选择不同方案。

2. 四种机制的本质抽象·

在深入每种机制之前,先从最高层看:所有代理机制要解决的核心问题都是一样的:如何把本不属于自己的流量,交给用户态程序处理?

四种机制的抽象差异,本质上是"在哪个层次拦截流量"的差异:

1
2
3
4
5
6
7
8
+------------------+----------+--------------------+
| Mechanism | Layer | Core Abstraction |
+------------------+----------+--------------------+
| SOCKS/HTTP Proxy | App | socket |
| REDIRECT | NAT | conntrack |
| TUN | Routing | net_device |
| TPROXY | netfilter| transparent socket |
+------------------+----------+--------------------+

2.1 SOCKS / HTTP Proxy:应用层显式代理·

这是最古老也最直接的方案。应用程序在建立连接时,不直接连接目标服务器,而是主动连接代理服务器,并通过协议协商(SOCKS5 握手或 HTTP CONNECT)告知代理程序真实的目标地址。

1
2
3
+-------+     SOCKS5 handshake     +-------+     TCP connect    +--------+
| App | -----------------------> | Proxy | -----------------> | Target |
+-------+ "connect to 1.2.3.4" +-------+ +--------+

关键特征:

  • 应用程序感知代理的存在,必须主动配置
  • 内核不做任何流量劫持,代理地址是连接的真实目标
  • 原始目标地址由应用层协议显式传递,不存在"恢复原始目标"的问题

这种方案的局限是显而易见的:只有支持 SOCKS/HTTP 代理协议的应用才能使用。系统级的透明代理,需要更底层的拦截机制。

2.2 REDIRECT:NAT 层劫持·

REDIRECT 是 iptables/netfilter 的 NAT 功能。它在内核的 NAT 表中修改数据包的目标地址,将发往任意目标的流量"重定向"到本地的一个端口,由监听在该端口的代理程序接收。

1
2
3
4
5
6
7
Original packet:  src=192.168.1.2:54321  dst=1.2.3.4:443
|
iptables REDIRECT
|
Modified packet: src=192.168.1.2:54321 dst=127.0.0.1:12345
|
proxy process (listening on :12345)

从代理程序的视角来看,它接收到的是一个普通的 TCP 连接,目标是 127.0.0.1:12345. 此时一个问题出现了:原始目标地址 1.2.3.4:443 去哪儿了?

代理程序必须知道原始目标才能建立出站连接,这就引出了 SO_ORIGINAL_DST,我们在第 3 节详细讨论。

2.3 TUN:虚拟网卡导流·

TUN 是一种虚拟的三层网络设备(L3 net_device)。它的工作原理是:通过路由规则(routing / policy routing)将流量导入 TUN 设备,而 TUN 设备的另一端连接着用户态程序通过 /dev/net/tun 打开的文件描述符。

1
2
3
4
5
6
7
+--------+     IP packets     +----------+     read(fd)     +-----------+
| Kernel | -----------------> | tun0 dev | ---------------> | User-mode |
| routing| | (L3 VIF) | | process |
+--------+ +----------+ +-----------+
|
write(fd) sends
reply packets back

TUN 的关键特征是:它工作在 IP 层,用户态程序拿到的是完整的 IP 数据包(而非 TCP 流)。代理程序自己解析 IP/TCP/UDP 头,从中提取目标地址,完全不依赖任何 NAT 或 conntrack 机制。

2.4 TPROXY:透明 socket 接管·

TPROXY 是 netfilter 提供的一种机制,它与 REDIRECT 最大的区别在于:不修改数据包的目标地址。流量到达本地 socket 时,目标地址仍然是原始的 1.2.3.4:443.

1
2
3
4
+--------+   dst=1.2.3.4:443    +-----------+   dst=1.2.3.4:443   +--------+
| Client | -------------------> | netfilter | ------------------> | local |
| | | (TPROXY) | (unchanged!) | socket |
+--------+ +-----------+ +--------+

这要求用户态程序的 socket 设置了 IP_TRANSPARENT 选项,允许其绑定并接收目标不是本机地址的数据包。这是 TPROXY 机制的核心——通过一个特殊的 socket 选项,绕过内核对"目标地址必须是本机地址"的检查。

3. SO_ORIGINAL_DST: REDIRECT 的历史包袱·

SO_ORIGINAL_DST 是理解 REDIRECT 机制时绕不开的一个细节,它的存在本身就说明了 REDIRECT 方案设计上的一个缺陷。

3.1 问题的来源·

REDIRECT 在 netfilter 的 NAT 表中修改了数据包的目标地址。当 TCP 三次握手完成,代理程序 accept() 到这个连接时,getpeername() 能拿到客户端地址,但 getsockname() 只能拿到本地监听地址 127.0.0.1:12345,原始目标 1.2.3.4:443 已经被 NAT 改写,无从得知。

1
2
3
proxy calls getsockname()  ->  127.0.0.1:12345   (local listen addr)
proxy calls getpeername() -> 192.168.1.2:54321 (client addr)
proxy needs to know -> 1.2.3.4:443 (?? lost ??)

3.2 解决方案:从 conntrack 取回·

内核在做 NAT 的时候,会在 connection tracking(conntrack)中记录一条 NAT 映射。conntrack 保存了连接的 original tuple(原始五元组),包括修改前的目标地址。

SO_ORIGINAL_DST 是一个 socket option,通过 getsockopt() 调用,让内核从 conntrack 查询当前连接的原始目标地址,返回给用户态程序:

1
2
3
4
5
struct sockaddr_in orig_dst;
socklen_t len = sizeof(orig_dst);
getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &orig_dst, &len);
// orig_dst.sin_addr = 1.2.3.4
// orig_dst.sin_port = 443

这个调用的内核实现路径大致是:

1
2
3
4
5
6
7
8
9
10
getsockopt(SO_ORIGINAL_DST)
|
v
nf_nat_getsockopt()
|
v
nf_conntrack_find() <- lookup by (src, dst, proto)
|
v
ct->tuplehash[ORIGINAL].tuple <- original 5-tuple before NAT

3.3 conntrack 的角色·

conntrack 是 netfilter 维护的连接跟踪表。每条 TCP/UDP 连接在首个数据包经过 netfilter 时被记录,包含两个方向的 tuple:

1
2
ORIGINAL direction:  192.168.1.2:54321 -> 1.2.3.4:443 (TCP)
REPLY direction: 1.2.3.4:443 -> 192.168.1.2:54321 (TCP)

NAT 修改数据包时,同时在 conntrack 条目中记录 NAT 变换。后续的 SO_ORIGINAL_DST 查询就是在读取这个 ORIGINAL tuple。

3.4 SO_ORIGINAL_DST 的限制·

这个机制有几个根本性的限制:

只适用于 REDIRECT/NAT:TUN 和 TPROXY 机制本就不修改目标地址,根本不需要这个调用。SOCKS 则是应用层协议,目标地址由应用显式传递。

依赖 conntrack:如果 conntrack 表项已过期或被清除,查询会失败。在高并发场景下,conntrack 本身也是性能瓶颈。

UDP 处理复杂:UDP 没有连接概念,conntrack 对 UDP 的跟踪是基于 5 元组的超时机制,在多路复用场景下行为不直观。

机制 是否需要 SO_ORIGINAL_DST 原因
SOCKS 目标地址由应用层协议显式传递
REDIRECT NAT 修改了 dst, 需从 conntrack 恢复
TUN 用户态直接解析 IP 包头,dst 始终可见
TPROXY dst 未被修改,socket 直接看到原始目标地址

4. TPROXY 是一个更干净的方案·

4.1 REDIRECT 的根本问题·

REDIRECT 方案的核心矛盾在于:它主动破坏了数据包的目标地址,然后又需要通过额外的机制 (SO_ORIGINAL_DST + conntrack) 把这个信息找回来。这不仅增加了复杂性,还引入了对 conntrack 的强依赖。

对于运行在路由器上的透明网关,这个问题更加突出:

  • 路由器要处理大量并发连接,conntrack 表是内存瓶颈
  • 转发的流量(非本机发起)用 REDIRECT 处理更加繁琐
  • 每个连接都要额外查询一次 conntrack, 有性能开销

4.2 TPROXY 的设计思路·

TPROXY(Transparent Proxy)的核心思想是:既然最终还是要把原始目标地址告诉代理程序,为什么不一开始就不修改它?

TPROXY 让数据包保持原始的目标地址不变,同时通过特殊的 socket 机制,让用户态程序的 socket 能够接收这个"目标不是自己"的数据包。代理程序直接从 getsockname() 就能拿到原始目标地址,不需要任何额外查询。

4.3 核心机制:五个关键组件·

TPROXY 的工作需要五个组件协同:

① netfilter mangle

TPROXY 规则设置在 mangle 表的 PREROUTING 链,而不是 nat 表。mangle 表不做地址转换,只做标记和流量引导。

1
2
iptables -t mangle -A PREROUTING -p tcp --dport 443 \
-j TPROXY --tproxy-mark 0x1/0x1 --on-port 12345

② TPROXY target

当数据包命中 TPROXY 规则时,netfilter 做两件事:

  • 在数据包上打 fwmark
  • 将数据包"关联"到监听在指定端口的本地 socket(通过内核的 socket lookup)

fwmark + ip rule

fwmark 是数据包上携带的一个整数标记,不会出现在网络上,只在本机内核内部使用。ip rule 根据 fwmark 决定使用哪张路由表:

1
2
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

这条路由规则的效果是:带有 fwmark 0x1 的数据包,走 table 100, 而 table 100 里有一条 local 路由把所有地址都指向 lo。这样内核就会把这个包交给本地 socket 处理,即使目标地址不是本机 IP.

IP_TRANSPARENT socket option

代理程序在创建监听 socket 时,必须设置 IP_TRANSPARENT

1
2
int val = 1;
setsockopt(listen_fd, SOL_IP, IP_TRANSPARENT, &val, sizeof(val));

这个选项做两件事:

  • 允许 socket 绑定非本机地址(用于出站连接伪装源地址,TPROXY 的出站侧)
  • 允许 socket 接收目标地址不是本机地址的数据包(用于入站侧接管)

没有这个选项,内核在做 socket lookup 时,发现目标地址不是本机地址,会拒绝将数据包交给该 socket。

⑤ 完整数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Client (192.168.1.2:54321) sends TCP SYN to 1.2.3.4:443
|
v
[netfilter PREROUTING - mangle table]
TPROXY rule matches: --tproxy-mark 0x1 --on-port 12345
-> mark packet with fwmark=0x1
-> associate with local socket on :12345
|
v
[routing: ip rule checks fwmark]
fwmark=0x1 -> use table 100
table 100: local 0.0.0.0/0 via lo
-> packet destined for local delivery
|
v
[socket lookup]
dst=1.2.3.4:443, proto=TCP
find socket: IP_TRANSPARENT socket listening on :12345
-> deliver to proxy process
|
v
[proxy process - accept()]
getsockname() -> 1.2.3.4:443 (original dst, unchanged!)
getpeername() -> 192.168.1.2:54321
-> proxy knows exactly where to connect upstream

4.4 为什么 IP_TRANSPARENT 能打破地址限制·

内核在做本地 socket delivery 时,正常流程是:查找目标地址是否是本机地址(在 local routing table 中),如果不是则转发或丢弃。

IP_TRANSPARENT 的引入,在 socket lookup 阶段增加了一个例外:如果找不到匹配的普通 socket,还会搜索带有 IP_TRANSPARENT 标记的 socket。这类 socket 被内核视为「可以接收任意目标地址」的特权 socket。

这个选项需要 CAP_NET_ADMIN 权限,因为它打破了正常的网络地址归属规则。

5. TUN 设备的内核机制·

5.1 TUN 是什么·

TUN(network TUNnel)是 Linux 内核提供的一种虚拟网络设备,实现在 drivers/net/tun.c. 它是一个标准的 net_device,从内核网络栈的角度来看,与物理网卡没有任何区别——可以配置 IP 地址、添加路由、被 netfilter 处理。

TUN 和 TAP 的区别:

  • TUN:工作在 L3,收发 IP 数据包
  • TAP:工作在 L2,收发以太网帧(带 Ethernet header)

代理场景通常使用 TUN,因为代理程序关心的是 IP/TCP/UDP,不需要处理以太网层。

5.2 /dev/net/tun 字符设备接口·

TUN 设备的用户态接口是字符设备 /dev/net/tun。用户态程序打开这个设备文件,通过 ioctl(TUNSETIFF) 创建或绑定到一个 TUN 网络接口,之后这个文件描述符就成为 TUN 设备的「另一端」:

1
2
3
4
5
6
7
8
9
10
11
12
+-------------------+       +-------------------+
| Kernel network | | User-mode |
| stack | | process |
| | | |
| tun0 net_device |<=====>| fd = open( |
| (IP packets flow) | pipe | "/dev/net/tun") |
+-------------------+ +-------------------+
^ |
| | read() -> get IP packet
| | write() -> inject IP packet
routing table |
"default via tun0"

5.3 数据包的发送路径:tun_net_xmit·

当内核网络栈要通过 tun0 发送一个数据包时(例如路由决策把某个 TCP 连接的报文送到了 tun0),调用链如下:

1
2
3
4
dev_queue_xmit()
-> tun_net_xmit() [drivers/net/tun.c]
-> skb_queue_tail(&tun->socket.sk->sk_receive_queue, skb)
-> wake_up_interruptible(&tun->wq.wait)

注意这里的关键:tun_net_xmit 把 skb(socket buffer,内核中表示数据包的结构)放入一个队列,然后唤醒等待在这个 TUN 设备上的用户态进程。

5.4 用户态读取路径:tun_chr_read_iter·

用户态程序调用 read(fd, buf, len) 读取 TUN 设备时,进入 tun_chr_read_iter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// simplified from drivers/net/tun.c
static ssize_t tun_chr_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct tun_file *tfile = iocb->ki_filp->private_data;
struct tun_struct *tun = tun_get(tfile);

// if queue is empty, block on wait queue
if (skb_queue_empty(&tfile->socket.sk->sk_receive_queue)) {
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
wait_event_interruptible(tfile->wq.wait,
!skb_queue_empty(...));
}

// dequeue packet and copy to user buffer
skb = skb_dequeue(&tfile->socket.sk->sk_receive_queue);
skb_copy_datagram_iter(skb, 0, to, skb->len);
...
}

没有 DMA,没有硬件中断——TUN 设备的"中断"是软件的 wake_up_interruptible(),对应 tun_net_xmit 中的 wakeup 调用。这是一个标准的 Linux wait queue 机制。

5.5 TUN 的通知机制·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Packet arrives at tun0 (via kernel routing)
|
v
tun_net_xmit()
skb_queue_tail() <- enqueue packet
wake_up() <- wake sleeping readers
|
v
User-mode process (blocked in read())
woken up by wait queue
read() returns with IP packet data
|
v
Process parses IP header -> extracts dst IP, proto, dst port
Process establishes upstream connection to dst

用户态程序也可以用 poll()/epoll() 监听 TUN fd, 这是代理程序通常的做法:用一个事件循环同时监听 TUN fd 和上游连接的 fd,实现非阻塞的全双工转发。

5.6 TUN 架构全图·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
+----------------------------------------------------------+
| User-mode proxy process |
| |
| epoll loop |
| +------------------+ +---------------------------+ |
| | tun_fd events | | upstream TCP connections | |
| | read() -> IP pkt | | connect() to real targets | |
| | parse dst | | recv/send data | |
| +------------------+ +---------------------------+ |
| | ^ |
| write() IP reply pkt | |
+----------|---------------------------|-------------------+
| /dev/net/tun |
v |
+----------------------------------------------------------+
| Kernel |
| |
| tun0 (net_device) |
| +----------------+ |
| | sk_receive_q | <-- tun_net_xmit() enqueues here |
| | wait_queue | <-- wakes up reader |
| +----------------+ |
| ^ |
| routing table: |
| default dev tun0 (all traffic -> tun0) |
| |
| policy routing: |
| ip rule: proxy process traffic -> main table |
| (prevents routing loop) |
+----------------------------------------------------------+

注意防环路的必要性:如果代理程序自身发出的流量也被路由到 tun0,会形成无限循环。通常用 policy routing 或 cgroup 标记解决:代理程序的流量走默认路由,其他进程的流量走 tun0。

6. 四种机制完整对比·

6.1 对比表格·

机制 拦截层 是否修改 dst 应用是否感知代理 原始目标获取方式 技术核心
SOCKS 应用层 应用层协议显式传递 socket
REDIRECT NAT 表 SO_ORIGINAL_DST conntrack
TUN routing 解析 IP 包头 net_device
TPROXY mangle 表 getsockname() transparent socket

6.2 数据流对比·

SOCKS:

1
2
App ----[SOCKS5 handshake: "connect to 1.2.3.4:443"]----> Proxy ----> 1.2.3.4:443
(app-level protocol, no kernel involvement)

REDIRECT:

1
2
3
App ---[TCP SYN to 1.2.3.4:443]---> netfilter NAT ---[dst=127.0.0.1:12345]---> Proxy
|
conntrack: {orig: 1.2.3.4:443} <-SO_ORIG_DST-+

TUN:

1
2
3
4
5
6
App ---[TCP SYN to 1.2.3.4:443]---> routing (default via tun0) ---> tun0 device
|
/dev/net/tun (read)
|
Proxy process
(parses IP pkt)

TPROXY:

1
2
3
4
5
6
7
8
App ---[TCP SYN to 1.2.3.4:443]---> netfilter mangle (TPROXY) ---> [fwmark=1]
|
ip rule: local route
|
socket lookup (IP_TRANSPARENT)
|
Proxy process
getsockname()=1.2.3.4:443

6.3 关键差异分析·

TUN vs TPROXY 的本质区别

这是最容易混淆的一对。虽然两者都不修改 dst,但工作层次不同:

  • TUN 工作在 routing 层,拿到的是 IP 数据包,代理程序必须自己实现 TCP/UDP 协议栈(或使用 gVisor 等用户态网络栈)
  • TPROXY 工作在 socket 层,内核完成了 TCP 三次握手,代理程序拿到的是 已建立的 TCP 连接

这个差异决定了实现复杂度:TUN 模式的代理需要在用户态处理完整的 TCP/IP 协议,TPROXY 模式只需处理应用层数据。

REDIRECT vs TPROXY 的根本区别

  • REDIRECT 在 nat 表操作,修改数据包,依赖 conntrack 恢复信息
  • TPROXY 在 mangle 表操作,不修改数据包,dst 信息始终保留

7. 选型分析·

7.1 什么场景用什么方案·

7.1.1 应用级代理:SOCKS·

适用场景:开发调试、单个应用代理、不需要系统级透明代理

SOCKS 是最简单的方案,没有内核配置,没有权限要求,浏览器和大多数开发工具原生支持。当你只需要"让这个程序走代理"时,SOCKS 是开销最低的选择。

7.1.2 简单透明代理:REDIRECT·

适用场景:单机、仅 TCP、不关心高性能

REDIRECT 配置简单(一条 iptables 规则),适合快速搭建。局限在于:UDP 支持复杂,conntrack 有内存和性能开销,不适合高并发的网关场景。

7.1.3 单机代理 / 桌面系统:TUN·

适用场景:桌面 Linux/macOS/Windows、单机代理、需要同时处理 TCP 和 UDP

TUN 模式在桌面系统上有几个优势:

  • 跨平台:macOS 和 Windows 没有 TPROXY,但都有 TUN
  • 全流量拦截:routing 层面的拦截,TCP 和 UDP 一视同仁
  • 防环路容易:用 cgroup 或 uid 标记区分代理进程和普通进程流量

sing-box 和 Clash 的 TUN 模式正是如此:创建一个 tun0 接口,通过 policy routing 把非代理进程的流量路由进去,在用户态运行一个简化的 TCP/UDP 协议栈。

7.1.4 路由器 / 网关 / OpenWrt:TPROXY·

适用场景:透明网关、OpenWrt、需要转发其他主机流量

TPROXY 在网关场景有明显优势:

  • 无需修改数据包,减少 conntrack 压力
  • 直接获取原始目标地址,无额外查询开销
  • 对转发流量(非本机发起)支持更好
  • OpenWrt 等嵌入式 Linux 通常内核已包含 TPROXY 支持

7.2 为什么现代代理软件这样选择·

7.2.1 sing-box / Clash 在桌面用 TUN·

根本原因是跨平台。TPROXY 是 Linux 独有的 netfilter 功能,macOS 没有 iptables,Windows 更不用说。TUN 设备在三大平台都有支持(Linux 的 /dev/net/tun,macOS 的 /dev/utun*,Windows 的 WinTun),实现一套代码逻辑,通过不同平台的 TUN 接口即可运行。

另一个原因是 DNS 劫持。TUN 模式下,代理程序拿到的是 IP 数据包,可以在 DNS 请求到达目标服务器之前就截获并伪造响应,实现 Fake IP 等技术,解决 DNS 泄露问题。

7.2.2 OpenClash / 路由器固件用 TPROXY·

网关场景的特点是:转发大量其他主机发起的连接,对性能和资源消耗敏感。TPROXY 方案:

  • 不涉及用户态 TCP 协议栈(相比 TUN),代理程序直接接管 TCP 连接
  • conntrack 压力比 REDIRECT 小(不需要 NAT 改写)
  • iptables/nftables 规则更简洁,调试更直观

7.2.3 为什么 SOCKS 仍然存在·

SOCKS 看似过时,但它有两个不可替代的场景:

浏览器和开发工具:Firefox, curl, git 等工具原生支持 SOCKS5,配置简单,不需要任何系统权限。

代理链(proxy chaining):多级代理场景下,SOCKS 协议可以嵌套,实现代理穿越代理。这是 TUN/TPROXY 在协议层面无法直接实现的。

8. 设计哲学:流量委托的四种抽象·

回顾这四种机制,它们解决的是同一个问题,但借助了不同层次的抽象:

1
2
3
4
5
6
7
8
+------------------+------------------+-------------------------------+
| Mechanism | Abstraction used | "Who handles the protocol?" |
+------------------+------------------+-------------------------------+
| SOCKS/HTTP | App protocol | Proxy (app-aware) |
| REDIRECT | NAT | Proxy (kernel does TCP setup) |
| TUN | Virtual NIC | Proxy (must impl TCP/UDP) |
| TPROXY | Socket | Proxy (kernel does TCP setup) |
+------------------+------------------+-------------------------------+

一个有趣的观察:

  • SOCKS 是"应用主动合作"。应用自己把目标地址告诉代理
  • REDIRECT 是"内核强制改写,事后修复"。代理被动接受,再通过 conntrack 查询被改写的信息
  • TUN 是"内核交出原始数据"。代理拿到最原始的 IP 包,自己处理一切
  • TPROXY 是"内核做最小介入"。内核只做流量引导,不修改数据,代理直接拿到完整信息

从系统设计的角度,TPROXY 体现了一种"最小修改原则":只做必要的事,不引入额外的状态(conntrack NAT 映射),不破坏已有信息(原始 dst 地址)。它的复杂性在于配置(mangle + fwmark + ip rule + IP_TRANSPARENT),而不在于运行时。

TUN 则是另一种极端:把所有协议处理权都交给用户态,内核只提供一个数据通道。这种方案最灵活(用户态可以完全自定义协议处理),也最重(需要在用户态实现 TCP/UDP 协议栈),但跨平台能力最强。

这四种机制的存在,不是因为某一种"更好",而是因为不同的约束(平台、性能、权限、协议支持)在不同场景下有不同的最优解。理解这些取舍,是选对工具、排查问题的基础。