STUN 支持两种类型的事务,分别是 REQUEST/RESPONSE 事务和 INDICATION 事务。STUN 本身并不是一种穿越解决方案,而只是协议,被 NAT 穿越的解决方案(比如 ICE 和 TURN)所使用。
STUN 协议能够帮助处于内网的终端确定 NAT 为其分配的外网 IP 地址和端口。
在 ICE 中,STUN 协议用于连通性检查(BINDING_REQUEST/RESPONSE)和 ICE 的保活(BINDING_INDICATION)。在 TURN 协议中,STUN 协议用于 Allocation 的建立,可以作为中继数据的载体(比如 sendindication 和 dataindication),ICE 和 TURN 是两种不同的 STUN 使用方式。
这里,我们暂时先讨论协议本身:
STUN 报文格式
STUN协议结构由20字节头和若干个属性组成,属性的个数可以为0,且统一是大端字节序的二进制序列。报文分为两部分:
- 20字节的 STUN Header
- Body,其中携带0或多个attribute
STUN Header
报文头一共20个字节,其中:
// STUN packet transaction id = 96 bits
#define STUN_TRANSACTION_ID_LEN (UINT16) 12
// STUN Header
typedef struct {
UINT16 stunMessageType; // 14 bit
UINT16 messageLength; // 16 bit
UINT32 magicCookie; // 32 bit
BYTE transactionId[STUN_TRANSACTION_ID_LEN]; // 96 bit
} StunHeader, *PStunHeader;
Message Type
Message Type是主机字节序,比较重要:
1、BINDING/地址捆绑
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0001 | BINDING REQUEST |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0011 | BINDING INDICATION |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0101 | BINDING RESPONSE SUCCESS |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0111 | BINDING RESPONSE ERROR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2、SHARED SECRET/共享私密
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0002 | SHARED SECRET REQUEST |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0102 | SHARED SECRET RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0112 | SHARED SECRET ERROR RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
3、ALLOCATE
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0003 | ALLOCATE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0103 | ALLOCATE SUCCESS RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0113 | ALLOCATE ERROR RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4、REFRESH
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0004 | REFRESH |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0104 | REFRESH SUCCESS RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0114 | REFRESH ERROR RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
5、SEND
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0006 | SEND |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0106 | SEND INDICATION |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6、DATA
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0007 | DATA |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0107 | DATA INDICATION |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7、CREATE PERMISSION
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0008 | CREATE PERMISSION |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0108 | CREATE PERMISSION SUCCESS RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0118 | CREATE PERMISSION ERROR RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8、CHANNEL BIND/通道捆绑
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0009 | CHANNEL BIND REQUEST |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0109 | CHANNEL BIND SUCCESS RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0x0119 | CHANNEL BIND ERROR RESPONSE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Message Length
这里的长度,指的是所有 STUN Attributes 的长度:
下面这段,就是 STUN Attributes:
Magic Cookie & Transaction ID
在传输中一般都认为 Cookie 是 Transaction ID 的一部分,故 Transaction ID 被认为是16字节内容。但是 Cookie 在 [RFC 3489] 或者 [RFC 5389] 中是固定数值0x2112A442
。
Transaction ID 是由请求一方产生,在回复时需要和请求时 Transaction ID 一样,同一请求使用相同的Transaction ID,但客户端需为新的事务选择新的Transaction ID。
STUN Attributes
属性是继在Header之后,以一个个排列的结构(STUN Attributes)组成:
其中,STUN Attributes 由 Attribute Header 和 Attribute Body 组成。其中 Attribute Header 包括了 Type 和 Length,长度固定为 4 字节。Attribute Body 长度不固定,由 Length 决定。
下面,解释一些比较重要的属性:
/**
* STUN attribute types
*/
typedef enum {
STUN_ATTRIBUTE_TYPE_MAPPED_ADDRESS = (UINT16)0x0001,
STUN_ATTRIBUTE_TYPE_RESPONSE_ADDRESS = (UINT16)0x0002,
STUN_ATTRIBUTE_TYPE_CHANGE_REQUEST = (UINT16)0x0003,
STUN_ATTRIBUTE_TYPE_SOURCE_ADDRESS = (UINT16)0x0004,
STUN_ATTRIBUTE_TYPE_CHANGED_ADDRESS = (UINT16)0x0005,
STUN_ATTRIBUTE_TYPE_USERNAME = (UINT16)0x0006,
STUN_ATTRIBUTE_TYPE_PASSWORD = (UINT16)0x0007,
STUN_ATTRIBUTE_TYPE_MESSAGE_INTEGRITY = (UINT16)0x0008,
STUN_ATTRIBUTE_TYPE_ERROR_CODE = (UINT16)0x0009,
STUN_ATTRIBUTE_TYPE_UNKNOWN_ATTRIBUTES = (UINT16)0x000A,
STUN_ATTRIBUTE_TYPE_REFLECTED_FROM = (UINT16)0x000B,
STUN_ATTRIBUTE_TYPE_XOR_MAPPED_ADDRESS = (UINT16)0x0020,
STUN_ATTRIBUTE_TYPE_PRIORITY = (UINT16)0x0024,
STUN_ATTRIBUTE_TYPE_USE_CANDIDATE = (UINT16)0x0025,
STUN_ATTRIBUTE_TYPE_FINGERPRINT = (UINT16)0x8028,
STUN_ATTRIBUTE_TYPE_ICE_CONTROLLED = (UINT16)0x8029,
STUN_ATTRIBUTE_TYPE_ICE_CONTROLLING = (UINT16)0x802A,
STUN_ATTRIBUTE_TYPE_CHANNEL_NUMBER = (UINT16)0x000C,
STUN_ATTRIBUTE_TYPE_LIFETIME = (UINT16)0x000D,
STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS = (UINT16)0x0012,
STUN_ATTRIBUTE_TYPE_DATA = (UINT16)0x0013,
STUN_ATTRIBUTE_TYPE_REALM = (UINT16)0x0014,
STUN_ATTRIBUTE_TYPE_NONCE = (UINT16)0x0015,
STUN_ATTRIBUTE_TYPE_XOR_RELAYED_ADDRESS = (UINT16)0x0016,
STUN_ATTRIBUTE_TYPE_EVEN_PORT = (UINT16)0x0018,
STUN_ATTRIBUTE_TYPE_REQUESTED_TRANSPORT = (UINT16)0x0019,
STUN_ATTRIBUTE_TYPE_DONT_FRAGMENT = (UINT16)0x001A,
STUN_ATTRIBUTE_TYPE_RESERVATION_TOKEN = (UINT16)0x0022,
} STUN_ATTRIBUTE_TYPE;
MAPPED-ADDRESS/映射地址
表示 “NAT客户端的反射地址”。其中,Family 标识协议族,包括 IPv4 和 IPv6。其中,IPv4 为 0x01,IPv6 为 0x02。需要注意的是,Port 和 Address 都是以网络序存在,本地查看时需要转换到主机序。
XOR-MAPPED-ADDRESS/亦或映射
与 MAPPED-ADDRESS 属性基本相同,区别在于反射地址经过一次异或(XOR)处理,异或运算是其自身的逆运算,客户端经过一次异或运算获得真实的反射地址。之所以这么做,目的就是解决ALG篡改地址和端口的问题。
USERNAME/用户名
用户名,用于消息完整性,识别在消息完整性检查中使用的用户名和密码组合。
MESSAGE-INTEGRITY/消息认证
STUN 消息的 HMAC-SHA1 值,长度 20 字节,用于消息完整性认证。
FINGERPRINT/指纹认证
FINGERPRINT 属性可以在所有的 STUN 消息中出现。该属性的值被计算为 STUN 消息的 CRC-32 值,与 32 位值 0x5354554e 进行异或操作(这种异或操作有助于处理应用包也在其中使用 CRC-32 的情况)。
32 位 CRC 是 ITU V.42 [ITU.V42.2002] 中定义的,具有生成多项式 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1。当存在时,FINGERPRINT 属性必须是消息中的最后一个属性,因此会出现在 MESSAGE-INTEGRITY 之后。
FINGERPRINT 属性可以帮助区分 STUN 数据包与其他协议的数据包。与 MESSAGE-INTEGRITY 一样,FINGERPRINT 属性中使用的 CRC 也覆盖了 STUN 消息头的长度字段。因此,在计算 CRC 之前,此值必须正确,并将 CRC 属性作为消息长度的一部分包括在内。
在消息中使用 FINGERPRINT 属性时,首先将属性放入消息中并赋予虚拟值,然后计算 CRC,最后更新属性的值。如果 MESSAGE-INTEGRITY 属性也存在,则在计算 CRC 之前必须出现具有正确 message-integrity 值的 MESSAGE-INTEGRITY 属性,因为 CRC 也会对 MESSAGE-INTEGRITY 属性的值进行操作。
PRIORITY 和 USE-CANDIDATE
终端必须在其 request 中包含 PRIORITY 属性,指明其优先级,优先级由公式计算而得。如果有需要也可以给出特别指定的候选(即 USE-CANDIDATE 属性)
UNKNOWN-ATTRIBUTES/未知属性
此属性只会在错误代码为 420 的错误响应中出现
ICE-CONTROLLING 和 ICE-CONTROLLED
ICE流程中定义了两种角色:Controlling 和 Controlled。
不同的角色在 Candidate Pair 优先级的计算,Pair Nominate 决策上有所不同。一般流程下,会话的双发各自的角色选择是与会话协商的流程相关的,Offer端是 Controlling,Answer端是 Controlled。
ICE-CONTROLLED 或者是 ICE-CONTROLLING,这两个属性都会携带一个 Tie breaker 这样的字段,其中包含一个本机产生的随机值。收到该 Binding Request 的一方会检查这两个字段,如果和当前本机的 Role 冲突,则检查本机的 Tie breaker 值和消息中携带的 Tie breaker 值进行判定本机合适的 Role。
判定的方法为 Tie breaker 值大的一方为 Controlling。如果判定本端变更角色,这直接修改;如果判定对端变更角色,则对此 Binding Request 发送487错误响应,收到此错误响应的一方变更角色即可。
ERROR-CODE/错误码
属性用于 “error response” 报文中,其中包含了300-699表示的错误码,以及一个UTF-8格式的文字出错信息(Reason phrase)。
SOFTWARE
此属性用于代理发送消息时包含版本的描述,用于客户端和服务器。它的值包括制造商和版本号。该属性对于协议的运行没有任何影响,仅为诊断和调试目的提供服务。SOFTWARE属性是个可变长度的,采用UTF-8编码的小于128个字符的序列号。
ALTERNATE-SERVER
属性表示 STUN 客户可以尝试的不同的 STUN 服务器地址,属性格式与 MAPPED-ADDRESS 相同。