4. NAT类型分类与检测方法

NAT穿透能否成功,往往不取决于打洞代码写得多漂亮,而取决于两端所处的NAT到底属于哪一类。RFC 3489最早把NAT行为归纳为四类:完全锥形(Full Cone)、受限锥形(Restricted Cone)、端口受限锥形(Port Restricted Cone)和对称NAT(Symmetric NAT)。RFC 5780后来用更精确的映射行为(Mapping Behavior)与过滤行为(Filtering Behavior)两维模型替代了这套分类,但四类模型在工程排障中依然直观好用。理解这四类的判别逻辑,是后续设计打洞策略、选择中继回退方案的前提。

四类NAT的行为差异

四类NAT的核心区别体现在两个维度:映射行为——内部主机同一源端口访问不同外部目标时,NAT是否分配同一个公网端口;过滤行为——外部主机向该公网端口发包时,NAT是否放行。

  • 完全锥形:内部主机(192.168.1.10:5000)首次访问外部目标后,NAT分配公网端口(203.0.113.5:62000)。此后任何外部主机向203.0.113.5:62000发包都能到达内部主机,不限制来源IP和端口。映射一旦建立即长期有效,过滤最宽松。
  • 受限锥形:映射行为与完全锥形相同,但过滤更严——只有内部主机曾经主动访问过的外部IP才能向该公网端口发包。即外部IP必须在NAT的允许列表中,但端口不限。
  • 端口受限锥形:过滤最严的锥形。只有内部主机曾经访问过的外部IP:端口组合才能回包。外部IP对但端口不对,包会被丢弃。
  • 对称NAT:映射行为不同。内部主机同一源端口访问不同外部目标时,NAT分配不同的公网端口。过滤行为通常类似端口受限。这意味着A通过STUN服务器S1学到的公网端口,无法用于与B通信,因为访问B时NAT会换一个新端口。

判别逻辑的关键在于:先测映射,再测过滤。RFC 3489的经典检测流程使用两台STUN服务器(不同IP、不同端口),通过对比多次Binding Request返回的MAPPED-ADDRESS来判断。

RFC 3489检测流程

假设客户端本地端口为5000,向STUN服务器发起Binding Request,服务器在响应中返回它观察到的客户端公网地址。检测步骤如下:

  1. 向STUN服务器S1(IP1:Port1)发送请求,得到映射地址M1。
  2. 向STUN服务器S2(IP2:Port2,与S1不同IP不同端口)发送请求,得到映射地址M2。
  3. 若M1与M2的IP和端口都相同 → 映射行为为Endpoint-Independent Mapping,属于锥形NAT,继续测过滤。
  4. 若M1与M2的IP相同但端口不同 → 映射行为为Address-Dependent Mapping,属于对称NAT的一种。
  5. 若M1与M2的IP和端口都不同 → 映射行为为Address and Port-Dependent Mapping,也是对称NAT。

确认是锥形后,再测过滤行为。让S1从另一个端口向M1发送数据包:

  • 若收到 → 过滤行为为Endpoint-Independent Filtering,即完全锥形。
  • 若收不到,再让S2(不同IP)向M1发包:收到则为Address-Dependent Filtering,即受限锥形;仍收不到则为Address and Port-Dependent Filtering,即端口受限锥形。

RFC 5780把上述逻辑形式化为两个独立的测试序列,并引入了CHANGE-REQUEST属性,让客户端可以要求STUN服务器从不同IP或不同端口回包,从而在单台双IP服务器上完成全部检测,比RFC 3489需要两台独立服务器更易部署。

检测实践与结果判读

实际工程中常用stunclient或pystun3做快速检测。以pystun3为例:

# 安装
pip install pystun3

# 执行检测,指定STUN服务器
pystun3 --stun-host stun.l.google.com --stun-port 19302

# 典型输出
# NAT Type: Full Cone
# External IP: 203.0.113.5
# External Port: 62000

但要注意:单次检测结果可能因NAT设备负载均衡、多公网IP池而波动。更可靠的做法是连续执行多次,观察External Port是否稳定。如果同一本地端口每次得到的公网端口都不同,基本可判定为对称NAT。

RFC 5780的CHANGE-REQUEST属性取值:0x01表示改变IP和端口,0x02表示只改变端口,0x04表示只改变IP。客户端通过组合这些值,可以精确探测NAT的过滤行为,而无需依赖多台服务器。

下面这张图展示了四类NAT在“映射一致性”和“过滤宽松度”两个维度上的判别路径,以及对应的RFC 5780行为标签。

NAT类型判别路径(RFC 3489 / 5780) 向 S1、S2 发送 Binding Request M1 与 M2 的 IP:Port 是否相同? 否 对称 NAT Address/Port-Dependent 是 S1 换端口回包 能否到达 M1? 能 完全锥形 Endpoint-Indep. Filter 不能 S2(不同IP)向 M1 发包 受限锥形 端口受限锥形 映射行为 Mapping 过滤行为 Filtering

检测结果的工程含义

得到NAT类型后,穿透策略的选择就清晰了:

NAT类型映射行为过滤行为打洞可行性
完全锥形Endpoint-IndependentEndpoint-Independent极易,任一方发包即可
受限锥形Endpoint-IndependentAddress-Dependent容易,需先向对方IP发包
端口受限锥形Endpoint-IndependentAddress+Port-Dependent可行,需精确预测端口
对称NATAddress/Port-Dependent通常Port-Dependent困难,需端口预测或中继

对称NAT之所以难打洞,是因为A访问B时NAT会分配新端口,而B无法提前知道这个端口。如果双方都是对称NAT,直接打洞几乎不可能,必须依赖TURN中继。如果一方是对称NAT、另一方是锥形NAT,则锥形方可以通过端口预测尝试连接,但成功率取决于对称NAT的端口分配规律(有些设备是递增的,有些是随机的)。

RFC 3489已被RFC 5389(STUN)和RFC 5780(NAT行为发现)取代。RFC 3489的CHANGE-REQUEST语义在新版中有所调整,且不再推荐用“四类”来硬性归类。实际开发中应优先使用RFC 5780的映射/过滤行为描述,因为它能更准确地刻画现代NAT的复杂行为。

最后强调一个实践要点:检测时务必使用与打洞相同的本地端口。NAT映射与本地端口绑定,用端口5000检测出的公网端口,不能直接套用到端口5001的通信上。很多穿透失败案例,根源就是检测端口与实际通信端口不一致,导致拿到的公网映射信息无效。