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,服务器在响应中返回它观察到的客户端公网地址。检测步骤如下:
- 向STUN服务器S1(IP1:Port1)发送请求,得到映射地址M1。
- 向STUN服务器S2(IP2:Port2,与S1不同IP不同端口)发送请求,得到映射地址M2。
- 若M1与M2的IP和端口都相同 → 映射行为为Endpoint-Independent Mapping,属于锥形NAT,继续测过滤。
- 若M1与M2的IP相同但端口不同 → 映射行为为Address-Dependent Mapping,属于对称NAT的一种。
- 若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。
下面这张图展示了四类NAT在“映射一致性”和“过滤宽松度”两个维度上的判别路径,以及对应的RFC 5780行为标签。
检测结果的工程含义
得到NAT类型后,穿透策略的选择就清晰了:
| NAT类型 | 映射行为 | 过滤行为 | 打洞可行性 |
|---|---|---|---|
| 完全锥形 | Endpoint-Independent | Endpoint-Independent | 极易,任一方发包即可 |
| 受限锥形 | Endpoint-Independent | Address-Dependent | 容易,需先向对方IP发包 |
| 端口受限锥形 | Endpoint-Independent | Address+Port-Dependent | 可行,需精确预测端口 |
| 对称NAT | Address/Port-Dependent | 通常Port-Dependent | 困难,需端口预测或中继 |
对称NAT之所以难打洞,是因为A访问B时NAT会分配新端口,而B无法提前知道这个端口。如果双方都是对称NAT,直接打洞几乎不可能,必须依赖TURN中继。如果一方是对称NAT、另一方是锥形NAT,则锥形方可以通过端口预测尝试连接,但成功率取决于对称NAT的端口分配规律(有些设备是递增的,有些是随机的)。
最后强调一个实践要点:检测时务必使用与打洞相同的本地端口。NAT映射与本地端口绑定,用端口5000检测出的公网端口,不能直接套用到端口5001的通信上。很多穿透失败案例,根源就是检测端口与实际通信端口不一致,导致拿到的公网映射信息无效。