16. Detailed Example (详细示例)
本节提供了一个 TURN 使用的示例,详细展示了交换的消息内容。该示例使用概述(图 1)中显示的网络图。
对于每条消息,显示了消息中包含的属性及其值。为方便起见,值以人类可读的格式显示,而不是显示实际的字节,例如,"XOR-RELAYED-ADDRESS=192.0.2.15:9000"表示包含 XOR-RELAYED-ADDRESS 属性,地址为 192.0.2.15,端口为 9000,这里的地址和端口是在执行 XOR 运算之前显示的。对于具有类字符串值的属性(例如,SOFTWARE="Example client, version 1.03"和 NONCE="adl7W7PeDU4hKE72jdaQvbAMcr6h39sm"),属性的值用引号显示以提高可读性,但这些引号不会出现在实际值中。
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=... | | |
客户端首先选择用于 TURN 会话的主机传输地址,在本示例中,客户端选择了 10.1.1.2:49721,如图 1 所示。然后,客户端在服务器传输地址向服务器发送 Allocate 请求。客户端随机选择了一个 96 位事务 ID 0xA56250D3F17ABE679422DE85 用于此事务,这被编码在固定头的事务 ID 字段中。客户端包含 SOFTWARE 属性,提供有关客户端软件的信息,这里的值是"Example client, version 1.03",表示这是名为 Example 客户端的版本 1.03。客户端包含 LIFETIME 属性,因为它希望分配的生存期长于默认的 10 分钟,此属性的值为 3600 秒,对应 1 小时。客户端必须始终在 Allocate 请求中包含 REQUESTED-TRANSPORT 属性,本规范允许的唯一值是 17,表示服务器和对等方之间的 UDP 传输。客户端还包含 DONT-FRAGMENT 属性,因为它希望稍后在 Send 指示中使用 DONT-FRAGMENT 属性,此属性仅包含属性头,没有值部分。我们假设客户端最近没有与服务器交互,因此客户端不包含 USERNAME、REALM、NONCE 或 MESSAGE-INTEGRITY 属性。最后,请注意,消息中属性的顺序是任意的(MESSAGE-INTEGRITY 和 FINGERPRINT 属性除外),客户端可以使用不同的顺序。
服务器要求对任何请求进行身份验证。因此,当服务器接收到初始 Allocate 请求时,它拒绝该请求,因为请求不包含身份验证属性。遵循 STUN [RFC5389] 长期凭证机制的过程,服务器包含值为 401(未授权,Unauthorized)的 ERROR-CODE 属性、指定服务器使用的身份验证域的 REALM 属性(在本例中为服务器的域"example.com")以及 NONCE 属性中的随机数值。服务器还包含 SOFTWARE 属性,提供有关服务器软件的信息。
客户端收到 401 错误后,重新尝试 Allocate 请求,这次包含身份验证属性。客户端选择一个新的事务 ID,然后使用与之前相同的属性填充新的 Allocate 请求。客户端包含 USERNAME 属性,并使用从服务器接收到的域值来帮助确定要使用的值,这里客户端配置为对域"example.com"使用用户名"George"。客户端还包含 REALM 和 NONCE 属性,这些属性只是从 401 错误响应中复制的。最后,客户端包含 MESSAGE-INTEGRITY 属性作为消息中的最后一个属性,其值是对消息内容的哈希消息认证码 - 安全哈希算法 1 (HMAC-SHA1) 哈希(上面仅显示为"..."),此 HMAC-SHA1 计算包含密码值。因此,攻击者无法在不知道秘密密码的情况下计算消息完整性值。
服务器收到经过身份验证的 Allocate 请求后,检查一切正常,然后创建分配。服务器回复 Allocate 成功响应。服务器包含 LIFETIME 属性,给出分配的生存期,这里,服务器将客户端请求的 1 小时生存期减少到仅 20 分钟,因为此特定服务器不允许超过 20 分钟的生存期。服务器包含 XOR-RELAYED-ADDRESS 属性,其值为分配的中继传输地址。服务器包含 XOR-MAPPED-ADDRESS 属性,其值为客户端的服务器反射地址,此值在 TURN 中不以其他方式使用,但作为对客户端的便利而返回。服务器包含 MESSAGE-INTEGRITY 属性来验证响应并确保其完整性,请注意,响应不包含 USERNAME、REALM 和 NONCE 属性。服务器还包含 SOFTWARE 属性。
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=... | | |
然后,客户端创建对对等方 A 的权限,准备向其发送一些应用程序数据。这通过 CreatePermission 请求完成。XOR-PEER-ADDRESS 属性包含建立权限的 IP 地址(对等方 A 的 IP 地址),请注意,在 CreatePermission 请求中使用时,属性中的端口号被忽略,这里已将其设置为 0,还要注意客户端如何使用对等方 A 的服务器反射 IP 地址而不是其(私有)主机地址。客户端在消息中重用其在分配上的最后一个请求中的用户名、域和随机数值。虽然允许这样做,但客户端选择不在此请求中包含 SOFTWARE 属性。
服务器接收 CreatePermission 请求,创建相应的权限,然后回复 CreatePermission 成功响应。与客户端一样,服务器选择不在其回复中包含 SOFTWARE 属性。再次注意成功响应如何包含 MESSAGE-INTEGRITY 属性(假设服务器使用长期凭证机制),但没有 USERNAME、REALM 和 NONCE 属性。
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=... | | |
客户端现在使用 Send 指示向对等方 A 发送应用程序数据。XOR-PEER-ADDRESS 属性中指定了对等方 A 的服务器反射传输地址,DATA 属性中指定了应用程序数据(这里仅显示为"...")。客户端正在应用层进行一种路径 MTU 发现,因此指定(通过包含 DONT-FRAGMENT 属性)服务器应在发送到对等方的 UDP 数据报中设置 DF 位。无法使用 STUN 的长期凭证机制对指示进行身份验证,因此消息中不包含 MESSAGE-INTEGRITY 属性。希望确保其数据不被更改或伪造的应用程序必须在应用层对其数据进行完整性保护。
服务器收到 Send 指示后,提取应用程序数据并在 UDP 数据报中将其发送到对等方 A,中继传输地址作为数据报的源传输地址,并按请求设置 DF 位。请注意,如果客户端之前没有为对等方 A 的服务器反射 IP 地址建立权限,则服务器将静默丢弃 Send 指示。
然后,对等方 A 使用包含应用程序数据的自己的 UDP 数据报进行回复。数据报发送到服务器上的中继传输地址。当这到达时,服务器创建一个 Data 指示,在 XOR-PEER-ADDRESS 属性中包含 UDP 数据报的源,在 DATA 属性中包含 UDP 数据报的数据。然后将生成的 Data 指示发送到客户端。
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=... | | |
客户端现在将通道绑定到对等方 B,在 CHANNEL-NUMBER 属性中指定空闲通道号(0x4000),在 XOR-PEER-ADDRESS 属性中指定对等方 B 的传输地址。和之前一样,客户端在消息中重用其最后一个请求中的用户名、域和随机数。
服务器收到请求后,将通道号绑定到对等方,为对等方 B 的 IP 地址安装权限,然后回复 ChannelBind 成功响应。
TURN TURN Peer Peer
client server A B
|--- ChannelData ------------------->| | |
| Channel-number=0x4000 |--- UDP datagram --------->|
| Data=... | Data=... |
| | | |
| |<-- UDP datagram ----------|
| | Data=... | |
|<-- ChannelData --------------------| | |
| Channel-number=0x4000 | | |
| Data=... | | |
客户端现在向服务器发送 ChannelData 消息,其中包含发往对等方 B 的数据。ChannelData 消息不是 STUN 消息,因此没有事务 ID。相反,它只有三个字段:通道号、数据和数据长度,这里的通道号字段是 0x4000(客户端刚刚绑定到对等方 B 的通道)。当服务器接收到 ChannelData 消息时,它检查通道当前是否已绑定(确实已绑定),然后在 UDP 数据报中将数据转发到对等方 B,使用中继传输地址作为源传输地址,使用 192.0.2.210:49191(ChannelBind 请求中 XOR-PEER-ADDRESS 属性的值)作为目标传输地址。
稍后,对等方 B 向中继传输地址发送一个 UDP 数据报。这导致服务器向客户端发送包含 UDP 数据报数据的 ChannelData 消息。服务器知道要向哪个客户端发送 ChannelData 消息,因为 UDP 数据报到达的中继传输地址,并且知道使用通道 0x4000,因为这是绑定到 192.0.2.210:49191 的通道。请注意,如果没有通道号绑定到该地址,服务器将使用 Data 指示。
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) | | |
在 20 分钟生存期到期之前的某个时候,客户端刷新分配。这通过 Refresh 请求完成。和之前一样,客户端在请求中包含最新的用户名、域和随机数值。客户端还包含 SOFTWARE 属性,遵循在 Allocate 和 Refresh 消息中始终包含此属性的推荐做法。当服务器接收到 Refresh 请求时,它注意到随机数值已过期,因此回复 438(陈旧随机数,Stale Nonce)错误,提供新的随机数值。然后,客户端重新尝试请求,这次使用新的随机数值。第二次尝试被接受,服务器回复成功响应。请注意,客户端在请求中没有包含 LIFETIME 属性,因此服务器为默认生存期 10 分钟刷新分配(如成功响应中的 LIFETIME 属性所示)。