TCP 拥塞控制

文档翻译自 https://tools.ietf.org/html/rfc5681html

摘要

本文档定义了 TCP 的四种拥塞控制算法:慢启动,拥塞避免,快速重传和快速恢复。 此外,文档还规定了 TCP 在相对较长的空闲时段以后应如何开始传输,以及讨论各类确认生成方法。RFC 2581 已废弃。web

目录

1. 简介

本文档规定了四种 TCP [RFC793] 拥塞控制算法:慢启动,拥塞避免,快速重传和快速恢复。 这些算法是在 [Jac88][Jac90] 中设计的。
[RFC1122] 中使用的是标准 TCP。 在 [CJ89] 中增长了拥塞控制。算法

[Ste94] 提供了这些算法的实例,[WS95] 提供了这些算法的 BSD 实现源码的说明。安全

除了指定这些拥塞控制算法以外,文档还规定了在相对较长的空闲时段以后 TCP 链接应该作什么,以及规定和阐明与 TCP ACK 生成有关的一些问题。服务器

2. 定义

本节提供了本文档其他部分将使用的几个术语的定义。网络

  • SEGMENT:数据包或确认包(或者同时都是)svg

  • SENDER MAXIMUM SEGMENT SIZE (SMSS):发送端能够传输的最大段的大小。该值能够基于网络的最大传输单元,MTU 发现算法 [RFC1191,RFC4821] 算法、RMSS 或其余因素。大小不包括 TCP/IP 报头和选项。性能

  • RECEIVER MAXIMUM SEGMENT SIZE (RMSS):接收端能接收的最大段的大小。 这是接收端启动期间发送的 MSS 选项中指定的值。 若是未使用 MSS 选项,则为 536 字节 [RFC1122]。大小不包括 TCP/IP 报头和选项。测试

  • FULL-SIZED SEGMENT:包含容许最大数据字节数的段(例如包含 SMSS 字节数据的段)。大数据

  • RECEIVER WINDOW (rwnd):最近通告的接收端窗口。

  • CONGESTION WINDOW (cwnd):TCP 状态变量,用于限制 TCP 能够发送的数据量。 在任何给定时间,TCP 都不能发送序列号高于最高确认序列号以及 cwnd 和 rwnd 的最小值的数据。

  • INITIAL WINDOW (IW):发送端完成三次握手后的拥塞窗口大小。

  • LOSS WINDOW (LW):TCP 发送端使用重传定时器检测到丢包后的拥塞窗口大小。

  • RESTART WINDOW (RW):TCP 在空闲时段以后从新开始传输以后的拥塞窗口大小。

  • FLIGHT SIZE:已发送但还没有确认的数据量。

  • DUPLICATE ACKNOWLEDGMENT:如下状况发生时则认为 ACK 重复。

    • a. 接收端的 ACK 具备未完成的数据。
    • b. 传入的 ACK 不携带数据。
    • c. SYN 和 FIN 位都已关闭。
    • d. ACK 等于给定链接上收到的最大 ACK。
    • e. 收到的 ACK 的通告窗口等于上次 ACK 的通告窗口。

或者,利用选择性确认(SACK)[RFC2018,RFC2883] 来肯定收到的 ACK 什么时候算是“重复”(例如,若是 ACK 包含先前未知的 SACK 信息)。

3.拥塞控制算法

本节定义了四种拥塞控制算法:慢启动,拥塞避免,快速重传, 在 [Jac88][Jac90] 中开发的快速恢复。 在某些状况下,对 TCP 发送端来讲,比算法容许的更保守多是有益的;可是,TCP 必须不能比如下算法容许的更激进(也就是说,当由如下算法计算的 cwnd 值不容许发送数据时,毫不能发送数据)。

3.1. 缓慢启动和拥塞避免

TCP 发送端必须使用慢启动和拥塞避免算法来控制注入网络的未完成数据量。为了实现这些算法,将两个变量添加到 TCP 每一个链接状态中。拥塞窗口(cwnd)是发送端在接收确认(ACK)以前能够发送到网络的数据量的限制,而接收端的通告窗口(rwnd)是接收端对未完成数据量的限制。cwnd 和 rwnd 的最小值控制数据传输。

另外一个状态变量,慢启动阈值(ssthresh),用于肯定慢启动或拥塞避免算法是否用于控制数据传输,以下所述。

开始传输数据到未知条件的网络中时,须要 TCP 缓慢地探测网络以肯定可用容量,以免因为大量数据致使网络拥塞。慢启动算法用于在传输开始时或在修复由重传定时器检测到的丢包以后。慢启动还能够启动 TCP 发送端使用的“ACK时钟”,在慢启动、拥塞避免和丢包恢复算法中将数据释放到网络中。

IW,cwnd 的初始值,必须用下面的指南做为上限来设置。

If SMSS > 2190 bytes:
    IW = 2 * SMSS bytes and MUST NOT be more than 2 segments
If (SMSS > 1095 bytes) and (SMSS <= 2190 bytes):
    IW = 3 * SMSS bytes and MUST NOT be more than 3 segments
if SMSS <= 1095 bytes:
    IW = 4 * SMSS bytes and MUST NOT be more than 4 segments

[RFC3390] 中所述,SYN/ACK 和 SYN/ACK 的确认不得增长拥塞窗口的大小。 此外,若是 SYN 或 SYN/ACK 丢失,则在正确发送的 SYN 以后发送端使用的初始窗口必须是最多 SMSS 字节的一个段大小。

[RFC3390] 中提供了 IW 设置的详细原理和讨论。

当多段的初始拥塞窗口与 MTU 路径发现 [RFC1191] 一块儿实施,而且发现使用的 MSS 太大时,应该减小拥塞窗口 cwnd 以防止较大段的突发。具体来讲,cwnd 应该经过旧段大小与新段大小的比率来减小。

ssthresh 的初始值应该被设置为任意高(例如,设置为最大通告窗口大小),可是 ssthresh 必须响应拥塞而减小。将 ssthresh 设置为尽量高,容许网络条件而不是某些主机限制来指定发送速率。若是终端系统对网络路径有充分理解的状况下,更仔细地设置初始 ssthresh 值可能具备价值(例如,终端主机不会在路径上形成拥塞)。

  • cwnd < ssthresh时使用慢启动算法
  • cwnd > ssthresh时使用拥塞避免算法。
  • cwnd == ssthresh时,发送方可使用慢启动或拥塞避免。

在慢启动过程当中,对于每一个接收到的 ACK,TCP 将 cwnd 增长最多 SMSS 字节,从而累积确认新数据。当 cwnd 超过 ssthresh(或者,当它到达它时)或观察到拥塞时,慢启动结束。虽然传统的 TCP 实如今收到覆盖新数据的 ACK 时精确地经过 SMSS 字节增长了 cwnd,但咱们建议 TCP 实现增长 cwnd,公式(2):

cwnd += min (N, SMSS) (2)

其中 N 是传入 ACK 中先前未确认字节的数量。这种调整是适当字节计数 [RFC3465] 的一部分,并提供针对行为不当的接收端的鲁棒性,这些接收端可能试图使用称为“ACK 分区” [SCWA99] 的机制诱使发送端人为地增长 cwnd。ACK 分区由接收端为单个 TCP 数据段发送多个 ACK,每一个 ACK 仅确认其部分数据。对于每一个这样的 ACK,经过 SMSS 增长 cwnd 的 TCP 将不适当地增长注入网络的数据量。

在拥塞避免期间,cwnd 每一个往返时间(RTT)增长大约 1 个完整大小的段。拥塞避免一直持续到检测到拥塞。在拥塞避免期间增长 cwnd 的基本准则是:

  • 能够经过 SMSS 字节增长 cwnd
  • 每一个 RTT,等式(2)增长 cwnd 一次
  • 毫不能将 cwnd 增长超过 SMSS 字节

咱们注意到 [RFC3465] 容许在实验的慢启动期间,为传入的确认增长超过 SMSS 字节数的 cwnd。然而,这种行为不容许做为标准的一部分。

在拥塞避免期间增长 cwnd 的推荐方法是计算 ACK 确认的字节数。(这种实现的一个缺点是它须要维护一个额外的状态变量。)当确认的字节数达到 cwnd 时,cwnd 能够增长到 SMSS 字节。请注意,在拥塞避免期间,每一个 RTT 的 cwnd 不得增长超过 SMSS 字节。该方法既容许 TCP 在面对延迟的 ACK 时每一个 RTT 将 cwnd 增长一个段,而且提供针对 ACK 分区攻击的鲁棒性。

在拥塞避免期间 TCP 更新 cwnd 的另外一个常见公式在公式(3)中给出:

cwnd += SMSS * SMSS / cwnd (3)

对每一个确认新数据的传入 ACK 执行此调整。公式(3)提供了一个可接受的近似原则,即每个 RTT 增长 1 个全尺寸的段。(注意,对于接收端确认每隔一个数据包的链接,(3)不容许那么激进 —— 大约每秒钟都增长了 cwnd。)

实现注意: 因为整数算术一般用于 TCP 实现,所以当拥塞窗口大于 SMSS * SMSS 时,等式(3)中给出的公式可能没法增长 cwnd。若是上面的公式获得 0,结果应该四舍五入为 1 个字节。

实现注意: 较旧的实如今公式(3)的右侧具备附加的附加常数。这是不正确的,实际上可能会致使性能降低 [RFC2525]

当 TCP 发送端使用重传定时器检测到段丢失而且还没有经过重传定时器从新发送给定的段时,ssthresh 的值必须设置为不大于等式(4)中给出的值:

ssthresh = max (FlightSize / 2, 2*SMSS) (4)

FlightSize 是网络中未完成数据的数量。

另外一方面,当 TCP 发送端使用重传定时器检测到段丢失而且已经经过重传定时器至少重传一次给定的段时,ssthresh 的值保持不变。

实现注意: 一个容易犯的错误就是简单地使用 cwnd 而不是 FlightSize,它在某些实现中可能会意外地增长到超出 rwnd 的范围。

此外,在超时(如 [RFC2988] 中所述)时,cwnd 必须设置为不超过丢失窗口 LW,它等于1个完整大小的段(无论 IW 的值如何)。所以,在从新发送丢弃的段以后,TCP 发送端使用慢启动算法将窗口从1个全尺寸段增长到 ssthresh 的新值,此时拥塞避免再次接管。

[FF96][RFC3782] 所示,超时后基于慢启动的丢包恢复可能致使虚假重传,从而触发重复的确认。对 TCP 实现中这些重复 ACK 的到达的反应各不相同。本文档未详细说明如何处理此类确认。

3.2. 快速重传 / 快速恢复

当无序段到达时,TCP 接收端应该马上发送重复的 ACK。此 ACK 的目的是通知发送端无序接收到的段以及预期的序列号。从发送端的角度来看,重复的 ACK 多是由许多网络问题引发的。

  • 首先,它们多是由丢弃的段引发的。在这种状况下,丢弃的段以后的全部段将触发重复的 ACK,直到丢失报文被修复。

  • 其次,重复的 ACK 多是由网络对数据段的从新排序引发的(不是沿某些网络路径的罕见事件 [Pax97])。

  • 最后,重复的 ACK 多是由网络复制 ACK 或数据段引发的。

此外,当接收段填充序列空间中的所有或部分间隙时,TCP 接收端应该当即发送 ACK。这将为发送端经过重传超时,快速重传或高级丢包恢复算法恢复丢失提供更及时的信息,如 4.3 节所述。

TCP 发送端应该根据传入的重复 ACK 使用“快速重传”算法来检测和修复丢包。快速重传算法使用3个重复 ACK 的到达做为段已丢失的标志。在接收到3个重复的 ACK 以后,TCP 执行丢失的段的重传,而不等待重传定时器到期。

在快速重传算法发送丢失的段以后,“快速恢复”算法控制新数据的传输,直到非重复的 ACK 到达。不执行慢启动的缘由是重复 ACK 的接收不只表示段已经丢失,并且还代表段极可能离开网络(尽管网络的大量段重复可能使该结论无效)。换句话说,因为接收端只能在一个段到达时生成重复的 ACK,该段已离开网络而且在接收端的缓冲区中,所以咱们知道它再也不消耗网络资源。此外,因为保留了 ACK“clock” [Jac88],TCP 发送端能够继续发送新的段(传输必须继续使用减小的 cwnd,丢包代表发生了拥塞)。

快速重传和快速恢复算法实现以下。

  • 在发送端收到的第1个和第2个重复的 ACK 时,TCP 应该按照 [RFC3042] 发送一段先前未发送的数据,前提是接收端的通告窗口容许,总的 FlightSize 将保持小于或等于 cwnd 加 2 * SMSS, 而且新数据可用于传输。此外,TCP 发送端不得更改 cwnd 以反映这两个段 [RFC3042]。请注意,使用 SACK [RFC2018] 的发送方不得发送新数据,除非传入的重复确认包含新的 SACK 信息。

  • 当接收到第三个重复 ACK 时,TCP 必须将 ssthresh 设置为不大于公式(4)中给出的值。 当使用 [RFC3042] 时,在有限传输中发送的附加数据不得包含在此计算中。

  • 从 SND.UNA 开始的丢失段必须从新发送而且 cwnd 设置为 ssthresh 加上 3 * SMSS。 经过已离开网络的段(三个)的数量和接收端已缓冲的段人为地“增大”拥塞窗口。

  • 对于收到的每一个附加剧复 ACK(在第三个以后),cwnd 必须由 SMSS 增长。 人为地增长拥塞窗口,以反映已经离开网络的附加段。

注意: [SCWA99] 讨论了基于接收端的攻击,其中许多伪造的重复 ACK 被发送到数据发送端,以便人为地增长 cwnd 并致使使用高于适当的发送速率。 所以,TCP 能够将丢包恢复期间人为增长的次数限制为未完成的段的数量(或其近似值)。

注意: 当未使用高级丢包恢复机制(如 4.3 节中所述)时,FlightSize 的这种增长会致使公式(4)略微增长 cwnd 和 ssthresh,如 SND.UNA 和 SND 之间的某些段。假设 NXT 已离开网络但仍在 FlightSize 中反映出来。

  • 当先前未发送的数据可用且 cwnd 的新值和接收端的通告窗口容许时,TCP 应该发送先前未发送数据的 1 * SMSS 字节。

  • 当下一个 ACK 到达时,它确认先前未确认的数据,TCP 必须将 cwnd 设置为 ssthresh(步骤2中设置的值)。 这被称为“收缩”窗口。该 ACK 应该是由来自步骤3的重传引发的确认,重传以后的一个 RTT(尽管它可能在接收端处存在大量的无序数据段传送时更快到达)。
    另外,若是没有丢失,则该 ACK 应该确认在丢失的段和第3个重复的 ACK 的接收之间发送的全部中间段。

注意: 该算法一般没法从单个数据包中的多个丢失中有效恢复 [FF96]。 下面的 4.3 节解决了这种状况。

4.其余考虑因素

4.1. 从新启动空闲链接

上面描述的 TCP 拥塞控制算法的一个已知问题是它们容许在 TCP 已经空闲相对长的一段时间以后发送可能不适当的流量。在空闲时段以后,TCP 没法使用 ACK 时钟将新段插入到网络中,由于全部 ACK 都已从网络中耗尽。所以,如上所述,TCP 可能在空闲时段以后将 cwnd 大小的线速率发送到网络中。此外,改变网络情况可能已经使得 TCP 在两个端点之间的可用端到端网络容量的概念,根据 cwnd 的估计,在长时间的空闲期间是不许确的。

[Jac88] 建议 TCP 在相对较长的空闲时段后使用慢启动重启传输。慢启动用于重启 ACK 时钟,就像在传输开始时同样。该机制已普遍部署。当 TCP 没有收到超过一次重传超时的段时,cwnd 在传输开始以前减小到重启窗口(RW)的值。

定义 RW = min(IW,cwnd)。

使用上次接收片断来肯定是否减小 cwnd 在持久 HTTP 链接 [HTH98] 的状况下不能缩小 cwnd。在这种状况下,Web 服务器在将数据传输到 Web 客户端以前接收请求。请求的接收使得空闲链接的测试失败,并容许 TCP 以可能不适当的大 cwnd 开始传输。

所以,若是 TCP 在超太重传超时的时间间隔内没有发送数据,则 TCP SHOULD 应该在开始传输以前将 cwnd 设置为不超过 RW。

4.2. 生成应答

[RFC1122] 中指定的延迟 ACK 算法应该由 TCP 接收端使用。使用延迟 ACK 时,TCP 接收端不得过分延迟确认。具体来讲,至少每秒钟都要生成一个 ACK,而且必须在第一个未确认的数据包到达的 500ms 内生成 ACK。

在某些状况下,发送端和接收端可能没法就构成一个全尺寸区段的内容达成一致。若是实现每次从发送端接收到 2 * RMSS 字节的新数据时发送至少一个确认,则认为该实现符合此要求,其中 RMSS 是接收端向发送端指定的最大段大小(或默认值)若是接收端在链接创建期间未指定 MSS 选项,则每一个为536个字节 [RFC1122]。因为最大传输单元(MTU),路径 MTU 发现算法或其余因素,可能迫使发送端使用小于 RMSS 的分段大小。例如,考虑接收端宣布 X 字节的 RMSS 但发送端最终使用因为路径 MTU 发现(或发送方的 MTU 大小)而致使 Y 字节(Y < X)的分段大小的状况。若是在发送 ACK 以前等待 2 * X 字节到达,接收端将生成拉伸 ACK。显然,这将占用大于 Y 字节的2个段。所以,虽然没有定义特定算法,可是但愿接收端试图防止这种状况,例如,经过确认至少每一个第二段,而无论大小如何。最后,咱们重复一遍,一个 ACK 不能被延迟超过500毫秒,等待第二个完整的区段到达。

应该当即确认无序数据段,以加速丢包恢复。为了触发快速重传算法,接收端应当在接收到序列空间中间隙之上的数据段时当即发送重复 ACK。

TCP 接收端毫不能为每一个收到段生成多个ACK,除了在接收应用程序消耗新数据时更新提供的窗口(参见 [RFC813][RFC793] 的第42页)。

4.3. 丢包恢复机制

TCP 研究人员已经提出了许多增长快速重传和快速恢复的丢包恢复算法,并在 RFC 系列中进行了说明。虽然其中一些算法基于 TCP 选择性确认(SACK)选项 [RFC2018],例如 [FF96][MM96a][MM96b][RFC3517],但其余算法不须要 SACK,例如 [Hoe96][FF96][RFC3782]。 非 SACK 算法使用“部分确认”(覆盖先前未确认的数据的 ACK,可是在检测到丢失时不是全部未完成的数据)来触发重传。 虽然本文档没有标准化任何能够改善快速重传/快速恢复的特定算法,可是只要它们遵循上面概述的基本四种算法的通常原则,就能够隐含地容许这些加强算法。

也就是说,
- 当检测到数据窗口中的第一次丢包时,必须将 ssthresh 设置为不大于公式(4)给出的值。

  • 其次,在修复所讨论数据窗口中的全部丢失段以前,每一个 RTT 中传输的段数必须不超过检测到丢失时未完成段的数量的一半。

  • 最后,在已成功从新传输给定窗口段中的全部丢失以后,必须将 cwnd 设置为不超过 ssthresh,而且必须使用拥塞避免来进一步增长 cwnd。两个连续数据窗口的丢失或重传的丢失应被视为拥塞的两个指示,所以,在这种状况下,cwnd(和 ssthresh)必须下降两次。

咱们建议 TCP 实现者采用某种形式的高级丢包恢复,以应对数据窗口中的多个丢失。[RFC3782][RFC3517] 中详述的算法符合上述通常原则。咱们注意到虽然这些算法并非惟一符合上述通常原则的两种算法,但这两种算法已通过社区审核,目前正处于标准中。

5. 安全考虑

文档要求 TCP 在存在重传超时和重复确认的状况降低低其发送速率。所以,攻击者可能经过致使数据包或其确认丢失,或经过伪造过多的重复确认来削弱 TCP 链接的性能。

为了响应 [SCWA99] 中概述的 ACK 分区攻击,本文档建议根据每一个到达 ACK 中新确认的字节数而不是每一个到达的 ACK 上的特定常量来增长拥塞窗口(如 3.1 节所述)。

互联网在很大程度上依赖于这些算法的正确实现,以保持网络稳定性并避免拥塞崩溃。攻击者能够经过伪造过多的重复确认或过多的新数据确认,使 TCP 端点在拥塞时更积极地响应。能够想象,这种攻击可能会致使网络的一部分陷入拥堵崩溃。