跳到主要内容

6. 创建分配

server 上的 allocation 使用 Allocate transaction 创建.

6.1. 发送 Allocate Request

client 按如下方式构造 Allocate request.

client 首先选择一个 host transport address. 推荐的做法是 client 选择一个未使用的 transport address, 通常通过允许底层 OS 为新 socket 选择一个未使用端口来实现.

随后 client 选择一种在 client 与 server 之间使用的 transport protocol. transport protocol 必须是 UDP, TCP, 或 TLS-over-TCP 之一. 由于本规范只允许 server 与 peer 之间使用 UDP, 因此推荐 client 选择 UDP, 除非它有理由使用其他 transport. 选择其他 transport 的一个理由是, client 认为 (可能通过配置或实验得知) 它无法使用 UDP 联系任何 TURN server. 更多讨论见第 2.1 节.

client 还要选择一个 server transport address, 这应该按如下方式完成. client 接收 (可能通过配置) 一个 TURN server 的域名. 然后 client 使用 [RFC5389] 中描述的 DNS 过程, 但使用 "turn" 作为 SRV service name (对于 TLS 上的 TURN 使用 "turns"), 而不是 "stun" (或 "stuns"). 例如, 若要查找 example.com 域中的 server, 如果 client 希望分别使用 UDP, TCP, 或 TLS-over-TCP 与 server 通信, 则执行对 '_turn._udp.example.com', '_turn._tcp.example.com', 和 '_turns._tcp.example.com' 的查找.

client 必须在请求中包含 REQUESTED-TRANSPORT 属性. 此属性指定 server 与 peer 之间的 transport protocol (注意, 这不是 5-tuple 中出现的 transport protocol). 在本规范中, REQUESTED-TRANSPORT 类型始终为 UDP. 包含此属性是为了允许未来扩展指定其他协议.

如果 client 希望 server 将 allocation 的 time-to-expiry 字段初始化为默认 lifetime 之外的某个值, 则可以包含一个 LIFETIME 属性来指定其期望值. 这只是一个请求, server 可以选择使用不同的值. 注意, server 会忽略将该字段初始化为小于默认值的请求.

如果 client 希望之后在此 allocation 上的一个或多个 Send indication 中使用 DONT-FRAGMENT 属性, 则 client 应该在 Allocate request 中包含 DONT-FRAGMENT 属性. 这允许 client 测试 server 是否支持此属性.

如果 client 要求 relayed transport address 的端口号为偶数, 则 client 包含 EVEN-PORT 属性. 如果未包含此属性, 则端口可以为偶数或奇数. 通过将 EVEN-PORT 属性中的 R bit 设置为 1, client 可以请求 server 为后续 allocation 保留下一个更高端口号 (在同一 IP 地址上). 如果 R bit 为 0, 则不提出此类请求.

client 可以还在请求中包含 RESERVATION-TOKEN 属性, 以请求 server 为该 allocation 使用先前保留的端口. 如果包含 RESERVATION-TOKEN 属性, 则 client 必须省略 EVEN-PORT 属性.

构造完成后, client 在 5-tuple 上发送 Allocate request.

6.2. 接收 Allocate Request

当 server 收到 Allocate request 时, 它执行以下检查:

  1. server 必须要求请求经过认证. 除非 client 和 server 通过本文档范围之外的某种过程约定使用另一机制, 否则该认证必须使用 [RFC5389] 的 long-term credential mechanism 完成.

  2. server 检查该 5-tuple 当前是否正被现有 allocation 使用. 如果是, server 以 437 (Allocation Mismatch) 错误拒绝该请求.

  3. server 检查请求是否包含 REQUESTED-TRANSPORT 属性. 如果未包含 REQUESTED-TRANSPORT 属性或该属性格式错误, server 以 400 (Bad Request) 错误拒绝请求. 否则, 如果包含该属性但指定了 UDP 以外的协议, server 以 442 (Unsupported Transport Protocol) 错误拒绝请求.

  4. 请求可能包含 DONT-FRAGMENT 属性. 如果包含该属性, 但 server 不支持发送 DF bit 设置为 1 的 UDP datagram (见第 12 章), 则 server 将 Allocate request 中的 DONT-FRAGMENT 属性视为未知的 comprehension-required 属性.

  5. server 检查请求是否包含 RESERVATION-TOKEN 属性. 如果包含, 且请求还包含 EVEN-PORT 属性, 则 server 以 400 (Bad Request) 错误拒绝请求. 否则, 它检查 token 是否有效 (即 token 在范围内, 尚未过期, 且对应的 relayed transport address 仍可用). 如果 token 因某种原因无效, server 以 508 (Insufficient Capacity) 错误拒绝请求.

  6. server 检查请求是否包含 EVEN-PORT 属性. 如果包含, 则 server 检查它是否能够满足该请求 (即能否按下文所述分配 relayed transport address). 如果 server 无法满足该请求, 则以 508 (Insufficient Capacity) 错误拒绝请求.

  7. 在任何时刻, 如果 server 认为 client 正试图超过某个本地定义的 allocation quota, 可以选择以 486 (Allocation Quota Reached) 错误拒绝请求. server 可以按任意方式定义此 allocation quota, 但应该基于用于认证请求的 username 来定义, 而不是基于 client 的 transport address.

  8. 同样在任何时刻, 如果 server 希望将 client 重定向到另一 server, 可以选择以 300 (Try Alternate) 错误拒绝请求. 此错误码和属性的使用遵循 [RFC5389] 中的规范.

如果所有检查均通过, server 创建 allocation. 5-tuple 设置为 Allocate request 中的 5-tuple, permission 列表和 channel 列表初始为空.

server 按如下方式为 allocation 选择 relayed transport address:

  • 如果请求包含 RESERVATION-TOKEN, server 使用与所包含 token 对应的先前保留 transport address (如果它仍可用). 注意, 该保留是 server 范围的保留, 并非特定于某个 allocation, 因为包含 RESERVATION-TOKEN 的 Allocate request 使用的 5-tuple 与创建该保留的 Allocate request 不同. 包含 RESERVATION-TOKEN 属性的 Allocate request 的 5-tuple 可以是任何允许的 5-tuple; 它可以使用不同的 client IP 地址和端口, 不同的 transport protocol, 甚至不同的 server IP 地址和端口 (前提是该 server IP 地址和端口是 server 正在监听 TURN request 的地址和端口).

  • 如果请求包含 R bit 设置为 0 的 EVEN-PORT 属性, 则 server 分配一个端口号为偶数的 relayed transport address.

  • 如果请求包含 R bit 设置为 1 的 EVEN-PORT 属性, 则 server 在同一 IP 地址上查找一对端口号 N 和 N+1, 其中 N 为偶数. 端口 N 用于当前 allocation, 而端口 N+1 的 relayed transport address 被分配一个 token 并为未来 allocation 保留. server 必须至少保持此保留 30 秒, 并可以选择保持更长时间 (例如直到使用端口 N 的 allocation 过期). 随后 server 在成功响应的 RESERVATION-TOKEN 属性中包含该 token.

  • 否则, server 分配任意可用的 relayed transport address.

在所有情况下, server 应该只从 49152 - 65535 范围 (Dynamic and/or Private Port range [Port-Numbers]) 分配端口, 除非 TURN server 应用通过此处未规定的某种方式知道, 在与 TURN server 应用运行于同一主机上的其他应用不会受到在此范围外分配端口的影响. 通过在专用机器上运行 TURN server 应用, 和/或安排该机器上的任何其他应用在 TURN server 应用启动前分配端口, 通常可以满足此条件. 无论如何, TURN server 不应该在 0 - 1023 范围 (Well-Known Port range) 内分配端口, 以避免鼓励 client 使用 TURN 运行标准服务.

NOTE: IETF 当前正在研究 randomized port assignments (随机化端口分配) 主题, 以避免某些类型的攻击 (见 [TSVWG-PORT]). 强烈鼓励 TURN 实现者持续关注此主题, 并在适当时实现 randomized port assignment algorithm. 这尤其适用于选择从底层 OS 预分配若干端口,之后再将它们分配给 allocation 的 server; 例如, server 可以选择此技术来实现 EVEN-PORT 属性.

server 按如下方式确定 time-to-expiry 字段的初始值. 如果请求包含 LIFETIME 属性, 则 server 计算 client 提议的 lifetime 与 server 允许的最大 lifetime 的较小值. 如果该计算值大于默认 lifetime, 则 server 使用该计算 lifetime 作为 time-to-expiry 字段的初始值. 否则, server 使用默认 lifetime. 推荐的做法是 server 使用不超过 3600 秒 (1 小时) 的最大允许 lifetime 值. 实现 allocation quota 或以某种方式向用户收取 allocation 费用的 server, 可能希望使用更小的最大允许 lifetime (也许小到默认 lifetime), 以便更快移除 orphaned allocation (即对应 client 崩溃, 终止, 或 client connection 因某种原因丢失的 allocation). 还要注意, time-to-expiry 会在每次成功的 Refresh request 中重新计算, 因此此处计算的值只适用到第一次刷新为止.

allocation 创建后, server 以成功响应回复. 成功响应包含:

  • 一个 XOR-RELAYED-ADDRESS 属性, 其中包含 relayed transport address.

  • 一个 LIFETIME 属性, 其中包含 time-to-expiry 计时器的当前值.

  • 一个 RESERVATION-TOKEN 属性 (如果保留了第二个 relayed transport address).

  • 一个 XOR-MAPPED-ADDRESS 属性, 其中包含 client 的 IP 地址和端口 (来自 5-tuple).

NOTE: 响应中包含 XOR-MAPPED-ADDRESS 属性是为了方便 client. TURN 本身不使用此值, 但运行 ICE 的 client 通常需要此值, 因而可以避免为了获取该值而额外与某个 STUN server 执行 Binding transaction.

响应 (成功或错误) 在 5-tuple 上发回给 client.

NOTE: 当 Allocate request 通过 UDP 发送时, [RFC5389] 第 7.3.1 节要求 server 处理请求的可能重传, 使重传不会导致创建多个 allocation. 实现可以按如下方式使用所谓 "stateless stack approach" 达成此目标. 为在原始请求成功创建 allocation 时检测重传, server 可以将创建请求中使用的 transaction id 与 allocation 数据一起存储, 并将其与同一 5-tuple 上传入的 Allocate request 比较. 一旦检测到此类请求, server 可以停止解析该请求并立即生成成功响应. 构建此响应时, LIFETIME 属性的值可以取自 allocation 状态数据中的 time-to-expiry 字段, 即使该值可能与最初返回的 LIFETIME 值略有不同. 此外, server 可能需要存储原始响应中返回的任何 reservation token 的指示, 以便在任何重传响应中返回它.

对于原始请求未能成功创建 allocation 的情况, server 可以选择不做任何特殊处理. 但是请注意, 有一种罕见情况是 server 拒绝原始请求, 却接受重传请求 (因为短暂间隔期间条件发生了变化). 如果 client 收到第一个 (拒绝) 响应, 它会忽略第二个 (成功) 响应, 并认为未创建 allocation. 以这种方式创建的 allocation 最终会超时, 因为 client 不会刷新它. 此外, 如果 client 稍后使用同一个 5-tuple 但不同 transaction id 重试, 它会收到 437 (Allocation Mismatch), 这会使它改用不同的 5-tuple 重试. server 可以通过使用较小的最大 lifetime 值来最小化以这种方式 "orphaned" 的 allocation 的 lifetime.

6.3. 接收 Allocate Success Response

如果 client 收到 Allocate success response, 则它必须检查 mapped address 和 relayed transport address 是否都属于 client 理解并准备处理的 address family. 本规范只覆盖两个地址均为 IPv4 地址的情况. 如果任一地址不属于 client 准备处理的 address family, 则 client 必须删除该 allocation (第 7 章), 并且不得尝试在该 server 上创建另一个 allocation, 直到它认为该不匹配已被修复.

IETF 当前正在考虑 IPv4 与 IPv6 之间的过渡机制, 这些机制可能导致 client 通过 IPv6 发起 Allocate request, 但该请求通过 IPv4 到达 server, 或反之.

否则, client 创建自己的 allocation 数据结构副本, 用于跟踪 server 上正在发生的情况. 特别是, client 需要记住从 server 返回的实际 lifetime, 而不是请求中发送给 server 的值. client 还必须记住请求使用的 5-tuple, 以及它用于认证该请求的 username 和 password, 以确保后续消息复用它们. client 还需要跟踪它在 server 上建立的 channel 和 permission.

client 可能希望将 relayed transport address 发送给 peer (使用此处未规定的某种方法), 以便 peer 能与其通信. client 也可能希望在其 ICE 处理中使用它在 XOR-MAPPED-ADDRESS 属性中收到的 server-reflexive address.

6.4. 接收 Allocate Error Response

如果 client 收到 Allocate error response, 则处理取决于返回的实际错误码:

  • (Request Timed Out): server 存在问题, 或者使用所选 transport 到达 server 时存在问题. client 认为当前 transaction 已失败, 但可以选择使用不同 transport (例如 TCP 而不是 UDP) 重试 Allocate request.

  • 300 (Try Alternate): server 希望 client 改用 ALTERNATE-SERVER 属性中指定的 server. client 认为当前 transaction 已失败, 但应该在尝试任何其他 server (例如使用 SRV 过程发现的其他 server) 之前, 先向 alternate server 尝试 Allocate request. 使用 alternate server 尝试 Allocate request 时, client 遵循 [RFC5389] 中指定的 ALTERNATE-SERVER 过程.

  • 400 (Bad Request): server 认为 client 的请求由于某种原因格式错误. client 认为当前 transaction 已失败. client 可以通知用户或操作员, 并且不应该在认为问题已被修复前使用此 server 重试请求.

  • 401 (Unauthorized): 如果 client 已遵循 long-term credential mechanism 的过程但仍收到此错误, 则 server 不接受 client 的凭据. 在这种情况下, client 认为当前 transaction 已失败, 并应该通知用户或操作员. client 不应该再向此 server 发送任何进一步请求, 直到它认为问题已被修复.

  • 403 (Forbidden): 请求有效, 但 server 拒绝执行它, 很可能是由于管理限制. client 认为当前 transaction 已失败. client 可以通知用户或操作员, 并且不应该在认为问题已被修复前使用此 server 重试同一请求.

  • 420 (Unknown Attribute): 如果 client 在请求中包含 DONT-FRAGMENT 属性, 且 server 以 420 错误码拒绝请求, 并在错误响应的 UNKNOWN-ATTRIBUTES 属性中列出 DONT-FRAGMENT 属性, 则 client 现在知道 server 不支持 DONT-FRAGMENT 属性. client 认为当前 transaction 已失败, 但可以选择不带 DONT-FRAGMENT 属性重试 Allocate request.

  • 437 (Allocation Mismatch): 这表示 client 选择了一个 server 视为已在使用的 5-tuple. 发生这种情况的一种方式是, 中间 NAT 分配了一个 mapped transport address, 而该地址最近曾由另一个已崩溃的 client 使用. client 认为当前 transaction 已失败. client 应该选择另一个 client transport address, 并重试 Allocate request (使用不同的 transaction id). 在放弃此 server 之前, client 应该尝试三个不同的 client transport address. 一旦 client 放弃此 server, 它应该在 2 分钟内不再尝试在该 server 上创建另一个 allocation.

  • 438 (Stale Nonce): 见 long-term credential mechanism [RFC5389] 的过程.

  • 441 (Wrong Credentials): client 不应在对 Allocate request 的响应中收到此错误. client 可以通知用户或操作员, 并且不应该在认为问题已被修复前使用此 server 重试同一请求.

  • 442 (Unsupported Transport Address): client 不应在对 UDP allocation 请求的响应中收到此错误. client 可以通知用户或操作员, 并且不应该在认为问题已被修复前使用此 server 再次尝试该请求.

  • 486 (Allocation Quota Reached): server 当前无法再使用此 username 创建更多 allocation. client 认为当前 transaction 已失败. client 应该至少等待 1 分钟, 然后再尝试在该 server 上创建更多 allocation.

  • 508 (Insufficient Capacity): server 没有更多可用的 relayed transport address, 或没有具备所请求属性的地址, 或与指定 reservation token 对应的地址不可用. client 认为当前操作已失败. 如果 client 正在使用 EVEN-PORT 或 RESERVATION-TOKEN 属性, 则 client 可以选择移除或修改该属性并立即重试. 否则, client 应该至少等待 1 分钟, 然后再尝试在此 server 上创建更多 allocation.

未知错误响应必须按 [RFC5389] 中所述处理.