目录
一. 前言
现实网络环境中绝大多数主机都是处于 NAT 之后,对于两个处于同一内网环境的主机,它们只要知道对端的内网地址就能进行通信,而对于不在同一内网的主机,如果它们想通信,要么借助带有公网地址的主机转发,要么通过一定的手段进行 NAT 穿越。
STUN 协议是用来 NAT 穿越的工具,它允许位于 NAT 之后的主机查找到自己 NAT 映射后的公网地址,需要通信的双方交换映射后的公网地址再进行连通性检测。
STUN 最先在 RFC3489 中定义,英文全称是 Simple Traversal of UDP Through NAT,即用 UDP 进行 NAT 穿越,而新的 RFC5389 把 STUN 定义为 Session Traversal Utilities for NAT,即 NAT 会话传输工具,RFC3489 与 RFC5389 相比,最大的区别是后者支持 TCP 穿越。
STUN 协议是一个 C/S 模型的协议,即一端发送请求,另一端进行响应,此外还有指示类型的消息,一端发送指示消息后,另一端不必响应。
二. STUN报文格式
RFC3489 和 RFC5389 定义的报文格式有些许差别,因此下面会分开说明。
STUN Header
RFC3489
STUN 报文以 20 个字节的头部开始,后面跟着若干属性。
RFC3489 STUN Header 包含三个字段(2 个字节的 STUN Message Type,2 个字节的 Message Length,16 个字节的 Transaction ID)。
STUN Message Type 的取值和对应的含义如下。
Message Type | 含义 |
0x0001 | 绑定请求 |
0x0101 | 绑定响应 |
0x0111 | 绑定错误响应 |
0x0002 | 共享私密请求 |
0x0102 | 共享私密响应 |
0x0112 | 共享私密错误响应 |
Message Length:STUN 报文长度(不包括固定的 20 字节的头部)。
Transaction ID:事务 ID,用于关联请求和对应的响应,同一事务的请求和响应事务 ID 相同。
RFC5389
RFC5389 的 STUN Header 也是 20 个字节,只是 STUN Message Type 从 16bit 变成 14bit,开头的 2bit 固定为 00,Transaction ID 从 128bit 变成 96bit,减少的 32bit 变成 Magic Cookie,其值固定为 0x2112A442,使用 Magic Cookie 可以区分 STUN RFC3489 还是 RFC5389。Message Length 含义与 RFC3489 一样,表示 STUN 报文除去头部后的长度。
RFC5389 的 STUN Message Type (14bit) 可以进一步分解成以下结构。
M11~M0 用来表示方法,RFC 规范目前只定义了一个方法:Binding,其他方法可以由使用者自行扩展。
C1C0 表示方法的类型,对于 C1C0=0b00 表示这是一个请求,C1C0=0b01 表示指示,C1C0=0b10 表示请求成功的响应,C1C0=0b11 表示请求失败的响应。
方法与方法的类型是正交的,即对于每一种方法,其请求,指示,请求成功响应,请求失败响应都是可能的。
STUN Message Body
STUN 报文头部之后有 0 或多个属性,每个属性使用 TLV 编码(Type, Length, Value)。
RFC3489
Type | 名称 | 说明 |
0x0001 | MAPPED-ADDRESS | 返回客户端NAT映射过的IP和端口 |
0x0002 | RESPONSE-ADDRESS | 指明对于MAPPED-ADDRESS的响应应该发送至哪里 |
0x0003 | CHANGE-REQUEST | 请求服务端使用不同的IP和端口发送响应 |
0x0004 | SOURCE-ADDRESS | 指示服务端的IP和端口 |
0x0005 | CHANGED-ADDRESS | CHANGE-REQUEST的响应 |
0x0006 | USERNAME | 用户名,用于安全认证 |
0x0007 | PASSWORD | 密码,用于安全认证 |
0x0008 | MESSAGE-INTEGRITY | 用于消息完整性验证 |
0x0009 | ERROR-CODE | 错误码 |
0x000a | UNKNOWN-ATTRIBUTES | 未知属性 |
0x000b | REFLECTED-FROM | 拒绝 |
上图表示的意思是 MAPPED-ADDRESS 这个属性一定不出现在 Binding Req 中,必须出现在 Binding Resp 中,而 RESPONSE-ADDRESS 属性可以出现在 Binding Req 中,一定不出现在其他类型的方法中。
N/A 表示不,M 表示必须,O 表示可选,其他类型的属性以此类推。
RFC5389
Type | 名称 | 说明 |
0x0020 | XOR-MAPPED-ADDRESS | 异或地址 |
0x8028 | FINGERPRINT | 消息指纹 |
RFC5389 的属性与 RFC3489 有些许不同,大部分 RFC3489 的属性在 RFC5389 中仍然能使用,此外 RFC5389 还扩展了一些属性,例如 XOR-MAPPED-ADDRESS 获取异或后的地址, FINGERPRINT 防止消息被篡改等,其他属性可查阅 RFC5389 文档,此处不过多介绍。
三. WebRTC对STUN协议的支持
WebRTC 支持的 STUN 消息类型除了 RFC 规范提及的之外,还扩展了 GOOG Ping 的请求/响应消息。
Message Type | 含义 |
0x0001 | 请求消息 |
0x0011 | 指示消息 |
0x0101 | 成功响应消息 |
0x0111 | 错误响应消息 |
0x0200 | GOOG Ping 请求消息 |
0x0300 | GOOG Ping 响应消息 |
0x0310 | GOOG Ping 错误响应消息 |
对于属性值,WebRTC 并没有支持 RFC 规范中提到的所有属性,支持的属性如下所示。
Type | 名称 |
0x0001 | MAPPED-ADDRESS |
0x0006 | USERNAME |
0x0008 | MESSAGE-INTEGRITY |
0x0009 | ERROR-CODE |
0x000a | UNKNOWN-ATTRIBUTES |
0x0014 | REALM |
0x0015 | NONCE |
0x0020 | XOR-MAPPED-ADDRESS |
0x8022 | SOFTWARE |
0x8023 | ALTERNATE-SERVER |
0x8028 | FINGERPRINT |
0x802F | ORIGIN |
0xFF00 | RETRANSMIT-COUNT |
四. STUN工作流程
1. 使用STUN获取NAT映射后的地址
现实网络环境中大部分主机是处于 NAT 之后,即通过 ifconfig/ipconfig 查看到的是内网地址,主机访问外网时 NAT 设备会将其内网地址映射成公网地址,主机本身是无法查看某次访问外网地址 NAT 映射后的地址是多少的,但是通过 STUN 协议,主机发送 STUN binding request, 再由 STUN 服务器回复 STUN binding reponse 即可从属性拿到映射后的地址。
webrtc.github.io 提供了一个获取服务器反射地址的页面工具,如下所示,输入 STUN 服务地址后,点击 Gather candidates 开始收集地址,type srflx 表示该地址候选项是服务器反射地址类型,即 NAT 映射后公网地址。type host 表示主机地址候选项,跟 ifconfig/ipconfig 查看到的内网地址是一样的。
我们通过 Wireshark 抓包分析上述流程,可以看到我主机发送的是一个 RFC5389 的 STUN binding request 消息,没有携带任何属性,而 stun1.l.google.com STUN 服务器回复的是一个 RFC5389 标准的 STUN binding success reponse 消息,消息携带了一个 XOR-MAPPED-ADDRESS 属性值,属性值中包含了映射后的地址信息。