一、STUN协议概述
STUN,全称是Session Traversal Utilities for NAT。说白了,它就是NAT穿透的侦察兵。
我刚开始做WebRTC的时候,以为只要两端都连上互联网,就能直接建立P2P连接。结果第一次联调就翻车了——两边都在公司内网,谁也连不上谁。后来我才明白,没有STUN,你连自己在NAT后面的位置都搞不清楚。
STUN的核心作用就三个:
- 发现公网IP和端口——告诉你在NAT外面长什么样
- 检测NAT类型——判断你处在哪种NAT后面
- 保持NAT绑定——通过周期性心跳维持映射关系
STUN是一个轻量级的客户端-服务器协议。它基于UDP,设计上尽量简单。为什么用UDP?因为TCP在NAT穿透场景下太笨重了,握手延迟高,而且很多NAT对TCP的处理方式不一样。
关键认知:STUN不负责穿透,它只负责探测。真正的穿透工作是由ICE(Interactive Connectivity Establishment)来协调完成的。STUN只是ICE手中的一把尺子。
二、STUN消息结构
STUN消息结构非常简洁。我当年第一次看RFC 5389的时候,觉得这协议设计得真干净——没有复杂的握手,没有状态机,就是一个请求一个响应。
2.1 消息头格式
所有STUN消息都共享同一个20字节的头部:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0| STUN Message Type | Message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Magic Cookie |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Transaction ID (96 bits) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
头部字段说明:
| 字段 | 长度 | 说明 |
|---|---|---|
| Message Type | 14位 | 高两位固定为0。区分请求、响应、指示等 |
| Message Length | 16位 | 不包括头部的消息体长度 |
| Magic Cookie | 32位 | 固定值0x2112A442,用于校验 |
| Transaction ID | 96位 | 唯一标识一次请求-响应交互 |
这里有个细节我想强调一下:Magic Cookie的值0x2112A442不是随便选的。它是RFC 5389引入的,用来区分老版STUN(RFC 3489)和新版STUN。我在一次排查问题时,发现某个老设备发的STUN消息没有这个Magic Cookie,结果解析全乱了。嗯,兼容性问题总是藏在最不起眼的地方。
2.2 属性结构
消息头后面跟着一系列TLV(Type-Length-Value)格式的属性:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Value (variable) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
常见的STUN属性:
- MAPPED-ADDRESS (0x0001)——服务器看到的客户端公网地址
- XOR-MAPPED-ADDRESS (0x0020)——经过异或处理的映射地址,更安全
- USERNAME (0x0006)——用于消息完整性校验的用户名
- MESSAGE-INTEGRITY (0x0008)——HMAC-SHA1摘要,防止篡改
- FINGERPRINT (0x8028)——CRC32校验,可选但推荐
避坑指南:我曾经在生产环境中发现,某些NAT设备会修改UDP负载中的IP地址。STUN的XOR-MAPPED-ADDRESS就是为了应对这种情况——通过异或操作让IP地址在传输中不可读,防止NAT误修改。
三、STUN请求/响应流程
STUN的交互流程非常简单。我画了一张图,你可以直观地看到整个过程:
流程其实就四步:
- 客户端发送Binding Request——客户端向STUN服务器发送一个UDP请求,里面包含自己的内网地址信息
- NAT进行地址转换——请求经过NAT时,源IP和端口被替换成公网地址
- 服务器返回Binding Response——STUN服务器收到请求后,把看到的公网地址(源IP+端口)封装在XOR-MAPPED-ADDRESS属性中返回
- 客户端解析响应——客户端拿到自己的公网地址,就知道自己在NAT外面的样子了
你可能会问:为什么客户端不直接查自己的网卡信息?因为内网地址在公网上不可路由。你想想看,如果两端都在192.168.1.x网段,它们怎么知道对方在公网上的真实地址?
核心要点:STUN服务器看到的源IP和端口,就是客户端在公网上的"身份"。这个信息会被ICE用来尝试建立P2P连接。
四、STUN的局限性
STUN不是万能的。我在项目中踩过不少坑,这里总结一下它的主要局限:
4.1 对对称NAT无能为力
这是STUN最大的痛点。对称NAT(Symmetric NAT)每次建立新连接时都会分配不同的公网端口。STUN探测到的地址只对当前连接有效,换一个目标地址就变了。
我曾经遇到一个客户,他们的网络环境就是对称NAT。STUN返回的地址看起来没问题,但ICE尝试连接时就是不通。折腾了两天,最后只能用TURN中继方案。
4.2 无法穿透防火墙
STUN只处理NAT,不处理防火墙。很多企业防火墙会深度检查UDP流量,发现不是预期的协议就直接丢弃。STUN包可能连服务器都到不了。
4.3 依赖公共STUN服务器
公共STUN服务器可能不稳定、有延迟,或者被运营商屏蔽。我建议在生产环境中自建STUN服务器集群,或者使用云服务商提供的STUN服务。
4.4 安全性问题
STUN本身没有加密机制。虽然MESSAGE-INTEGRITY提供了完整性校验,但无法防止中间人攻击。如果你的应用对安全性要求高,建议在DTLS之上运行STUN。
重要提醒:不要指望STUN解决所有NAT穿透问题。它只是一个探测工具。真正的穿透策略需要结合ICE、TURN、甚至UPnP等多种技术。我见过太多团队把STUN当成万能药,结果上线后问题频发。
4.5 性能开销
每次STUN交互都会增加连接建立的延迟。在移动网络环境下,UDP包可能被重传多次,导致延迟飙升。我记得有一次在3G网络下测试,STUN请求平均耗时超过2秒,用户体验非常差。
总结一下STUN的适用场景:
| 场景 | STUN效果 | 建议 |
|---|---|---|
| 锥形NAT(Full Cone / Restricted Cone) | ✅ 有效 | 直接使用STUN |
| 端口限制锥形NAT | ⚠️ 部分有效 | 需要配合ICE |
| 对称NAT | ❌ 无效 | 必须使用TURN |
| 防火墙后 | ❌ 可能无效 | 尝试TURN或HTTP代理 |
嗯,STUN就讲到这里。它虽然简单,但却是WebRTC网络穿透的基石。理解STUN的工作原理,你就能更好地理解ICE为什么需要收集那么多候选地址——因为不是所有NAT都那么好说话。
公众号:蓝海资料掘金营,微信deep3321