摘要
本文档定义了 TCP 的四种拥塞控制算法:慢启动,拥塞避免,快速重传和快速恢复。 此外,文档还规定了 TCP 在相对较长的空闲时段之后应如何开始传输,以及讨论各种确认生成方法。RFC 2581 已废弃。
目录
1. 简介
本文档规定了四种 TCP [RFC793] 拥塞控制算法:慢启动,拥塞避免,快速重传和快速恢复。 这些算法是在 [Jac88] 和 [Jac90] 中设计的。
在 [RFC1122] 中使用的是标准 TCP。 在 [CJ89] 中增加了拥塞控制。
[Ste94] 提供了这些算法的实例,[WS95] 提供了这些算法的 BSD 实现源码的说明。
除了指定这些拥塞控制算法之外,文档还规定了在相对较长的空闲时段之后 TCP 连接应该做什么,以及规定和阐明与 TCP ACK 生成有关的一些问题。
2. 定义
本节提供了本文档其余部分将使用的几个术语的定义。
SEGMENT:数据包或确认包(或者同时都是)
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 端点在拥塞时更积极地响应。可以想象,这种攻击可能会导致网络的一部分陷入拥堵崩溃。