rfc9000: 13. Packetization and Reliability
well, 还是AI翻译方便。
13. 打包和可靠性 (Packetization and Reliability)
发送方在一个 QUIC 数据包中发送一个或多个帧;参见章节 12.4。
发送方可以通过在每个 QUIC 数据包中包含尽可能多的帧,来最小化每个数据包的带宽和计算成本。发送方可以等待一小段时间以收集多个帧,然后再发送未被最大化填充的数据包,以避免发送大量的小数据包。实现可以利用有关应用程序发送行为的知识或启发式方法来决定是否等待以及等待多长时间。这个等待周期是一个实现决策,实现应该谨慎地进行延迟,因为任何延迟都可能增加应用程序可见的延迟。
流的多路复用是通过将来自多个流的 STREAM 帧交错到一个或多个 QUIC 数据包中实现的。单个 QUIC 数据包可以包含来自一个或多个流的多个 STREAM 帧。
QUIC 的一个好处是避免了跨多个流的队头阻塞。当发生数据包丢失时,只有在该数据包中有数据的流会被阻塞,等待重传被接收,而其他流可以继续进行。请注意,当来自多个流的数据包含在单个 QUIC 数据包中时,该数据包的丢失会阻止所有这些流的进展。建议实现者在传出的数据包中包含尽可能少的必要流,同时避免因数据包填充不足而损失传输效率。
13.1. 数据包处理 (Packet Processing)
在一个数据包的包保护被成功移除并且其中包含的所有帧都被处理之前,该数据包绝不能被确认。对于 STREAM 帧,这意味着数据已经入队准备被应用协议接收,但并不要求数据已经被交付和消费。
一旦数据包被完全处理,接收方通过发送一个或多个包含该已接收数据包的包序号的 ACK 帧来确认收据。
如果一个端点能够检测到它收到了一个对自己未发送的数据包的确认,它应该将这种情况视为 PROTOCOL_VIOLATION 类型的连接错误。关于如何实现这一点的进一步讨论,请参见章节 21.4。
13.2. 生成确认 (Generating Acknowledgments)
端点确认它们接收并处理的所有数据包。然而,只有引发确认(ack-eliciting)的数据包才会导致在最大确认延迟(maximum ack delay)内发送一个 ACK 帧。不引发确认的数据包只有在因其他原因发送 ACK 帧时才被确认。
当出于任何原因发送数据包时,如果近期没有发送过 ACK 帧,端点应该尝试包含一个 ACK 帧。这样做有助于对端及时进行丢包检测。
总的来说,来自接收方的频繁反馈可以改善丢包和拥塞响应,但这必须与接收方为响应每个引发确认的数据包而发送 ACK 帧所产生的过高负载相平衡。下面提供的指导旨在寻求这种平衡。
13.2.1. 发送 ACK 帧 (Sending ACK Frames)
每个数据包应该至少被确认一次,而引发确认的数据包必须在端点使用 max_ack_delay 传输参数(参见章节 18.2)通知的最大延迟内至少被确认一次。max_ack_delay 声明了一个明确的契约:一个端点承诺永远不会故意将一个引发确认的数据包的确认延迟超过所指示的值。如果这样做,任何超出的延迟都会累积到 RTT 估算中,并可能导致对端的虚假或延迟的重传。发送方在确定基于计时器的重传超时时,会使用接收方的 max_ack_delay 值,详见 [QUIC-RECOVERY] 的章节 6.2。
一个端点必须立即确认所有引发确认的 Initial 和 Handshake 数据包,并在其通告的 max_ack_delay 内确认所有引发确认的 0-RTT 和 1-RTT 数据包,但有以下例外。在握手确认之前,一个端点在接收到 Handshake、0-RTT 或 1-RTT 数据包时可能没有用于解密的数据包保护密钥。因此,它可能会缓冲这些数据包,并在获得所需密钥时再对它们进行确认。
由于只包含 ACK 帧的数据包不受拥塞控制,一个端点绝不能在响应一个引发确认的数据包时发送超过一个这样的数据包。
一个端点绝不能发送一个不引发确认的数据包来响应另一个不引发确认的数据包,即使在收到的数据包之前存在数据包间隙。这避免了确认的无限反馈循环,这种循环可能阻止连接进入空闲状态。不引发确认的数据包最终会在端点为响应其他事件而发送 ACK 帧时被确认。
一个只发送 ACK 帧的端点不会从其对端接收到确认,除非这些确认被包含在带有引发确认的帧的数据包中。当有新的引发确认的数据包需要确认时,端点应该将 ACK 帧与其他帧一起发送。当只有不引发确认的数据包需要确认时,端点可以选择在收到一个引发确认的数据包之前,不在传出的帧中发送 ACK 帧。
一个只发送不引发确认的数据包的端点可能会选择偶尔在这些数据包中添加一个引发确认的帧,以确保它能收到一个确认;参见章节 13.2.4。在这种情况下,一个端点绝不能在所有原本不引发确认的数据包中都发送一个引发确认的帧,以避免无限的确认反馈循环。
为了帮助发送方进行丢包检测,当一个端点收到一个引发确认的数据包时,如果出现以下任一情况,它应该立即生成并发送一个 ACK 帧:
- 收到的数据包的包序号小于另一个已经收到的引发确认的数据包。
- 该数据包的包序号大于已收到的最高编号的引发确认的数据包,并且在该数据包与此数据包之间存在丢失的数据包。
同样,在 IP 头部中标记了 ECN 拥塞经历(CE)码点的数据包应该被立即确认,以减少对端对拥塞事件的响应时间。
[QUIC-RECOVERY] 中的算法被期望对不遵循上述指导的接收方具有弹性。然而,实现者只有在仔细考虑了变更对端点建立的连接以及对网络其他用户的性能影响之后,才应偏离这些要求。
13.2.2. 确认频率 (Acknowledgment Frequency)
接收方决定响应引发确认的数据包发送确认的频率。这个决定涉及到一个权衡。
端点依赖及时的确认来检测丢包;参见 [QUIC-RECOVERY] 的章节 6。基于窗口的拥塞控制器,例如 [QUIC-RECOVERY] 章节 7 中描述的控制器,依赖确认来管理其拥塞窗口。在这两种情况下,延迟确认都会对性能产生不利影响。
另一方面,减少只携带确认的数据包的频率,可以降低两端的包传输和处理成本。它可以改善严重不对称链路上的连接吞吐量,并减少使用返回路径容量的确认流量;参见 [RFC3449] 的章节 3。
接收方应该在收到至少两个引发确认的数据包后发送一个 ACK 帧。这个建议是通用的,并且与 TCP 端点行为的建议 [RFC5681] 一致。对网络状况的了解、对对端拥塞控制器的了解,或进一步的研究和实验可能会提出具有更好性能特征的替代确认策略。
接收方可以在决定是否发送 ACK 帧响应之前处理多个可用的数据包。
13.2.3. 管理 ACK 范围 (Managing ACK Ranges)
当发送 ACK 帧时,会包含一个或多个已确认数据包的范围。包含对较早数据包的确认可以减少因丢失先前发送的 ACK 帧而导致的虚假重传的几率,但代价是 ACK 帧会更大。
ACK 帧应该总是确认最近收到的数据包,而且数据包乱序越严重,快速发送更新的 ACK 帧就越重要,以防止对端将数据包声明为丢失并虚假地重传其包含的帧。
一个 ACK 帧预计能装入单个 QUIC 数据包中。如果不能,则会省略较旧的范围(那些包序号最小的范围)。
接收方会限制它记住并在 ACK 帧中发送的 ACK 范围(章节 19.3.1)的数量,既是为了限制 ACK 帧的大小,也是为了避免资源耗尽。在收到对某个 ACK 帧的确认后,接收方应该停止跟踪那些已被确认的 ACK 范围。发送方可以期望大多数数据包得到确认,但 QUIC 不保证接收方处理的每个数据包都能收到确认。
保留许多 ACK 范围可能会导致 ACK 帧变得过大。接收方可以通过丢弃未被确认的 ACK 范围来限制 ACK 帧的大小,代价是增加了来自发送方的重传。如果一个 ACK 帧太大而无法放入一个数据包中,这是必要的。接收方也可以进一步限制 ACK 帧的大小,以便为其他帧保留空间,或限制确认所消耗的容量。
接收方必须保留一个 ACK 范围,除非它能确保后续不会接受该范围内的包序号。随着范围被丢弃,维持一个不断增加的最小包序号是实现此目的的一种状态最小化的方法。
接收方可以丢弃所有的 ACK 范围,但它们必须保留已成功处理的最大包序号,因为这用于从后续数据包中恢复包序号;参见章节 17.1。
接收方应该在每个 ACK 帧中都包含一个含有已接收最大包序号的 ACK 范围。最大确认(Largest Acknowledged)字段在发送方进行 ECN 验证时使用,包含一个比前一个 ACK 帧中更低的值可能会导致 ECN 被不必要地禁用;参见章节 13.4.2。
章节 13.2.4 描述了一种用于确定在每个 ACK 帧中确认哪些数据包的示例性方法。尽管该算法的目标是为每个处理过的数据包生成确认,但确认仍有可能丢失。
13.2.4. 通过跟踪 ACK 帧来限制范围 (Limiting Ranges by Tracking ACK Frames)
当一个包含 ACK 帧的数据包被发送时,可以保存该帧中的最大确认(Largest Acknowledged)字段。当一个包含 ACK 帧的数据包被确认时,接收方可以停止确认那些小于或等于已发送 ACK 帧中最大确认字段的数据包。
一个只发送不引发确认的数据包(如 ACK 帧)的接收方,可能在很长一段时间内都收不到确认。这可能导致接收方为大量的 ACK 帧长时间维持状态,并且它发送的 ACK 帧可能会变得不必要地大。在这种情况下,接收方可以偶尔发送一个 PING 或其他小的引发确认的帧,例如每个往返时间一次,以从对端引出一个 ACK。
在没有 ACK 帧丢失的情况下,此算法允许至少 1 RTT 的乱序。在有 ACK 帧丢失和乱序的情况下,此方法不能保证发送方在每个确认不再被包含在 ACK 帧中之前都能看到它。数据包可能会乱序到达,并且所有包含它们的后续 ACK 帧都可能丢失。在这种情况下,丢包恢复算法可能导致虚假重传,但发送方将继续向前推进。
13.2.5. 测量和报告主机延迟 (Measuring and Reporting Host Delay)
一个端点会测量从收到最大包序号的数据包到发送确认之间有意引入的延迟。端点将这个确认延迟编码到 ACK 帧的 ACK Delay 字段中;参见章节 19.3。这使得 ACK 帧的接收方能够调整任何故意的延迟,这对于在确认被延迟时获得更准确的路径 RTT 估计非常重要。
数据包在被处理前可能会被保存在操作系统内核或主机的其他地方。一个端点在填充 ACK 帧中的 ACK Delay 字段时,绝不能包含它无法控制的延迟。然而,端点应该包含因解密密钥不可用而导致的缓冲延迟,因为这些延迟可能很大,并且很可能不会重复出现。
当测量的确认延迟大于其 max_ack_delay 时,端点应该报告测量的延迟。此信息在握手期间尤其有用,因为此时延迟可能很大;参见章节 13.2.1。
13.2.6. ACK 帧和包保护 (ACK Frames and Packet Protection)
ACK 帧必须仅在与被确认的数据包具有相同包序号空间的数据包中携带;参见章节 12.1。例如,用 1-RTT 密钥保护的数据包必须在同样用 1-RTT 密钥保护的数据包中进行确认。
客户端用 0-RTT 包保护发送的数据包必须由服务器在用 1-RTT 密钥保护的数据包中确认。这可能意味着如果服务器的加密握手消息被延迟或丢失,客户端将无法使用这些确认。请注意,同样的限制也适用于服务器发送的受 1-RTT 密钥保护的其他数据。
13.2.7. PADDING 帧消耗拥塞窗口 (PADDING Frames Consume Congestion Window)
包含 PADDING 帧的数据包,就拥塞控制而言,被认为是飞行中的(in flight)[QUIC-RECOVERY]。因此,只包含 PADDING 帧的数据包会消耗拥塞窗口,但不会产生能打开拥塞窗口的确认。为避免死锁,发送方应该确保除了 PADDING 帧之外,还定期发送其他帧,以从接收方引出确认。
13.3. 信息的重传 (Retransmission of Information)
被确定为丢失的 QUIC 数据包不会整个重传。这同样适用于丢失数据包中包含的帧。取而代之的是,根据需要,帧中可能携带的信息会通过新的帧再次发送。
新的帧和数据包用于携带被确定为已丢失的信息。通常,当包含该信息的数据包被确定为丢失时,信息会再次发送,而当包含该信息的数据包被确认时,发送则停止。
CRYPTO帧中发送的数据根据 [QUIC-RECOVERY] 中的规则进行重传,直到所有数据都被确认为止。用于 Initial 和 Handshake 数据包的CRYPTO帧中的数据在相应包序号空间的密钥被丢弃时也被丢弃。STREAM帧中发送的应用数据会在新的STREAM帧中重传,除非该端点已经为该流发送了RESET_STREAM。一旦端点发送了RESET_STREAM帧,就不再需要更多的STREAM帧。ACK帧携带最近的一组确认以及来自最大被确认数据包的确认延迟,如章节 13.2.1 所述。延迟包含ACK帧的数据包的传输或重发旧的ACK帧可能导致对端产生虚高的 RTT 样本或不必要地禁用 ECN。流传输的取消,如
RESET_STREAM帧中所携带的,会一直发送直到被确认,或者直到所有流数据被对端确认(即,流的发送部分达到 “Reset Recvd” 或 “Data Recvd” 状态)。RESET_STREAM帧的内容在再次发送时绝不能改变。类似地,取消流传输的请求,如
STOP_SENDING帧中编码的,会一直发送,直到流的接收部分进入 “Data Recvd” 或 “Reset Recvd” 状态;参见章节 3.5。连接关闭信号,包括包含
CONNECTION_CLOSE帧的数据包,在检测到丢包时不会再次发送。重发这些信号的描述见章节 10。当前的连接最大数据量在
MAX_DATA帧中发送。如果包含最近发送的MAX_DATA帧的数据包被声明为丢失,或者当端点决定更新限制时,会用一个新的MAX_DATA帧发送更新后的值。需要注意避免过于频繁地发送此帧,因为限制可能会频繁增加,导致发送不必要的大量MAX_DATA帧;参见章节 4.2。当前的最大流数据偏移量在
MAX_STREAM_DATA帧中发送。与MAX_DATA类似,当包含某个流的最近MAX_STREAM_DATA帧的数据包丢失时,或者当限制更新时,会发送一个更新的值,并注意防止该帧发送过于频繁。当流的接收部分进入 “Size Known” 或 “Reset Recvd” 状态时,端点应该停止发送MAX_STREAM_DATA帧。给定类型的流的数量限制在
MAX_STREAMS帧中发送。与MAX_DATA类似,当包含某个流类型的最近MAX_STREAMS帧的数据包被声明为丢失,或者当限制更新时,会发送一个更新的值,并注意防止该帧发送过于频繁。阻塞信号在
DATA_BLOCKED、STREAM_DATA_BLOCKED和STREAMS_BLOCKED帧中携带。DATA_BLOCKED帧具有连接范围,STREAM_DATA_BLOCKED帧具有流范围,而STREAMS_BLOCKED帧则限定于特定的流类型。如果包含某个范围的最近帧的数据包丢失,只有当端点在相应限制上被阻塞时,才会发送一个新的帧。这些帧总是包含它们传输时导致阻塞的限制值。使用
PATH_CHALLENGE帧的活性或路径验证检查会定期发送,直到收到匹配的PATH_RESPONSE帧,或者直到不再需要活性或路径验证检查。PATH_CHALLENGE帧每次发送时都包含不同的有效载荷。使用
PATH_RESPONSE帧对路径验证的响应只发送一次。期望对端根据需要发送更多的PATH_CHALLENGE帧以引出额外的PATH_RESPONSE帧。新的连接 ID 在
NEW_CONNECTION_ID帧中发送,如果包含它们的包丢失则进行重传。该帧的重传携带相同的序列号值。同样,废弃的连接 ID 在RETIRE_CONNECTION_ID帧中发送,如果包含它们的包丢失则进行重传。NEW_TOKEN帧在包含它们的包丢失时会被重传。除了直接比较帧内容外,没有特殊的支持来检测重排和重复的NEW_TOKEN帧。PING和PADDING帧不包含任何信息,因此丢失的PING或PADDING帧不需要修复。HANDSHAKE_DONE帧必须被重传直到它被确认为止。
端点应该优先重传数据,而不是发送新数据,除非应用程序指定的优先级另有指示;参见章节 2.3。
尽管鼓励发送方每次发送数据包时都组装包含最新信息的帧,但并不禁止重传丢失数据包中帧的副本。重传帧副本的发送方需要处理因包序号长度、连接 ID 长度和路径 MTU 变化而导致的可用有效载荷大小的减少。接收方必须接受包含过时帧的数据包,例如携带比旧数据包中发现的更小的最大数据值的 MAX_DATA 帧。
发送方应该避免在数据包被确认后重传其中的信息。这包括在被声明丢失后又被确认的数据包,这在网络乱序的情况下可能发生。这样做要求发送方在声明数据包丢失后保留有关它们的信息。发送方可以在经过一段足够允许乱序的时间(例如一个 PTO,见 [QUIC-RECOVERY] 的章节 6.2)后,或基于其他事件(例如达到内存限制)丢弃此信息。
在检测到丢包时,发送方必须采取适当的拥塞控制措施。丢包检测和拥塞控制的细节在 [QUIC-RECOVERY] 中描述。
13.4. 显式拥塞通知 (Explicit Congestion Notification)
QUIC 端点可以使用 ECN [RFC3168] 来检测和响应网络拥塞。ECN 允许一个端点在 IP 数据包的 ECN 字段中设置一个 ECN 可用传输(ECT)码点。网络节点随后可以通过在 ECN 字段中设置 ECN-CE 码点来指示拥塞,而不是丢弃数据包 [RFC8087]。端点通过降低其发送速率来对报告的拥塞做出反应,如 [QUIC-RECOVERY] 中所述。
要启用 ECN,发送 QUIC 的端点首先要确定路径是否支持 ECN 标记,以及对端是否报告接收到的 IP 头中的 ECN 值;参见章节 13.4.2。
13.4.1. 报告 ECN 计数 (Reporting ECN Counts)
ECN 的使用要求接收端点从 IP 数据包中读取 ECN 字段,这在所有平台上都是不可能的。如果一个端点没有实现 ECN 支持或无法访问接收到的 ECN 字段,它就不会报告它收到的数据包的 ECN 计数。
即使一个端点不在它发送的数据包中设置 ECT 字段,该端点必须提供它收到的 ECN 标记的反馈,如果这些标记是可访问的。未能报告 ECN 计数将导致发送方禁用此连接的 ECN 使用。
在收到一个带有 ECT(0)、ECT(1) 或 ECN-CE 码点的 IP 数据包时,一个启用了 ECN 的端点会访问 ECN 字段并增加相应的 ECT(0)、ECT(1) 或 ECN-CE 计数。这些 ECN 计数包含在后续的 ACK 帧中;参见章节 13.2 和 19.3。
每个包序号空间维护独立的确认状态和独立的 ECN 计数。合并的 QUIC 数据包(参见章节 12.2)共享同一个 IP 头部,因此每个合并的 QUIC 数据包的 ECN 计数都会增加一次。
例如,如果一个 Initial、一个 Handshake 和一个 1-RTT 的 QUIC 数据包被合并成一个 UDP 数据报,那么所有三个包序号空间的 ECN 计数将根据单个 IP 头部的 ECN 字段各自增加一。
ECN 计数仅在处理了从收到的 IP 数据包中的 QUIC 数据包时才会增加。因此,重复的 QUIC 数据包不会被处理,也不会增加 ECN 计数;相关安全问题参见章节 21.10。
13.4.2. ECN 验证 (ECN Validation)
有故障的网络设备可能会损坏或错误地丢弃携带非零 ECN 码点的数据包。为了在这种设备存在的情况下确保连通性,端点会对每个网络路径的 ECN 计数进行验证,并在检测到错误时禁用该路径上的 ECN 使用。
要对新路径执行 ECN 验证:
- 端点在新路径上发送给对端的早期传出数据包的 IP 头部中设置一个 ECT(0) 码点 [RFC8311]。
- 端点监控所有以 ECT 码点发送的数据包是否最终被视为丢失([QUIC-RECOVERY] 的章节 6),这表明 ECN 验证失败。
如果一个端点有理由预期带有 ECT 码点的数据包可能会被有故障的网络元素丢弃,该端点可以只为路径上的前十个传出数据包,或在三个 PTO(参见 [QUIC-RECOVERY] 的章节 6.2)的时间内设置 ECT 码点。如果所有标记有非零 ECN 码点的数据包随后都丢失了,它可以禁用标记,假设是标记导致了丢失。
因此,一个端点会尝试使用 ECN,并在每个新连接、切换到服务器的首选地址以及在活动连接迁移到新路径时进行验证。附录 A.4 描述了一种可能的算法。
探测路径的 ECN 支持还有其他可能的方法,以及不同的标记策略。实现可以使用 RFCs 中定义的其他方法;参见 [RFC8311]。使用 ECT(1) 码点的实现需要使用报告的 ECT(1) 计数来执行 ECN 验证。
13.4.2.1. 接收带有 ECN 计数的 ACK 帧 (Receiving ACK Frames with ECN Counts)
网络对 ECN-CE 标记的错误应用可能导致连接性能下降。因此,一个收到带有 ECN 计数的 ACK 帧的端点在使用它们之前会验证这些计数。它通过将新收到的计数与上一个成功处理的 ACK 帧中的计数进行比较来执行此验证。ECN 计数的任何增加都会根据应用于 ACK 帧中新确认的数据包的 ECN 标记进行验证。
如果一个 ACK 帧新确认了一个端点发送时设置了 ECT(0) 或 ECT(1) 码点的数据包,但 ACK 帧中没有相应的 ECN 计数,则 ECN 验证失败。此检查可以检测到将 ECN 字段清零的网络元素或不报告 ECN 标记的对端。
如果 ECT(0) 和 ECN-CE 计数的增量总和小于最初以 ECT(0) 标记发送的新确认数据包的数量,ECN 验证也会失败。同样,如果 ECT(1) 和 ECN-CE 计数的增量总和小于以 ECT(1) 标记发送的新确认数据包的数量,ECN 验证也会失败。这些检查可以检测网络对 ECN-CE 标记的重标记。
当 ACK 帧丢失时,端点可能会错过对某个数据包的确认。因此,ECT(0)、ECT(1) 和 ECN-CE 计数的总增量可能大于 ACK 帧新确认的数据包数量。这就是为什么 ECN 计数被允许大于被确认的数据包总数。
验证乱序的 ACK 帧中的 ECN 计数可能导致失败。一个端点绝不能因为处理了一个没有增加最大确认包序号的 ACK 帧而导致 ECN 验证失败。
如果收到的 ECT(0) 或 ECT(1) 的总计数超过了用相应 ECT 码点发送的数据包总数,ECN 验证可能会失败。特别是,当一个端点收到了一个对应于它从未应用的 ECT 码点的非零 ECN 计数时,验证将失败。此检查可以检测到数据包在网络中被重标记为 ECT(0) 或 ECT(1)。
13.4.2.2. ECN 验证结果 (ECN Validation Outcomes)
如果验证失败,那么端点必须禁用 ECN。它停止在其发送的 IP 数据包中设置 ECT 码点,假设网络路径或对端不支持 ECN。
即使验证失败,端点也可以在连接中的任何后续时间为同一路径重新验证 ECN。一个端点可以继续周期性地尝试验证。
验证成功后,端点可以继续在其发送的后续数据包中设置 ECT 码点,期望该路径是 ECN 可用的。网络路由和路径元素可能在连接中途改变;如果验证后来失败,端点必须禁用 ECN。