6. Creating an Allocation (创建分配)
服务器上的分配使用 Allocate 事务创建。
6.1. Sending an Allocate Request (发送分配请求)
客户端按如下方式构造 Allocate 请求。
客户端首先选择一个主机传输地址。推荐 (RECOMMENDED) 客户端选择当前未使用的传输地址,通常通过允许底层操作系统为新套接字选择当前未使用的端口来实现。
然后,客户端选择在客户端和服务器之间使用的传输协议。传输协议必须 (MUST) 是 UDP、TCP 或 TLS-over-TCP 之一。由于本规范仅允许在服务器和对等方之间使用 UDP,推荐 (RECOMMENDED) 客户端选择 UDP,除非它有使用不同传输的理由。选择不同传输的一个理由是,客户端认为(通过配置或实验)它无法使用 UDP 联系任何 TURN 服务器。有关更多讨论,请参见第 2.1 节。
客户端还选择服务器传输地址,应该 (SHOULD) 按如下方式完成。客户端接收(可能通过配置)TURN 服务器的域名。然后,客户端使用 [RFC5389] 中描述的 DNS 过程,但使用 SRV 服务名称"turn"(或用于 TURN over TLS 的"turns")而不是"stun"(或"stuns")。例如,要在 example.com 域中查找服务器,如果客户端希望分别使用 UDP、TCP 或 TLS-over-TCP 与服务器通信,则客户端执行对 '_turn._udp.example.com'、'_turn._tcp.example.com' 和 '_turns._tcp.example.com' 的查找。
客户端必须 (MUST) 在请求中包含 REQUESTED-TRANSPORT 属性。此属性指定服务器和对等方之间的传输协议(请注意,这不是出现在 5 元组中的传输协议)。在本规范中,REQUESTED-TRANSPORT 类型始终为 UDP。包含此属性是为了允许未来的扩展指定其他协议。
如果客户端希望服务器将分配的过期时间字段初始化为默认生存期以外的某个值,则它可以 (MAY) 包含 LIFETIME 属性,指定其所需的值。这只是一个请求,服务器可以选择使用不同的值。请注意,服务器将忽略将字段初始化为小于默认值的请求。
如果客户端希望稍后在此分配上的一个或多个 Send 指示中使用 DONT-FRAGMENT 属性,则客户端应该 (SHOULD) 在 Allocate 请求中包含 DONT-FRAGMENT 属性。这允许客户端测试服务器是否支持此属性。
如果客户端要求中继传输地址的端口号为偶数,则客户端包含 EVEN-PORT 属性。如果不包含此属性,则端口可以是偶数或奇数。通过将 EVEN-PORT 属性中的 R 位设置为 1,客户端可以请求服务器为后续分配保留下一个最高端口号(在同一 IP 地址上)。如果 R 位为 0,则不会发出此类请求。
客户端也可以 (MAY) 在请求中包含 RESERVATION-TOKEN 属性,以请求服务器对分配使用先前保留的端口。如果包含 RESERVATION-TOKEN 属性,则客户端必须 (MUST) 省略 EVEN-PORT 属性。
构造完成后,客户端在 5 元组上发送 Allocate 请求。
6.2. Receiving an Allocate Request (接收分配请求)
当服务器接收到 Allocate 请求时,它执行以下检查:
-
服务器必须 (MUST) 要求对请求进行身份验证。此身份验证必须 (MUST) 使用 [RFC5389] 的长期凭证机制完成,除非客户端和服务器通过本文档范围之外的某些过程同意使用另一种机制。
-
服务器检查 5 元组当前是否被现有分配使用。如果是,服务器拒绝请求并返回 437(分配不匹配,Allocation Mismatch)错误。
-
服务器检查请求是否包含 REQUESTED-TRANSPORT 属性。如果未包含 REQUESTED-TRANSPORT 属性或格式错误,服务器拒绝请求并返回 400(错误请求,Bad Request)错误。否则,如果包含该属性但指定的协议不是 UDP,则服务器拒绝请求并返回 442(不支持的传输协议,Unsupported Transport Protocol)错误。
-
请求可能包含 DONT-FRAGMENT 属性。如果包含,但服务器不支持发送 DF 位设置为 1 的 UDP 数据报(参见第 12 节),则服务器将 Allocate 请求中的 DONT-FRAGMENT 属性视为未知的必须理解的属性 (Unknown Comprehension-Required Attribute)。
-
服务器检查请求是否包含 RESERVATION-TOKEN 属性。如果是,并且请求还包含 EVEN-PORT 属性,则服务器拒绝请求并返回 400(错误请求,Bad Request)错误。否则,它检查令牌是否有效(即,令牌在范围内且未过期,并且相应的中继传输地址仍然可用)。如果令牌由于某种原因无效,服务器拒绝请求并返回 508(容量不足,Insufficient Capacity)错误。
-
服务器检查请求是否包含 EVEN-PORT 属性。如果是,则服务器检查它是否可以满足请求(即,可以按如下所述分配中继传输地址)。如果服务器无法满足请求,则服务器拒绝请求并返回 508(容量不足,Insufficient Capacity)错误。
-
在任何时候,如果服务器认为客户端试图超过某些本地定义的分配配额,服务器可以 (MAY) 选择拒绝请求并返回 486(达到分配配额,Allocation Quota Reached)错误。服务器可以自由地以任何方式定义此分配配额,但应该 (SHOULD) 基于用于验证请求的用户名而不是客户端的传输地址来定义它。
-
同样在任何时候,如果服务器希望将客户端重定向到不同的服务器,服务器可以 (MAY) 选择拒绝请求并返回 300(尝试备用,Try Alternate)错误。此错误代码和属性的使用遵循 [RFC5389] 中的规范。
如果所有检查都通过,服务器将创建分配。5 元组设置为 Allocate 请求中的 5 元组,而权限列表和通道列表最初为空。
服务器按如下方式为分配选择中继传输地址:
-
如果请求包含 RESERVATION-TOKEN,则服务器使用与包含的令牌相对应的先前保留的传输地址(如果它仍然可用)。请注意,保留是服务器范围的保留,并不特定于特定的分配,因为包含 RESERVATION-TOKEN 的 Allocate 请求使用与进行保留的 Allocate 请求不同的 5 元组。包含 RESERVATION-TOKEN 属性的 Allocate 请求的 5 元组可以是任何允许的 5 元组,它可以使用不同的客户端 IP 地址和端口、不同的传输协议,甚至不同的服务器 IP 地址和端口(当然,前提是服务器 IP 地址和端口是服务器正在侦听 TURN 请求的地址和端口)。
-
如果请求包含 R 位设置为 0 的 EVEN-PORT 属性,则服务器分配具有偶数端口号的中继传输地址。
-
如果请求包含 R 位设置为 1 的 EVEN-PORT 属性,则服务器在同一 IP 地址上查找一对端口号 N 和 N+1,其中 N 为偶数。端口 N 用于当前分配,而具有端口 N+1 的中继传输地址被分配一个令牌并保留用于将来的分配。服务器必须 (MUST) 至少保持此保留 30 秒,并且可以 (MAY) 选择保持更长时间(例如,直到具有端口 N 的分配过期)。然后,服务器在成功响应中的 RESERVATION-TOKEN 属性中包含令牌。
-
否则,服务器分配任何可用的中继传输地址。
在所有情况下,服务器应该 (SHOULD) 仅从范围 49152 - 65535(动态和/或私有端口范围 [Port-Numbers])分配端口,除非 TURN 服务器应用程序通过此处未指定的某些方式知道,在与 TURN 服务器应用程序相同的主机上运行的其他应用程序不会因在此范围之外分配端口而受到影响。这种情况通常可以通过在专用机器上运行 TURN 服务器应用程序和/或通过安排机器上的任何其他应用程序在 TURN 服务器应用程序启动之前分配端口来满足。在任何情况下,TURN 服务器都不应该 (SHOULD NOT) 从范围 0 - 1023(众所周知的端口范围,Well-Known Port Range)分配端口,以阻止客户端使用 TURN 运行标准服务。
注意:IETF 目前正在调查随机端口分配的主题,以避免某些类型的攻击(参见 [TSVWG-PORT])。强烈建议 TURN 实现者关注此主题,并在适当时实现随机端口分配算法。这尤其适用于选择从底层操作系统预分配多个端口然后稍后将它们分配给分配的服务器,例如,服务器可能选择此技术来实现 EVEN-PORT 属性。
服务器按如下方式确定过期时间字段的初始值。如果请求包含 LIFETIME 属性,则服务器计算客户端提议的生存期和服务器的最大允许生存期的最小值。如果此计算值大于默认生存期,则服务器使用计算的生存期作为过期时间字段的初始值。否则,服务器使用默认生存期。推荐 (RECOMMENDED) 服务器使用不超过 3600 秒(1 小时)的最大允许生存期值。实现分配配额或以某种方式向用户收取分配费用的服务器可能希望使用较小的最大允许生存期(可能与默认生存期一样小),以更快地删除孤立的分配(即,相应客户端已崩溃或终止或客户端连接由于某种原因丢失的分配)。还要注意,过期时间在每次成功的 Refresh 请求时重新计算,因此此处计算的值仅适用于第一次刷新之前。
创建分配后,服务器回复成功响应。成功响应包含:
-
包含中继传输地址的 XOR-RELAYED-ADDRESS 属性。
-
包含过期时间计时器的当前值的 LIFETIME 属性。
-
RESERVATION-TOKEN 属性(如果保留了第二个中继传输地址)。
-
包含客户端的 IP 地址和端口(来自 5 元组)的 XOR-MAPPED-ADDRESS 属性。
注意:XOR-MAPPED-ADDRESS 属性包含在响应中,以方便客户端。TURN 本身不使用此值,但运行 ICE 的客户端通常需要此值,因此可以避免必须与某些 STUN 服务器进行额外的 Binding 事务来获取它。
响应(成功或错误)在 5 元组上发送回客户端。
注意:当 Allocate 请求通过 UDP 发送时,[RFC5389] 的第 7.3.1 节要求服务器处理请求的可能重传,以便重传不会导致创建多个分配。实现可以使用所谓的"无状态栈方法"来实现这一点,如下所示。为了在原始请求成功创建分配时检测重传,服务器可以存储创建请求的事务 ID 以及分配数据,并将其与同一 5 元组上的传入 Allocate 请求进行比较。一旦检测到这样的请求,服务器可以停止解析请求并立即生成成功响应。在构建此响应时,LIFETIME 属性的值可以从分配状态数据中的过期时间字段获取,即使此值可能与最初返回的 LIFETIME 值略有不同。此外,服务器可能需要存储在原始响应中返回的任何保留令牌的指示,以便可以在任何重传的响应中返回它。
对于原始请求未能创建分配的情况,服务器可以选择不做任何特殊处理。但是,请注意,在极少数情况下,服务器拒绝原始请求但接受重传的请求(因为条件在短暂的间隔时间内发生了变化)。如果客户端收到第一个失败响应,它将忽略第二个(成功)响应,并认为未创建分配。以这种方式创建的分配最终将超时,因为客户端不会刷新它。此外,如果客户端稍后使用相同的 5 元组但不同的事务 ID 重试,它将收到 437(分配不匹配,Allocation Mismatch),这将导致它使用不同的 5 元组重试。服务器可以使用较小的最大生存期值来最小化以这种方式"孤立"的分配的生存期。
6.3. Receiving an Allocate Success Response (接收分配成功响应)
如果客户端收到 Allocate 成功响应,则它必须 (MUST) 检查映射地址和中继传输地址是否在客户端理解并准备处理的地址族中。本规范仅涵盖这两个地址都是 IPv4 地址的情况。如果这两个地址不在客户端准备处理的地址族中,则客户端必须 (MUST) 删除分配(第 7 节),并且在它认为不匹配已被修复之前,禁止 (MUST NOT) 尝试在该服务器上创建另一个分配。
IETF 目前正在考虑用于在 IPv4 和 IPv6 之间转换的机制,这可能导致客户端通过 IPv6 发起 Allocate 请求,但请求将通过 IPv4 到达服务器,反之亦然。
否则,客户端创建自己的分配数据结构副本,以跟踪服务器上发生的情况。特别是,客户端需要记住从服务器收到的实际生存期,而不是在请求中发送到服务器的值。客户端还必须记住用于请求的 5 元组以及用于验证请求的用户名和密码,以确保在后续消息中重用它们。客户端还需要跟踪它在服务器上建立的通道和权限。
客户端可能希望将中继传输地址发送给对等方(使用此处未指定的某种方法),以便对等方可以与其通信。客户端还可能希望在其 ICE 处理中使用它在 XOR-MAPPED-ADDRESS 属性中接收到的服务器反射地址。
6.4. Receiving an Allocate Error Response (接收分配错误响应)
如果客户端收到 Allocate 错误响应,则处理取决于返回的实际错误代码:
-
(请求超时,Request Timed Out): 服务器存在问题,或者使用所选传输到达服务器时存在问题。客户端将当前事务视为已失败,但可以 (MAY) 选择使用不同的传输(例如,TCP 而不是 UDP)重试 Allocate 请求。
-
300(尝试备用,Try Alternate): 服务器希望客户端改用 ALTERNATE-SERVER 属性中指定的服务器。客户端将当前事务视为已失败,但应该 (SHOULD) 在尝试任何其他服务器(例如,使用 SRV 过程发现的其他服务器)之前尝试使用备用服务器的 Allocate 请求。当尝试使用备用服务器的 Allocate 请求时,客户端遵循 [RFC5389] 中指定的 ALTERNATE-SERVER 过程。
-
400(错误请求,Bad Request): 服务器认为客户端的请求由于某种原因格式错误。客户端将当前事务视为已失败。客户端可以 (MAY) 通知用户或操作员,并且在它认为问题已被修复之前,不应该 (SHOULD NOT) 使用此服务器重试请求。
-
401(未授权,Unauthorized): 如果客户端已遵循长期凭证机制的过程并仍然收到此错误,则服务器不接受客户端的凭证。在这种情况下,客户端将当前事务视为已失败,并应该 (SHOULD) 通知用户或操作员。在它认为问题已被修复之前,客户端不应该 (SHOULD NOT) 向此服务器发送任何进一步的请求。
-
403(禁止,Forbidden): 请求有效,但服务器拒绝执行它,可能是由于管理限制。客户端将当前事务视为已失败。客户端可以 (MAY) 通知用户或操作员,并且在它认为问题已被修复之前,不应该 (SHOULD NOT) 使用此服务器重试相同的请求。
-
420(未知属性,Unknown Attribute): 如果客户端在请求中包含 DONT-FRAGMENT 属性,并且服务器以 420 错误代码拒绝请求,并在错误响应的 UNKNOWN-ATTRIBUTES 属性中列出了 DONT-FRAGMENT 属性,则客户端现在知道服务器不支持 DONT-FRAGMENT 属性。客户端将当前事务视为已失败,但可以 (MAY) 选择在不使用 DONT-FRAGMENT 属性的情况下重试 Allocate 请求。
-
437(分配不匹配,Allocation Mismatch): 这表明客户端选择了服务器认为已在使用的 5 元组。发生这种情况的一种方式是,如果介入的 NAT 分配了另一个最近崩溃的客户端使用的映射传输地址。客户端将当前事务视为已失败。客户端应该 (SHOULD) 选择另一个客户端传输地址并重试 Allocate 请求(使用不同的事务 ID)。在放弃此服务器之前,客户端应该 (SHOULD) 尝试三个不同的客户端传输地址。一旦客户端放弃服务器,它不应该 (SHOULD NOT) 尝试在服务器上创建另一个分配 2 分钟。
-
438(陈旧随机数,Stale Nonce): 参见长期凭证机制 [RFC5389] 的过程。
-
441(错误凭证,Wrong Credentials): 客户端不应该收到此错误以响应 Allocate 请求。客户端可以 (MAY) 通知用户或操作员,并且在它认为问题已被修复之前,不应该 (SHOULD NOT) 使用此服务器重试相同的请求。
-
442(不支持的传输地址,Unsupported Transport Address): 客户端不应该收到此错误以响应 UDP 分配请求。客户端可以 (MAY) 通知用户或操作员,并且在它认为问题已被修复之前,不应该 (SHOULD NOT) 使用此服务器重新尝试请求。
-
486(达到分配配额,Allocation Quota Reached): 服务器当前无法使用此用户名创建更多分配。客户端将当前事务视为已失败。在尝试在服务器上创建更多分配之前,客户端应该 (SHOULD) 至少等待 1 分钟。
-
508(容量不足,Insufficient Capacity): 服务器没有更多可用的中继传输地址,或者没有具有请求属性的地址,或者保留的地址不再可用。客户端将当前操作视为已失败。如果客户端正在使用 EVEN-PORT 或 RESERVATION-TOKEN 属性,则客户端可以 (MAY) 选择删除或修改此属性并立即再次尝试。否则,在尝试在此服务器上创建更多分配之前,客户端应该 (SHOULD) 至少等待 1 分钟。
未知错误响应必须 (MUST) 按照 [RFC5389] 中的描述进行处理。