《TCP/IP详解 卷1》12-17章TCP笔记

文章目录

  • 第12章 TCP:传输控制协议(初步)
    • 12.1 引言
      • 12.1.1 ARQ和重传
      • 12.1.2 分组窗口和滑动窗口
      • 12.1.3 流量控制和拥塞控制
    • 12.2 TCP的引入
      • 12.2.2 TCP中的可靠性
    • 12.3 TCP头部和封装
  • 第13章 TCP连接管理
    • 13.2 TCP连接的建立与终止
      • 三次握手
      • 四次挥手
      • 为什么需要**三次**握手?
      • 为什么需要四次挥手?
      • 13.2.1 TCP半关闭
      • 13.2.2 同时打开与关闭
      • 13.2.3 初始序列号
      • 13.2.5 连接建立超时
    • 13.3 TCP选项
      • 13.3.1 最大段大小选项
      • 13.3.2 选择确认选项
      • 13.3.3 窗口缩放选项
      • 13.3.4 时间戳选项与防回绕序列号
    • 13.4 路径MTU发现
    • 13.5 TCP状态转换
      • 13.5.2 TIME_WAIT状态
      • 13.5.4 FIN_WAIT_2状态
    • 13.6 RST重置报文段
      • 13.6.1 针对不存在的端口的连接请求
      • 13.6.2 终止一条连接
      • 13.6.3 半开连接
    • 13.7 TCP服务器选项
      • 13.7.1 TCP端口号
      • 13.7.3 限制外部节点
      • 13.7.4 进入连接队列
    • 13.8 与TCP连接管理相关的攻击
      • SYN泛洪
      • 超小MTU攻击
      • 序列号/劫持攻击
      • 欺骗攻击
  • 第14章 TCP超时与重传
    • 14.2 简单的超时与重传
    • 14.3 设置重传超时(RTO)
      • 14.3.1 经典方法
      • 14.3.2 标准方法
        • 14.3.2.2 初始值
        • 14.3.2.4 带时间戳的RTT测量
      • 14.3.3 Linux采用的方法
    • 14.4 基于计时器的重传
    • 14.5 快速重传
    • 14.6 带选择确认的重传
      • 例子
    • 14.7 伪超时与重传
      • 14.7.1 重复SACK(DSACK)扩展
      • 14.7.2 Eifel检测算法
      • 14.7.3 前移RTO恢复(F-RTO)
      • 14.7.4 Eifel响应算法
    • 14.8 包失序与包重复
      • 14.8.1 失序
      • 14.8.2 重复
    • 14.9 目的度量
    • 14.10 重新组包
    • 14.11 与TCP重传相关的攻击
  • 第15章 TCP数据流与窗口管理
    • 15.2 交互式通信
    • 15.3 延时确认
    • 15.4 Nagle算法
      • 15.4.1 延时ACK和Nagle算法结合
    • 15.5 流量控制与窗口管理
      • 15.5.1 滑动窗口
      • 15.5.2 零窗口与TCP持续计时器
      • 15.5.3 糊涂窗口综合征(SWS)
      • 15.5.4 大容量缓存与自动调优
    • 15.6 紧急机制
  • 第16章 TCP拥塞控制
      • 16.1.2 减缓TCP发送
    • 16.2 经典算法
      • 16.2.1 慢启动
      • 16.2.2 拥塞避免
      • 16.2.3 慢启动和拥塞避免的选择
      • 16.2.4 Tahoe、Reno以及快速恢复算法
      • 16.2.5 标准TCP
    • 16.3 对标准算法的改进
      • 16.3.1 NewReno
      • 16.3.2 采用选择确认机制的TCP拥塞控制
      • 16.3.4 限制传输
      • 16.3.5 拥塞窗口校验
    • 16.4 伪RTO处理-Eifel响应算法
    • 16.5 扩展举例
      • 16.5.1 慢启动行为
      • 16.5.2 发送暂停与本地拥塞
    • 16.6 共享拥塞状态信息
    • 16.7 TCP友好性
    • 16.8 高速环境下的TCP
      • 16.8.2 二进制增长拥塞控制
        • 16.8.1 BIC-TCP算法
        • 16.8.2.2 CUBIC
    • 16.9 基于延迟的拥塞控制算法
      • 16.9.1 Vagas 算法
    • 16.10 缓冲区膨胀
    • 16.11 积极队列管理和ECN
    • 16.12 与TCP拥塞控制相关的攻击
  • 第17章 TCP保活机制
    • 17.1 引言
    • 17.2 描述
      • 保活步骤

第12章 TCP:传输控制协议(初步)

12.1 引言

  • 通信媒介可能会丢失或改变被传递的消息。——信息理论(information theory)& 编码理论(coding theory)
  • 如何使信息在通信信道中避免出错:
    • 使用差错校正码(某些比特的冗余)。
    • 尝试重新发送(自动重复请求,Automatic Repeat Request,ARQ)。

12.1.1 ARQ和重传

考虑多跳通信信道,有这些差错种类:

  • 分组丢失
  • 比特差错
  • 分组重新排序
  • 分组重复

最直接处理分组丢失、比特差错(无法自动纠正的那种)的方法:重发分组直到正确接收

前提是发送方需要能够判断:

  • 接收方是否已收到分组
  • 接收方收到的分组是否与之前发送方发送的一样

接收方因此需要给发送方一个信号来表明已接收到一个分组,这个方法就是ACK。但有几个问题:

  • 发送方等待ACK应多长时间才重传?
  • ACK丢失怎么办?
  • 分组已接收,但有差错怎么办?

对应解决办法:

  • 设为RTT均值,第14章详细介绍。
  • 超时重传。
  • 使用编码检错(循环冗余码/奇偶校验码)或纠错(汉明码),检出错误不发送ACK,等待重传。

对于分组复制、分组乱序问题,解决方法则很简单:每个分组加序列号。

目前为止,上面这套简单ACK机制,就可以实现可靠通信了,然而效率不高。需要允许多个分组进入网络来提高吞吐量:引出窗口的概念。

12.1.2 分组窗口和滑动窗口

  • 分组窗口:已被发送方注入但还没完成确认的分组的集合。

  • 窗口大小:窗口中的分组数量。

    滑动窗口(sliding window)协议

《TCP/IP详解 卷1》12-17章TCP笔记_第1张图片

  • 对发送方:哪些分组可被释放,哪些正在等待确认,哪些还不能发送。
  • 对接收方:哪些分组已被接收和确认,哪些是下一步期望的(已经分配多少内存来保存它们),哪些即使被接收也因限制而被丢弃。

12.1.3 流量控制和拥塞控制

流量控制:在接收方跟不上时会强迫发送方慢下来。

两种方式:

  • 基于速率的流控:给发送方指定某个速率,确保数据永远不能超过这个速率发送。
  • 基于窗口的流控:窗口大小不再固定,为了让接收方可以通知发送方应使用多大的窗口,出现了窗口更新/通告概念。 一般和 ACK 是同一个分组携带的 ,因此发送方窗口右滑的同时调整它的大小。

拥塞控制: 中间网络出现瓶颈 ,发送方速率可能超过某个路由器的能力,导致丢包,需要降低发送方速度。

流量控制方式:

  • 显式发信:即上面说的窗口更新协议,由接收方显式地告诉发送方正在发生什么。
  • 隐式发信:发送方根据其他证据(猜测)减慢发送速率。

12.2 TCP的引入

TCP 提供了一种面向连接的(connection-oriented)、可靠的字节流服务。

  • 面向连接:交换数据前先建立连接。
  • 字节流:没有消息边界,接收端无法得知每次写入的字节大小。每个端点独立选择自己的读和写大小。

12.2.2 TCP中的可靠性

  • 组包(packetization):把字节流转换成一组IP可以携带的TCP分组
  • TCP分组包含序列号,表示的是第一个字节相对于整个字节流的字节偏移
  • 序列号的机制使得分组在传送中是可变大小
  • TCP分组可以重新组合,称为重新组包(repacketization)
  • 应用程序数据被打散成TCP认为的最佳大小的块来发送

报文段:由TCP传给IP的块称为报文段。

可靠性保障关键点:

  • 校验和:校验和覆盖范围是TCP、IP头部+承载数据,但TCP校验和可能会不够强壮,所以最好要有自己的差错保护机制。
  • 重传定时器:当TCP发送一组报文段时,它通常设置一个重传计时器,等待对方的确认接收。TCP不会为每个报文段设置一个不同的重传计时器。相反,发送一个窗口的数据,它只设置一个计时器,当ACK到达时再更新超时。如果有一个确认没有及时接收到,这个报文段就会被重传。14章重点讲解。
  • ACK:可能会延迟发送;如果是双向通讯,那么ACK可能会由(反向发送的)数据分组捎带;ACK是累积的,收到N号ACK就表示
  • 序列号:TCP 会丢弃重复的报文段,记录乱序的报文段,保证不以杂乱的顺序交给应用程序数据。

12.3 TCP头部和封装

IP数据包:
《TCP/IP详解 卷1》12-17章TCP笔记_第2张图片TCP头部:
《TCP/IP详解 卷1》12-17章TCP笔记_第3张图片

  • TCP 头部的长度:20字节(无选项)~60字节。
  • 端口号:和 IP 地址组成套接字(socket)或称端点(endpoint)。每个TCP连接由一对套接字唯一地标识
  • 序列号:标识了发送端到接收端代的数据流中的第一个字节。每个字节都被赋予一个序列号,到达 2^32-1 后再循环到 0。
  • 确认号(ACK):期待接收的下一个序列号。这个字段只有在 ACK 位被启用的情况下生效。通常会一直使用(除了主动发的SYN外所有报文段都会开启)。
    • 现代 TCP 有一个选择确认(Selective ACKnowledgment,SACK)选项,允许告之发送方接收到了次序杂乱的数据。如果发送方支持选择重发(selective repeat),性能可得到明显改善。
  • SYN 位:客户端向服务器建立新连接发送第一个报文段时被启用。此时序列号字段包含了本次连接的这个方向上要使用的初始序列号(Initial Sequence Number,ISN,出于安全考虑,一般不从 0 或 1 开始)。SYN位字段消耗一个序列号,消耗序列号意味着使用重传进行可靠传输
  • 头部长度:4位,以4字节为单位,头部长度为20~60字节,20字节必须,选项可选。
  • 8位字段:一些老的实现只理解后6位。
    • CWR:拥塞窗口减(发送方降低发送速率)。
    • ECE:ECN 回显(发送方接收到一个更早的拥塞通告)。
    • URG:紧急(紧急指针字段有效,很少使用)。
    • ACK:确认。
    • PSH:推送(发送方应立即发送,接收方应尽快给应用程序传送这个数据)。
    • RST:重置连接(连接取消,常因为错误)。
    • SYN:初始化序列号。
    • FIN:结束发送数据。
  • 窗口大小:16字节,最大 65535,但可利用窗口缩放选项进行扩大。实现流量控制,是一个字节数。
  • TCP 校验和。
  • 紧急指针(Urgent Pointer):一个必须要加到序列号字段上的正偏移,以产生紧急数据的最后一个字节的序列号。是一种发送方给接收方提供特殊标志数据的方法。
  • 最大段大小(MSS):指定发送方在相反方向上希望接收到的报文段的最大值。
  • 数据部分是可选的。当相反方向没有数据发送,ACK、FIN只能被单独发送,因此没有数据部分(纯(pure)ACK)。



第13章 TCP连接管理

13.2 TCP连接的建立与终止

《TCP/IP详解 卷1》12-17章TCP笔记_第4张图片

三次握手

  • 客户端(Client)发送SYN报文,指明想连接的端口号和 ISN(c)(主动打开);
  • 服务端(Server)发送ACK(=ISN©+1)作为响应,同时置位SYN,包含 ISN(s) (被动打开);
  • 客户端发送ACK(可以携带数据)以响应服务端的SYN报文。

四次挥手

  • 主动关闭端 , 发送一个设置了FIN字段的TCP报文 ,带有当前序列号K;
  • 被动关闭端收到对端发来的关闭请求 ,发送ACK=K+1作为响应(如果该端已无数据发送,则第二次挥手和第三次可以合并);

这中间如果被动关闭端仍然在发送数据,则称为半关闭。主动关闭端不能再发送数据,但是仍然需要发送ACK ;

  • 在被动关闭端传输完数据以后会发送FIN (转变为主动关闭端),序列号为L;
  • 另一方发送ACK以响应FIN

close、shutdown双方都可以调用,close是全关闭,shutdown能实现半关闭。

为什么需要三次握手?

TCP 需要 seq 序列号来接受数据(排序、去重)和可靠重传 ,因此两端需要可靠地交换彼此的初始序列号(ISN),通过两对SYN和ACK,其中被动方发出的SYN和ACK可以合并,因此需要三次握手。

为什么需要四次挥手?

因为tcp是全双工通信,断开通信等于要关闭2个方向的数据流。 关闭一个方向需要一对FIN和ACK,关闭两个方向则需要两对。如果第二次和第三次挥手之间没有数据需要发送,则两者有可能合并(延迟确认)。

13.2.1 TCP半关闭

TCP主动关闭端发送FIN,并接受ACK响应后,另一端仍然可以继续发送数据,此时处于半关闭状态。应用程序只需要调用shutdown()函数来代替基本的close()函数,就能实现上述操作。然而,绝大部分应用程序仍然会调用close()函数来同时关闭一条连接的两个传输方向。
《TCP/IP详解 卷1》12-17章TCP笔记_第5张图片如果使用close()则没有半关闭状态,此时不会确认(ACK)接收到的数据。

13.2.2 同时打开与关闭

同时打开十分少见,通信双方在接收到来自对方的SYN之前必须先发送一个SYN;两个SYN必须经过网络送达对方。该场景还要求通信双方都拥有一个IP地址与端口号,并且将其告知对方。一旦发生,可称其为同时打开。

一个同时打开过程需要交换4个报文段,比普通的三次握手增加了一个。同时关闭同理。

《TCP/IP详解 卷1》12-17章TCP笔记_第6张图片《TCP/IP详解 卷1》12-17章TCP笔记_第7张图片


13.2.3 初始序列号

初始序列号需要仔细选择以应对:

  1. 上次连接中延迟的失效报文被重新建立的连接当成有效数据;

  2. 恶意攻击:只要四元组一样,序列号合适,就能伪造TCP报文段。

现代操作系统通常采用半随机的方法选择初始序列号,比如:

ISN = M + F(localhost, localport, remotehost, remoteport)

M是一个32位计数器,这个计时器每隔4毫秒加1,以防止ISN重叠。

F是一个Hash算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值。散列函数每隔5分钟改变一次。

13.2.5 连接建立超时

  • syn重发次数:指定配置文件配置,一般等于5。
  • synack重发次数:指定文件配置,默认为5。
  • 指数回退:syn每次重发的间隔是上一次的一倍。

13.3 TCP选项

《TCP/IP详解 卷1》12-17章TCP笔记_第8张图片

注:tcp数据长度,是没有在tcp头显式保存的,而是通过ip层的分组长度来算出,tcp数据长度 = ip分组长度 - tcp头长度。

因为TCP头部长度字段以4字节为单位,因此长度应该是四字节的倍数,必要时需要用NOP选项填充。

13.3.1 最大段大小选项

MSS:

  • 指TCP协议所允许的从对方接收到的最大报文段,通信两端各自于SYN报文段指定。

  • 最大段大小,记录数据(不包括头部)长度,占2个字节。默认为536字节(任何主机至少能处理536字节),IPv4的典型值是1460字节(IPv6为1440字节)

  • MTU:最大传输单元。路径MTU为1500字节。ipv4:1460+40字节,ipv6:1440+60字节。

  • 65535的MSS是特殊值,ipv6网络中超长数据包会用到,表示无限大。但实际MSS仍受路径MTU限制,所以MSS值为MTU-(40 IPv6头+20 tcp头)

13.3.2 选择确认选项

SACK和SACK-Permitted:

  • 发SYN/SYNACK时就得发SACK-Permitted选项告诉对方支持SACK(双向)
  • 当接收到乱序的数据时,可向发送方发送SACK选项
  • SACK选项长度为8n+2,2个字节一个表示选项种类一个表示n,n等于SACK块数量
  • SACK块:已经成功接收的序列号范围(序列号32位,需要start和end,所以总共要8个字节)
  • 最大n为3,SACK最多占8*3 + 2 = 26字节(因为一般还有个时间戳)

13.3.3 窗口缩放选项

WSOPT(或WSCALE):

  • 长度:16bits
  • 范围: 0-14,0表示没有缩放。
  • 最大窗口大小:65535 x 2^14 ,约1GB。
  • 只能出现在SYN报文中。
  • 主动打开者发WSCALE,被动打开者接收到WSCALE才能在SYNACK中发WSCALE。
  • 必须两者都设定该选项,如果主动打开者没接收到被动打开者的WSCALE,就设为0。
  • 缩放大小是根据接收缓存的大小自动选取的。
  • 大带宽、高延迟网络上提供海量数据传输服务时,窗口缩放选项就非常有意义。

13.3.4 时间戳选项与防回绕序列号

时间截选项(记作TSOPT或TSopt)要求发送方在每一个报文段中添加2个4字节的时间戳数值。

当使用时间戳选项时,发送方将一个32位的数值填充到时间戳数值字段(称作TSV或TSval)作为时间戳选项的第一个部分;而接收方则将收到的时间戳数值原封不动地填充至第二部分的Timestamp Echo Retry字段(称作TSER或TSecr)。

时间戳只是一个单调增加的数值,两端时间戳并不需要同步。

作用:

  1. 估算RTT,以设置重传超时。RTT = current time - TSER
  2. 防回绕序列号:若旧的失效报文延迟出现时,当前序列号已经回绕到旧序列号处,则不考虑时间戳的情况下旧报文会被误收。时间戳可以被看作时32位的扩展序列号,有效防止回绕序列号问题。

13.4 路径MTU发现

MTU指经过两台主机之间路径的所有网络报文中最大传输单元的最小值

注:一条连接的两个方向的TCP中的路径最大传输单元是不同的

当报数据包大小大于MTU时,会在传输过程中被分片,影响吞吐量。因此需要使用路径MTU发现来找到合适的数据包大小。

路径MTU发现过程:

连接建立时,TCP根据对端声明的MSS(没声明则默认536字节)或对外接口的最大传输单元来选择最大段大小。

一旦选定了最大段大小初始值,该方向所有IPv4数据包(IPv6不支持中间节点分片)都会将DF(Don’t Fragment)位置位。分组大小超过某链路MTU时,发送端会收到ICMP PTB(Packet Too Big)消息(携带推荐的MTU信息),得以修改路径MTU。可能需要重复多次。

路由是动态变化的 ,因此每隔10min需要尝试一个更大的数值。

13.5 TCP状态转换

《TCP/IP详解 卷1》12-17章TCP笔记_第9张图片非粗实线为同时打开、同时关闭时状态转移。

《TCP/IP详解 卷1》12-17章TCP笔记_第10张图片
ESTABLISHED是通信双方双向传输数据的状态。从LISTEN到SYN_SENT的状态转换在TCP协议中是合法的,但却不被伯克利套接字所支持,因此比较少见。

13.5.2 TIME_WAIT状态

TIME_WAIT状态也称为2MSL等待状态。在该状态中,TCP将会等待两倍于最大段生存期(Maximum Segment Lifetime,MSL,常设为30s、1min或2min)的时间。

作用:

  • 为了重传最终的ACK,或者叫可靠地实现TCP全双工连接的终止。TCP总是重传FIN,直到它收到一个最终的ACK。因此需要TIME_WAIT响应重传的FIN。
  • 允许老的分组在网络中消逝。2MSL足够让旧报文甚至旧报文的ACK报文被丢弃,以避免新连接混淆旧连接的报文。

TIME_WAIT状态只是限制短时间内与另一端在相同端口上建立新连接,只有一个端口会有该状态。并且只有主动关闭端的端口会有TIME_WAIT状态,因为主动关闭端通常时客户端,使用的是临时端口不影响使用其他临时端口重新连接。服务端使用的是知名端口,不应该被浪费。

许多实现与API都提供了绕开这一约束的方法。在伯克利套接字API中,SO_REUSEADDR套接字选项就支持绕开操作即使有TIME_WAIT状态的端口仍然可以使用,不过,TCP的规则仍会防止该端口号被处于2MSL等待状态的同一连接的其他实例重新使用(比如通过时间戳、序列号来防止)。

13.5.4 FIN_WAIT_2状态

非半关闭状态下FIN_WAIT_2无限等待问题:

  • 主动关闭方发送FIN并收到ACK就进入了FIN_WAIT_2;

  • 处于FIN_WAIT_2时需要等另一方发送FIN(永久等待);

  • 全关闭时会自动启动定时器,60秒(net.ipv4.tcp_fin_timeout)后若连接处于空闲,则强制进入CLOSED状态。

13.6 RST重置报文段

当发现一个到达的报文段对于相关连接而言是不正确的时, TCP就会发送一个重置报文段(RST位置位的报文段)。

重置报文段通常会导致TCP连接的快速拆卸

13.6.1 针对不存在的端口的连接请求

UDP协议规定,当一个数据报到达一个不能使用的目的端口时就会生成一个ICMP目的地不可达(端口不可达)的消息,TCP协议则使用重置报文段来代替完成相关工作(提示Connection refused)。

重置报文的ACK位置位,并且确认号为接收到的SYN序列号+1,以防止伪造的重置报文段攻击。

13.6.2 终止一条连接

发送FIN终止连接,叫做有序释放,FIN是在所有排队数据发送完后才被发送出去,不会丢失数据

但在任意时刻,可以发送重置报文代替FIN来终止连接,称为终止释放,任何排队数据都被抛弃,重置报文段被立即发送出去。

套接字API将SO_LINGER套接字选项数值设为0来实现终止释放。

13.6.3 半开连接

如果一端在未告知另一端情况下关闭或终止连接(比如主机崩溃),那么该条TCP处于半开状态。

只要不尝试通过半开连接传输数据,正常工作的一端将不会检测出另一端已经崩溃(除非使用了keepalive选项)。

再次传输数据时,会接收到重置响应。

13.7 TCP服务器选项

13.7.1 TCP端口号

《TCP/IP详解 卷1》12-17章TCP笔记_第11张图片
处于LISTEN状态的本地节点(监听套接字)会独自地运行。它用于为并行服务器接收未来可能出现的请求。当有新的连接请求到达并被接收时,操作系统中的TCP模块创建处于ESTABLISHED状态的新节点。其端口号与LISTEN状态时相同。

只有处于LISTEN状态的节点能接受SYN报文段,接受进入的连接请求。

13.7.3 限制外部节点

《TCP/IP详解 卷1》12-17章TCP笔记_第12张图片

TCP允许一台服务器为一个完全指定的外部节点被动打开,不过普通的伯克利套接字API没有提供这一实现

13.7.4 进入连接队列

被用于应用程序之前新的连接可能会处于下述两个状态:

  1. 一种是连接尚未完成但是已经接收到SYN(也就是处于SYN_RCVD状态)。
  2. 另一种是连接已经完成了三次握手并且处于ESTABLISHED状态,但还未被应用程序接受。

因此在内部操作系统通常会使用两个不同的连接队列分别对应上述两种不同的情况。

在现代Linux内核中,套接字API(通过backlog)可以控制第二种状况下的连接数目(ESTABLISHED状态的连接),因此,应用程序能够限制完全形成的等待处理的连接数目。在Linux中,将会适用以下规则:

  1. 当一个连接请求(SYN报文段)到达,系统将会检查处于SYN_RCVD的连接数是否超过阈值,超过则拒绝进入。
  2. 每一个监听节点(LISTEN状态)都拥有一个固定长度的连接队列(backlog)。其中的连接已经被TCP完全接受(即三次握手已经完成),但未被应用程序接受。backlog的数目必须在0~net.core.somaxconn(默认128)。
  3. 只要监听节点队列有空间,TCP就会应答SYN完成三次握手。客户端可能认为服务器已经准备好接收数据,但服务端应用程序可能还未收到通知(直到accept()返回),此时TCP模块会把到来的数据存入缓存区。
  4. 如果队列空间不足,TCP将会延迟对SYN做出响应。也可以设置为发送重置报文,但不可取。

在借助伯克利套接字实现的TCP中,当应用程序被告知一条连接已经到达时,TCP的三次握手过程已经完成。上述行为也意味着一个TCP服务器无法让一个客户端的主动打开操作失败。如果此后服务器决定不向该客户端提供服务,那它只能关闭(发送一个FIN)或者重置这条连接(发送一个RST)。

13.8 与TCP连接管理相关的攻击

SYN泛洪

SYN Flood是一种TCP拒绝服务攻击(Dos),恶意攻击者发送大量伪造源IP的SYN报文给服务器,使其浪费资源维持大量半打开连接,导致拒绝后续合法连接请求。

一种解决方法为SYN cookie

服务器不会为SYN报文分配任何资源,而是用四元组以及一些仅有该服务器知道的参数进行散列,作为SYN+ACK报文段的初始序列号。

如果客户端合法,则返回ACK报文(确认号为ISN(s)+1)。服务器收到后,用四元组和参数的哈希验证ACK报文确认号是否正确。

SYN Cookies 是根据以下规则构造的初始序号:

  • t为一个缓慢递增的时间戳(64s增1);
  • m为服务器会在 SYN 队列条目中存储的最大分段大小(Maximum segment size);
  • s为一个加密散列函数对服务器和客户端各自的 IP 地址和端口号以及t进行运算的结果。返回得到的数值s必须是一个24位值。

初始 TCP 序号,也就是所谓的SYN cookie,按照如下算法得到:

  • 头五位:t mod 32;
  • 中三位:m编码后的数值;
  • 末24位:s本身;

缺陷:MSS大小有限,服务端无法保存TCP选项,因此只能丢弃选项。所以SYN Cookie并未作为默认设置。

超小MTU攻击

攻击者伪造一个ICMP PTB消息,消息包含一个非常小的MTU值(如68字节),就迫使受害者的TCP尝试采用非常小的数据包来填充数据,大大降低了性能。禁用最大MTU发现功能就能抵御这个攻击。

序列号/劫持攻击

就是对已建立的连接插入攻击数据的攻击。解决办法应该是用校验码。

欺骗攻击

破坏会改变现有TCP连接的行为。

例如伪造RST报文段:前提是序列号要在该连接的序列号范围内。

抵御方式:认证每一个报文段(TCP-AO);要求RST报文段拥有一个特殊的序列号而不只是在范围内的序列号、要求时间戳选项具有特定的数值。



第14章 TCP超时与重传

TCP根据接收端返回至发送端的一系列确认信息来判断是否出现丢包。当数据段或确认信息丢失,TCP启动重传操作,重传尚未确认的数据。

TCP拥有两套独立机制来完成重传,一是基于时间,二是基于确认信息的构成。第二种方法通常比第一种更高效。

  1. TCP发送数据时会设置一个计时器,若计时器超时未收到数据包的ACK,则会引发相应的基于计时器的重传操作,计时器超时称为重传超时(RTO)
  2. 另一种重传方式称为快速重传,通常发生在没有延时的情况下,若累积确认无法返回新的ACK(返回的可能是重复ACK),或是ACK包含的选择确认信息SACK表明出现数据丢失时,快速重传会推断出现丢包。

14.2 简单的超时与重传

每次重传间隔时间加倍称为二进制指数回退

TCP拥有两个阈值决定如何重传一个报文段

  1. R1表示TCP在向IP层传递消极建议前(如重新评估当前IP路径),愿意重传的次数。
  2. R2(大于R1)指示TCP放弃当前连接的时机。

R1和R2分别至少设为三次重传和100s。SYN除外,至少设为3min。

Linux中,对于一般数据,R1 R2可以通过应用程序,或使用系统变量(单位都是重传次数)设置。

  • tcp_retries1(R1):连接已经建立后,基本重传次数。次数达到后,会先尝试让网络层更新路由再继续发包。默认为3次。

  • tcp_retries2(R2):连接已经建立后,最大重传次数。次数达到后,会断开连接。默认为15,对应13~20min。该值又称为TcpMaxDataRetransmissions,是最主要的值。

对于SYN报文段,变量net.ipv4.tcp_syn_retries和net.ipv4.tcp_synack_retries限定重传次数,默认值为5(约180秒)。

14.3 设置重传超时(RTO)

RTT的测量可以采用两种方法:

  1. 重传队列中数据包的TCP控制块
    在TCP重传队列中保存着发送而未被确认的数据包,数据包skb中的TCP控制块包含着一个变量,
    tcp_skb_cb->when,记录了该数据包的第一次发送时间。
    RTT = 当前时间 - when
  2. TCP Timestamp选项
    RTT = 当前时间 - ACK中Timestamp选项的回显时间(TSER)

方法1无法分辨是原始数据包还是重传数据包,而时间戳可以。

TCP超时和重传的基础是怎样根据连接的RTT设置RTO。

14.3.1 经典方法

# 采用如下公式得到平滑的RTT估计值(称为SRTT):
SRTT ← α(SRTT)+(1-α)RTTs (s为下标)
# SRTT是基于现存值和新的样本值RTTs得到的更新结果,常量α是平滑因子,推荐值为0.8~0.9,这个方法被称为指数加权移动平均(EWMA)或者低通过滤器。

# 采用以下公式设置RTO:
RTO = min(ubound,max(1bound,(SRTT)β))
# β为时延离散因子,推荐值为1.3~2.0,ubound为RTO的上边界(可设定建议值,如1分钟),lbound为RTO的下边界(可设定建议值,如1秒)。它使得RTO的值设置为1秒或约2倍的SRTT。

相对于稳定的RTT来说,这种方法能取得不错的性能,然而若TCP运行于RTT变化较大的网络中,则无法获得期望的效果。

14.3.2 标准方法

结合了平均值和平均偏差来进行估算,更适应RTT变化幅度较大的情况。

srtt ← (1 - g)(srtt) + (g)M
rttvar ← (1 - h)(rttvar) + (h)(|M - srtt|)
RTO = srtt + 4(rttvar)
# srtt代替了之前的SRTT,且rttvar为平均偏差的EWMA,M代替了之前的RTTs。

便于计算机计算的形式:

Err = M - srtt
srtt ← srtt + g(Err)
rttvar ← rttvar + h(|Err| - rttvar)
RTO = srtt + 4(rttvar)
# srtt为均值的EWMA,rttcar为绝对误差|Err|的EWMA,Err为测量值M与当前RTT估计值srtt之间的偏差。

增量g为新RTT样本M占srtt估计值的权重,取1/8,增量h为新平均偏差样本(新样本M与当前平均值srtt之间的绝对误差)占偏差估计值rttvar的权重,取1/4。

TCP时钟一个"滴答"的时间长度称为粒度,通常该值相对较大(约500ms),标准方法适用于该粒度。但近期实现的时钟使用更细的粒度(如linux的1ms)。

14.3.2.2 初始值

根据[RFC2018],RTO的初始值设为1s,第一个SYN报文段采用的超时间隔为3s。当得到首个RTT样本值M后,来根据下面的式子初始化srtt和rttvar,然后算出RTO。

srtt <-- M
rttvar <-- M/2

14.3.2.4 带时间戳的RTT测量

TCP并非对其接收到的每个报文段都返回ACK,例如,当传输大批量数据时,TCP通常采取每两个报文段返回一个ACK的方法。

另外,当数据出现丢失、失序或重传成功时,TCP的累积确认机制表明报文段与其ACK之间并非严格的一一对应关系。

为解决这些问题,使用时间截选项的TCP(大部分的Linux和Windows版本都包含)采用如下算法来测量RTT样本值:

  1. TCP发送端在其发送的每个报文段的TSOPT的TSV部分携带一个32比特的时间戳值。该值包含数据发送时刻的TCP时钟值。

  2. 接收端记录接收到的TSV值(名为TsRecent的变量)并在对应的ACK中返回,并且记录其上一个发送的ACK号(名为LastACK的变量)。

  3. 当一个新的报文段到达(接收端)时,如果其序列号与LastACK的值吻合(即为下一个期望接收的报文段),则将其TSV值存入TsRecent。

  4. 接收端发送的任何一个ACK都包含TSOPT,TsRecent变量包含的时间戳值被写入其TSER部分。

  5. 发送端接收到ACK后,将当前TCP时钟减去TSER值,得到的差即为新的RTT样本估计值。

FreeBSD,Linux以及近期的Windows版本都默认启用时间戳选项。在连接初始化过程中,TCP通信的一方使用时间戳,则另一方默认启用。

14.3.3 Linux采用的方法

与标准方法稍微有点差别,因为标准方法发布的时候,时钟粒度普遍为500ms,而linux的时候,时钟粒度为1ms。linux采用更频繁的RTT测量与更细的时钟粒度,这会提高RTT的准确度。但是,

  1. 多次测量之后,由于积累了大量的RTT样本值,这会导致rttvar随时间减小趋近于0,基于大数定律。这是不好的,这样RTO就接近等于srtt了。
  2. 当某个RTT样本值显著低于srtt时,标准方法会增大rttvar,这会导致RTO不降反升,为什么?因为RTO = srtt +4*rttvar,rttvar是4倍,占的权重更大。这是不应该的,此时我们应该让RTO减小才对。

为了解决这两个问题。linux新增了两个新的变量,mdev和mdev_max,mdev记录瞬时平均偏差估计值,mdev_max记录测量过程中最大的mdev,它的下限是50ms。
rttvar需要定期更新保证其不小于mdeg_max,第1个问题解决了。

用标准方法得到初始srtt rttvar RTO后,采用下面的方法估计RTO:

m为得到的RTT。
// 计算srtt,与标准方法一样
srtt = (7/8)*srtt + (1/8)*m
// 若m小于RTT的下界,就减小它的权重。
if(m < (srtt - mdev)
	mdev = (31/32)*mdev + (1/32)*|srtt-m|
else
	mdev = (3/4)*mdev +(1/4)*|srtt-m|
mdev_max = max(mdev_max, mdev)
// 计算rttvar
rttvar = mdev_max
// 得到RTO,这里与标准方法一样。
RTO = srtt + 4*rttvar

转自:https://www.cnblogs.com/K-Cream/p/14088076.html

详细过程:
《TCP/IP详解 卷1》12-17章TCP笔记_第13张图片

14.4 基于计时器的重传

设定计时器,记录被计时的报文段序列号,若及时收到该报文段的ACK,那么计时器被取消。之后发送新数据报时,设定新计时器,并记录新序列号。如此重复。(同一时间只有一个计时器运行)

若在设定的RTO内,没收到ACK,就会触发超时重传。

TCP将超时重传视为相当重要的事件,当发生这种情况时,它通过降低当前数据发送率来对此进行快速响应。实现方法:

  • 基于拥塞控制减小发送窗口大小;
  • 重传大于1次时,增大RTO退避因子,RTO = γRTO,γ = 1 2 4 8 ··· ,但不能超过上限。一旦收到ACK,γ重置为1。

当网络无法正常传输时,重传计时器为TCP连接提供了“最后一招的重新启动”。但大多数情况下,计时器超时并触发重传是不必要的,会导致网络利用率下降。快速重传更加高效。

14.5 快速重传

当接收到失序报文段时,TCP需要立即生成确认信息(重复ACK),并且失序情况表明接收端缓存出现了空缺。

当发送端收到重复ACK的次数(不包括第一个非重复ACK和窗口更新ACK)达到重复ACK阈值(dupthresh,通常为3),就会重传对应的分组。 当然也可以同时发送新数据

img

不采用SACK时,在接收到有效ACK前至多只能重传一个报文段。采用SACK,ACK可包含额外信息,使得发送端在每个RTT时间内可以填补多个空缺。

下图可通过Wireshark的“统计ITCP流图1时间序列图"(Statistics | TCP Stream Graph | Time-Sequence Graph)功能得到。

NewReno算法

《TCP/IP详解 卷1》12-17章TCP笔记_第14张图片

恢复点:发送端在执行重传前发送的最大序列号。

当收到新的ACK,如果ACK值小于恢复点(称为部分ACK),不足以到达恢复点,那么TCP发送端立即发送可能丢失的报文段,直到ACK到达或超过恢复点,重传结束,恢复阶段结束。(不需要多次重复发送重复ACK)

14.6 带选择确认的重传

接收端在TCP连接建立期间(SYN报文段)收到SACK许可选项即可生成SACK。

当采用SACK选项时,一个ACK可包含三四个告知失序数据的SACK信息。每个SACK信息包含2个32位的序列号,代表接收端存储的失序数据的起始至最后一个序列号(加1)。

SACK选项指定n个块的长度为8n+2字节,并且通常SACK会与TSOPT一同使用,因此需要额外的10个字节,意味着SACK在每个ACK中只能包含三个块。包含一个或多个SACK块的ACK有时也简单称为"SACK"。

因为SACK可能丢失,所以多个SACK中可能会出现重复的块信息。

例子

《TCP/IP详解 卷1》12-17章TCP笔记_第15张图片

0.967s时刻到达的SACK包含两个块:[28001,29401]和[25201,26601]。该SACK为序列号23801的重复ACK,表明接收端现在需要起始序列号为23801和26601的两个报文段。发送端立即响应,启动快速重传,但由于拥塞控制机制,发送端只重传了一个报文段23801,随着另外的两个ACK的到达,发送端被允许发送第二个重传报文段26601。

SACK快速重传不需要三次重复ACK,TCP更早地启动了重传,但恢复的出口本质上是一致的。

发送端采用SACK并不能百分百地提高整传输性能。但在RTT较大,丢包严重的情况下,SACK的优势就能很好地体现出来,因为在这样的环境下,一个RTT内能填补多个空缺显得尤为重要。

14.7 伪超时与重传

在很多情况下,即使没有出现数据丢失也可能引发重传。这种不必要的重传称为伪重传(spurious retransmission),其主要造成原因是伪超时(spurious timeout),即过早判定超时,其他因素如包失序、包重复,或ACK丢失也可能导致该现象。在实际RTT显著增长,超过当前RTO时,可能出现伪超时。

处理伪超时问题的方法通常包含检测(detection)算法与响应(response)算法。检测算法用于判断某个超时或基于计时器的重传是否真实,一旦认定出现伪超时则执行响应算法,用于撤销或减轻该超时带来的影响

检测方法有: D-SACK、Eifel检测算法、F-RTO。

回退N步(GBN,go-back-N):连续发了n个报文段,如果网络突然缓慢,最前面的那个超时了,会触发重传,此时后面的n-1个包也没被确认,那么n个报文段都重传(回退)。

14.7.1 重复SACK(DSACK)扩展

在接收端采用DSACK(重复SACK),并结合通常的(开启了)SACK发送端,可在第一个SACK块中告知接收端收到的重复报文段序列号。DSACK的主要目的是判断何时的重传是不必要的,并了解网络中的其他事项。因此发送端至少可以推断是否发生了包失序、ACK丢失、包重复或伪重传。

原理:SACK接收端的变化在于,允许包含序列号小于(或等于)累积ACK号字段的SACK块。

14.7.2 Eifel检测算法

利用了TCP的TSOPT来检测伪重传。

发生超时重传时,Eifel算法等待接收下一个ACK,若为针对第一次传输(即原始传输)的ACK(用时间戳判断),则判定该重传是伪重传。

14.7.3 前移RTO恢复(F-RTO)

F-RTO是检测伪重传的标准算法,它不需要任何TCP选项。该算法只检测由重传计时器超时引发的伪重传,对之前提到的其他原因引起的伪重传则无法判断。

F-RTO会修改TCP的行为,在超时重传后收到第一个ACK时,TCP会发送新的数据,之后再响应后一个到达的ACK。如果其中有一个为重复ACK,则认为此次重传没问题,如果两个都不是重复ACK,则表示该重传是伪重传。

如果新数据的传输得到了相应的ACK,就使得接收端窗口前移。如果新数据的发送导致了重复ACK,那么接收端至少有一个或更多的空缺。

14.7.4 Eifel响应算法

说白了就是更新srtt和rttvar。因为伪超时导致临时修改了srtt和rttvar(RTO变了),检测发现伪超时,那就得恢复到正常的srtt、rttvar。

一旦判断出现伪重传,则会引发一套标准操作,即Eifel响应算法。原则上超时重传和快速重传都可使用Eifel响应算法,但目前只针对超时重传做了相关规定。

Eifel响应算法根据是否能尽早或较迟检测出伪超时的不同而有所区别。前者称为伪超时,通过检查ACK或原始传输来实现;后者称为迟伪超时,基于有超时而引发的重传所返回的ACK来判定。

在重传计时器超时后,它会查看srtt和rttvar的值,并按如下方式记录新的变量srtt_prev和rttvar_prev:

srtt_prev = srtt + 2(G)
rttvar_prev = rttvar

在任何一次计时器超时后,都会指定这两个变量,但只要在判定出现伪超时才会使用它们,用于设定新的RTO。在上式中,G代表TCP时钟粒度。srtt_prev设为rtt加上两倍的时钟粒度是由于srtt的值过小,可能会出现伪超时。

完成srtt_prev和rttvar_prev的存储后,就要触发某些检测算法。运行检测算法后可得到一个特殊的值,称为伪恢复。如果检测到一次伪超时,则将伪恢复置为SPUR_TO。如果检测到迟伪超时,则将其置为LATE_SPUR_TO。

若伪恢复为SPUR_TO,TCP可在恢复阶段完成之前进行操作,通过将下一个要发送报文段(称为SND.NXT)的序列号修改为最新的未发送过的报文段(称为SND.MAX)。这样就可在首次重传后避免不必要的"回退N"行为。

如果检测到一次迟伪超时,此时已生成对首次重传的ACK,则SND.NXT不变。在以上两种情况下,都要重新设置拥塞控制状态(第16章介绍)。一旦接收到重传计时器超时后发送的报文段的ACK,就按如下方式更新srtt、rttvar、RTO:

srtt ← max(srtt_prev,m)
rttvar ← max(rttvar_prev,m/2)
RTO ← srtt + max(G,4(rttvar))

m是一个RTT样本值,它是基于超时后收个发送数据收到的ACK计算得到的。

14.8 包失序与包重复

包传输异常有:失序、重复、丢失。TCP需要区分。

14.8.1 失序

失序原因:

  • IP可以选择另一条传输链路(比如更快的);
  • 一些高性能路由器采用多个并行链路。

失序问题可能存在于TCP连接的正向或反向链路中:

  • 反向(ACK)链路失序,就会使得TCP发送端窗口快速前移,接着又可能收到一些显然重复而应被丢弃的ACK。由于TCP的拥塞控制行为,这种情况可能导致发送端出现不必要的流量突发行为,影响可用网络带宽。
  • 正向链路失序,TCP可能无法识别失序和丢包。当失序比较严重时(数据包的字节值差距大),TCP会误认为数据已经丢失,从而触发快速重传,导致伪重传。为解决这一问题,快速重传仅在达到重复阈值(dupthresh)后才会被触发。
    《TCP/IP详解 卷1》12-17章TCP笔记_第16张图片

互联网中严重的失序并不常见,且通过将dupthresh设为相对较小值(3)就能处理大部分情况,或者一些方法可以动态调整dupthresh值,如Linux的TCP实现。

14.8.2 重复

链路层重传时会生成重复副本,TCP可能出现混淆,导致接收端生成一系列重复的ACK,触发伪快速重传。

《TCP/IP详解 卷1》12-17章TCP笔记_第17张图片

DSACK能简单地忽略这个问题,因为重复ACK没有包含失序信息,意味着ACK是重复数据。

14.9 目的度量

TCP能不断“学习”发送端与接收端之间的链路特征。学习的结果显示为发送端记录一些状态变量,如srtt和rttvar。一些TCP实现也记录一段时间内已出现的失序包的估计值。

较新的TCP实现将这些数据保存了下来。当创立一个新的连接时,首先查看数据结构中是否存在与该目的端的先前通信信息。如果存在,则选择较近的信息,据此为srtt,rttvar以及其他变量设置初值。

14.10 重新组包

当TCP超时重传,它并不需要完全重传相同的报文段。TCP允许执行重新组包(repacketization),发送一个更大的报文段来提高性能。(不能超过接收端通告的MSS,也不能大于路径MTU)

14.11 与TCP重传相关的攻击

低速率DoS攻击:

  • 预测受害TCP的重传时间,每次受害TCP重传时,发一堆数据给它并导致重传超时,进而导致对方减小发送速率、退避发送,最终导致无法正常使用网络带宽。
  • 预防方法:随机随选RTO,使得攻击者无法预知确切的重传时间。

与DoS相关但不同的一种攻击:减慢受害TCP的发送,使RTT估计值过大(过分被动),导致丢包后不会立即重传。

相反的攻击:攻击者在数据发送完成但还未到达接收端时伪造ACK。这样攻击者就能使受害TCP认为连接的RTT远小于实际值,导致过分发送而造成大量的无效传输。



第15章 TCP数据流与窗口管理

15.2 交互式通信

TCP流量研究表明,通常90%或者更多的TCP报文段都包含大批量数据(如Web、文件共享、电子邮件、备份),其余部分则包含交互式数据(如远程登录、网络游戏)。批量数据段通常较大(1500字节或更大),而交互式数据段则会比较小(几十字节的用户数据)。

交互式TCP连接指该连接需要在客户端和服务器之间传输用户输入信息,如按键操作、短消息、操作杆或鼠标操作。对于这些操作,如果用较小的报文段来承载信息,则传输协议需要耗费很高的代价;反之采用较大的报文段则会引入更大的延时,对延迟敏感类应用造成负面影响。因此需要权衡相关因素来找到适合的方法。

以ssh(安全外壳)应用为例,对一个ssh连接,每个交互按键通常都会生成一个单独的数据报(每个按键独立传输)。另外,ssh会在远程系统调用shell,对客户端的输入字符做出回显。

15.3 延时确认

在许多情况下,TCP并不对每个到来的数据包都返回ACK,利用TCP的累积ACK字段可能实现这个功能。累积确认可以允许TCP延迟一段时间发送ACK,以便将ACK和相同方向上需要传的数据结合发送。主机需求RFC[RFC1122]指出,TCP实现ACK延迟的时延应小于500ms,实践中最大取200ms

采用延时ACK的方法会减少ACK传输数目,减轻网络负载。对于批量数据传输通常为2 : 1的比例。

延迟发送ACK的最大时延可以动态配置,Linux使用了一种动态调节算法,可以在每个报文段返回一个ACK(称为“快速确认”模式)与传统延时ACK模式间相互切换

15.4 Nagle算法

ssh连接中,微型数据包内有效数据所占比例甚微,会加重广域网的拥塞。因此,Nagle算法被提出。

Nagle算法要求:

  1. 当一个TCP连接中有在传数据(已发送但未确认),小的报文段(长度小于SMSS, sender’s MSS)就不能被发送,直到所有的在传数据都收到ACK。
  2. 并且,在收到ACK后,TCP需要收集这些小数据,将其整合到一个报文段中发送。

特点:

  • 迫使TCP遵循停等规程(stop-and-wait)——只有等接收到所有在传数据的ACK后才能继续发送。发包间隔等于RTT。
  • 实现了自时钟控制(self-clocking)。ACK返回越快,数据传输也越快。
  • 传输的包数目更少而长度更大,但同时传输时延也更长

对比图:

《TCP/IP详解 卷1》12-17章TCP笔记_第18张图片

15.4.1 延时ACK和Nagle算法结合

简单来说就是会死锁:

  • 服务端(Nagle)发一个包后开始等待ACK
  • 客户端(延时ACK)收包后不马上发送ACK而是等待一段时间再发

因为客户端最终会发出ACK(延时ACK计时器超时),所以死锁可解。但最终性能变差。

禁用Nagle算法:

  • socket设置TCP_NODELAY选项
  • 整个系统设置nodelay

15.5 流量控制与窗口管理

流量控制通过可变滑动窗口实现。

《TCP/IP详解 卷1》12-17章TCP笔记_第19张图片
每个TCP头部的窗口大小字段表明接收端可用缓存空间的大小,以字节为单位。该字段长度为16位,但可以通过窗口缩放选项来扩大长度。当进程处理数据较慢时,可用存储空间就会减小,窗口大小随之减小。

15.5.1 滑动窗口

TCP连接的收发数据量是通过一组窗口结构来维护的。每个TCP活动连接的两端都维护一个发送窗口结构和接收窗口结构。

TCP发送窗口结构图:

《TCP/IP详解 卷1》12-17章TCP笔记_第20张图片

  • 字节为单位
  • 由接收端通告的窗口称为提供窗口:SND.WND
  • 发送窗口左边界: SND.UNA
  • 可用窗口值:SND.UNA + SND.WND - SND.NXT

窗口运动描述术语:

  • 关闭(close):左边界右移(窗口缩小)。发生在已发送数据得到ACK确认。
  • 打开(open):右边界右移(窗口变大)。可发送数据量变大。当已确认数据得到处理,可用缓存变大,窗口随之变大。
  • 收缩(shrink):右边界左移。RFC [RFC1122] 不支持,TCP必须避免这一问题。

当得到的ACK号增大而窗口大小保持不变时,称为窗口向前滑动;随着ACK号增大窗口却减小,则左右边界距离减小,当左右边界相等时,称为零窗口。零窗口时,发送端不能再发送新数据,且TCP发送端开始探测对方窗口以伺机增大提供窗口。

接收端也维护一个窗口结构:

《TCP/IP详解 卷1》12-17章TCP笔记_第21张图片
该窗口结构记录了已接收并确认的数据,以及它能够接收的最大序列号。该窗口可以保证其接收数据的正确性,特别是接收端希望避免存储重复的已接收和确认的数据以及避免存储不应接收的数据

15.5.2 零窗口与TCP持续计时器

零窗口可以有效组织发送端发送数据。当接收端重新获得可用空间时,会给发送端传输一个窗口更新(window update),告知其可继续发送数据。这样的窗口更新通常都不包含数据(为“纯ACK"),不能保证其传输的可靠性。因此TCP必须有相应措施能处理这类丢包,否则会造成死锁。

为防止这种死锁的发生,发送端会采用一个持续计时器间歇性地询问接收端的窗口。当发送方收到了窗口为0的确认,就会启动一个持续计时器,持续计时器超时后会触发窗口探测(window probe)的传输,强制要求接收端返回ACK(其中包含了窗口大小字段)。RFC[RFC1122]建议在一个RTO之后发送第一个窗口探测,随后以指数回退。窗口探测包含一个字节的数据,因此被TCP可靠传输

15.5.3 糊涂窗口综合征(SWS)

基于窗口的流量控制机制,尤其是不使用大小固定的报文段的情况(如TCP),可能会出现称为糊涂窗口综合征(SWS)的缺陷。指当发送端应用进程产生数据很慢、或接收端应用进程处理接收缓冲区数据很慢,或二者兼而有之;就会使应用进程间传送的报文段很小,特别是有效载荷很小。因此耗费资源,传输效率很低。

SWS出现的原因:

  • 接收端通告窗口较小
  • 发送端发送的数据段较小

避免SWS需遵循的规则:

  1. 对于接收端来说,不应通告小的窗口值。[RFC1122]描述的接收算法中,在窗口增至一个全长的报文段(接收端MSS)或者接收端缓存空间的一半(取两者中较小值)之前,不能通告比当前窗口(可能为0)更大的窗口值。可能有两种情况会用到该规则:当应用程序处理接收到的数据后使得可用缓存增大;TCP接收端需要强制返回对窗口探测的响应。

  2. 对于发送端来说,不应发送小的报文段,而且需要由Nagle算法控制何时发送。为避免SWS问题,只有指示满足以下条件之一时才能传输报文段:

    • 全长(发送MSS字节)的报文段可以发送。
    • 数据段长度 ≥ 接收端通告过的最大窗口值的一半的,可以发送。
    • 满足一下任一条件的都可以发送:(i)某一ACK不是目前期盼的(即没有未经确认的在传数据);(ii)该连接禁用Nagle算法。

例外:有时如果不通告小窗口,可能会造成右窗口左移(之前通告过大窗口,接收端接收了大部分数据后,剩下小窗口)。此时,需要违反如上规则,发送小窗口,避免右窗口左移。(避免窗口收缩优于避免SWS

15.5.4 大容量缓存与自动调优

接受缓存、发送缓存较小会导致TCP应用的吞吐性能差。

这个问题非常严重,因此很多TCP协议栈中上层应用不能指定接收缓存大小。在多数情况下,上层应用指定的缓存会被忽视,而由操作系统来指定一个较大的固定值或者动态变化的计算值。

在Windows和linux中,支持接收窗口自动调优。自动调优连接 的在传输数据值 需要不断被估算,通告窗口值不能小于这个值。这种方法使得TCP达到其最大可用吞吐量,而不必提前在发送端或接收端设置过大的缓存。
自动调优受制于缓存大小。Windows系统中,默认自动设置接收端缓存,Linux可以手动配置。

15.6 紧急机制

socket套接字选项MSG_OOB可以将数据标记为“紧急数据”(URG置位)。TCP把这个数据放置在该套接字发送缓冲区的下一个可用位置,并把该连接的TCP紧急指针(urgent pointer)设置成再下一个位置。如果发送多个字节,只有最后一个字节会被标记为紧急数据。

因为窗口大小原因,可能不能立即发出这个字节,但是TCP会知道已经进入紧急状态,所有发出去的包都打开了URG标志。

当有新的紧急指针到达时,接受进程被通知到。内核会给接受套接字的属主进程发送SIGURG信号,或者接收端使用select()函数返回异常条件。

接收端的带外数据字节会有两个存储方式:一个是和普通数据一样的在线留存,另外一个是独立的单字节带外缓冲区。

  • 接收进程从这个单字节带外缓冲区读入数据的方法是指定MSG_OOB调用recvrecvfromrecvmsg
  • 使用开启了SO_OOBINLINE选项(默认关闭)的socket接收数据时,带外数据和普通数据同一通道。

详细介绍:《UNP 卷1》24.2
https://blog.csdn.net/qq_40586164/article/details/108717753



第16章 TCP拥塞控制

拥塞:路由器无法处理高速率到达的流量而被迫丢弃数据信息的现象 。

本地拥塞:在发送端系统产生的丢包称为本地拥塞,产生原因在于,TCP产生数据包的速度大于下层队列的发送速度。

拥塞控制是TCP通信的每一方需要执行的一系列行为,由特定算法规定,用于防止网络因为大规模的通信负载而瘫痪。其基本方法是当有理由认为网络即将进入拥塞状态(或已由于拥塞而出现路由丢包情况)时减缓TCP传输。

注:有线网络中,出现在路由器或交换机中的拥塞是造成丢包的主要原因。而在无线网络中,传输和接收错误是导致丢包的重要因素。

16.1.2 减缓TCP发送

当接收速率和网络传输速率过慢时,便需要降低发送速率。可以在发送端引入一个窗口控制变量,确保发送窗口大小不超过接收端接收能力和网络传输能力。

反映网络传输能力的变量称为拥塞窗口,记作cwnd。因此发送端实际可用窗口W就是接收端通告窗口awnd和拥塞窗口cwnd的较小值:

W = min(cwnd,awnd)

W的值不能过大或过小,我们希望其接近带宽延迟积(BDP),也称为最佳窗口大小。W反映网络中可存储的待发数据量大小,其计算值等于RTT与链路中最小通行速率的乘积。

16.2 经典算法

《TCP/IP详解 卷1》12-17章TCP笔记_第22张图片

16.2.1 慢启动

在传输初始阶段,由于未知网络传输能力,需要缓慢探测可用传输资源,防止短时间内大量数据注入导致拥塞。慢启动算法正是针对这一问题而设计,在数据传输之初或者重传计时器检测到丢包后,需要执行慢启动 [RFC5682]。

TCP以发送一定数目的数据段开始慢启动(在SYN交换之后),称为初始窗口(IW)。IW的值初始设为一个SMSS,但在[RFC5682]中设为一个稍大的值,计算公式如下:

IW = 2*(SMSS)且小于等于2个数据段(当SMSS > 2190字节)
IW = 3*(SMSS)且小于等于3个数据段(当 2190 ≥ SMSS > 1095字节)
IW = 4*(SMSS)且小于等于4个数据段(其他)

SMSS: 发送方能够发送的最大段大小 = min(接收方MSS,路径MTU)

每接收到一个"正确的ACK"(即没出现丢包情况正确返回的数据接收响应ACK),慢启动算法会以min(N,SMSS)来增加cwnd值,N指的是在未经确认的传输数据中能通过"正确的ACK"确认的字节数。

为简单起见,以TCP连接初始的cwnd = 1 SMSS为例。

《TCP/IP详解 卷1》12-17章TCP笔记_第23张图片

如果接收端开启了ACK时延,接收端就每接收两个数据报响应一个ACK,那么增速变慢。

cwnd会随着RTT呈指数增长,直至慢启动阈值。

慢启动实例见书16.5.1章。

16.2.2 拥塞避免

慢启动一旦达到阈值,便是意味着连接可以进入"下个阶段"了。"下个阶段"指的是为了得到更多的传输资源而不致影响其他连接传输的数据传输,这个阶段TCP实现拥塞避免算法。

拥塞避免阶段,cwnd每次的增长值近似于成功传输的数据段大小, cwnd不再翻倍,而是线性增长。每接收一个新的ACK,cwnd会做以下的更新:

c w n d t + 1 = c w n d t + S M S S ∗ S M S S / c w n d t cwnd_{t+1} = cwnd_t + SMSS * SMSS/cwnd_t cwndt+1=cwndt+SMSSSMSS/cwndt

《TCP/IP详解 卷1》12-17章TCP笔记_第24张图片

16.2.3 慢启动和拥塞避免的选择

在通常操作中,某个TCP连接总是选择运行慢启动和拥塞避免中的一个,不会出现两者同时进行的情况。

使用慢启动还是拥塞避免 由慢启动阈值(ssthresh)和cwnd的关系决定

  • 当cwnd < ssthresh:慢启动
  • 当cwnd > ssthresh:拥塞避免
  • 当cwnd = ssthresh:两者皆可

慢启动和拥塞避免之间的区别:当新的ACK到达时,cwnd怎样增长。

慢启动阈值随时间改变,它的主要目的:在没有丢包发生的情况下,记住上一次“最好的”操作窗口估计值。换言之,它记录TCP最优窗口估计值的下界

当有重传情况(超时、快速)发生,ssthresh会按下式改变:

ssthresh = max(在外数据值/2, 2 * SMSS) 

注意,在微软最近(“下一代”)TCP/IP协议中,上述等式变为 ssthresh = max(min(cwnd,awnd)/2, 2 * SMSS)[NB08]。

16.2.4 Tahoe、Reno以及快速恢复算法

TCP拥塞控制于4.2版本BSD被提出。

Tahoe(4.2版本的BSD,伯克利软件版本)包含了一个TCP版本,它在连接之初处于慢启动阶段,若检测到丢包,不论由于超时还是快速重传,都会重新进入慢启动状态。缺点是导致带宽利用率低下。

Reno(4.3版本的BSD)中的快速恢复机制就是为解决Tohoe的带宽利用率低下问题而提出的。针对不同的丢包情况,重新考虑是否需要重回慢启动状态。若是由快速重传检测的丢包,cwnd值将进行特殊操作(见下一节)。

16.2.5 标准TCP

总结[RFC5681]中的结合算法:

在TCP连接建立之初首先是慢启动阶段(cwnd = IW), ssthresh通常取一较大值(至少为awnd)。当接收到一个好的ACK (表明新的数据传输成功), cwnd会相应更新:

  • cwnd += SMSS (若cwnd
  • cwnd += SMSS*SMSS/cwnd (若cwnd>ssthresh)拥塞避免

当收到三次重复ACK (或其他表明需要快速重传的信号)肘,会执行以下行为:

  1. ssthresh更新为大于 ssthresh = max(在外数据值/2, 2*SMSS) 中的值。
  2. 启用快速重传算法,将cwnd设为(ssthresh + 3*SMSS)。(3为dupthresh)
  3. 每接收一个重复ACK, cwnd值暂时增加1 SMSS。
  4. 当接收到一个好的ACK,将cwnd重设为ssthresh(收缩)。

以上第2步和第3步构成了快速恢复
步骤2设置cwnd大小,首先cwnd通常会被减为之前值的一半,考虑到每接收一个重复ACK,就意味着相应的数据包已成功传输,cwnd值会相应的暂时增大。步骤3维持cwnd的增大过程,使得发送方可以继续发送新的数据包(在不超过awnd的情况下)。步骤4假设TCP已完成恢复阶段,所以对cwnd的临时膨胀进行消除。

以下两种情况总会执行慢启动:新连接的建立以及出现重传超时。当发送方长时间处于空闲状态,或者有理由怀疑cwnd不能精确反映网络当前拥塞状态(参见16.3.5节)时,也可能引发慢启动。在这种情况下,cwnd的初始值将被设为重启窗口(RW),在文献[RFC5681]中,推荐RW值=min(w,cwnd),其他情况下,慢启动中cwnd初始设为IW。

16.3 对标准算法的改进

16.3.1 NewReno

Reno中的快速恢复机制存在问题:当一个传输窗口出现多个数据包丢失时,一旦其中一个包重传成功,发送方就会收到一个好的的ACK,这样快速恢复阶段中cwnd窗口的暂时膨胀就会停止,而事实上丢失的其他数据包可能并未完成重传。导致出现这种状况的ACK称为局部ACK。

NewReno对快速恢复做了改进,它记录了上一个数据传输窗口的最高序列号,仅当接收到序列号不小于恢复点的ACK,才停止快速恢复阶段。

16.3.2 采用选择确认机制的TCP拥塞控制

TCP采用SACK机制后,发送方可以知晓多个数据段的丢失情况。

因为需要避免由于触发重传而导致短时间内大量数据被注入网络,SACK TCP强调拥塞管理和选择重传机制的分离(传统TCP则将两者结合)。

一种实现分离方法是,除了维护窗口,TCP还负责记录注入网络的数据量。[RFC3517]称其为**管道(pipe)**变量,以字节为单位,是对外在数据的估计值,记录传输和重传情况。假设awnd值比较大,只要不等式 cwnd - pipe ≥ SMSS成立,在任何时候SACK TCP均可发送数据。

速率减半算法:能够在检测出丢包后使拥塞窗口逐步而不是快速地减小。

16.3.4 限制传输

[RFC3042]描述了其对TCP做出了微小改进,采用限制传输策略,TCP发送方每接收两个连续的重复ACK,就能发送一个新数据包。这就使得网络中的数据包维持一定数量,足以触发快速重传,TCP因此也可以避免长时间等待RTO而导致吞吐量性能下降。

16.3.5 拥塞窗口校验

拥塞窗口校验:在发送操作持续一段时间后,cwnd值可能会增至一个较大值,若此时发送端需要暂停发送且暂定的时间足够长,则之前的cwnd可能无法准确反映路径中的拥塞状况。[RFC2861]提出了一种拥塞窗口校验机制(CWV)。

CWV算法如下:当需要发送新数据时,首先看距离上次发送操作是否超过一个RTO。如果超过,则

  • 更新ssthresh值,设为max(ssthresh,(3/4) * cwnd)。
  • 每经一个空闲RTT时间,cwnd值就减半,但不小于 1 SMSS。

对于应用受限阶段(非空闲阶段),执行相似的操作:

  • 已使用的窗口大小记为W_used。
  • 更新ssthresh值,设为max(ssthresh,(3/4) * cwnd)。
  • cwnd设为cwnd和W_used的平均值。

16.4 伪RTO处理-Eifel响应算法

若TCP出现突发的延时,可能出现伪重传,造成TCP调整ssthresh和cwnd,进入慢启动状态。这种伪重传现象的发生可能由于链路层的某些变化(如蜂窝转换),也可能是由于突然出现严重拥塞造成RTT大幅增长。

针对上述问题,在14章提出了一些检测方法:DSACK、Eifel、F-RTO,用于检测是否伪重传。结合响应算法(比如Eifel响应算法),就能"还原"TCP对拥塞控制变量的操作。

Eifel响应算法:

在所有情况下,若因RTO而需改变ssthresh值,在修改前需要记录一个特殊变量:pipe_prev = min(在外数据值,ssthresh)。然后需要运行一个检测算法(即之前提到的检测方法中的某个)来判断RTO是否真实,如果出现伪重传,则当到达一个ACK时,执行以下步骤:

  1. 若接收的是包含ECN-Echo标志位的正确的ACK,停止操作;
  2. cwnd = 在外数据值 + min(bytes_acked, IW);
  3. ssthresh = pipe_prev。

步骤1针对待ECN标志位的ACK的情况,这种情况下撤销ssthresh修改会引入不安全因素,所以算法终止。
步骤2将cwnd设置为一定值,允许不超过IW的新数据进入传输通道,因为即使在未知链路拥塞与否的状况下,发送IW的新数据也被认为是安全的。
步骤3在真正的RTO发生前重置ssthresh,至此撤销操作完成。

16.5 扩展举例

16.5.1 慢启动行为

《TCP/IP详解 卷1》12-17章TCP笔记_第25张图片
Wireshark分析显示了从连接建立起传输的数据包和ACK号。发送端每接收一个ACK就会发送两三个新数据包,这是慢启动的典型行为。

16.5.2 发送暂停与本地拥塞

《TCP/IP详解 卷1》12-17章TCP笔记_第26张图片
在发送端系统产生的丢包有时称为本地拥塞,产生原因在于,TCP产生数据包的速度大于下层队列的发送速度。

本地拥塞是Linux TCP实行拥塞窗口缩减(Congestion Window Reducing,CWR)策略[SK02]的原因之一。

首先,将ssthresh值设置为cwnd/2,并将cwnd设为min(cwnd,在外数据值+1),在CWR阶段,发送方每接收两个ACK就将cwnd减1,直到cwnd达到新的ssthresh值或者由于其他原因结束CWR(如出现丢包)。若TCP发送端接收到TCP头部中的ECN-Echo也会进入CWR状态(参见16.11节)。

16.6 共享拥塞状态信息

在许多情况下,新连接可能会用到相同主机之间的其他连接的信息,包括已关闭的连接或者正处于活动状态的其他连接。这就是相同主机间多个连接共享拥塞状态信息。

[RFC2140]描述了相关内容,其中注意区分了暂时共享(temporal sharing,新连接与已关闭连接间的信息共享)和总体共享(ensemble sharing,新连接与其他活动连接间的信息共享)。

Linux在包含路由信息的子系统中实现了信息保存(目的度量),即当一个TCP连接关闭前,需要保存以下信息:RTT测量值(包 括srtt和rttvar)、重排估计值以及拥塞控制变量cwnd和ssthresh。当相同主机间的新连接建 立时,就可以通过这些信息来初始化相关变量 。

16.7 TCP友好性

TCP作为最主要的网络传输协议,在传输路径中经常会出现几个TCP链接共享一个或多个路由的情况,然而它们并非均匀的共享带宽资源,而是根据其他连接动态地调节分配。为避免多个TCP连接对传输资源的恶性竞争,提出了一种基于计算公式的速率控制方法,限制特定环境下TCP连接对带宽资源的使用,该方法被称为TCP友好速率控制(TFRC),它基于连接参数和环境变量实现速率限制。

TFRC使用如下公式来决定发送率:

img
这里的X指吞吐率限制(字节/秒),s为包大小(字节,包含头部),R是RTT(秒),p为丢包率[0,0.1], t R T O t_{RTO} tRTO为重传超时(秒),b指一个ACK能确认的最大包个数。建议 t R T O t_{RTO} tRTO设为4R,b设为1。

在拥塞避免阶段,如何根据接收的无重复ACK来调整窗口大小?使用拥塞避免算法时,每接收一个正确的ACK,cwnd就会增加1/cwnd,而每当出现一次丢包,cwnd就会减半,这被称为和氏增加/积式减少(AIMD)拥塞控制。通过将1/cwnd和1/2替换为a和b,得到AIMD等式:

c w n d t + 1 = c w n d t + a / c w n d t cwnd_{t+1} = cwnd_{t} + a/cwnd_{t} cwndt+1=cwndt+a/cwndt
c w n d t + 1 = c w n d t − b / c w n d t cwnd_{t+1} = cwnd_{t} - b/cwnd_{t} cwndt+1=cwndtb/cwndt

根据[FHPW00]给出的结果,上述等式得出的发送率为(以包个数/RTT为单位):

《TCP/IP详解 卷1》12-17章TCP笔记_第27张图片

对于传统TCP,a = 1,b = 0.5,简化得出 T = 1.2 / p T = 1.2/\sqrt{p} T=1.2/p

16.8 高速环境下的TCP

在BDP较大的高速网络中(如1Gb/s或者更大的无线局域网),传统TCP可能不能表现出很好的性能。因为它的窗口增加算法(特别是拥塞避免算法)需要很长一段时间才能使窗口增至传输链路饱和。产生这一问题的原因主要在于拥塞避免算法中的增量为固定值

带宽时延积(BDP)是一种网络性能指标, 是一个数据链路的能力(每秒比特)与来回通信延迟(单位秒)的乘积。其结果是以比特(或字节)为单位的一个数据总量,等同在任何特定时间该网络线路上的最大数据量——已发送但尚未确认的数据。

16.8.2 二进制增长拥塞控制

HSTCP是为在大BDP网络中实现高吞吐量的一种TCP修改方案。它兼顾了在普通环境下与传统TCP的公平性,但在特殊环境中能够达到更快的发送速度。

16.8.1 BIC-TCP算法

建立一种可扩展的TCP及解决RTT公平性问题,提出了BIC-TCP算法。BIC TCP算法的主要目的在于,即使在拥塞窗口非常大的情况下(需要使用高带宽的连接),也能满足线性RTT公平性。线性RTT公平性是指连接得到的带宽与其RTT成反比,而不是一些更复杂的函数。

该算法使用了两种算法来修改标准TCP发送端,二分搜索增大和加法增大。

二分搜索增大算法操作过程如下:当前最小窗口是最近一次在一个完整RTT中没有出现丢包的窗口大小,最大窗口是最近一次出现丢包时的窗口大小。预期窗口位于这两个值之间,BIC-TCP则使用二分搜索选择中点作为试验窗口,依次递归。如果这个点依然会发生丢包,那么将它设置为最大窗口,然后继续重复二分搜索试验。反之依然。直到最大窗口和最小窗口的差小于一个预先设置好的阈值时才停止,这个阈值称为最小增量。

加法增大算法操作过程则如下:当使用二分搜索增大算法时,可能会出现当前窗口大小与中间点之间差距很大。由于可能出现突然大量数据注入网络的情况,所以在一个RTT内将窗口增大到中间点可能并不是一个好方法。这种情况下,就需要采用加法增大算法。此时增量被限制为每个RTT增加 S m a x S_{max} Smax,这一增量被称为窗口夹。一旦中间点距离试验窗口比距离 S m a x S_{max} Smax值更近时,则转换为使用二分搜索增大算法。

总的来说,当检测到丢包现象,窗口会使用乘法系数β来减小,而窗口增大时,首先使用加法增大算法,之后一旦确认加法增量小于 S m a x S_{max} Smax时就转为使用二分搜索增大算法。这种组合算法称为二进制增长,或者BI。

16.8.2.2 CUBIC

CUBIC为Linux系统的默认拥塞控制算法(还有Reno供选择)。是BIC-TCP的改进版本:使用一个高阶多项式函数(具体来说是一个三次方程)来控制窗口的增大。三次方程的曲线既有凸的部分也有凹的部分,这就意味着在一些部分(凹的部分)增长比较缓慢,而在另一些部分(凸的部分)增长比较迅速。

CUBIC(立方)算法中,这个特殊的窗口增长函数如下所示:

W ( t ) = C ( t − K ) ³ + W m a x W(t) = C(t - K)³ + W_{max} W(t)=C(tK)³+Wmax

W(t)代表在时刻t的窗口大小,C是一个常量(默认为4),t是距离最近一次窗口减小所经过的时间(以秒为单位),K是在没有丢包情况下窗口从W增长到 W m a x W_{max} Wmax所用的时间, W m a x W_{max} Wmax是最后一次调整前的窗口大小。其中K可依据以下表达式计算:

《TCP/IP详解 卷1》12-17章TCP笔记_第28张图片
当发生快速重传时, W m a x W_{max} Wmax被设置为cwnd,新的cwnd值和ssthresh值被设置 β ∗ c w n d β*cwnd βcwnd。CUBIC算法中的 β β β默认为0.8, W ( t + R T T ) W(t+ RTT) W(t+RTT)值是下一个目标窗口的值。当在拥塞避免阶段,每收到一个ACK,cwnd值增加 ( W ( t + R T T ) − c w n d ) / c w n d (W(t+ RTT) - cwnd)/cwnd (W(t+RTT)cwnd)/cwnd

下图是CUBIC窗口增长函数的三次方程式:

《TCP/IP详解 卷1》12-17章TCP笔记_第29张图片

除了三次方程之外,CUBIC还有"TCP友好性"策略。当窗口太小使得CUBIC不能获得比传统TCP更好的性能时,它将开始工作。根据t可以得到标准TCP的窗口大小 W t c p ( t ) W_{tcp}(t) Wtcp(t)

《TCP/IP详解 卷1》12-17章TCP笔记_第30张图片

当在拥塞避免阶段有一个ACK到达时,如果cwnd值小于 W t c p ( t ) W_{tcp}(t) Wtcp(t),那么CUBIC将cwnd值设置为 W t c p ( t ) W_{tcp}(t) Wtcp(t),这种方法确保了CUBIC在一般的中低速网络中的TCP友好性。

16.9 基于延迟的拥塞控制算法

当发送端不断地向网络中发送数据包,不断增长的RTT值也可以作为拥塞形成的信号。新到达的数据包没有被发送而是进入了等待队列,这就造成了RTT值不断增大(直到数据包最终被丢弃)。一些根据这种情况提出的拥塞控制技术成为基于延迟的拥塞控制算法,与我们至今为止看到的基于丢包的拥塞控制算法相对。

16.9.1 Vagas 算法

Vagas算法于1994年被提出[BP95],它是TCP协议发布后的第一个基于延迟的拥塞控制方法,并经过了TCP协议开发组的测试。该算法首先估算了一定时间内网络能够传输的数据量,然后与实际传输能力进行比较。若本该传输的数据并没有被传传输,那么它有可能被链路上的某个路由器挂起,如果这种情况持续不断发生,那么Vages发送端将降低发送速率。

16.10 缓冲区膨胀

网络中的路由设备,其缓冲区的大小不是越大越好,庞大的缓冲区会导致TCP性能下降,这一问题被称为“缓冲区膨胀”。

  • 缓冲区过小:

    很容易就被写满,丢包率变高,导致传输效率差。

  • 缓冲区过大:

    如果路由器接收速率大于发送速率,就会有大量数据在路由器排队,延迟很大,此时还不算是丢包。丢包要等到发送端超时才算,然后又往路由器塞入了重复报文段。

不是所有的网络设备中都存在缓冲区膨胀的问题。实际上,主要问题是缓冲区端用户接入设备过满。使用缓冲区大小可动态改变的接入设备或积极队列管理可以解决该问题。

16.11 积极队列管理和ECN

路由器通常不会把拥塞信息发给tcp两端,不利于拥塞控制。路由器默认应用FIFO和尾部丢弃机制,先到的包会发出去,超负荷时丢弃数据包。

应用FIFO和尾部丢弃以外的调度算法和缓存管理策略被认为是积极的,路由器用来管理队列的相应方法称为积极队列管理(AQM)机制

相关的很多RFC都描述了显式拥塞通知( Explicit Congestion Notification, ECN),它对经过路由器的数据包进行标记(设置IP头中的两个ECN标志位),以此得到拥塞状况。

随机早期检测(RED)网关[FJ93]机制能够探测拥塞情况的发生,并且控制数据包标记。这些网关实现了一种衡量平均占用时间的队列管理方法,如果占用队列的时间超过最小值(minthresh),并且小于最大值(maxthresh),那么这个数据包将被标记一个不断增长的概率值。如果平均队列占用时间超过了maxthresh,数据包将被标记一个可配置的最大概率值(MaxP),MaxP可设置为1.0,RED也可以将数据包丢弃而不是标记它们。

注意:RED算法有多种版本,很多路由器和交换机都支持。

当数据包被接收时,其中的拥塞标记表明这个包经过了一个拥塞的路由器。不过,需要接收端通过向发送端返回一个ACK数据包来通知拥塞状况,以降低发送端发送速率。

ECN机制主要在IP层进行操作,其过程如下:

  1. 一 个包含ECN功能的路由器经过长时间的拥塞,接收到一个IP数据包后,它会查看IP头中的 ECN传输能力(ECT)标识(在IP头中由两位ECN标志位定义,用来开启ECN功能)。若有效,路由器会在IP头设置一个已发生拥塞(CE)标识 (将ECN位都置为1),然后继续向下转发数据报。接收端接收到后需要将拥塞信息通告发送端。
  2. 因为ACK数据包不包含数据,不被可靠传输,为避免拥塞标识丢失,TCP实现了一个小型的可靠连接协议:TCP接收端接收到CE标识被置位的数据包之后,它会将每一个ACK数据包的“ ECN回显” (ECN-Echo,ECE)位字段置位,直到接收到一个从发送端发来的CWR位字段置位的数据包,说明拥塞窗口(也就是发送速率)已经降低。
  3. 当TCP发送端接收到ECN-Echo标识的数据包时,会与探测到单个数据包丢失时一样调整cwnd值(快速恢复),同时还会重新设置后续数据包的CWR位字段。

16.12 与TCP拥塞控制相关的攻击

一种复杂、常见的攻击方式是基于接收端的不当行为。这里将描述三种攻击形式,它们都可以使TCP发送端以一个比正常状态更快的速率进行数据发送。

  • ACK分割攻击:将原有的确认字节范围拆分成多个ACK信号并返回给发送端,使得发送端的cwnd会比正常情况更快速增长。可通过计算每个ACK能确认的数据量来判断是否为正确的ACK来解决这一问题。

  • 重复ACK欺骗攻击:该攻击可以使发送端在快速恢复阶段增长它的拥塞窗口。这种攻击会比正常情况更快地生成多余的重复ACK。使用时间戳选项可以解决这一问题,然而最好的方法是限制发送端在恢复阶段的在外数据。

  • 乐观响应攻击:对那些还没有到达的报文段产生ACK,导致发送端计算出的RTT比实际的小,从而比正常情况下更快的做出反应。为防范这类攻击,可通过定义一个可累加的随机数使得发送数据段大小可随时间动态改变,以此来更好地匹配数据段和它的对应ACK。当发现得到的ACK和数据段不匹配时,发送端就可以采取相应的行为。



第17章 TCP保活机制

17.1 引言

TCP保活机制是为了解决两个问题而设计的:

  • 客户端和服务器需要了解什么时候终止进程或者与对方断开连接
  • 虽然应用进程之间没有任何数据交换,但仍然需要通过连接保持一个最小的数据流。

保活机制是一种在不影响数据流内容的情况下探测对方的方式。它是由一个保活计时器实现的。当计时器被激发,连接一端将发送一个保活探测报文,另一端接收报文的同时会发送一个ACK作为响应。

保活机制并不是TCP规范中的一部分,对此[RFC1122]给出三个理由(保活机制的缺陷):

  1. 在出现短暂的网络错误的时候,保活机制会使一个好的连接断开(例如,如果在中间路由器崩溃并重新启动的时候保活探测,那么TCP协议将错误地认为对方主机已经崩溃。);
  2. 保活机制会占用不必要的带宽;
  3. 在按流量计费的情况下会产生更多经济开销。

不过,很多应用需要用到保活机制,因此,现在所有主流TCP版本都实现了保活功能。

17.2 描述

该功能在默认情况下是关闭的,是一个可选择激活的功能。TCP连接的任何一端都可以请求打开这一功能,且可以只设置在连接的一端。

组成:

  • keepalive time:保活时间,即发送保活探测的计时器的timeout时间。一般为无数据传输后2小时。

  • keepalive interval:保活时间间隔,第一个探测发送后,如果没收到回包,就需要紧跟着再次发送探测。一般为75秒。

  • keepalive probe:保活探测数,探测多少次后才认为对端不可达,中断连接。

  • 保活探测报文:一个空报文段或带一个字节(垃圾数据)的报文段。序列号等于对端发送的ACK最大序列号减1,因为这个序列号已经被成功接收,所以不会对对端造成影响。探测及其响应报文都不包含任何新的有效数据(它是“垃圾”数据),当它们丢失时也不会进行重传,因此需要保活探测数。

保活时间、保活时间间隔和保活探测数的设置通常是可以变更的。

保活步骤

  1. 如果在一段时间(保活时间,keepalive time)内连接处于非活动状态,开启保活功能的一端将向对方发送一个“保活探测报文”
  2. 如果发送端没有收到响应报文,那么经过一个已经提前配置好的保活时间间隔(keepalive interval),将继续发送保活探测报文,直到发送探测报文的次数达到“保活探测数”(keepalive probe),这时对方主机将被确认为不可到达,连接也将被中断(通过主动发送RST)。

探测前后,对端可能的四种状态:

  1. 对端正常工作,正常响应了探测。探测定时器重置。
  2. 对端已崩溃或重启中,此时不会响应探测。探测端会一直探测直到超过探测次数,就认为对端已经关闭,断开这个连接。
  3. 对端已崩溃但已重启,会发送RST重置报文段作为响应,探测端会断开连接。
  4. 对端正常工作,但因其他原因探测报文不能到达(例如网络无法传输)。TCP无法区分状态4和状态2。

你可能感兴趣的:(读书笔记,计算机网络)