Catalog
  1. 1. 参考
  2. 2. 定义
  3. 3. STUN 消息结构
  4. 4. 协议描述
    1. 4.1. 构造请求或者指示(Indication)
    2. 4.2. 发送请求和指示
      1. 4.2.1. 通过 UDP 发送
  5. 5. STUN 属性
    1. 5.1. MAP-ADDRESS
    2. 5.2. XOR-MAPPED-ADDRESS
    3. 5.3. MESSAGE-INTEGRITY
    4. 5.4. FINGERPRINT
    5. 5.5. ERROR-CODE 错误码
    6. 5.6. REALM
    7. 5.7. NONCE
    8. 5.8. UNKNOWN_ATTRIBUTES
    9. 5.9. SOFTWARE
    10. 5.10. ALTERNATE_SERVER
STUN协议

参考

rfc5389

STUN 协议用于协商 NAT 穿透。终端使用它来获取 NAT 映射地址。它也被用于进行终端间的连通性测试以及终端间的连接保活。STUN 可工作于多种 NAT 下(有多种 NAT 实现)。

需要注意的是,STUN 不是 NAT 穿透的解决方案,它只是 NAT 穿透解决方案中用到的工具。

下面是参考 RFC,翻译了其中一些片段。

定义

STUN Agent: 实现了 STUN 协议的实体被称为 STUN 代理,它可以是 STUN 客户端,也可以是 STUN 服务器。

STUN Client: STUN 客户端发送 STUN 请求并接收响应。它也可以发送标识(indications).

STUN Server: STUN 服务器接收 STUN 请求并发送返回消息。他也可以发送标识。

Transport Address: IP 和 端口的组合

Reflexive Transport Address: 由网络端的另一台主机,比如 STUN 服务器发现的这个客户端的地址。当客户端和另一个主机间隔了 NAT, 反射地址则是客户端在 NAT 公网端的映射地址。反射地址保存在 STUN 返回的 MAPPED-ADDERSS 或者 XOR-MAPPED-ADDRESS 属性上。

Mapped Address: 和 Reflexive 地址一样的含义。

Long-Term Credential: 用户名和密码是客户端与服务器共享的机密。当订阅者加入了一个服务,Long-term 凭据通常会授予客户端,凭据会一直存在,直到订阅者离开服务或者主动变更了凭据。

Long-Term Password: 与 Long-Term 配对使用

Short-Term Credential: 临时用户和密码也是客户端和服务器共享的机密。Short-term 凭证是通过客户端和服务器间的某些协议机制获得的。Short-term 凭据有一个确切的范围,可能是5分钟,可能是一个事件开始到结束的间隔。这个间隔是有应用自行定义的。

Short-Term Password: Short-term 凭据的密码

Attribute: 这是 STUN 中 TVL 对象的术语,它被加入到 STUN 消息中。Attribute 被分成两个类型: comprehension-required 和 comprehension-optional。STUN 代理可以忽略它无法解析的 comprehension-optional, 但是无法处理一个有未知 comprehension-required 属性的消息。

RTO: 重传超时

STUN 消息结构

1
2
3
4
5
6
7
8
9
10
11
12
13
 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) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

STUN 消息头

头两个比特必须是 0 。当一个端口被用于多种协议传输是,它被用于做协议区分。

Message Type 定义了 STUN
消息的分类(request,success,response,failure response,indication) 和消息方法(the primary function)。尽管有 4 中消息类型,但是 STUN 只有两种事务: request/response 交易和指示(indication)事务(包含了一个单独的指示消息)。Response 类型分成 error 和 success response 为了更快的处理 STUN 消息。

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
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+
|M |M |M|M|M|C|M|M|M|C|M|M|M|M|
|11|10|9|8|7|1|6|5|4|0|3|2|1|0|
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+

STUN Message Type 字段

上图是 Message Type 字段的比特,从 M11 ~ M0。 M11 ~ M0 12bit 编码了方法。 C1 和 C0 表示 2bit 的类型编码。 类型 0b00 是 request, 0b01 是 indication, 0b10 是 success response, 0b11 是 error response。这篇说明只定义了一个方法,Binding。方法和类型是正交的,因此每个方法都有 request、success response,error response以及indication。

例如, Binding request 的 class=0b00, method= 0b000000000001,他们的编码就是 0x0001。
Binding response 的 class=0b10, method=0b000000000001, 编码就是 0x0101

Magic Cookie 字段以网络字节序填充了 0x2112A442。在 rfc3489 中,这个字段是 transaction ID 的一部分; 这个字段放在这个位置为了让 STUN 服务器探测客户端是否能识别本文提到的一些属性。此外,在多协议共用这个端口的情况下,它也被用来区分 STUN 包和其他协议包。

transication ID 是 96bit 标识,用于标识 STUN 事务。 STUN 客户端生成事务ID,然后服务器在响应中要原样返回。 对 indications 来说, 这个 ID 由 发送 indication 的代理生成。主要用户请求和响应匹配,同时还能起到一定的攻击防御作用。服务器也用这个 ID 来区分所有的客户端请求。因此, 这个 ID 必须唯一, 从 0 ~ 2^96 - 1 中随机选择一个。 不同的请求必须要使用不同的 ID,除非新的请求与前一个一样,包括通信地址也一样。成功和失败的返回也必须原样返回请求中的 ID。 当一个代理的 STUN 客户端和服务器用了同一个端口,这个代理的客户端的 ID 和服务器收到的 ID 是不一样的。

Message Len 表示消息长度,字节数,不包含 20 字节的头。 由于所有的 STUN 属性都填充4字节,所以这个字段最后的 2 位(不理解,这指的还是最开始的两位吧)总是 0。它提供了另一种区分协议的方法。

STUN 头之后就紧跟着 0 个或多个属性。 每个属性是 TVL 编码。

协议描述

这个章节描述了 STUN 协议工作过程。描述了消息的格式化,发送以及处理。也描述了 Binding 方法。其他章节描述了一些可选的过程。

构造请求或者指示(Indication)

代理必须按照规定的协议头来构造 STUN 请求或者指示。类型必须是 Request 或 Indication,方法则必须是 Binding 或者在其他标准中新增的方法。

根据方法或使用方式添加指定的属性。比如,当需要使用到 authentication method 或者 FINGERPRINT 属性时,代理需要在请求中添加 SOFTWARE 属性。

所有通过 UDP 信道发送的 STUN 消息的长度需要小于 MTU。如果不清楚 MTU,IPv4下长度度要小于 576 (缺省 MTU) 字节, IPv6下长度要小于 1280 字节。STU 协议无法处理长度大于 MTU 的返回。STUN 协议没有预先考虑这个长度的限制。MUT 显示不是必须的,当 STUN 消息作为探针来探测 MUT 字节数时就不需要遵循这个规则。除此之外或类似的应用,必须要遵从 MTU 长约束。

发送请求和指示

描述了如何通过 UDP,TCP,TLS-over-TCP 或者未来可能新增的传输协议来发送 STUN 消息。首先必须要选择传输协议,以及如何决定 IP 地址和端口。一种是通过 DNS 来决定 IP 和端口。STUN 可以使用任播地址,但是只有不带身份验证的 UDP 才符合。

在任何时刻,一个客户端可能在一个服务器上有多个未完成的 STUN 请求(有不同的交易 ID)。缺少了新增交易的限制机制。在发送一个新的交易时,至少需要等待一个 RTO,并且对同一个服务器的最大同时处理交易应该限制在 10 以内。

通过 UDP 发送

当通过 UDP 发送 STUN,那么可能出现丢包的情况。客户端实现了重传机制来确保 STUN 请求和响应的可靠性。STUN 指示消息不会重传,因此,基于 UDP 的指示消息是不可靠的。

STUN 属性

在 STUN 头之后是 0 个或多个属性。每个属性以 TLV 编码,16bit的 type(类型), 16bit 的 length(长度),然后是具体的内容。每个 STUN 属性必须要 32bit 对齐。

1
2
3
4
5
6
7
8

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) ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Length 字段描述的是 Value 的字节数。为了 4 字节对齐,不够的话使用功能填充字符补齐,填充字段可以使任何值。

STUN 消息中可能有重复的属性,在没有特殊说明的情况下,默认只处理第一个,忽略后面的。

为了可扩展,属性的类型划分了两个区域。type 在 0x0000 ~ 0x7FFF 是 comprehension-required 属性,意思是如果代理无法理解这些属性的话,那么消息处理就失败了。type 从 0x8000 ~ 0xFFFF 是 comprehension-optional 属性,意思是,如果代理无法解析的话,可以忽略它。

MAP-ADDRESS

MAP-ADDRESS 指示了客户端的反射地址(reflexive)。包含 8bits 地址族,16bits 端口,后面是相应长度的 IP 地址,IPv4的话就是 32bits, v6 就是 128bits。所有字段都是网络字节序。

1
2
3
4
5
6
7
8
9
10

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) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

0x01: IPv4
0x02: IPv6

头 8bits 置零,接受者不需要处理。这些位用来做对齐的。

XOR-MAPPED-ADDRESS

这个属性和 MAPPED-ADDRESS 属性一直,只不过这个属性的值是反射地址经过异或运算后得到的。

1
2
3
4
5
6
7
 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|x x x x x x x x| Family | X-Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| X-Address (Variable)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

端口和 IP 地址(包括 IPv4 和 IPv6) 都经过一些算法(不描述算法了,可以参考文档)计算出结果后放到消息中。为什么要做一系列的运算呢?因为公网中有一些 NAT 的实现会替换数据包中的公网地址,所以这个运算操作是为了防止数据被篡改。

MESSAGE-INTEGRITY

这个字段包含了STUN 消息的 HMAC-SHA1。 这个字段出现在任何类型的 STUN 消息中。 因为使用了 SHA1 哈希, 所以 HMAC 是 20 字节。 用于计算 HMAC 的输入包括 STUN 消息(包括投),一直到并包含 MESSAGE-INTERGRITY 前一个属性。代理必须忽略 MESSAGE-INTERGRITY 后的所有属性,除了 FINGERPRINT 属性。

FINGERPRINT

这个属性可能出现在所有的 STUN 消息中。 这个字段的值是 STUN 消息(不包含 FINGERPRINT属性)的 CRC-32 校验值,然后异或 0x5354554e。这个属性必须是 STUN 消息的最后一个属性。

这个属性有助于区分 STUN 协议和其他协议。

ERROR-CODE 错误码

这个属性包含了一个错误码, 从 300 ~ 699,还会附带一个文本(utf8编码)的错误提示,这些错误码的定义与 SIP 和 HTTP 协议一样。错误提示可以给出任何合适的语句,只要符合错误码就行了。错误提示需要以 UTF-8 编码,长度小于 128 字符(注意,是 utf8编码,那么字节数大概是 763)。

1
2
3
4
5
6
7
 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) ..
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

为了容易处理,错误码分成几个部分,如上图所示。

Reserved bit 必须是 0,使用来做字节对齐的。收到消息的代理需要忽略这个字段。Class 表示错误码的百位,其值在 3 ~ 6 之间(between 是否包含?) Number 表示错误码模 100 的值,在 0 ~ 99 范围内。

错误码定义:

300 Try Alternate: 客户端可以请求另一个服务器。只有在请求中包含 USERNAME,并且 MESSAGE-INTERGRITE 合法时才发送这个错误,否则只能发送 400 错误码。客户端在从新请求信服务器时,同样需要验证消息完整性。

400 Bad Request: 请求畸形,也就是消息不完整吧或者格式不对。客户端不应该用这个消息重复请求,除非进行了修改。服务器可能不会对这个错误生成合法的 MESSAGE-INTEGRITY,所以客户端不用期望在返回中得到一个合法的 MESSAGE-INTEGRITY 属性。

401 Unauthorized: 请求没有包含正确的凭据。客户端需要使用合适的凭据来发起请求。

420 Unknow Attribute: 服务器收到了一个包含了无法理解的 comprehension-require 属性的 STUN 消息。服务器需要将这个无法解析的属性放在返回中的 UNKNOWN-ATTRIBUTE 属性。

438 Stale Nonce: 客户端使用的 NONCE 失效了,客户端用新的 NONCE 重试。

500: Server Error: 服务器内部错误,客户端可以重试一次。

REALM

这个字段可能出现在请求和返回消息中。它包含了一段文本,语法符合在 RFC3261 中定义的 “realm-value”,但是不包含双引号以及空格。因此,这是一个没有引号的 realm-value。它必须是 UTF8 编码,长度小于 128 个字符(utf8字符), 必须使用 SASLprep 处理。

请求中包含 REALM 表示身份验证方式用的是 long-term 凭据。在错误返回中包含 REALM 表示服务器希望客户端使用 long-term 凭据来做身份验证。

NONCE

可以出现在请求和返回消息中。包含了一系列的 qdtext 或者 quoted-air, 他们被定义在 RFC3261。这意味着 NONCE 属性中不会有引号。RFC 2617 对如何选择 nonce 值做了指导。

长度需小于 128 字符。

UNKNOWN_ATTRIBUTES

仅出现在错误返回中,并且错误码是 420。

这个属性包含了一个 16-bit 值的列表,每一个都表示了一个服务器无法识别的属性类型。

1
2
3
4
5
6
7
 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Attribute 1 Type | Attribute 2 Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Attribute 3 Type | Attribute 4 Type ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

SOFTWARE

这个属性存放了发送这个消息的代理的用途。客户端和服务器都要使用这个字段。它应该包含厂商和版本号。这个属性对 STUN 协议没有实际的影响,被用来做调试信息。需要是 UTF8编码,小于 128 字符。

ALTERNATE_SERVER

这个属性表示了一个可用的stun服务传输地址,客户端可以尝试请求这个新的地址。

编码方式和 MAPPED-ADDRES 一样。

Author: 42
Link: http://blog.ikernel.cn/2020/02/28/STUN%E5%8D%8F%E8%AE%AE/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment