NAT 穿越如何工作:UDP 打洞、STUN、CGNAT 与 IPv6

点对点网络想让两台位于不同私网中的设备直接通信,通常会同时面对状态防火墙与源地址转换(SNAT)。这篇文章以 Tailscale、WireGuard 和 WebRTC 为例,解释为什么出站 UDP 探测能为回包打开临时通道、为什么 STUN 有时仍不足以建立连接,以及如何用中继兜底。
状态防火墙与同时打洞
许多防火墙默认允许出站连接、阻止未经请求的入站连接。对 UDP 来说,它会记住已看到的出站五元组;若稍后收到匹配的“回应”方向数据包,就允许它进入。两个客户端若都等对方先发,便会互相等待。若双方事先知道彼此的地址和端口,并在相近时间向对方发送 UDP 包,双方的防火墙都可能先记录出站状态,再放行对端发来的包。这种同时探测通常称为 UDP 打洞。
这要求程序能直接控制用于主协议的网络 socket,因为每个 socket 在 NAT 上可能对应不同映射。探测逻辑与应用协议共享 socket;双方还需一个低延迟的协调信道交换地址和安排开始时间。UDP 包可能丢失,因此探测不能承载唯一副本的业务数据。状态会超时,常见 UDP 空闲时间可能在数十秒量级;需要心跳或按需重建。
按数据包顺序看同时打洞
下面沿用原文的示意端点和端口来解释两个有状态防火墙的状态变化;它们只是讲解用地址,不是建议照搬的网络配置。核心规则是:先出现方向匹配的出站 UDP 包,防火墙才会暂时允许相反方向的回包。
| 顺序 | 数据包 | 端点 A 的状态 | 端点 B 的状态 |
|---|---|---|---|
| 1 | A 发 2.2.2.2:1234 → 7.7.7.7:5678 |
记下允许相反方向的回包 | 没有先前出站状态,丢弃该包 |
| 2 | B 发 7.7.7.7:5678 → 2.2.2.2:1234 |
把它识别为第 1 步出站包的反向流量并放行 | 记下允许相反方向的回包 |
| 3 | A 回发 2.2.2.2:1234 → 7.7.7.7:5678 |
允许出站 | 把它识别为第 2 步出站包的反向流量并放行 |
因此,两个端点都要在相近时间发送探测包,且探测包可能丢失;不能把唯一副本的业务数据放进探测包。原文也指出,状态会超时,端点需要保活或在需要时重新建立映射。真实 NAT 会改写地址和端口,下一节单独展开。
NAT 映射与 STUN
家用路由器把内网地址(例如 192.168.0.20:1234)改写为公网地址与端口(示例中的 2.2.2.2:4242),并记住映射以便把回应送回内网。只有发出过流量后,映射才存在。STUN 服务器看到的是改写后的公网 IP:port,可以把这个端点告诉客户端,再由协调信道转告对端。
| 位置 | UDP 源端点 | 目的端点 |
|---|---|---|
| 家庭网络内 | 192.168.0.20:1234 |
7.7.7.7:5678 |
| 经过家庭路由器后 | 2.2.2.2:4242 |
7.7.7.7:5678 |
| 回应返回家庭路由器 | 7.7.7.7:5678 |
2.2.2.2:4242 |
| 路由器反向改写后 | 7.7.7.7:5678 |
192.168.0.20:1234 |
这些地址取自原文的家庭 NAT 示意;真实公网端点由 NAT 分配,可能会随目的地址或映射策略变化。
但 STUN 观察到的映射不一定对所有目的地都相同。端点无关映射(EIM)对不同目标复用相同外部映射,通常较容易穿越;端点相关映射(EDM)会按目的 IP 或端口分配不同映射,若用对 STUN 服务器观察到的端口去联系另一个目标,连接就可能失败。旧文中“Full Cone / Symmetric NAT”等称呼把映射行为与防火墙过滤行为混在一起;RFC 4787、5382、5508 的行为术语更精确。单有 STUN 并不能让 EIM/EDM 的所有组合都可达。
映射与防火墙过滤是两个维度
原文给出的旧式锥形 NAT 名称同时混合映射与防火墙过滤。下表保留原文分类关系;实务分析优先直接记录 EIM/EDM 映射和过滤条件,不要只凭“对称 NAT”等俗称推断行为。
| 防火墙过滤 | 端点无关映射 EIM | 端点相关映射 EDM |
|---|---|---|
| 端点无关 | Full Cone NAT | 原文称理论上可有,未见于实际环境 |
| 按目的 IP 过滤 | Restricted Cone NAT | 原文称理论上可有,未见于实际环境 |
| 按目的 IP 与端口过滤 | Port-Restricted Cone NAT | Symmetric NAT |
原文进一步简化为:在常见的端点相关防火墙下,EIM 是“较易穿越”一类,EDM 是“较难穿越”一类。该简化描述的是映射难度,不是安全等级。
难以穿越的 NAT 与中继回退
有些 NAT 会为每个不同目的地创建不同公网映射。STUN 服务器观察到的 IP:port 因而不一定适用于另一端;一个端点相关映射就可能让同时发送的 UDP 探测失败。原文把这种路径称为难穿越的 NAT,并指出同一路径中只要有一端难穿越,当前直连尝试就可能失败。
直连失败时,原文提出让双方都能访问的中继来转发数据。中继会增加一些延迟,也可能降低吞吐,但仍比完全断连好。文章还举例说明,有的网络会直接阻止除 DNS 以外的所有出站 UDP;这种防火墙行为不能靠 NAT 打洞绕过,系统必须有可达的回退路径。
当前 Tailscale 文档把连接分为直连、Peer Relay 中继和 DERP 中继:若 tailnet 配置了 Peer Relay,会先用它;否则在直连不可用时使用 DERP。DERP 同时用于协商/建立连接,并在必要时转发加密的 WireGuard 数据包;中继不会因此变成解密端。这个版本说明补充了 2020 年原文的通用中继描述;是否能建立直连仍取决于两端 NAT 与网络策略。
多层 NAT 与运营商 CGNAT
家庭网络可能先由家用路由器转换一次,再由互联网服务提供商的运营商级 NAT(CGN)共享公网 IPv4 地址。RFC 6888 将 CGN 描述为由运营商网络管理、供多个订户共享 IPv4 地址的 NAT;订户通常不能配置这一层。RFC 6598 为连接家用设备与 CGN 的运营商网络分配了共享地址段 100.64.0.0/10,它不是可在公网路由的地址,也不等同 RFC 1918 私网段。若路由器 WAN 地址落在该范围,可能表明存在上游 CGN;运营商也可能采用其他部署方式,不能只凭一个地址断定拓扑。
家用路由器上的端口映射只配置本地这一层,不能代替运营商配置上游 CGN。STUN 看到的是数据包经过各层转换后、从该 STUN 服务器一侧观察到的地址;若任一层按目的端点改变映射,拿这个结果联系另一端仍可能失败。多层 NAT 下打洞仍可能成功,但不保证;Tailscale 当前文档也明确把多层 NAT/CGNAT列为可能的连接环境,直连失败时会转用中继。相关判断来自 RFC 6888、RFC 6598 与 Tailscale IPv4/IPv6 FAQ。
IPv6 如何改变直连条件
IPv6 地址空间充足,通常不需要仅为共享公网地址而做 IPv4 式的地址转换;但防火墙仍可限制入站流量,IPv6 也不保证两端必然直连。Tailscale 当前文档说明,若两端都有公共 IPv6 地址,可尝试直接使用 IPv6;一端没有 IPv6 时可退回 IPv4;IPv6-only 与 IPv4-only 设备无法直接使用同一 IP 家族时,则需要中继。公共 IPv6 的可用性、双方地址族和防火墙策略都要一起考虑。这里是对 Tailscale 当前文档的补充,不是把 2020 年原文未写出的细节归于原作者。
具体连接类型会随产品版本和网络变化。Tailscale 当前说明优先直连,直连不可能时用 Peer Relay(若已配置),再退回 DERP;这些是产品行为,不能泛化成所有 NAT 穿越实现。参见 Tailscale IPv6 支持文档与设备连接文档。
安全边界
路径可能动态切换,IP 地址不能作为身份凭据。承载的应用协议仍须提供端到端认证与加密,例如 WireGuard 公钥或 QUIC/TLS;路径发现探测最好也加密并认证,避免伪造探测造成流量被引向攻击者。UDP 探测会建立临时网络状态;应由用户明确授权的端点软件按协议执行,不应替代为手工扫描或盲目放宽防火墙规则。上述概念用于理解连通性。
原文由 David Anderson 于 2020 年 8 月 21 日发表于 Tailscale Blog;原文版权归 Tailscale Inc.,原页面未标明适用于全文的开放内容许可证。本文译写及自绘图依据另行取得的授权使用;该授权不改变 Tailscale 原文版权或原页面未声明开放内容许可证这一事实。正文中的 IPv6 与 CGNAT 补充依据 RFC 6888、RFC 6598、Tailscale IPv6 支持、IPv4/IPv6 FAQ及设备连接文档核验于 2026 年 10 月 9 日;产品连接类型可能变化。补充段落不归为原作者原文。











暂无评论内容