STUN协议详解
2022-12-30

NAT会话遍历实用程序(STUN)是一种服务于作为其他协议处理NAT穿透的工具 | 可以使用它来确定分配的IP地址和端口并通过NAT访问它 | 它还可以用于检查两个端点之间的连通性 | STUN与许多现有的NAT一起工作 | 并作为保持NAT绑定活动的协议.
STUN本身并不是一个NAT穿越解决方案 | 而是作为一种工具在NAT穿透解决方案的上下文中使用.

STUN协议可以使用TCP或者UDP传输,但一般情况下使用UDP | 一个完整的STUN消息由一个消息头和多个属性组成.

RFC 8489: Session Traversal Utilities for NAT (STUN)
https://www.rfc-editor.org/rfc/rfc8489

STUN消息头

            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)                  |
           |                                                               |
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段长度说明
type2消息类型
len2消息长度
cookie4固定值: 0x2112A442u32
transaction id12交易号

消息类型

类型
BindingRequest0x0001
BindingResponse0x0101
BindingError0x0111
AllocateRequest0x0003
AllocateResponse0x0103
AllocateError0x0113
CreatePermissionRequest0x0008
CreatePermissionResponse0x0108
CreatePermissionError0x0118
ChannelBindRequest0x0009
ChannelBindResponse0x0109
ChannelBindError0x0119
RefreshRequest0x0004
RefreshResponse0x0104
RefreshError0x0114
SendIndication0x0016
DataIndication0x0017

交易号

STUN消息中的交易号是客户端生成的随机数据,服务端返回STUN响应的时候需要关联请求交易号.


STUN消息属性

            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)                ....
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段长度说明
type2属性类型
len2内容长度
value属性内容

属性内容为了对齐会存在填充位,长度为4的倍数,如果不足则填充0,如:内容长度为5,则len5,但value的长度为8, 填充30, 读取value的时候需要排除掉填充位.

属性类型

类型
UserName0x0006
Data0x0013
Realm0x0014
Nonce0x0015
XorPeerAddress0x0012
XorRelayedAddress0x0016
XorMappedAddress0x0020
MappedAddress0x0001
ResponseOrigin0x802B
Software0x8022
MessageIntegrity0x0008
ErrorCode0x0009
Lifetime0x000D
ReqeestedTransport0x0019
Fingerprint0x8028
ChannelNumber0x000C
IceControlled0x8029
Priority0x0024
UseCandidate0x0025
IceControlling0x802A

UserName

这个属性在确保消息一致性时使用,这个属性包含一个可变长度的字符串,字符串编码必须为UTF-8,对于最大长度有限制,最大长度为508.

Date

DataIndication消息中使用,这个属性的值是可变长度的.

Realm

可以出现在请求或者响应中,但一般在确保消息一致性的时候使用,字符串编码必须为UTF-8,最大长度为127.

Nonce

一段随机字符串,可以出现在请求或者响应中,字符串编码必须为UTF-8,最大长度为127,对于一个对端Nonce值是唯一并且持久的,这里可以理解为对端的标识,Nonce存在过期时间,过期之后需要重新刷新随机字符串,默认过期时间为3600秒.

Software

包含自身程序的文本描述,这个值对于协议没有任何影响,一般情况下包含制造商和版本,用于参考信息.

MessageIntegrity

消息完整性检查使用,包含一个HMAC-SHA1值,对于短期凭证和长期凭证有不同的计算方法.
这里以长期认证举例: HMAC-SHA1(username + password + realm)

XorPeerAddress

指定对等方的地址和端口. (XOR)

XorRelayedAddress

服务器分配的地址和端口. (XOR)

XorMappedAddress

MappedAddress相同. (XOR)

MappedAddress

客户端的自反地址. (客户端NAT最外层地址)

ResponseOrigin

服务器的对外地址. (用来处理双重NAT)

ErrorCode

用于错误消息响应.

           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
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          |           Reserved, should be 0         |Class|     Number    |
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          |      Reason Phrase (variable)                                ..
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段长度说明
code2错误代码
message错误消息
错误码
TryAlternate0x0300
BadRequest0x0400
Unauthorized0x0401
Forbidden0x0403
RequestTimedout0x0408
UnknownAttribute0x0414
AllocationMismatch0x0425
StaleNonce0x0426
AddressFamilyNotSupported0x0428
WrongCredentials0x0429
UnsupportedTransportAddress0x042A
AllocationQuotaReached0x0456
ServerError0x0500
InsufficientCapacity0x0508

Lifetime

用于指示和刷新生命周期.

ReqeestedTransport

客户端请求使用的传输协议.

           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
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          |    Protocol   |                    RFFU                       |
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
协议
UDP0x01
TCP0x02

Fingerprint

STUN消息摘要,这个属性必须是属性列表的最后一个属性,通过对整个消息经常CRC计算得到.

在计算Fingerprint之前,STUN消息的长度字段必须包含Fingerprint属性的长度,但并不用真正包含Fingerprint字段.

ChannelNumber

在绑定和发送channel消息时使用,内部包含一个通道号,这个属性的长度是4个字节.

           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
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          |        Channel Number         |         RFFU = 0              |
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

#Address

           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 0 0 0 0 0 0|    Family     |           Port                |
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
          |                                                               |
          |                 Address (32 bits or 128 bits)                 |
          |                                                               |
          +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段长度说明
family2ip family
port2端口
address4 / 16地址
IP Family
IPv40x01
IPv60x02

#XOR Address

            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 0 0 0 0 0 0|    Family     |         X-Port                |
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           |                X-Address (Variable)
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Family字段代表IP地址族,其编码与MAPPED-ADDRESS中的Family字段相同.

X-Port是通过对映射端口与magic cookie的最高16位进行异或计算得出的,如果是IPv4,则X-Address是通过对映射的IP地址与magic cookie进行异或计算得出的,如果IPIPv6,则X-Address是通过对映射的IP地址与magic cookie96transaction id串联进行异或运算来计算的,在所有情况下,XOR操作都以网络字节顺序(即它们将在消息中编码的顺序)对其输入进行操作.

编码和处理属性值前8位的规则、处理属性多次出现的规则、处理地址族的规则与MAPPED-ADDRESS相同.


实现

GitHub - colourful-rtc/turn-rs: A pure rust-implemented turn server.
A pure rust-implemented turn server. Contribute to colourful-rtc/turn-rs development by creating an account on GitHub.
https://github.com/colourful-rtc/turn-rs

这是一个纯Rust实现的STUN/TURN服务器,内部包含STUN消息编解码器和TURN会话处理库.