TCP:控制传输协议

计算机网络学习笔记之一

Posted by     "Eric" on Friday, February 14, 2020

1. TCP概述

不同于UDP,TCP提供一种面向连接的、可靠的字节流服务,没有由TCP自动插入的记录标志或消息边界,TCP根本不会解读字节流里的字节内容。

为了实现可靠的连接服务,它使用了ARQ(自动重复请求)纠正差错,在发送方和接收方使用分组窗口和滑动窗口管理分组,并以此为基础进行流量控制和拥塞控制(流量控制是为了接收方,拥塞控制是为了不压垮发送方与接收方之间的网络)。

TCP的可靠性体现在一下几个方面:

  1. TCP把一个发送应用程序的字节流以最佳大小转换成一组IP可以携带的分组,称为组包,并允许他们在传输过程中组合,(称为重新组包)
  2. TCP维持了一个强制的校验和(UDP不是强制的)
  3. TCP为一个窗口设置一个计时器,超时重传
  4. TCP使用的ACK是累积的,一个ACK丢失了,后续的ACK足以确认前面的报文
  5. TCP提供一种双工服务,每个报文段包含一个窗口通告以实现相反方向的流量控制
  6. 使用序列号,TCP接收端可以丢弃重复的报文段和记录以杂乱次序到达的报文段

2. TCP头部和封装

标准TCP头部由20个字节构成,TCP头部必须是32位的倍数。阴影字段用于与该报文段发送方关联的相反方向的数据流。

序列号代表着包含该序列号的报文段的数据从哪一个字节开始。SYN和FIN会消耗一个字节,而ACk则不消耗。消耗一个序列号意味着使用重传进行可靠传输。也就是说如果一个纯ACK数据报的序列号是K,那么相同方向下一个数据报的序列号依然是K。

字段 用途
CWR 拥塞窗口减
ECE ECN回显(发送方接收到了一个更早的拥塞通告)
URG 紧急(紧急指针字段有效)
ACK 确认,该位置1时,确认号才有效
PSH 推送,通常表示发送端缓存为空
RST 重置连接
SYN 用于初始化
FIN 发送方已经结束向对方发送数据

TCP头部包含多个选项,每个选项的头一个字节为“种类”,第二个字节显示该选项的总长度。

种类 长度 名称 描述与目的
2 4 MSS 最大段大小
3 3 WSOPT 窗口缩放因子
4 2 SACK-Permitted 发送者支持SACK选项
5 可变 SACK SACK段
8 10 TSOPT 时间戳选项
  • 最大段大小:是指TCP协议所允许的从对方接受到的最大报文段,最大报文段只记录TCP数据的字节数,最大段大小并不是TCP通信双方的协商结果,还是一个限定的数字。(告知而非协商)

  • 窗口缩放选项:将TCP窗口大小字段由16位扩展为32位。该选项只能出现在一个SYN报文段中,因此当连接建立以后比例因子是与方向绑定的。每个方向的比例因子可各不相同。

  • 选择确认:通过接收SYN报文段中的“允许选择确认”选项,TCP通信方会了解自身具有发布SACK信息的能力,使用SACK块描述接收到的杂乱数据,每个SACK块由一对32位序列号表示,最多由3块。

  • 时间戳:时间戳选项要求发送方在每一个报文段中添加2个4字节的时间戳数值,接收方将会在确认中反映这些数值,允许发送方针对每一个接收到的ACK估算TCP连接的往返时间。发送方将一个32位的数值填充到时间戳数值字段(TSV或TSval)作为第一部分,接收方则将收到的时间戳数值原封不动的填充到第二部分的时间戳回显重试字段(TSER或TSecr),并将自己当前时间放入TSV中。借助时间戳选项,能够获得往返时间相对精确的测量结果,同时还可以作为序列号的一个扩展,作为防回绕序列号。(所谓回绕就是一个数值增大到上限后重新从最小值开始计数)

3. TCP连接管理

image-20200210213656465

3.1 为什么是三次握手

如果是两次握手,B不知道A是否建立了连接,比如A主动提出建立连接,但是因为网络原因,该报文段消失于网络中,于是A重传了一个SYN报文段,并在传输完信息后断开了连接,若此时,之前的SYN报文段来到了B,则B建立了连接,但其实A是空闲状态的。

3.2 为什么是四次挥手

因为TCP连接是全双工的。

3.3 连接建立超时

客户端TCP为了建立连接频繁地发送SYN报文段,在首个报文段发送后3秒发送第二个,6秒发送第三个,12秒发送第四个,这一行为称之为指数回退。

3.4 初始序列号

一个IP地址和一个端口组成的网络单位被称为一个端点或套接字,它是两台机器间通信的端点。TCP链接是被一对套接字唯一表示。

一个TCP报文段同时具备连接的4元组与当前活动窗口的序列号才会在通信过程中被对方认为是正确的,这也体现了TCP的脆弱性,为了抵御这一脆弱,一种方法是使初始序列号变得更加难以猜出。

3.5 TCP状态转换

image-20200208230254019

image-20200208230314520

3.5.1 TIME_WAIT状态

TIME_WAIT状态也称为2MSL等待状态,它有两个用途

  1. 能够让TCP重新发送最终的ACK,以避免出现丢失的情况
  2. 2MSL时,通讯双方将该连接定义为不可重复使用,更严厉的,如果一个端口处于2MSL,则该端口不可被再次使用,这可以吞掉迟到的数据包,防止干扰新的连接

3.5.2 FIN_WAIT_2状态

因为半关闭的存在,FIN_WAIT_2和CLOSE_WAIT状态会继续保持下去,为了防止这种无限制的等待过程,会设置一个计时器,如果计时器超时时连接是空闲的,那么TCP连接就会转移到CLOSED状态。

3.6 重置报文段

  1. 针对不存在的端口的连接请求,在发送SYN后,会收到一条带有R的报文段
  2. 主动终止一条连接,发送一条带有R的报文段,重置报文段不会令通信另一端做出任何响应,任何排队的数据都将被抛弃。
  3. 半开连接:服务器直接down掉,重新开机后,客户端发送消息,服务器回复一条重置报文段结束掉连接。

3.7 TCP的路径最大传输单元发现

  1. 在一个连接建立时,TCP使用对外接口的最大传输单元的最小值,或者根据通信对方声明的最大段大小来选择发送方的最大段大小(SMSS)
  2. TCP通过这条连接发送的所有IPv4数据报都会对DF位字段进行设置
  3. 如果收到了PTB消息,TCP就会减少段的大小,然后用修改过的段大小进行重传
  4. 减少段大小的数值一段时间后需要尝试一个更大的数

3.8 syn泛洪攻击原理

这种攻击中一个或多个客户端通过伪装随机的ip地址,产生一系列TCP连接尝试(SYN报文段),并将它们发送给一台服务器。服务器收到连接请求,将此信息加入到未连接队列,并发送请求包给客户,此时进入SYN_RECV状态。当服务器未收到客户端的确认包时,重发请求包,一直到超时,才将此条目从连接队列中删除。

防御措施有syn cookie;syn cookie是对TCP服务器端的三次握手协议做了一些修改,在TCP服务器收到TCP syn包并返回syn+ack包时,不分配一个专门的内存,而是根据这个syn包的必要信息(客户端ip地址、端口号等)计算出一个cookie值,将这个值作为tcp服务器端的初始序列号。当服务器端收到客户端发回来的ACK包时,会重新根据客户端ip地址、端口号等计算cookie值,如果该cookie值+1等于ACK,则证明本次连接合法。

4. TCP超时与重传

有两种方式的重传:第一种称为基于计时器的超时重传,TCP在发送数据时会设置一个计时器,并设置一个阈值(RTO)若计时器超时时仍未收到数据确认信息,则会引发超时重传。另一种方式称为快速重传,若TCP累计确认无法返回新的ACK,或者当ACK包含选择确认信息表明出现失序报文段时,发送端会推断出丢包的范围并且进行重传。

4.1 带时间戳选项的RTT测量

image-20200210113052567

  1. 接收端发送一个ACK,并更新LastACK
  2. 当接收到新数据时,如果序列号==LastACK,记录下该报文段的TSV,记为TsRecent
  3. 返回ACK报文段时,将TsRecent写入TSER,并更新LastACK
  4. 发送端将收到的ACK报文段中的TSER减TCP时钟,即可得到一个RTT样本

4.2 设置重传超时(RTO)

网络是一个动态的过程,我们需要根据RTT(往返时间估计)来设定一个合理的RTO值,如果RTO过小,会在网络中引入不必要的重复数据。如果RTO过大,则使得整个网络的利用率下降。因此,如何设置合理的RTO值变得至关重要。常用的方法有经典方法、标准方法。

4.3 基于计时器的重传

在设计计时器前,需记录被计时的报文段序列号,若及时收到了该报文段的ACK,那么计时器被取消。之后发送端发送一个新的数据包时,需设定一个新的计时器。

TCP将超时重传视为相当重要的事件,当发生该情况时,它通过降低当前数据发送率来对此进行快速响应。有两种方法:第一种基于拥塞控制机制减小发送窗口的大小;另一种方法为每当一个重传报文被再次重传时,增大RTO的退避因子(使得RTO值变大)

4.4 快速重传

快速重传的前提是当接收到失序报文段时,重复ACK应立即返回,不能延迟发送。

重复ACK到达发送端表明先前发送的当前期盼的包可能丢失,或者延迟到达,TCP等待一定数目重复ACK,来决定数据是否丢失并触发快速重传(重复ACK阈值,dupthresh)。

快速重传算法可以概括如下:TCP发送端在观测到至少dupthresh个重复ACK后,即重传可能丢失的数据分组,而不必等到重传计时器超时。根据重复ACK推断的丢包通常与网络拥塞有关,因此伴随快速重传应触发拥塞控制机制。

在重传发生时,发送端在执行重传前已发送的最大序列号称为恢复点,TCP在接收到序列号等于或大于恢复点的ACK时,才会被认为从重传中恢复。如果收到的ACK大于之前收到的最大ACK值,但不足以到达恢复点,这种类型的ACK被称为部分ACK,当部分ACK到达时,TCP发送端立即发送可能丢失的报文段。如果拥塞控制机制允许,也可以同时发送新的数据。

4.5 带选择确认的重传

在SYN阶段,发送方和接收方会商讨双方是否支持SACK,每个SACK段都用来描述收到的失序数据块,在每个ACK报文段中,SACK最多有3块。

发送端基于接收到的SACK块来进行丢失重传,该过程被称为选择性重传。为了利用SACK块信息避免重传正确接收的数据,当接收到响应序列号范围的ACK时,在重传缓存中标记该报文段的选择重传成功。当发送端收到了SACK或重复ACK,它可以选择发送新数据或重传旧数据,最简单的方法是使发送端首先填补接收端的空缺,然后再继续发送新数据。(若拥塞控制机制允许)

但要注意,接收端可能提供一个SACK告诉发送端已成功接收一定序列号范围的数据,而之后做出变更,由于这个原因,SACK发送端不能再收到一个SACK后立即清空其重传缓存中的数据;只有当接收端的普通TCP ACK号大于其最大序列号值时,才可清除。

4.6 伪超时与重传

过早的判定超时,会导致伪超时。处理伪超时包括检测算法与响应算法。检测算法用于判断某个基于计时器的超时是否真实,一旦认定出现伪超时执行响应算法,撤销或减轻该超时带来的影响。

  1. 重复SACK扩展(DSACK)(没看懂)

  2. Eifel检测算法

    它需要使用TCP的TSOPT,当发送一个重传后,保存其TSV值。当接受到相应分组的ACK后,检查该ACK的TSER部分,若TSER值小于之前存储的TSV值,则可判定该ACK对应的是原始传输分组,即该重传为伪重传。

  3. 前移RTO恢复

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

  4. Eifel响应算法(没看懂)

4.7 包失序

如果失序发生在反向ACK链路,就会使得TCP发送端窗口快速前移。由于TCP的拥塞控制行为,这种情况会导致发送端出现不必要的流量突发行为,影响可用网络带宽。

如果失序发生在正向链路,当失序程度很大,发送端会因为收到多个重复ACK而触发快速重传。幸运的是,严重的包失序并不常见,并且可以通过动态调整dupthresh来避免因包失序导致的伪重传。

image-20200210162639503

4.8 重复

image-20200210163110857

发送5,6包之后仍收到A3,会引发快速重传,但是利用SACK就可以简单地忽略这个问题,因为该情况下,SACK没有空缺。

4.9 重新组包

当TCP超时重传,它并不需要完全重传相同的报文段。TCP允许执行重新组包,发送一个更大的报文段来提高性能。

5. TCP数据流与窗口管理

5.1 交互式通信

交互式数据传输的报文段通常小于SMSS(发送最大报文段),例如在SSH中,每在客户端输一个字符,都会发送一个数据报,然后接收端返回一个ACK;之后接收端再返回一个回显,发送端再返回一个ACK。通常,第2和第3段可以合并,称之为捎带延时确认。

image-20200211101603757

5.2 延时确认

在许多情况下,TCP不对每一个到来的数据包都返回ACK,累积确认可以允许TCP延迟一段时间发送ACK,一边将ACK和相同方向上需要传的数据结合发送。TCP不能任意时长的延迟ACK;否则对方会误认为数据丢失而出现不必要的重传。

5.3 Nagle算法

小包会造成相当高的网络传输代价,因此要避免这种情况发生。Nagle算法要求,当一个TCP连接中有在传数据(已经发送但没有收到确认),小的报文段(长度小于SMSS)就不能发送,直到所有的在传数据都收到ACK。并且,在收到ACK后,TCP需要收集这些小数据,将其整合到一个报文段中发送。这种方法迫使TCP遵循停等规程——只有等接收到所有在传数据的ACK后才能继续发送。

Nagle算法实现了自时钟控制:ACK返回越快,数据传输越快。

Nagle算法传输的包数目更少而长度更长,但同时传输时延也更长。

如果延时ACK与Nagle算法结合起来,可能会导致死锁。一边延时ACK,等待更多的数据一起返回,一边没有收到ACK则不再发送数据。这种死锁不是永久的,在延时ACK计时器超时后,死锁会解除。

image-20200211102550638

5.4 流量控制与窗口管理

窗口通告数值表示接收端可用缓存空间的大小。当TCP应用程序空闲时,就会排队处理这些数据,致使窗口大小字段保持不变。当系统处理速度较慢,或者程序忙于执行其他操作,到来的数据返回ACK后,就需要排队等待被读取或“消耗”。若这种排队状况持续,新数据的可用存储空间就会减小,窗口大小值也随之减小。最终,若应用程序一直不处理这些数据,没有空间来存储新数据,将窗口通告设为0。

5.4.1 滑动窗口

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

image-20200211111230819

由接收端通告的窗口称为提供窗口(awnd)。可用窗口即它可以立即发送的数据量。其计算方法为提供窗口大小减去在传数据值。

用三种术语来描述窗口左右边界的运动:

  1. 关闭,即窗口左边界右移,当已发送数据得到ACK确认时,窗口会减小。
  2. 打开,即窗口右边界右移,使得可发送数据量增大。当接收端确认数据得到处理,可用缓存变大,窗口也随之变大。
  3. 收缩,窗口右边界左移,RFC不支持这一做法,但TCP必须要处理这一问题。

当得到的ACK号增大而窗口大小保持不变时,我们就说窗口向前“滑动”。窗口大小可能减小,但是窗口的右边界不会左移,以此避免窗口收缩。

接收端也维护一个窗口结构,但比发送端窗口简单。该窗口可以保证其接收数据的正确性。对接收端而言,到达序列号小于左窗口边界,被认为是重复数据而丢弃,超过右边界的则超出处理范围,也被丢弃。使用SACK选项,窗口内的其他报文段也可以被确认,但只有当数据序列号等于左边界时,窗口才能向前滑动。

image-20200211141403330

5.4.2 零窗口与TCP持续计时器

TCP通过接收端的通告窗口来实现流量控制,当窗口值变为0时,可以阻止发送端继续发送数据。当接收端重新获得可用空间时,会给发送端传输一个窗口更新,告知其可继续发送数据。这样的窗口更新通常都不包含数据(纯ACK),不能保证其传输的可靠性。如果一个包含窗口更新的ACK丢失,通信双方都会一直处于等待状态,为了防止这种死锁发生,发送端会采用一个持续计时器查询接收端,看其窗口是否增长。持续计时器会触发窗口探测的传输,强制要求接收端返回ACK。窗口探测包含至少一个字节的数据(往往会捎带更多的数据),采用TCP可靠传输。其捎带的数据是否会被接收,取决于接收端的可用缓存空间大小。

与TCP重传计时器类似,采用指数时间退避来计算持续计时器的超时。

5.4.3 糊涂窗口综合征

基于窗口的流量控制机制,尤其是不使用大小固定的报文段的情况,会出现糊涂窗口综合征。当出现该问题时,交换数据段大小不是全长而是一些较小的数据段。

TCP连接的两端都可能导致SWS的出现:接收端的通告窗口较小(没有等到窗口变大才通告),发送端发送的数据段较小(没有等待将其他数据组合成一个更大的报文段)。

要避免SWS现象,必须在发送端或接收端实现相关规则。

  1. 对于接收端而言,不应通告小的窗口值,在窗口可增至一个全长的报文段或接收端缓存空间的一半之前(取两者较小者),不能通告比上次窗口更大的窗口值,只有当窗口至少为一个MSS或超过总接收缓存的四分之一才响应窗口探测(不响应的表现为保持0窗口)。当应用程序处理接收到的数据使得缓存增大,以及接收端需要强制响应窗口探测时会用到该条规则。

  2. 对于发送端来说,不应该发送小的报文段,需要通过Nagle所犯控制何时发送。至少满足以下条件之一时才能传输报文段:

    1. 全长的报文段可以发送
    2. 数据段长度≥接收端通告过的最大窗口值的一半,可以发送
    3. 满足以下任一条件 (a)某一ACK不是目前期盼的(即没有未经确认的在传数据)(b)该连接禁用Nagle算法

5.4.4 大容量缓存与自动调优

较小的接收缓存的TCP应用的吞吐性能较差,很多TCP协议栈中上层应用不能指定接收缓存大小,即使指定了也会被忽视,而是由操作系统来指定一个较大的固定值或者动态变化的计算值

在较新的Windows版本,或者Linux中,支持接收窗口自动调优。某个连接的在传数据值(连接的带宽延时积)需要不断被估算,如果剩余缓存空间足够,通告窗口值不能小于这个值。这种方法使得TCP达到其最大可用吞吐率,而不必提前在发送端或接收端设置过大的缓存。

6. TCP拥塞控制

路由器因无法处理高速到达的流量而被迫丢弃数据信息的现象称为拥塞。整个网络的所有计算机都要一起努力,避免网络中出现拥塞现象。

6.1 TCP拥塞检测

对于TCP发送方而言,没有一个精确的方法去知晓中间路由器的状态,在TCP中,丢包被用作判断拥塞发生与否的指标。当发生丢包时,若只发生重传操作,无疑加剧了网络的拥塞情况。因此在重传之外,还会进行拥塞控制。

6.2 减缓TCP发送

基于对网络传输能力的估计,可以在发送端引入一个窗口控制变量,确保发送窗口大小不超过接收端接受能力和网络传输能力,即TCP发送端的发送速率等于接收速率和传输速率两者中较小的值。反映传输能力的变量称为拥塞窗口,记为cwnd。因此发送端的实际窗口为: $$ W=min(cwnd,awnd) $$ 我们希望W的值能够接近带宽延迟积,也称为最佳窗口大小,其值等于RTT与链路中最小通行速率的乘积。(形象一些,窗口大小合适使管路充满数据)

6.3 慢启动

慢启动的目的是在用拥塞避免探寻更多可用带宽之前得到cwnd值,以及帮助TCP建立ACK时钟。

新的TCP连接建立或检测到超时重传导致的丢包时,需要执行慢启动。初始阶段,cwnd设为初始窗口。每接收到一个“好的ACK响应”(ACK号大于之前收到的ACK),慢启动以min(N,SMSS)增加cwnd值(N为ACK确认的数据大小,SMSS是MTU和MSS中较小的那一个)。(以指数形式增长)

6.4 拥塞避免

当cwnd增长到一个阈值后(慢启动阈值),TCP进入拥塞避免阶段。cwnd每次增长值近似于成功传输的数据段大小。(线性增长)

6.5 快重传与快恢复

当收到三次重复ACK,会执行快速重传以及快速恢复(因为如果因为网络出现拥塞的话,就不会收到好几个重复的确认):

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

若发生重传超时,则触发慢启动。

image-20200213112734708