标准定义
Session Traversal Utilities for NAT (STUN) 是作为处理 NAT穿透 的工具来服务其他协议的协议。endpoint 可以使用它来确定 NAT 分配给它的 IP 地址和端口。它还可用于检查两个 endpoint 之间的连接,并作为保持 NAT 绑定的保持活动协议(保活)。 STUN 与许多现有的 NAT 一起工作,并且不需要它们的任何特殊行为。STUN 本身并不是一个 NAT 穿透解决方案。相反,它是在 NAT 穿透解决方案的上下文中使用的工具。
在计算机科学中,NAT穿越(NAT traversal)涉及TCP/IP网络中的一个常见问题,即在处于使用了NAT设备的私有TCP/IP网络中的主机之间创建连接的问题。
Traversal Using Relays around NAT (TURN) 是 Session Traversal Utilities for NAT (STUN) 的中继扩展。
Interactive Connectivity Establishment (ICE) 描述了一种用于网络地址转换器 (NAT) 遍历的协议,用于使用 offer/answer 模型建立的基于 UDP 的多媒体会话。该协议称为 Interactive Connectivity Establishment (ICE)。 ICE 使用 Session Traversal Utilities for NAT (STUN) 协议及其扩展,使用 Traversal Using Relay NAT (TURN) 。 ICE 可以被任何使用 offer/answer 模型的协议使用,例如会话发起协议 (SIP)。
Trickle ICE: Incremental Provisioning of Candidates for the Interactive Connectivity Establishment (ICE) Protocol 描述了 Interactive Connectivity Establishment (ICE) Protocol 的扩展,该协议允许 ICE agents 以增量方式发送和接收 candidates ,而不是交换完整列表。
NAT
NAT(Network Address Translation)——网络地址转换,解决IPv4地址随着网络终端剧增而趋于耗尽的问题。NAT 通常部署在一个组织的网络出口位置,通过将内部网络IP地址替换为出口的IP地址,提供公网可达性和上层协议的连接能力。
内部网络IP地址:RFC1918 规定的三个保留地址段:10.0.0.0-10.255.255.255; 172.16.0.0-172.31.255.255; 192.168.0.0-192.168.255.255 。这三个范围的地址段分别处于 A,B,C类的地址段,这些地址被 IANA 作为私有地址保留,这些地址可以用在任何组织或企业内部。
对于有公网访问需求而内部又使用私有地址的网络,就要在组织的出口位置部署 NAT 网关,在报文离开私网进入公网时,NAT 将源 IP 替换为公网地址,当报文从公网进入私网时,NAT 将目的 IP 替换为私网地址。
NAT处理报文的几个关键特点:
- 网络被分为私网和公网两个部分,NAT 网关设置在私网到公网的路由出口位置,双向流量必须都要经过 NAT 网关
- 网络访问只能先有私网侧发起,公网无法主动访问私网主机(破坏了IP协议架构中所有节点在通讯中的对等地位,为对等通讯带来诸多问题,NAT穿透正是为了解决这一问题)
- NAT 网关在两个访问方向上完成两次地址的转换或翻译,出方向做源信息替换,入方向做目的信息替换
- NAT 网关的存在对通信双方是保持透明的(实际并不,在对等通讯中,我们需要完成 NAT 识别和穿越)
- NAT 网关为了实现双向翻译的功能,需要维护一张关联表,把会话的信息保持下来。
NAT 类型 :
-
一对一的 NAT
一个内部主机唯一占用一个公网 IP,但这显然不能节约公网 IP 啊。 -
一对多的 NAT
一个组织网络,在出口位置部署 NAT 网关,NAT 根据传输层信息或其他上层协议去区分不同的会话,比如TCP 或 UDP 端口号,这样 NAT 网关就可以 将不同的内部连接访问映射到同一公网 IP 的不同传输层端口 ,通过这种方式实现公网IP的复用和解复用,这种方式也被称为端口转换PAT(Port Address Transfer)、NAPT(Network Address Port Transfer) 或 IP 伪装,但更多时候可直接称为 NAT,这是最典型的应用模式。
按 NAT 端口映射方式分类的 NAT 类型:
2.1 全锥形 NAT- 一旦内部主机端口对(iAddr:iPort) 被 NAT 网关映射到(eAddr:ePort),所有后续的(iAddr:iPort) 报文都会被转换为 (eAddr:ePort);
- 任何一个外部主机 发送到(eAddr:ePort) 的报文都会被转换后发送到 (iAddr:iPort)。
2.2 限制锥形 NAT
- 一旦内部主机端口对(iAddr:iPort) 被 NAT 网关映射到(eAddr:ePort),所有后续的(iAddr:iPort) 报文都会被转换为 (eAddr:ePort);
- 只有(iAddr:iPort)向特定的外部主机 hAddr 发送过数据,主机 hAddr 从任意端口 发送到(eAddr:ePort) 的报文将会被转换后发送到 (iAddr:iPort)。
2.3 端口限制锥形 NAT
- 一旦内部主机端口对(iAddr:iPort) 被 NAT 网关映射到(eAddr:ePort),所有后续的(iAddr:iPort) 报文都会被转换为 (eAddr:ePort);
- 只有(iAddr:iPort)向特定的外部主机 (hAddr:hPort) 发送过数据,由主机 (hAddr:hPort) 发送到(eAddr:ePort) 的报文才会被转换后发送到 (iAddr:iPort)。
2.4 对称型 NAT
- NAT 网关使用 4 元组(iAddr:iPort, hAddr:hPort)进行映射 (eAddr:ePort, hAddr:hPort)
NAT 的限制和解决方案 :
弊端:
- 破坏 IP 端到端通信能力。
- NAT 使 IP 会话的保持时效变短,在会话静默一段时间后,NAT网关会进行老化回收操作(回收IP和端口)。
- NAT 在实现上将多个内部主机发出的连接复用到一个IP上,这就使依赖IP进行主机追踪的机制失效。
- NAT 通过修改 IP 首部的信息变换通信的地址。
- NAT 工作机制依赖于修改 IP 包头的信息,这会妨碍一些安全协议的工作。
解决方案:
- 本文主要讨论两个位于 NAT 之内的终端如何建立端到端的通信能力。
STUN
Session Traversal Utilities for NAT (STUN)提供了处理 NAT 的工具。它为 endpoint 提供了一种方法来确定由 NAT 分配的与其私有 IP 地址和端口(iAddr:iPort)相对应的 IP 地址和端口(eAddr:ePort)。它还为 endpoint 提供了一种保持 NAT 绑定(地址映射保活)的方法(避免 NAT 绑定被 NAT 网关回收)。
STUN 是一种 client-server 协议。它支持两种类型的事务:
- 一种是 request/response 事务,client 向 server 发送 request,server 返回 response。
- 第二个是 indication 事务,其中 agent(client或server)发送 indication ,但对方不需要生成 response。
两种类型的事务都包含一个事务 ID(Transaction ID
),它是一个 96 位随机数字。对于 request/response 事务,此事务 ID 允许 client 将 server 的 response 与它生成的request 相关联;对于 indication 事务,事务 ID 用作帮助调试。
定义
STUN Client:stun 协议中发送 request、接收 response 的实体。client 也可以发送指示(indication )。
STUN Server:stun 协议中接收 request、返回 response 的实体。server 也可以发送指示(indication )。
STUN Agent:stun 协议中的任意实体,可能是一个 STUN client 也可能是 STUN server,相当于 client 和 server 的一个统称。
Transport Address:IP 地址和端口号的组合。
Reflexive Transport Address:自反传输地址表示在 NAT 的公共端分配给客户端的映射地址。自反传输地址是客户端从 STUN 响应中的映射地址属性(MAPPED-ADDRESS 或 XOR-MAPPED-ADDRESS)中获取的。
Mapped Address:与 Reflexive Transport Address
含义相同。保留该术语仅出于历史原因以及由于 MAPPED-ADDRESS 和 XOR-MAPPED-ADDRESS 属性的命名。
Long-Term Credential:长期凭证,代表客户端和服务器之间共享机密的 username 和associated password。Long-Term Credential
通常在 subscriber 注册服务时授予 client ,并一直持续到 subscriber 离开服务或明确更改凭证为止。
Long-Term Password:Long-Term Credential
中的 password 。
Short-Term Credential:短期凭证,代表客户端和服务器之间共享机密的临时 username 和associated password。在 STUN 交换之前,Short-Term Credential
是通过客户端和服务器之间的某种协议机制获得的。Short-Term Credential
具有明确的时间范围,可以基于特定的时间(例如 5 分钟)或事件(例如 SIP 对话的终止)。
Short-Term Password:Short-Term Credentia
中的 password。
STUN Indication:一个无需响应的 STUN Message 。
Attribute:一个 Type-Length-Value (TLV) 对象,可以添加到 STUN Message。Attribute 分为两种类型:需要理解和理解可选。 STUN agent 可以忽略他们不理解的理解可选的Attribute ,但如果 message 包含 agent 不理解的必需理解属性,则 agent 无法成功处理消息。
RTO:Retransmission TimeOut,重传超时,定义请求和首次请求重传的初始时间间隔。
STUN Message
STUN Message Header
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 Message Type
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|
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+
- M11 - M0 = 12-bit 编码为 method (类似于HTTP的 method - get, post 等)
- 0b000000000001 表示 Binding
- C1C0 = 2-bit 编码为 class
- 0b00 表示 request
- 0b01 表示 indication
- 0b10 表示 success response
- 0b11 表示 error response
Message Length :包含消息的大小(以字节为单位),不包括 20 字节的 STUN 头。由于 所有 STUN Attribute 都填充为 4 字节的倍数,因此该字段的最后 2 位始终为零。这提供了另一种将 STUN 数据包与其他协议的数据包区分开来的方法。
Magic Cookie:固定值 0x2112A442
(网络字节序)。在 RFC 3489 [RFC3489] (旧的STUN)中,该字段是事务 ID 的一部分;将 Magic Cookie 放置在此位置允许服务器检测客户端是否会理解在 [RFC389] (最新的STUN)中添加的某些属性。此外,当 STUN 与同一端口上的其他协议复用时,Magic Cookie 有助于区分 STUN 数据包和其他协议的数据包。
Transaction ID 是一个 96 位的标识符(client 随机生成,client 确保唯一),用于唯一标识 STUN 事务,它主要用于将请求与响应相关联。
STUN Attributes
Format of STUN Attributes
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) ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
rfc5389 定义的初始 STUN Attributes types :
要求理解的范围: (0x0000-0x7FFF):
-
0x0000
:保留 -
0x0001
:MAPPED-ADDRESS -
0x0002
:保留,RESPONSE-ADDRESS -
0x0003
:保留,CHANGE-REQUEST -
0x0004
:保留,SOURCE-ADDRESS -
0x0005
:保留,CHANGED-ADDRESS -
0x0006
:USERNAME -
0x0007
:保留,PASSWORD -
0x0007
:MESSAGE-INTEGRITY -
0x0009
:ERROR-CODE -
0x000A
:UNKNOWN-ATTRIBUTES -
0x000B
:保留,REFLECTED-FROM -
0x0014
:REALM -
0x0015
:NONCE -
0x0020
:XOR-MAPPED-ADDRESS
可选理解的范围:(0x8000-0xFFFF)
-
0x8022
:SOFTWARE -
0x8023
:ALTERNATE-SERVER -
0x8028
:FINGERPRINT
MAPPED-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) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
MAPPED-ADDRESS
属性指示客户端的 reflexive transport address
。它由一个 8 位地址族(0x01:IPv4,0x02:IPv6)和一个 16 位端口组成,后跟一个表示 IP 地址的固定长度值。如果地址族是 IPv4,地址必须是 32 位。如果地址族是 IPv6,地址必须是 128 位。所有字段必须按网络字节顺序排列。
XOR-MAPPED-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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|x x x x x x x x| Family | X-Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| X-Address (Variable)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
XOR-MAPPED-ADDRESS
属性与 MAPPED-ADDRESS
属性相同,只是反射传输地址通过 XOR 函数进行了混淆。
X-Port
:是通过按照主机字节顺序获取映射端口,与 magic cookie 的最高有效 16 位进行异或运算,然后将结果转换为网络字节顺序来计算的。
X-Address
:
- 如果 IP 地址系列是 IPv4,则 X-Address 的计算方法是按照主机字节顺序获取映射的 IP 地址,将其与 magic cookie 进行异或,然后将结果转换为网络字节顺序。
- 如果 IP 地址族是 IPv6,则 X-Address 的计算方法是按照主机字节顺序获取映射的 IP 地址,将其与 magic cookie 和 96 位 transaction ID 的连接进行异或,并将结果转换为网络字节命令。
RESPONSE-ADDRESS
RESPONSE-ADDRESS 属性用于 BindingRequest ,指出 Binding Request 的 Binding Response 应该发送到哪里(IP+Port),它的格式/语法与 MAPPED-ADDRESS 相同。
CHANGED-ADDRESS
CHANGED-ADDRESS 属性用于 Binding Response,指出发送 Response 的 IP 地址和端口。当且仅当 Binding Request 的 CHANGE-REQUEST 属性设置了 "change IP" 和 "change port" 的 flag 时,Binding Response 携带 CHANGED-ADDRESS 属性。它的格式/语法与 MAPPED-ADDRESS 相同。
CHANGE-REQUEST
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 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 A B 0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
CHANGE-REQUEST 属性用于 Binding Request ,被 client 用于请求 server 使用其他的 IP 地址或端口来发送 Binding Response (用于探测 NAT 的类型:全锥形NAT能收到不同地址和 Port 的 Response,限制锥形只能收到同地址不同 Port 的 Response,端口限制锥形和对称型 NAT 不在本次探测范围)
- A:"change IP" flag, 请求 stun server 改变 IP 发送 Binding response 。
- B:"change port" flag, 请求 stun server 改变 Port 发送 Binding Response 。
SOURCE-ADDRESS
SOURCE-ADDRESS 属性出现在 Binding Response 中,指示发送 Response 的 server 的 IP 地址和端口。它的格式/语法与 MAPPED-ADDRESS 相同。
USERNAME
USERNAME 属性用于消息完整性。它标识在消息完整性检查中使用的 username 和 password 组合。
USERNAME 的值是一个可变长度值。它必须包含小于 513 字节的 UTF-8 [RFC3629] 编码序列,并且必须使用 SASLprep [RFC4013] 进行处理。
USERNAME 伴随着 PASSWORD 出现在 Shared Secret Response 中,用于 Binding Request 。
USERNAME 伴随着 MESSAGE-INTEGRITY 出现在 Binding Request 中,用于检查请求合法性和完整性。
PASSWORD
PASSWORD 伴随着 USERNAME 出现在 Shared Secret Response 中,用于生成:生成 MESSAGE-INTEGRITY 的 key 。
REALM
REALM 属性可能出现在请求和响应中。它包含符合 RFC 3261 [RFC3261] 中描述的 "realm-value" 语法的文本,但没有双引号及其周围的空格。也就是说,它是一个未引用的域值(因此是一个 qdtext 或 quoted-pair 的序列)。它必须是少于 128 个字符(可以长达 763 个字节)的 UTF-8 [RFC3629] 编码序列,并且必须使用 SASLprep [RFC4013] 进行处理。
请求中存在 REALM 属性表明长期凭证正在用于身份验证。某些错误响应中 REALM 属性存在表明服务器希望客户端使用长期凭据进行身份验证。
NONCE
NONCE 属性可能出现在请求和响应中。它包含一系列 qdtext 或 quoted-pair,在 RFC 3261 [RFC3261] 中定义。请注意,这意味着 NONCE 属性将不包含实际的引号字符。请参阅 RFC 2617 [RFC2617] 第 4.3 节,了解有关在服务器中选择 nonce 值的指南。
MESSAGE-INTEGRITY
消息完整性:
MESSAGE-INTEGRITY
属性包含 STUN 消息的 HMAC-SHA1 [RFC2104]。 MESSAGE-INTEGRITY
属性可以出现在任何 STUN 消息类型中。
agent 必须忽略除了出现在 MESSAGE-INTEGRITY
之后的 FINGERPRINT 属性外的出现在 MESSAGE-INTEGRITY
之后的所有其他属性。
由于 MESSAGE-INTEGRITY
使用 HMAC-SHA1,因此 HMAC 将是 20 个字节。HMAC 输入是 STUN 消息中包括报头和MESSAGE-INTEGRITY
属性之前的属性。HMAC 的 key 取决于使用的是长期凭证(long-term credentials)还是短期凭证(short-term credentials)。
- 对于长期凭证,密钥为 16 个字节:
key = MD5(username ":" realm ":" SASLprep(password))
- 对于短期凭证:
key = SASLprep(password)
MD5 定义在 RFC1321 ,SASLprep() 定义在 RFC4013 。
FINGERPRINT
指纹(消息):
FINGERPRINT
属性可以出现在所有 STUN 消息中。该属性的值计算为 STUN 消息(直到(但不包括)FINGERPRINT 属性)的 CRC-32,并将结果与 32 位值 0x5354554e 进行异或(异或有助于在应用程序包也使用 CRC-32 的情况下)。 32 位 CRC 是在 ITU V.42 [ITU.V42.2002] 中定义的。
当 FINGERPRINT
存在时,FINGERPRINT
属性必须是消息中的最后一个属性,因此将出现在 MESSAGE-INTEGRITY 之后。
ERROR-CODE
错误码:
ERROR-CODE
属性用于 error response
Message。
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) ..
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
包含一个 300 到 699 范围内的数字错误代码值(Number
)加上一个以 UTF-8 [RFC3629] 编码的文本原因短语(Reason Phrase
)。原因短语是供用户使用的,可以是任何适合错误代码的内容。
错误码-Number:
-
300
:建议 client 尝试请求备用 server 。当请求包含USERNAME
属性和有效的MESSAGE-INTEGRITY
属性时,server 才可回复此错误响应;否则,不得发送,并建议错误代码 400(错误请求)。此错误响应必须使用MESSAGE-INTEGRITY
属性进行保护,并且接收者必须在将自己重定向到备用服务器之前验证此响应的MESSAGE-INTEGRITY
。 -
400
:请求格式不正确。 -
401
:未经授权:请求包含凭据(MESSAGE-INTERITY属性)不正确。客户端应使用正确的凭据重试请求。 -
420
:未知属性:服务器收到一个 STUN 数据包,其中包含它不理解的需要理解的属性。服务器必须将此未知属性放在其错误响应的UNKNOWN-ATTRIBUTE
属性中。 -
420
:未知属性: -
420
:未知属性: -
433
:请使用 TLS 请求 Share Secret (Username 和 Password) -
438
:客户端使用的 NONCE 不再有效。客户端应使用响应中提供的 NONCE 重试。 -
500
:服务器错误:服务器出现临时错误。客户应该再试一次。 -
600
:服务器拒绝完成请求,客户端不应该重试。
UNKNOWN-ATTRIBUTES
UNKNOWN-ATTRIBUTES 属性仅在 ERROR-CODE 属性中的错误响应的错误码为 420 时出现在错误响应中。
该属性包含一个 16 位值的列表,每个值都表示服务器无法理解的属性类型。
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
SOFTWARE 属性包含发送消息的 agent 正在使用的软件的文本描述。它由客户端和服务器使用。它的值应该包括制造商和版本号。该属性对协议的运行没有影响,仅用作诊断和调试目的的工具。 SOFTWARE 的值是可变长度的。它必须是少于 128 个字符的 UTF-8 [RFC3629] 编码序列(可以长达 763 个字节)。
ALTERNATE-SERVER
备用服务器(配合错误码 300 使用),用于指示 STUN 客户端应该尝试的不同 STUN 服务器。它的编码方式与 MAPPED-ADDRESS 相同,因此通过 IP 地址引用单个服务器。 IP 地址族(family)必须与请求的源 IP 地址相同。
基本协议流程
本节定义了 STUN 协议的基本过程。 它描述了消息是如何形成的,它们是如何发送的,以及它们在接收时是如何处理的。 它还定义了Binding方法的详细处理。
形成一个 Request 或 Indication
- agent 创建 stun messgae header
- agent 将 message class 置为 request 或 indication
- agent 将 message method 置为 binding 或 other method
- agent 根据 method 或 usage 添加 attributes
收到一个 STUN Message
处理一个 Request
- 如果请求包含一个或多个未知的要求理解(comprehension-required)的属性,服务器将用一个错误代码为 420 (未知属性) 的错误响应进行响应,并在响应中包含一个unknown - attributes属性,在unknown - attributes属性中列出未知的要求理解(comprehension-required)的属性。
对于 Binding 方法,除非用法另有指定,否则不需要进行额外检查。在形成成功响应时,服务器向响应添加一个XOR-MAPPED-ADDRESS属性,其中属性的内容是请求消息的源传输地址。对于UDP,这是请求消息的源IP地址和源UDP端口。对于TCP和tls over-TCP,这是服务器所看到的TCP连接的源IP地址和源TCP端口。
处理一个 Indication
- 如果 Indication 包含未知的要求理解(comprehension-required)的属性,则丢弃 Indication 并停止处理。
- agent 执行该方法或特定用法指定的任何附加检查。如果所有检查都成功,agent 将继续处理 Indication。
对于 Binding 方法,不需要额外的检查或处理,除非用法另有指定。agent 仅接收到消息就刷新了中间 NAT 中的 “bindings”。
处理一个成功的 Response
- 如果成功响应包含未知的要求理解(comprehension-required)的属性,则丢弃响应,并认为事务失败。
- 客户端执行该方法或特定用法所需的任何附加检查。如果所有检查都成功,那么客户机将处理成功响应。
对于 Binding 方法,客户机将检查响应中是否存在XOR-MAPPED-ADDRESS属性。客户端检查指定的地址族。如果它是不受支持的地址族,则该属性应被忽略。如果它是一个意外但受支持的地址族(例如,绑定事务通过IPv4发送,但指定的地址族是IPv6),则客户端可以接受并使用该值。
处理一个错误的 Response
- 如果错误响应包含未知的要求理解(comprehension-required)的属性,或者错误响应不包含error - code属性,则认为事务已经失败。
- 客户端执行由身份验证机制指定的任何处理。如果身份验证成功,则此时的处理取决于错误代码、方法和用法,默认规则如下:
- 如果错误码是300到399,客户端应该认为事务失败,除非使用ALTERNATE-SERVER扩展。参见第11节。
- 如果错误码是400到499,客户端认为事务失败,在420(未知属性)的情况下,响应应该包含一个提供额外信息的Unknown - attributes属性。
- 如果错误码是500到599,客户端可以重新发送请求,但是客户端必须限制这样做的次数。
- 任何其他错误代码都会导致客户端认为事务失败。
基本的、独立的 STUN Server
一个基本的 STUN 服务器通过接收和响应 STUN Binding requests
向客户端提供 reflexive transport addresses
。
在静默抑制的情况下,可能会有客户端接收不到媒体的时期。在这种情况下,NAT绑定可能会超时(NAT中的UDP绑定通常很短; 30秒很常见)。为了处理这个问题,应用程序可以定期重传 Binding Request,以保持绑定的新鲜度。
Binding Method
- 在 Binding request/response transaction 中,Binding request 从 STUN client 发送到 STUN server。
- 当 Binding request 到达 STUN server 时,它可能已经通过了 STUN client 和 STUN server 之间的一个或多个 NAT。
- 当 Binding request 报文经过 NAT 时,NAT会修改数据包的源传输地址(即源IP地址和源端口)。因此,服务器接收到的请求的源传输地址将是距离服务器最近的 NAT 创建的公共 IP 地址和端口。这称为自反传输地址(
reflexive transport addresses
)。 - STUN server 将该 自反传输地址(
reflexive transport addresses
)复制到 STUN Binding response 中的 XOR-MAPPED-ADDRESS 属性中,并将 STUN Binding response 发送回 STUN 客户端。 - 当此数据包通过 NAT 传回时,NAT 将修改 IP 标头中的目标传输地址,但 STUN 响应正文中 XOR-MAPPED-ADDRESS 属性中的
reflexive transport addresses
将保持不变。这样,client 就可以获知其最外层 NAT 分配给 STUN server 的reflexive transport addresses
为什么说是 NAT 给 STUN server 分配地址呢,因为同一个 client 可以像多个不同的 server 发送各种各样请求,NAT 根据 server 来给 client 分配公网地址,因此将地址称为 NAT 分配给 server 的
reflexive transport addresses
。在NAT 给 server 分配reflexive transport addresses
后,server 才能往这个地址发送数据并正确发往 client 。
实现原理
- 使用TCP协议发送 Shared Secret Request / Response 。
- 使用UDP协议发送 Binding Request / Response 。
- 使用UDP协议发送 Binding Indication 进行保活
Server Behavior (Binding)
Binding Requests
一个 STUN server 必须做好从四组 (address, port) - (A1, P1), (A2, P1), (A1, P2) 和 (A2, P2) 接收 Binding Request 的准备。(A1, P1) 是主地址和端口,通常 P1 是 3478(默认的 STUN 端口)。A2 和 P2 可以是任意地址和端口。A2 和 P2 是由 server 通过 CHANGED-ADDRESS 属性向 client 发布的。
推荐 server 检查 Binding Request 的 MESSAGE-INTEGRITY 属(完整性校验)。
假设,完整性校验通过,Binding Request 格式正确;那么,server 必须生成一个 Binding Response,Binding Response 必须包含相同的 transaction ID,Message header 必须包含 message 中除 header 以外的字节数,Message Type 为 "Binding Response" 。
- server 必须将 MAPPED-ADDRESS 属性添加到 Binding Response 中,MAPPED-ADDRESS 属性中的 IP 地址设置为从 Binding Request 观察到的源 IP 地址,Port 设置为从 Binding Request 观察到的源端口。
如果 Binding Request 中缺少 RESPONSE-ADDRESS 属性,那么 Binding Response 的目的地址和端口号必须与 Binding Request 的源地址和端口号相同。否则,Binding Response 的目的地址和端口号必须为 Binding Request 中 RESPONSE-ADDRESS 属性的 IP 地址和端口号。
Binding Response 的源地址和端口号取决于 Binding Request 的 CHANGE-REQUEST 属性,CHANGE-REQUEST 属性指出 server 应答 Binding Response 时是否应该改变 server 的 IP 地址和端口。
- server 必须添加一个 SOURCE-ADDRESS 属性到 Binding Response,包含用于发送 Binding Response 的 source address 和 port 。
- server 必须添加一个 CHANGED-ADDRESS 属性到 Binding Response,包含将来 client 发送设置了 change IP 和 change port flags 的 Binding Request 时,server 应答使用的 source address 和 port (通常称之为 Ca 和 Cb)。
- Da and Dp :(destination IP address and port of the Binding Request)
- SOURCE ADDRESS:Binding Response 的 SOURCE-ADDRESS 属性中的 address
- SOURCE PORT:Binding Response 的 SOURCE-ADDRESS 属性中的 port
- CHANGED-ADDRESS:Binding Response 的 CHANGED-ADDRESS 属性
-
如果 Binding Request 包含 USERNAME 和 MESSAGE-INTEGRITY 属性,那么 server 必须在 Binding Response 中添加 MESSAGE-INTEGRITY 属性。
-
如果 Binding Request 包含 RESPONSE-ADDRESS 属性(指示 Response 发往何处),那么 server 必须在 Binding Response 中添加 REFLECTED-FROM 属性(指示哪个地址发的 Binding Request 搞过来的 Binding Response)。
- 如果使用从 Shared Secret Request 获得的 username 对 Binding Request 进行身份验证,则 REFLECTED-FROM 属性必须包含 Shared Secret Request 来自的源IP地址和端口。
- 如果 Binding Request 中的 username 不是使用 Shared Secret Request 分配的,那么 REFLECTED-FROM 属性必须包含获取 username 的实体的源地址和端口,最好使用用于分配用户名的机制进行验证。
- 如果 username 没有出现在 Binding Request 中,并且服务器愿意处理请求,那么 REFLECTED-FROM 属性应该包含该 Binding Request 的源IP地址和端口。
注意:server 不应该重传 response 。可靠性是通过让 client 定期重新发送 request 来实现的,每个请求都可触发 server 的 response 。
如果 server 收到 Shared Secret Request,它必须验证请求是否采用 TLS 连接。如果它没有通过 TLS 请求,则 server 必须生成一个 Shared Secret Error Response,Response 必须包含一个带有 433 响应码的 Error - code 属性。
假设 Shared Secret Request 为正确的 Request ,server 将创建一个 Shared Secret Response。
- Shared Secret Response 必须包含 Shared Secret Request 中包含的相同事务ID。Message Header 中的长度必须包含除 Message Header 以外的 Message 总长度(以字节为单位)。
- Shared Secret Response 必须包含 “Shared Secret Response” Message Type 。
- Shared Secret Response 必须包含 USERNAME 属性和 PASSWORD 属性。USERNAME 属性作为 PASSWORD 的索引(server 使用 username 查找 password),PASSWORD 包含在 PASSWORD 属性中(client 直接使用 password)。
USERNAME = <prefix,rounded-time,clientIP,hmac>
password = <hmac(USERNAME,anotherprivatekey)>
Client Bahavior (Binding)
Obtaining a Shared Secret
Shared Secret 的作用:避免 STUN 被攻击。
Formulating the Binding Request
- RESPONSE-ADDRESS 可选属性
- CHANGE-REQUEST 可选属性
- MESSAGE-INTEGRITY and USERNAME 属性应该加入 Binding Request
client 应该通过重传确保可靠性: 0ms, 100ms, 300ms, 700ms, 1500ms, 3100ms,
4700ms, 6300ms, 7900ms 退避重试。在 9500ms 时,如果没有收到响应,客户端就认为事务失败了。
Response 可以是 Binding Response 或 Binding Error Response。 Binding Error Response 总是在发送请求的源地址和端口上接收。Binding Response 将在 Request 的 RESPONSE-ADDRESS 属性中的地址和端口上接收。如果 Request 没有 RESPONSE-ADDRESS 属性,Binding Response 将在发送请求的源地址和端口上接收。
如果 Response 是 Binding Response,client 应该检查 Response 的MESSAGE-INTEGRITY 属性。如果属性不存在,并且 client 在请求中放置了MESSAGE-INTEGRITY属性,那么 client 必须丢弃 Response 。
此外,如果 client 接收到的 Binding Response 数量是它发送的 Binding Request 数量的两倍以上,client 绝对不能使用这些 response 中的任何一个的映射地址,并且应该提醒用户潜在的攻击。
如果 Binding Response 通过了校验,并且映射地址没有因为潜在的攻击而被丢弃,client 可以使用 MAPPED-ADDRESS and SOURCE-ADDRESS 属性。、
Keepalives (Binding Indication)
Indication 绝对不能使用任何身份验证机制。它可以包含 FINGERPRINT 属性以帮助进行多路复用,但不应该包含任何其他属性。
STUN 用法
STUN 的三种用法:
- Interactive Connectivity Establishment (ICE) [MMUSIC-ICE]
- Client-initiated connections for SIP [SIP-OUTBOUND]
- NAT Behavior Discovery [BEHAVE-NAT]
STUN 用法定义了 STUN 的实际使用方式——何时发送请求,如何处理响应,以及将使用此处定义的(或在 STUN 的扩展中)哪些可选过程。用法还将定义:
- 使用了哪些 STUN 方法。
- 使用什么身份验证和消息完整性机制。
- 关于完整性机制的手动与自动密钥派生的考虑,如 [RFC4107] 中所讨论的。
- 使用什么机制将 STUN 消息与其他消息区分开来。当 STUN 在 TCP 上运行时,可能需要一个成帧机制。
- STUN 客户端如何确定 STUN 服务器的 IP 地址和端口。
- 是否需要向后兼容 RFC 3489。
- 需要在此处(例如 FINGERPRINT 和 ALTERNATE-SERVER)或其他扩展中定义的可选属性。
在这些用法中,必须有一种方法来检查数据包并确定它是否是 STUN 数据包。 STUN 在 STUN 标头(STUN Message Header)中提供了三个具有固定值的字段,可用于此目的。如果这还不够,那么 STUN 数据包还可以包含一个 FINGERPRINT 值,该值可以进一步用于区分数据包。
STUN 定义了一组可供用户决定使用的可选过程,称为机制。这些机制包括:
- DNS 发现
- 到备用服务器的重定向技术
- 用于解复用的指纹属性
- 两个身份验证和消息完整性交换
身份验证机制围绕用户名、密码和消息完整性值的使用展开。本规范定义了两种认证机制,长期凭证机制和短期凭证机制。每种用法都指定了该用法所允许的机制。
- 在长期凭证机制中,客户端和服务器共享一个预先配置的用户名和密码,并执行一个摘要质询/响应交换,其灵感来自(但在细节上有所不同)为 HTTP [RFC2617] 定义的一个。
- 在短期凭证机制中,客户端和服务器在 STUN 交换之前通过某种带外方法交换用户名和密码。例如,在 ICE 使用 [MMUSIC-ICE] 中,两个端点使用带外信令来交换用户名和密码。这些用于完整性保护和验证请求和响应。没有使用挑战或随机数。
TURN
TURN - Traversal Using Relays around NAT :Relay Extensions to Session Traversal Utilities for NAT (使用中继穿透 NAT :STUN的中继扩展)。
在 NAT 的介绍中的几种 NAT 类型中,显然如果点对点通信的双方出现对称型 NAT 时,双方是无法实现通信的。在这种情况下,通信双方需要使用中间网点提供的中继服务实现通信。
client 使用 TURN 消息在 TURN 服务器上创建一个 ALLOCATION。一旦 ALLOCATION 创建完成,client 在 TURN 消息中指示与哪些对端通信,并包含应用数据,server 将应用数据 提取出来,并 以 UDP 数据包方式发送给对端;反向上,对端以 UDP 数据包方式发送应用数据到 server,server 转发到 client;因为 TURN 消息中总是指示对端通信的地址,因此 client 可以使用单一的 ALLOCATION 来与多个对端通讯。
这不就是一个典型的 SFU 服务器吗?
TURN 消息 即 STUN Message,新增 STUN methods 和 STUN Attributes 。
术语
- TURN client :STUN client
- TURN server :STUN server
- Peer :TURN client 希望连接的主机。TURN server 为 TURN client 和 TURN client 的对端中转流量,但 Peer 并不与 TURN server 使用 TURN 协议进行交互,它接收从 TURN server 发送过来的数据(application data over udp),并向 TURN server 发送数据(application data over udp)。
- Transport Address :IP地址与端口号的组合。
- Host Transport Address :客户端或对端的传输地址。
- Server-Reflexive Transport Address :NAT 公网侧的传输地址,该地址由 NAT 分配,相当于一个特定的主机传输地址。
- Relayed Transport Address :TURN服务器用于中继的地址,用于客户端和对端中继数据。
- TURN Server Transport Address :TURN 服务器用于接收 STUN 消息的地址,用于客户端发送 STUN 消息给服务器。
- Peer Transport Address :TURN 服务器看到的对端的传输地址,当对端是在 NAT 后面,则是对端的服务器反射传输地址(Server-Reflexive Transport Address)。
-
Allocation :通过
Allocate request
申请 Allocation:中继传输地址(Relayed Transport Address)、Permission 和超时定时器等。一旦分配了中继传输地址,客户端必须保持分配是有效的。为此,客户端定期向服务器发送一个Refresh request
。TURN 故意使用不同的方法 ( Refresh 而不是 Allocate ) 进行刷新,以确保在分配由于某种原因消失时通知客户端。 - 5-tuple :五元组,<client_ip, client_port, server_ip, server_port, protocol>
-
Channel :通道为客户机和服务器提供了一种使用 ChannelData 消息发送应用程序数据的方法,这比
send
和data
指示的开销更小。(channel binding 包括:一个 channel number,一个 transport address of the peer,一个到期时间计时器) - Permission :一个 Allocation 可以有零个或多个 Permissions 。每个 Permission 由一个IP地址(对端地址)和一个生命周期组成。当 TURN server 收到分配的中继传输地址的UDP数据报(来自 Peer)时,它首先检查权限列表。如果数据报的源IP地址匹配 Permission ,则将应用数据转发给 TURN client,否则将丢弃UDP数据报。
- Realm:告诉客户端哪些用户名和密码的组合可用于认证请求。
- Nonce:服务器随机选择的一个字符串,包含在报文摘要中。为了防止中继攻击,服务器应该有规律的改变这个nonce。
New STUN Methods
-
0x003
:Allocate
,TURN 客户端在 TURN 服务器上申请 Allocation 的方法(仅定义了 request/response 语义) -
0x004
:Refresh
,TURN 客户端在 TURN 服务器上刷新 Allocation 的方法(保活)。 (仅定义了 request/response 语义) -
0x006
:Send
,TURN client 向 TURN server 发送数据(仅定义了 indication 语义) -
0x007
:Data
,TURN server 向 TURN client 转发 Peer 数据(仅定义了 indication 语义) -
0x008
:CreatePermission
,TURN 客户端在 TURN 服务器上安装或刷新 Permission 的方法。(仅定义了 request/response 语义) -
0x009
:ChannelBind
,TURN 客户端在 TURN 服务器上创建或刷新 Channel 的方法。ChannelBind 还创建或刷新对 peer 的 Permission 。(仅定义了 request/response 语义)
New STUN Attributes
-
0x000C
:CHANNEL-NUMBER
包含 channel 的 number 。RFFU (Reserved For
Future Use) 。
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
0x000D
:LIFETIME
表示 server 在没有收到 refresh request 的情况下维护 allocation 的持续时间。此属性的值部分为 4 字节长,由一个 32 位无符号整型值组成,该值表示知道过期的剩余秒数。 -
0x0010
:Reserved (was BANDWIDTH)
-
0x0012
:XOR-PEER-ADDRESS
指定从 TURN server 看到的 peer 的 address 和 port 。同XOR-MAPPED-ADDRESS
一样编码。 -
0x0013
:DATA
出现在所有send
和data
的指示中。该属性的值部分是可变长度的,由 application data 组成,如果此属性的长度不是4的倍数,则必须在此属性之后添加填充。 -
0x0016
:XOR-RELAYED-ADDRESS
出现在Allocate Response
中,指定 TURN server 为 TURN client 分配的 address 和 port,同XOR-MAPPED-ADDRESS
一样编码。 -
0x0018
:EVEN-PORT
属性允许 TURN client 请求 relayed transport address 中的端口号为偶数,并且(可选)请求 server 保留 next-higher port number 。该属性的值部分为 1 字节:第 1 位表示是否请求 server 保留 next-higher port number ,RFFU 必须设为 0 。
0
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|R| RFFU |
+-+-+-+-+-+-+-+-+
-
0x0019
:REQUESTED-TRANSPORT
,用于为所分配的传输地址请求特定的传输协议。Protocol
字段指定协议 [ Protocol-Numbers ]。RFFU 必须设为 0 。
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
0x001A
:DONT-FRAGMENT
,当向 peer 转发 application data 时,client 使用此属性请求 server 在 IP 头中设置 DF 位(不要分配),该属性没有值部分,因此属性长度字段为 0 。 -
0x0021
:Reserved (was TIMER-VAL)
-
0x0022
:RESERVATION-TOKEN
包含一个令牌,表示 server 保留的中继传输地址。服务器将此属性包含在成功响应中,以告知客户端有关令牌的信息,而客户端将此属性包含在后续的 Allocate request 中,以请求服务器使用该中继传输地址进行分配。属性值为8字节,包含令牌值。
其中一些属性的长度不是4的倍数。根据STUN的规则,任何长度不是4字节倍数的属性必须紧跟1到3个填充字节,以确保下一个属性(如果有的话)将从4字节边界开始(参见[RFC5389])。
New STUN Error Response Codes
- 403 :禁止,该请求有效,但由于管理或类似的限制而无法执行。
- 437 :allocation 不匹配,server 接收到一个请求,该请求需要 allocation,但不存在allocation; 或者接收到一个请求,该请求不需要 allocation,但存在 allocation 。
- 441 :错误的凭证,非 allocation 请求中的凭据与用于创建 allocation 的凭据不匹配。
- 442 :不支持的传输协议,allocation 请求要求服务器在服务器和对等体之间使用服务器不支持的传输协议。注意:这不是指5元组中使用的传输协议。
- 486 :已达 allocation 额度,目前不能使用此 username 创建更多的 allocation 。
- 508 :容量不足,由于达到某些容量限制,服务器无法执行请求。在 allocation 响应中,这可能是由于服务器当时没有更多可用的中继传输地址,没有具有请求属性的中继传输地址,或者与指定的
RESERVATION-TOKEN
相对应的中继传输地址不可用。
Detailed Example
Allocate
TURN TURN Peer Peer
client server A B
| | | |
|--- Allocate request -------------->| | |
| Transaction-Id=0xA56250D3F17ABE679422DE85 | |
| SOFTWARE="Example client, version 1.03" | |
| LIFETIME=3600 (1 hour) | | |
| REQUESTED-TRANSPORT=17 (UDP) | | |
| DONT-FRAGMENT | | |
| | | |
|<-- Allocate error response --------| | |
| Transaction-Id=0xA56250D3F17ABE679422DE85 | |
| SOFTWARE="Example server, version 1.17" | |
| ERROR-CODE=401 (Unauthorized) | | |
| REALM="example.com" | | |
| NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" | |
| | | |
|--- Allocate request -------------->| | |
| Transaction-Id=0xC271E932AD7446A32C234492 | |
| SOFTWARE="Example client 1.03" | | |
| LIFETIME=3600 (1 hour) | | |
| REQUESTED-TRANSPORT=17 (UDP) | | |
| DONT-FRAGMENT | | |
| USERNAME="George" | | |
| REALM="example.com" | | |
| NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" | |
| MESSAGE-INTEGRITY=... | | |
| | | |
|<-- Allocate success response ------| | |
| Transaction-Id=0xC271E932AD7446A32C234492 | |
| SOFTWARE="Example server, version 1.17" | |
| LIFETIME=1200 (20 minutes) | | |
| XOR-RELAYED-ADDRESS=192.0.2.15:50000 | |
| XOR-MAPPED-ADDRESS=192.0.2.1:7000 | |
| MESSAGE-INTEGRITY=... | | |
CreatePermission
TURN TURN Peer Peer
client server A B
|--- CreatePermission request ------>| | |
| Transaction-Id=0xE5913A8F460956CA277D3319 | |
| XOR-PEER-ADDRESS=192.0.2.150:0 | | |
| USERNAME="George" | | |
| REALM="example.com" | | |
| NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" | |
| MESSAGE-INTEGRITY=... | | |
| | | |
|<-- CreatePermission success resp.--| | |
| Transaction-Id=0xE5913A8F460956CA277D3319 | |
| MESSAGE-INTEGRITY=... | | |
-
Send
和Data
TURN TURN Peer Peer
client server A B
|--- Send indication --------------->| | |
| Transaction-Id=0x1278E9ACA2711637EF7D3328 | |
| XOR-PEER-ADDRESS=192.0.2.150:32102 | |
| DONT-FRAGMENT | | |
| DATA=... | | |
| |-- UDP dgm ->| |
| | data=... | |
| | | |
| |<- UDP dgm --| |
| | data=... | |
|<-- Data indication ----------------| | |
| Transaction-Id=0x8231AE8F9242DA9FF287FEFF | |
| XOR-PEER-ADDRESS=192.0.2.150:32102 | |
| DATA=... | | |
ChannelBind
TURN TURN Peer Peer
client server A B
|--- ChannelBind request ----------->| | |
| Transaction-Id=0x6490D3BC175AFF3D84513212 | |
| CHANNEL-NUMBER=0x4000 | | |
| XOR-PEER-ADDRESS=192.0.2.210:49191 | |
| USERNAME="George" | | |
| REALM="example.com" | | |
| NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" | |
| MESSAGE-INTEGRITY=... | | |
| | | |
|<-- ChannelBind success response ---| | |
| Transaction-Id=0x6490D3BC175AFF3D84513212 | |
| MESSAGE-INTEGRITY=... | | |
ChannelData
TURN TURN Peer Peer
client server A B
|--- ChannelData ------------------->| | |
| Channel-number=0x4000 |--- UDP datagram --------->|
| Data=... | Data=... |
| | | |
| |<-- UDP datagram ----------|
| | Data=... | |
|<-- ChannelData --------------------| | |
| Channel-number=0x4000 | | |
| Data=... | | |
Refresh
TURN TURN Peer Peer
client server A B
|--- Refresh request --------------->| | |
| Transaction-Id=0x0864B3C27ADE9354B4312414 | |
| SOFTWARE="Example client 1.03" | | |
| USERNAME="George" | | |
| REALM="example.com" | | |
| NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm" | |
| MESSAGE-INTEGRITY=... | | |
| | | |
|<-- Refresh error response ---------| | |
| Transaction-Id=0x0864B3C27ADE9354B4312414 | |
| SOFTWARE="Example server, version 1.17" | |
| ERROR-CODE=438 (Stale Nonce) | | |
| REALM="example.com" | | |
| NONCE="npSw1Xw239bBwGYhjNWgz2yH47sxB2j" | |
| | | |
|--- Refresh request --------------->| | |
| Transaction-Id=0x427BD3E625A85FC731DC4191 | |
| SOFTWARE="Example client 1.03" | | |
| USERNAME="George" | | |
| REALM="example.com" | | |
| NONCE="npSw1Xw239bBwGYhjNWgz2yH47sxB2j" | |
| MESSAGE-INTEGRITY=... | | |
| | | |
|<-- Refresh success response -------| | |
| Transaction-Id=0x427BD3E625A85FC731DC4191 | |
| SOFTWARE="Example server, version 1.17" | |
| LIFETIME=600 (10 minutes) | | |
ICE
ICE ( Interactive Connectivity Establishment ) : A Protocol for Network Address Translator (NAT) Traversal for Offer/Answer Protocols - 交互式连接建立 : 一种用于 offer/answer 协议的网络地址转换器(NAT)穿越协议。
实际上,ICE 是一个基于 STUN、TURN、SDP (offer/answer) 等实现 NAT 穿透的统一解决方案。ICE 建立在多种NAT穿透协议的基础之上,提供一个统一的框架,因此 ICE 具备了所有这些技术的优点,同时还避免了任何单个协议可能存在的缺陷。
ICE 为实现端到端通信提供了一个完整的 NAT 穿透解决方案。ICE 的主要流程:
- 收集候选地址
- 连通性检查
- 候选地址排序和匹配
- 冻结候选地址
- 角色选择
- 候选地址对状态机
术语
-
base:server reflexive candidate 的 base 是派生它的 host candidate;host candidate 的 base 是 host candidate 本身;relayed candidate 的 base 也是 relayed candidate 本身。
-
Local Candidate:agent 包含在其 发送 的 offer 或 answer 中的 candidate。
-
Remote Candidate:agent 接收 的 offer 或 answer 中的 candidate。
-
Candidate Pair:包含 Local Candidate 和 Remote Candidate 的配对。
-
Selected Pair, Selected Candidate:ICE选择的用于发送和接收媒体的 Candidate Pair 称为 Selected Pair,Selected Pair, 的 candidate 称为 Selected Candidate。
-
Foundation:任意字符串,对于具有相同类型((host, relayed, server reflexive, peer reflexive)、基本IP地址、协议(UDP、TCP等)和 STUN 或 TURN server 的两个候选对象,该字符串是相同的。如果其中任何一个不一样,那么 foundation 也会不一样。具有 same foundation pairs 的 two candidate pairs 很可能具有相似的网络特征。frozen 算法中使用 Foundations 。
收集候选地址
ICE 规范定义和收集了四种类型候选地址—— host candidates, server reflexive candidates, peer reflexive candidates, 和 relayed candidates 。
Host Candidates
通过 bind 连接到主机上某个接口 (物理或虚拟,包括VPN接口) 的IP地址上的端口 (通常是临时的) 来获得 Host Candidates 。
Server Reflexive and Relayed Candidates
- 如果 agent 同时收集 Server Reflexive and Relayed Candidates,则使用 TURN 服务器。
- 如果 agent 只收集 Server Reflexive Candidates,则使用STUN服务器。
- 如果 agent 不连接到公网或封闭网络外的端点,则不需要获取 Server Reflexive and Relayed Candidates 。
Peer reflexive candidates
Peer reflexive candidates 是在 ICE 的后期得到的,这是连接检查的结果。
计算 candidate 的 foundation
foundation 是一个标识符,作用域在 session 内。以下所有条件都为真的两个 candidate 必须具有相同的foundation ID :
- 相同的类型 (host, relayed, server reflexive, or peer reflexive)
- 相同的 IP 地址(端口可以不同)
- 对于 reflexive 和 relayed candidates ,获取它们的STUN或TURN服务器的IP地址相同。
- 使用相同的传输协议(TCP、UDP 等等)
否则就要用不同的 foundation ID。
candidate keepalive(保活)
通过 STUN server 或 TURN server 获取的 server reflexive and relayed candidates 必须保持存活直到 ICE 处理完成,否则 NAT 或 TURN server 有可能将 candidate 地址回收。
- server reflexive candidates 可以通过 Binding Request 或 Indication 保活。
- reflexive candidate 可以通过 Refresh Request 保活。
候选优先级
priority = (2^24)*(type preference) + (2^8)*(local preference) + (2^0)*(256 - component ID)
- type preference 是 0 到 126 (含0和126) 之间的整数,0 为最低优先级,126 为最大优先级。
- local preference 是 0 到 65535 (含0和65535) 之间的整数。0 为最低优先级,65535 为最高优先级。
- component ID 是 0 到 256 (含0和256) 之间的整数,用于标识作为候选媒体流的特定组件。对于基于RTP的媒体流,RTP的候选地址的 component ID 必须为1, RTCP 的候选地址的 component ID 必须为2。其他类型的媒体流必须开发定义 候选 到 component ID 的映射的规范。
删除多余候选
Agent 删除多余的候选者。如果一个候选者的传输地址等于另一个候选者,并且它的foundation 等于那个候选者的 foundation,那么这个候选者就是多余的。注意,两个候选地址可以具有相同的传输地址,但具有不同的基数,这不会被认为是多余的。
通常情况下,当 agent 不在NAT后时(此时 agent 具有的便是公网地址), server reflexive candidate 和 host candidate 将是冗余的。代理程序应该删除优先级较低的冗余候选地址。
选择默认候选
agent 必须选择一组候选对象,每个正在使用的媒体流的每个 component 都应该有一个默认候选对象。如果媒体流没有将端口设置为 0 (在RFC 3264中用于拒绝媒体流),则表明该媒体流正在使用中。
default candidates 是 relayed candidates (如果 relayed candidates 可用)、其次是 server reflexive candidates (如果 server reflexive candidates 对象可用),最后是 host candidates。
生成 offer
v=0
o=jdoe 2890844526 2890842807 IN IP4 10.0.1.1
s=
c=IN IP4 192.0.2.3
t=0 0
a=ice-pwd:asd88fgpdd777uzjYhagZg
a=ice-ufrag:8hhY
m=audio 45664 RTP/AVP 0
b=RS:0
b=RR:0
a=rtpmap:0 PCMU/8000
a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host
a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr
"candidate" Attribute
candidate-attribute = "candidate" ":" foundation SP component-id SP
transport SP
priority SP
connection-address SP
port
SP cand-type
[SP rel-addr]
[SP rel-port]
*(SP extension-att-name SP
extension-att-value)
foundation = 1*32ice-char
component-id = 1*5DIGIT
transport = "UDP" / transport-extension
transport-extension = token
priority = 1*10DIGIT
cand-type = "typ" SP candidate-types
candidate-types = "host" / "srflx" / "prflx" / "relay" / token
rel-addr = "raddr" SP connection-address
rel-port = "rport" SP port
extension-att-name = byte-string
extension-att-value = byte-string
ice-char = ALPHA / DIGIT / "+" / "/"</pre>
SP :specific
接收 offer
验证 ICE support
如果对于接收到的SDP中的每个媒体流,该媒体流的每个组件的默认目的地出现在候选属性中(例如,在RTP协议中,c=
行中的IP地址 和 m=
行中的端口同时出现在一个a=candidate
属性中),则代理将继续执行本规范中定义的ICE过程。
- 如果 agent 不处理ICE是因为 SDP 有a=candidate,但没有匹配媒体流的默认目的地,则 agent 必须在其 answer 中包含 a=ice-mismatch 属性。
决定角色
对于ICE流程中的每个会话,每个终端都扮演一个角色。ICE定义了两个角色:控制和被控制。
- 被控制终端负责最后一对候选配对的选择,用于通信。这意味着提名候选地址对,用于每个媒体流。
- 控制终端被告知哪些候选对用于每个媒体流。
决定角色的规则如下:
- 双端都是全实现:一端在发起请求的时候是控制者,另外一端则是被控制者,双端都要跑ICE的状态机,做可连接性检测。
- 一端是全实现, 另外一端是轻实现:全实现的发起请求作为控制者跑ICE的状态机,做可连接性检测;轻实现者作为被控制者。
- 双端都是轻实现:一端在发起请求的时候是控制者,另外一端则是被控制者。
Full Implementation - 全实现,Lite Implementation - 轻实现
解决角色冲突
在连通性检查的阶段:
- 发送的 bind request 要求携带 role 相关的 STUN 属性,ICE-CONTROLLED 或者 ICE-CONTROLLING,这两个属性都会携带一个 Tie breaker (取值 0 - 2的64次方-1)字段,包含一个本机产生的随机值。
- 收到 bind request 的一方会检查这两个字段,如果和当前本机的 role 冲突,则检查本机的 Tie breaker 值和消息中携带的 tie breaker 值进行判断本机合适的 role 。
- Tie breaker 值大的一方为 controlling,如果判定本端变更角色,就会直接修改角色;如果判定对端变更角色,则对此 bind request 发送 487 错误响应,收到此错误响应的一端改变角色即可。
新增 STUN 属性:
-
0x8029
:ICE-CONTROLLED -
0x802A
:ICE-CONTROLLING
新增 STUN 错误:
- 487,Role Conflict: 终端指定的角色和 server 指定的角色冲突。
收集候选地址
同上文
选择默认候选地址
同上文
生成 answer
注意:answer 中的 candidate 是本端的 candidate address 。规则同 offer 中的 candidate 。
形成 Check List
只有 Full-Implementations 才执行 形成 CheckList 。Lite-Implementations 跳过本步骤。
每个正在使用的媒体流都有一个 CheckList ,由 offer/answer 交换产生。形成媒体流的 CheckLists 流程:
- 形成候选对
- 计算候选对优先级
- 按优先级排序
- 裁剪候选对
- 设置状态。
形成候选对
首先,agent 将它的每个 candidates 用于媒体流 (称为LOCAL CANDIDATES),并将它们与它从 peer 接收到的该媒体流的 candidates (称为REMOTE CANDIDATES) 配对。
当且仅当 local candidate 与 remote candidate 具有相同的 component ID 和相同的IP地址版本 (IPv4/IPv6) 时,local candidate 才与 remote candidate 配对。
在 RTP 的情况下,当一个代理为RTCP提供候选对象,但可能另一个代理没有提供。这是因为 offerer 虽然能在同一个端口复用 RTP/RTCP,但是 offerer 不直到 answerer 否能复用,因此 offerer 包含两个 candidate 分别用于 RTP 和 RTCP,因此 offer sdp 的每一个 media stream 将有 2 个 components。如果 answerer 能够复用 RTP/RTCP,则 answer 可以只携带单个 component for each candidate 用于表示 RTP/RTCP 复用。
+------------------------------------------+
| |
| +---------------------+ |
| |+----+ +----+ +----+ | +Type |
| || IP | |Port| |Tran| | +Priority |
| ||Addr| | | | | | +Foundation |
| |+----+ +----+ +----+ | +ComponentiD |
| | Transport | +RelatedAddr |
| | Addr | |
| +---------------------+ +Base |
| Candidate |
+------------------------------------------+
* *
* *************************************
* *
+-------------------------------+
.| |
| Local Remote |
| +----+ +----+ +default? |
| |Cand| |Cand| +valid? |
| +----+ +----+ +nominated?|
| +State |
| |
| |
| Candidate Pair |
+-------------------------------+
* *
* ************
* *
+------------------+
| Candidate Pair |
+------------------+
+------------------+
| Candidate Pair |
+------------------+
+------------------+
| Candidate Pair |
+------------------+
候选对优先级计算和排序
pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
G
为 controlling agent 提供的 candidate 优先级。
D
为 controlled agent 提供的 candidate 优先级。
一旦完成优先级计算,代理将按照优先级的递减顺序对 candidate pair 进行排序。如果两个 pair 具有相同的优先级,它们之间的顺序是任意的。
裁剪候选对
裁剪1:candiate pair 的排序列表用于确定将要执行的连通性检查顺序。每次连通性检查都涉及从本local candidate 向 remote candidate 发送请求。由于 agent 不能直接从 reflexive candidate 发送请求,而只能从它的 base 发送请求,因此代理接下来将遍历已排序的候选对象对列表进行额外的处理:对于每个 local candidate 是 server reflexive 的 pair,使用 base 替换掉 server reflexive candidate。
裁剪2:如果一个 pair 的 local candidate 和 remote candidate 与优先级列表中更高的 pair 的 local candidate 和 remote candidate 相同,则删除该pair。
最终得到一个有序的候选对序列,称为该媒体流的 check list。
设置状态
check list 中的每一个 candidate pair 都有一个 foundation 和一个 state,foundation 是 local candidate 和 remote candidate 的 foundation 的组合。一旦计算出每个媒体流的 check list,就给 candidate pair 设置 state。
共有五种 state :
-
Frozen
: 该 pair 的检查还没有被执行,直到其他检查成功,它才会被允许解冻并进入 Waiting 状态。 -
Waiting
: 该 pair 的检查还没有被执行,只有检查列表中优先级最高的且在 Waiting 状态的 pair 才可以执行检查,进入 In-Progress 状态。 -
In-Progress
: 已为该 pair 发起 check,事务正在进行中。 -
Succeeded
: 该 pair 的检查已经完成并且成功。 -
Failed
: 该 pair 的检查已经完成并且失败(要么无响应,要么收到一个不可恢复的失败响应)。
+-----------+
| |
| |
| Frozen |
| |
| |
+-----------+
|
|unfreeze
|
V
+-----------+ +-----------+
| | | |
| | perform | |
| Waiting |-------->|In-Progress|
| | | |
| | | |
+-----------+ +-----------+
/ |
/ |
/ |
/ |
/ |
/ |
failure / |success
/ |
/ |
/ |
/ |
/ |
V V
+-----------+ +-----------+
| | | |
| | | |
| Failed | | Succeeded |
| | | |
| | | |
+-----------+ +-----------+
agent 通过执行以下步骤计算检查列表中每个对的初始状态:
- agent 将所有的 pair 的状态设置为 Frozen。
- agent 检查第一个媒体流中的列表:对于所有具有相同 Foudation 的 pair,把最小 componentID 的 pair 设置为 Waiting,如果 Foudation 相同且最小 componentID 的 pair 的数量超过一个,那么将具有最高优先级的 pair 设置为 Waiting。
连通性检查见下文。
连通性检查
连通性检查只有 full-implementation 才会执行,连通性检查又分为平常检查和触发检查,两者都是由定时器驱动。
agent 持有一个先进先出队列,称为触发队列,队列中存放着即将触发检查的 candidate pair 。当定时器触发后,agent 从触发队列中拿出最上面的 candidate pair 执行连通性检查,并将该 candidate pair 状态置为 In-Progress 。
一旦 agent 形成 Check List 就给 active (m=
行的端口非0)的 check list 设置定时器,有 N 个 active media stream check list 就设置 N 个定时器,时间间隔为 Ta*N 秒。
一旦定时器触发,如果触发队列为空,则执行平常检查,如下:
- 找 check list 中处于 Waiting 状态的优先级最高的 pair 。
- 如果找到 Waiting 状态的 pair,则从该 pair 的 local candidate 向该 pair 的 remote candidate 发送一个 STUN 检查,并设置该 pair 的状态为 In-Progress 。
- 如果未找到 Waiting 状态的 pair,则找 check list 中处于 Frozen 状态的优先级最高的 pair。
- 如果找到 Frozen 状态的 pair ,则 Unfreeze 该 pair,对该 pair 进行 check,并设置状态为 In-Progress 。
- 如果未找到 Frozen 状态的 pair,终止该 check list 的定时器。
为了计算用于检查的消息完整性,agent 使用从 remote 的 SDP 获取的 ice-ufrag 和 ice-pwd 用于 stun 信息的安全有效校验。agent 知道 local 的 ice-ufrag 和 ice-pwd 。
接收 answer
验证 ICE Support
同上
决定角色
同上
形成 Check List
同上
连通性检查
同上,注意 role - controlling or controlled
SDP 新增属性
a=candidate
candidate-attribute = "candidate" ":" foundation SP component-id SP
transport SP
priority SP
connection-address SP
port
SP cand-type
[SP rel-addr]
[SP rel-port]
*(SP extension-att-name SP
extension-att-value)
a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host
a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr
a=ice-ufrag
ice-ufrag-att = "ice-ufrag" ":" ufrag
a=ice-ufrag:8hhY
提供用于在STUN连接检查中构造用户名的片段。
8~256 字节。
a=ice-pwd
ice-pwd-att = "ice-pwd" ":" password
a=ice-pwd:asd88fgpdd777uzjYhagZg
提供用于保护STUN连通性检查的密码。
22~256 字节。
offer:
v=0
o=jdoe 2890844526 2890842807 IN IP4 10.0.1.1
s=
c=IN IP4 192.0.2.3
t=0 0
a=ice-pwd:asd88fgpdd777uzjYhagZg
a=ice-ufrag:8hhY
m=audio 45664 RTP/AVP 0
b=RS:0
b=RR:0
a=rtpmap:0 PCMU/8000
a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host
a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr
answer:
v=0
o=bob 2808844564 2808844564 IN IP4 192.0.2.1
s=
c=IN IP4 192.0.2.1
t=0 0
a=ice-pwd:YH75Fviy6338Vbrhrlp8Yh
a=ice-ufrag:9uB6
m=audio 3478 RTP/AVP 0
b=RS:0
b=RR:0
a=rtpmap:0 PCMU/8000
a=candidate:1 1 UDP 2130706431 192.0.2.1 3478 typ host
更多属性 rfc5245-section-15:
remote-candidate-att = "remote-candidates" ":" remote-candidate
0*(SP remote-candidate)
remote-candidate = component-ID SP connection-address SP port
ice-lite = "ice-lite"
ice-mismatch = "ice-mismatch"
ice-options = "ice-options" ":" ice-option-tag
0*(SP ice-option-tag)
ice-option-tag = 1*ice-char
Example Flow
L NAT STUN R
|RTP STUN alloc. | |
|(1) STUN Req | | |
|S=$L-PRIV-1 | | |
|D=$STUN-PUB-1 | | |
|------------->| | |
| |(2) STUN Req | |
| |S=$NAT-PUB-1 | |
| |D=$STUN-PUB-1 | |
| |------------->| |
| |(3) STUN Res | |
| |S=$STUN-PUB-1 | |
| |D=$NAT-PUB-1 | |
| |MA=$NAT-PUB-1 | |
| |<-------------| |
|(4) STUN Res | | |
|S=$STUN-PUB-1 | | |
|D=$L-PRIV-1 | | |
|MA=$NAT-PUB-1 | | |
|<-------------| | |
|(5) Offer | | |
|------------------------------------------->|
| | | |RTP STUN
alloc.
| | |(6) STUN Req |
| | |S=$R-PUB-1 |
| | |D=$STUN-PUB-1 |
| | |<-------------|
| | |(7) STUN Res |
| | |S=$STUN-PUB-1 |
| | |D=$R-PUB-1 |
| | |MA=$R-PUB-1 |
| | |------------->|
|(8) answer | | |
|<-------------------------------------------|
| |(9) Bind Req | |Begin
| |S=$R-PUB-1 | |Connectivity
| |D=L-PRIV-1 | |Checks
| |<----------------------------|
| |Dropped | |
|(10) Bind Req | | |
|S=$L-PRIV-1 | | |
|D=$R-PUB-1 | | |
|USE-CAND | | |
|------------->| | |
| |(11) Bind Req | |
| |S=$NAT-PUB-1 | |
| |D=$R-PUB-1 | |
| |USE-CAND | |
| |---------------------------->|
| |(12) Bind Res | |
| |S=$R-PUB-1 | |
| |D=$NAT-PUB-1 | |
| |MA=$NAT-PUB-1 | |
| |<----------------------------|
|(13) Bind Res | | |
|S=$R-PUB-1 | | |
|D=$L-PRIV-1 | | |
|MA=$NAT-PUB-1 | | |
|<-------------| | |
|RTP flows | | |
| |(14) Bind Req | |
| |S=$R-PUB-1 | |
| |D=$NAT-PUB-1 | |
| |<----------------------------|
|(15) Bind Req | | |
|S=$R-PUB-1 | | |
|D=$L-PRIV-1 | | |
|<-------------| | |
|(16) Bind Res | | |
|S=$L-PRIV-1 | | |
|D=$R-PUB-1 | | |
|MA=$R-PUB-1 | | |
|------------->| | |
| |(17) Bind Res | |
| |S=$NAT-PUB-1 | |
| |D=$R-PUB-1 | |
| |MA=$R-PUB-1 | |
| |---------------------------->|
| | | |RTP flows
-
L
: agent L -
R
: agent R -
PUB
: public transport address -
PRIV
: private transport address -
S=
: source transport address of the message -
D=
: destination transport address of the message -
MA=
: mapped address
ICE Restart
重新启动 ICE,agent 必须更改 offer 中媒体流的 ice-pwd 和 ice-ufrag。
一个agent 必须重启ICE,当列情况出现的时候:
- 如果 agent 想生成一个更新的offer,而新的媒体流的 ICE 又没有使用,将会为该媒体流重启 ICE
ICE Restart 和新的会话不同的点就是在重启的过程中,媒体依然可以从前面有效的地址对发送。
网友评论