3. NAT工作原理深度解析
要理解 UDP 打洞为什么有时成功、有时失败,必须先看清 NAT 设备内部到底维护了什么状态。NAT 并不是一个“把内网地址换成公网地址”的简单函数,而是一台带状态的有状态转发设备:它靠一张会话表决定哪些入站报文可以放行,哪些必须丢弃。四种典型 NAT 形态的差别,本质上就是这张表的建立方式、匹配维度和老化策略的差别。
四种 NAT 形态:从一对一映射到多路复用
静态 NAT是最“笨”也最稳定的一种:管理员手工把某个内网地址固定映射到某个公网地址,例如 192.168.1.10 <-> 203.0.113.10。这条映射长期存在,不依赖流量触发,因此外部主机可以主动向 203.0.113.10 发起连接并到达内网主机。它常用于对外提供服务的服务器,代价是公网地址消耗大,且内网主机完全暴露。
动态 NAT使用一个公网地址池,当内网主机首次向外发起流量时,从池中临时分配一个公网地址,会话结束后归还。它仍然是“一个内网地址对应一个公网地址”,只是分配时机由流量触发。由于映射是临时的、且不携带端口维度,外部主机无法预知当前某内网主机占用的是哪个公网地址,因此主动入站基本不可行。
NAPT(网络地址端口转换)是家庭路由器和企业出口最常见的形态,也就是我们口语中的“NAT”。它同时改写源 IP 和源端口,让成百上千台内网主机复用同一个公网 IP,靠不同的公网端口区分不同会话。例如内网 192.168.1.10:5000 访问 198.51.100.20:3478,出口可能被改写为 203.0.113.5:62001。NAPT 是 P2P 穿透的主战场,因为它的映射行为直接决定了打洞能否命中。
端口映射表(也叫端口转发/静态端口映射)则是人为在 NAPT 设备上开一个固定入口,例如把公网 203.0.113.5:8080 永久转发到 192.168.1.10:80。它相当于在 NAPT 上手工“钉”了一条静态会话,使外部可以主动访问内网指定端口。在 P2P 场景中,如果双方都能配置端口映射,穿透问题就退化为直接连接问题。
会话表匹配:入站报文为什么会被丢弃
NAPT 的转发逻辑可以概括为两条规则。出站方向:内网报文到达时,若表中已有匹配会话则刷新计时并复用原映射;若没有,则新建一条映射(分配公网端口)并记录五元组。入站方向:报文到达公网口后,NAT 用“目的公网 IP + 目的公网端口 + 源 IP + 源端口 + 协议”去查表,只有命中已有会话才改写目的地址并转发给内网主机;查不到就丢弃。
这正是 P2P 打洞的核心矛盾:A 想主动给 B 发 UDP 包,但 B 的 NAT 表里如果没有一条“允许 A 的地址进入”的会话,包就会被丢掉。打洞的做法是让 A 和 B 先各自向对方(或通过信令服务器协调)发出一个出站包,使双方 NAT 表里都提前建立一条指向对方的会话,之后对方的入站包就能命中放行。所以打洞成功的前提是:双方 NAT 对同一内网源端口保持稳定映射,且允许来自不同目的地址的入站包命中同一条会话。
映射行为:锥形与对称型的分水岭
同样是 NAPT,不同厂商对“同一内网源端口访问不同目的时是否复用同一公网端口”的处理不同,这直接决定打洞策略:
- 锥形 NAT(Cone):内网
192.168.1.10:5000无论访问哪个目的,都映射到同一个公网端口,例如始终是203.0.113.5:62001。只要这个映射存在,任何外部地址发往该公网端口的包都可能被放行(完全锥形)或仅限已通信过的地址(受限锥形)。 - 对称 NAT(Symmetric):同一内网源端口访问不同目的时,分配不同的公网端口。上图中
192.168.1.10:5000访问 STUN 得到62001,访问另一目的又变成62004。此时 A 通过 STUN 探测到的公网端口,与它真正发给 B 时使用的端口不一致,B 拿到的候选地址是错的,打洞极易失败,通常需要中继(TURN)兜底。
生命周期与超时:打洞的时间窗口
NAT 会话不是永久的。每条会话都有一个空闲计时器,出站或入站报文命中时会重置计时。UDP 会话的空闲超时通常较短,常见范围约 30 秒到 120 秒(不同设备差异较大,也有设备对已建立的 UDP 映射给到数分钟);TCP 已建立连接的超时通常更长,约数小时量级。超时到达后,NAT 删除该会话,公网端口被回收,之后外部再发往该端口的包会被丢弃。
这对 P2P 的工程含义非常直接:打洞必须在双方会话都还“活着”的时间窗口内完成,并且连接建立后要靠心跳(keepalive)持续刷新映射。若心跳间隔大于 NAT 的空闲超时,会话会被删除,链路表现为“静默一段时间后突然不通”。实践中常把 UDP 心跳设为 15~25 秒,以覆盖大多数家用 NAT 的超时下限;同时心跳本身也是探测链路是否仍可用的手段。
工程观察要点
排查穿透问题时,建议按以下顺序确认:先看双方 STUN 探测得到的公网地址与端口是否稳定(多次探测是否一致);再看双方是否属于对称型(不同目的端口是否变化);然后确认打洞包是否在会话超时窗口内发出;最后检查心跳间隔是否小于 NAT 空闲超时。若 NAT 为对称型或对入站限制严格,应准备 TURN 中继作为可靠回退路径,而不是无限重试打洞。
理解这张会话表,就理解了 NAT 穿透的全部约束来源:映射是否稳定、入站是否放行、会话能活多久。后续的 STUN、打洞时序与中继策略,都是围绕这三个变量展开的工程应对。