深入解析:NAT 穿透

NAT 穿透深入解析:从 NAT 分类到打洞全流程解析

把首页介绍的 STUN/TURN/打洞一挖到底:NAT 类型分类、逐包级的时序、对称型 NAT 的对策、以及 TCP 上的应用。这是 P2P 工程中最「脏」也最重要的领域。

对等方 ANAT ASNAT B对等方 B12××34
  1. 注册:双方向 S 发送(NAT 中生成通向 S 的映射)
  2. S 互相通知双方的公网/内网端点
  3. 同时发送:首发包被丢弃,但在自家 NAT 中开洞
  4. 双方洞打通 → 建立直连(S 不再需要)

UDP 打洞的四个阶段。关键在于「由自己先发送,在自己的 NAT 中为对方造出通路」。

NAT 的四种分类:好不好穿由类型决定

NAT 按「如何创建映射(内部 IP:端口 ⇔ 外部 IP:端口)、对入站包放行到什么程度」经典地分为四类(RFC 3489;现代的 RFC 4787 把映射行为与过滤行为分开描述,但四分类对直观理解仍最有用)。

  • Full Cone NAT:为内部套接字固定分配一个外部端口,映射一旦建立,任何人发来的包都放行。最容易穿透。
  • Restricted Cone NAT:映射固定,但只放行内部曾发送过的目的 IP 发来的包。
  • Port Restricted Cone NAT:更严格,需目的 IP+端口都匹配才放行。到此为止,标准打洞尚可穿透。
  • Symmetric NAT按目的地分配不同的外部端口。对 STUN 服务器暴露的端口与同对等方通信所用端口不是同一个,教科书式打洞从原理上失效。常见于企业网络和移动网络(CGNAT,运营商级 NAT)。

UDP 打洞:逐包追踪的完整时序

打洞的本质,是利用 「NAT 会在一段时间内放行与出站包同一路径的回程流量」 这一性质。双方都「先由自己发出」,从而在彼此的 NAT 中预先造出通向对方的通道(映射=洞)。我们一步一步来。登场者:位于各自 NAT 之后的对等方 A 与 B,以及两者最初都连接的会合服务器 S(中介,也称信令服务器)。

  1. 注册:A 和 B 各自向 S 发送 UDP 包。此时各 NAT 中生成「通向 S」的映射,S 则观测到各对等方的公网端点(从 NAT 外可见的 IP:端口)。
  2. 端点交换:S 把 B 的公网与内网端点告知 A,反之亦然。连内网端点也交换,这一点至关重要(原因见下)。
  3. 同时发送(打洞):A 向 B 的端点、B 向 A 的端点,几乎同时开始发送 UDP 包。最初几发会被对方 NAT 当作「未知来源」丢弃,但这一发送本身在自己的 NAT 中造出了通向对方的映射(洞)
  4. 打通:双方的洞都开启之后到达的包,在各自 NAT 看来是「对内部发出通信的回复」,于是放行。此后即为直接的双向 UDP 通信,会合服务器不再需要(可以断开)。

为什么连内网端点也要尝试?因为当 A 和 B 处于同一个 NAT 之内(同一家庭、同一办公室)时,公网端点之间的通信依赖 NAT 的回环(hairpinning)支持,常常失败。同时也向内网地址直接发送、谁先通就用谁,这是实现上的定式(ICE,即 Interactive Connectivity Establishment、交互式连通建立协议,尝试所有候选对,正是它的一般化)。

此外「同时」并不要求严格同刻。需要的只是双方都在对方第一个包到达之前,完成自己的首次发送。实现中通常以数十毫秒间隔重试数秒;即便一方的头几发被丢弃,后续的包也会从洞中通过。

按 NAT 类型配对的成功与否矩阵

标准 UDP 打洞(不做端口预测)的成败,基本由两端 NAT 类型的组合决定。

A \ BFull ConeRestrictedPort RestrictedSymmetric
Full Cone
Restricted
Port Restricted×
Symmetric××

对称型一侧与对方通信时使用新的外部端口,因此只要对方要求「端口也一致」(Port Restricted 及以上)就会被拒,这就是 × 的原因。若对方是不看端口的 Full Cone/Restricted,即便混有对称型也能打通。换言之,只有「Symmetric × Port Restricted」和「Symmetric × Symmetric」在标准手法之外

攻克对称型 NAT:端口预测与生日悖论

  • 端口预测:许多对称型 NAT 并非完全随机、而是顺序(逐次 +1 等)分配端口。向 STUN 查询两次即可推断分配规律,从而瞄准「下一个将被使用的端口」。
  • 多端口并发尝试(生日悖论):即便随机分配也能靠概率取胜。对称一侧从数百个源套接字开洞、对方向数百个端口试探,依「生日悖论」碰撞(命中)概率会急剧上升:在 65,535 端口空间中双方各试约 400 个端口,成功率即超九成。Tailscale 等网状 VPN 实际就在使用这一技巧。
  • 仍不行时:多层 CGNAT 或完全随机分配有时确实无解。此时便直接回退到TURN 中继。设计要诀是「包括回退在内,数秒内自动定夺」。

TCP 打洞:同时打开这一冷门绝技

TCP 原理上也能打洞。关键是 TCP 状态机中早已定义的同时打开(simultaneous open)。双方几乎同时互发 SYN 时,无需 SYN+ACK 握手、仅凭两个 SYN 即可建立连接,这是平时几乎不会发生的状态转移。双方(借助 SO_REUSEADDR)从同一本地端口反复向对方 connect,即可像 UDP 一样开洞。

但成功率明显低于 UDP。原因有三:(1)许多 NAT 会跟踪 TCP 状态(SYN 的方向),对「外来的先行 SYN」回以 RST;(2)双方 SYN 的时间窗口比 UDP 苛刻;(3)各 OS 的 TCP 栈行为差异大。实务上更常见的做法是「用 UDP 打洞,在其上叠加可靠层(QUIC 或 SCTP)」,WebRTC 数据通道正是这一构造(UDP 之上的 SCTP)。

keep-alive 与映射寿命:洞放着不管就会闭合

NAT 的映射有超时。UDP 在许多设备上静默 30 秒至数分钟即失效(RFC 4787 建议至少 2 分钟,实际 30 秒左右的设备并不罕见);TCP 建立后建议 2 小时以上,但同样因设备而异。因此维持 P2P 连接需要即使无数据也定期发送 keep-alive 包,实现中 15 到 25 秒间隔的小 UDP 包是常规做法。移动端上 keep-alive 频率直接关系电池消耗,因此也有「实测超时后自适应调整间隔」的高级实现。为洞已闭合的情形准备好重新打洞(re-punch)流程,同样是健壮 P2P 应用的必备要件。

真实世界中的用法

  • WebRTC / ICE:打洞以 ICE「连通性检查」的标准化形态,每天被执行数十亿次。在候选收集(主机/STUN 反射/TURN 中继)→ SDP 交换 → 按优先级检查所有候选对 → 选定最佳路径的流程中,互发 STUN 绑定请求本身就是打洞。
  • 在线游戏:主机游戏的匹配服务器兼任会合服务器、用打洞把对战双方直连,是经典构成。常见的「NAT 类型 A/B/C」显示,就是对打洞能否成功的预诊断。
  • 网状 VPN(Tailscale 等):各节点经协调服务器交换密钥与端点,用打洞直连 WireGuard 隧道。对对称型 NAT 采用端口预测与生日悖论试探,实在不行则经名为 DERP 的中继服务器(相当于 TURN)保底连通,是多级策略的绝佳范例。

ICE:从候选收集到连接建立

  1. 候选收集:各对等方收集本地地址(主机候选)、STUN 探得的外部地址(服务器反射候选)、TURN 的中继地址(中继候选)。
  2. 候选交换:经信令通道(WebRTC 典型为经 WebSocket 服务器)装入 SDP 交换。
  3. 连通性检查:为所有候选对排定优先级,互发 STUN 绑定请求确认连通。这一相互检查本身就承担了打洞的功能。
  4. 选定:在连通的候选对中选出优先级最高的路径(直连 > 反射 > 中继)用于后续通信;网络变化时重新检查(ICE restart)。

现实中的坑

移动网络中运营商侧的 NAT(CGNAT)叠加在家用路由器之上,双重 NAT 并不罕见。在封锁 UDP 的防火墙环境中,需要回退到经 TCP 或 TURN over TLS(443 端口)。治本之策是 IPv6 的普及:终端彼此拥有全局地址后,NAT 穿透这一问题本身就会消失。但眼下,ICE 的「泥泞世界」仍将持续。NAT 穿透相关的测量与基础研究见参考文献

返回首页