跳到主要内容

TUN 协议栈

开启 TUN 后,操作系统把所有 IP 包交给 QuicProxy。协议栈负责把这些 IP 包还原成一条条 TCP 连接和 UDP 报文,再交给分流和代理。协议栈的好坏直接决定了 TUN 模式下的速度、耗电和内存。

通过 TUN 入站的 tun_stack 选择协议栈:

协议栈实现方式说明
nativeQuicProxy 自研的纯 Rust 用户态协议栈实验性,需要手动开启
systemTCP 交给系统内核,UDP 在用户态处理移动端默认
lwip基于 C 语言的 lwIP桌面端默认
smoltcp基于 Rust 的 smoltcp可选

native 栈为什么更省电​

空闲时零唤醒​

手机锁屏后,大部分连接都处在空闲状态。这时候协议栈如果还在定时醒来检查,就会不断打断 CPU 休眠。

协议栈没有流量时的唤醒次数
native0 次/秒:只在收到数据、应用读写或某个计时器真正到期时才醒
system0 次/秒(每 30 秒清理一次 NAT 表)
lwip4 次/秒:固定每 250ms 检查一次计时器,永不停止
smoltcp最多 200 次/秒:没有计时器时也用 5ms 兜底轮询

native 栈把所有连接的计时器放在一起,按最早到期的时间睡眠;没有计时器就一直睡到下一个包到来。

数据少进出几次内核​

system 栈的 TCP 交给系统内核处理:一个数据段要先从 TUN 读出来、改写地址再写回 TUN,然后由内核 TCP 处理,最后再从本地 socket 读出来,前后要穿越内核 3 到 4 次。native 栈在用户态直接终结 TCP 连接,每个包只读一次、写一次。CPU 做的事更少,耗电自然更低。

内存按需分配​

收发缓冲按需增长,用完即释放,不像 smoltcp 那样给每条连接预先分配固定大小的缓冲区。移动端使用更小的一套参数:接收窗口从 32KB 起步,最大 256KB,最多 1024 条连接,确保在 iOS Network Extension 的 50MB 内存限制之内。

其他优势​

  • 不依赖系统行为:system 栈需要在 TUN 网段里借一个假地址、把监听 socket 绑到 utun,在 Windows 上还可能触发防火墙。native 栈不需要这些,各平台行为一致。
  • 弱网和丢包下更快:支持 SACK 选择确认、快速恢复和尾部丢包探测,一次丢包不必等 300ms 以上的重传超时。
  • ping 可用:直接在本地回复 ICMP / ICMPv6 Echo,ping 不再超时。system 栈会直接丢弃 ping。
  • IPv6 更省事:TUN 只分到一个 IPv6 地址(如 /128)时,system 栈的 IPv6 TCP 无法工作,native 栈不受影响。
  • 纯 Rust:没有 C 代码依赖,跨平台编译更简单,也更安全。

实测数据​

测试环境:Linux 真实 TUN 设备,MTU 1500,系统内核 TCP 作为客户端,iperf3 和 curl 经过 native 栈访问本机服务,并人为注入随机丢包。

场景上行(客户端发送)下行(客户端接收)
无丢包约 3.1 Gbps约 2.9 Gbps
1% 丢包约 1.4 Gbps约 480 Mbps(8 条并发约 1.9 Gbps)
5% 丢包约 25 Mbps约 67 Mbps
  • 50MB 文件在 0% 到 5% 丢包下下载,校验和都完全一致。
  • 3% 丢包下,800 条半关闭和服务端先关的连接全部正确完成,没有连接泄漏。
  • 空闲 10 秒内,进程上下文切换为 0。
备注

上行由客户端的系统内核负责发送,下行由 native 栈负责发送。5% 丢包时,native 栈的下行速度高于系统内核自身的上行速度。

当前限制​

native 栈目前仍是实验性功能:

  • 还没有在 iOS / Android 真机上做能耗实测。上面的省电分析基于设计和 Linux 上的测试。
  • 不重组 IPv4 分片,也不解析 IPv6 扩展头,这两类包会被丢弃。
  • 目前收到 SYN 后立即完成握手;如果代理端连接失败,应用看到的是连接被重置,而不是连接超时。

如何启用​

在 TUN 入站中设置 tun_stack:

{
"type": "tun",
"tun_address": ["172.19.0.1/24"],
"mtu": 1384,
"auto_route": true,
"tun_stack": "native"
}

遇到问题时,把 tun_stack 改回 system 或 lwip 即可。