[学习笔记] - TCP/IP

TCP提供一种面向连接的,可靠的字节流服务

三次握手 四次挥手
  • TCP是面向连接的传输层协议
  • 每一条TCP连接只能有两个端点(单播)
  • TCP提供可靠交付的服务
  • TCP提供全双工通信
  • 面向字节流

概览

TCP说是连接,其实是通过状态来表示连接,整个IT里面除开物理层,实际上无法有人们常识中那种连接。TCP的一切功能都是通过一系列算法和TCP的包里那些字段实现的

基本概念

  • TCP数据包
  • 源端口和目的端口:用于寻找发端和收端的应用程序
    • 接口(socket):一个IP地址和一个端口号的统称
  • 序号:用来标识从TCP发端向TCP收端发送的数据字节流,表示本报文段所发送数据的第一个字节的编号,用于对序列号进行同步
    • 当SYN的值是1时,这个字段的值是初始序列值(ISN)
    • 当SYN标记不为1时,这是当前数据分段第一个字母的序列号
  • 确认号:表示接收方期望收到发送方下一个报文段的第一个字节数据的编号
    • 确认号应当是上次已成功收到数据字节序号+1
    • 只有ACK标识符1时,才有效
  • 数据偏移:数据段中的数据部分起始处距离TCP数据段起始处的字节偏移量。确定TCP数据段头部分的长度,告诉接收端的应用程序,数据从何处开始
  • 保留字段:用于TCP后续发展,全部为0
  • 标识符
    • CWR:拥塞窗口减少标志
      • 表明它接收到了设置ECE标志的TCP包
      • 发送方收到消息之后,通过减小发送窗口的大小来降低发送速率
    • ECE:在TCP三次握手时表明一个TCP端是具备ECN功能
      • 在数据传输过程中,用来表明接收到的TCP包的IP头部的ECN被设置为11,即网络线路拥堵
    • URG:紧急指针
      • 1:表明有紧急数据,紧急指针字段生效
    • ACK:确认序号有效
      • TCP连接建立后,此字段必须为1
    • PSH:接收方应该尽快将这个报文交给应用层
      • 1:表示应当立即把数据提交给上层,而不是缓存起来
    • RST:重建连接
      • 1:说明TCP连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接
    • SYN:同步序号用来发起一个连接
      • SYN=1, ACK=0:表示这是一个请求建立连接的报文段
      • SYN=1, ACK=1:表示对方同意建立连接
      • 只有在前两次握手中SYN才为1
    • FIN:发端完成发送任务
      • 表示数据已经发送完成,可以释放连接。
  • 窗口大小:它表示从ACK开始还可以接收多少字节的数据量,也表示当前接收端的接收窗口还有多少剩余空间,用于 TCP 的流量控制
  • 检验和:它用于确认传输的数据是否有损坏
    • 发端计算和储存,收端进行验证
  • 紧急指针:指出本数据段中为紧急数据的字节数,和序号字段相加标识紧急数据最后一个字节的序号

可选项

  • timestamp:
    • 计算往返时延RTT
    • 防止序列号的回绕问题
  • Fast Open:用于TCP连接快读打开

  • IP数据包

连接建立

三次握手

3次握手本质是指发送了3个报文段

            sequenceDiagram
            Note left of Client: ClOSED
Note right of Server: ClOSED
Client->>Server: SYN=1, ACK=0, seq=x
Note left of Client: SYN_SEND
Note right of Server: SYN_RCVD
Server->>Client: SYN=1, ACK=1, seq=y, ack=x + 1
Note left of Client: ESTABLISHED
Client->>Server: ACK=1, seq=x + 1, ack=y + 1
Note right of Server: ESTABLISHED
          
  • 第一次握手
    • 服务端确认客户端的发送能力、和自己的接收能力是正常的
    • 客户端:ClOSED -> SYN_SEND
    • 服务端:LISTEN -> SYN_RCVD
  • 第二次握手
    • 客户端确认服务端的接收、发送能力,和自己的接收、发送能力是正常
    • 客户端:SYN_SEND -> ESTABLISHED
    • 服务端:不变化
  • 第三次握手
    • 服务端确认客户端的接收、发送能力,和自己的接收、发送能力是正常
    • 客户端:不变化
    • 服务端:SYN_RCVD -> ESTABLISHED

C:×:表示客户端不知道,C:√:表示客户端知道
S:×:表示服务端不知道,S:√:表示服务端知道

第几次握手 Client接收能力 Client发送能力 Server接收能力 Server发送能力
第一次 C:×,S:× C:×,S:√ C:×,S:√ C:×,S:×
第二次 C:√,S:× C:√,S:√ C:√,S:√ C:√,S:√
第三次 C:√,S:√ C:√,S:√ C:√,S:√ C:√,S:√

当双方状态皆为ESTABLISHED, 表面建立起来了建立

3次可以保证双方有最基本的通信条件,因为没有完全可靠的通信协议,多于3次握手的确认是没有意义的,会陷入无限套娃确认中。而少于3次握手的话,客户端发一个握手就建立起一个连接,一旦有建立连接的包因为路由关系多次发送,则会建立无效的连接

ISN

通过SYN报文来交换ISN,Initial Sequence Number(初始序列号)。如果ISN被攻击者预测到,要知道源IP和源端口号都是很容易伪造的,当攻击者猜测ISN之后,直接伪造一个RST后,就可以强制连接关闭的,这是非常危险的。

三次握手过程中可以携带数据

第三次握手的时候,可以携带。前两次握手不能携带数据。

如果前两次握手能够携带数据,那么一旦有人想攻击服务器,那么他只需要在第一次握手中的 SYN 报文中放大量数据,那么服务器势必会消耗更多的时间和内存空间去处理这些数据,增大了服务器被攻击的风险。

第三次握手的时候,客户端已经处于 ESTABLISHED 状态,并且已经能够确认服务器的接收、发送能力正常,这个时候相对安全了,可以携带数据。

快速打开

利用类似Cookies的原理。

  • 首先客户端发送SYN给服务端,服务端接收到。
  • 服务端不是立刻回复SYN + ACK,而是通过计算得到一个SYN Cookie,将这个Cookie放到TCP报文的Fast Open选项中,然后才返回给客户端
  • 客户端拿到这个Cookie的值缓存下来。后面正常完成三次握手

后面的三次握手中

  • 客户端会将之前缓存的Cookie、SYN和HTTP请求发送给服务端
  • 服务端验证了Cookie的合法性,如果不合法直接丢弃,如果是合法的,那么就正常返回SYN + ACK
  • 服务端直接向客户端发HTTP回复

四次挥手

4次挥手是指发送了4个报文段

            sequenceDiagram
            Note left of Client: ESTABLISHED
Note right of Server: ESTABLISHED
Client->>Server: FIN=1, seq=u
Note left of Client: FIN-WAIT1
Note right of Server: CLOSE-WAIT
Server->>Client: ACK=1, seq=v, ack=u + 1
Note left of Client: FIN-WAIT2
Server->>Client: FIN=1, ACK=1, seq=w, ack=u + 1
Note right of Server: LAST-ACK
Note left of Client: TIME-WAIT
Client->>Server: ACK=1, seq=u + 1, ack=w + 1
Note right of Server: CLOSED
          
  • 第一次挥手
    • 客户端告诉服务端要结束连接,等待服务端确认
    • 客户端:ESTABLISHED -> FIN-WAIT1
    • 服务端:不变化
  • 第二次挥手
    • 服务器收到,回复ACK确认,进入等待关闭状态。客户端收到,等待服务器将剩余数据传输完成
    • 客户端:FIN-WAIT1 -> FIN-WAIT2
    • 服务端:ESTABLISHED -> CLOSE-WAIT
  • 第三次挥手
    • 服务端发送连接释放报文,等待客户端确认。
    • 客户端:不变化
    • 服务端:CLOSE-WAIT -> LAST-ACK
  • 第四次挥手
    • 客户端收到,回复ACK确认,服务端收到后CLOSED,客户端等待2MSL后进入CLOSE
    • 客户端:FIN-WAIT2 -> TIME-WAIT
    • 服务端:LAST-ACK -> CLOSED
第几次挥手 Client要结束了 Server要结束了 Client已经结束了 Server已经结束了
第一次 C:√,S:× C:×,S:× C:×,S:× C:×,S:×
第二次 C:√,S:√ C:×,S:× C:×,S:× C:×,S:×
第三次 C:√,S:√ C:×,S:√ C:x,S:x C:×,S:×
第四次 C:√,S:√ C:√,S:√ C:x,S:x C:x,S:√
2MSL之后 - - C:√,S:√ C:√,S:√

等待2MSL的时间在于防止客户端发送的确认报文丢失,服务端在LAST-ACK如果收不到确认报文,会重传,如果客户端已经关闭,则会丢弃,导致服务端无法正常关闭连接。同时,超过2MSL后,可以使本次连接所有报文在网络中消失

断开连接,由于可能还有数据要传输,所以多一次报文传输(本质因为TCP是全双工,即使一边表面关闭没有数据传输,另外一方也可以传输数据,叫半关闭

TCP状态

2MSL等待状态

TIME_WAIT状态称为2MSL等待状态。用于防止最后发送的ACK丢失。但是会导致在这个期间,定义这个连接的Socket不能再被使用。也就是客户端不能再向服务端建立连接。在2MSL期间,任何迟到的报文将被丢弃。所以如果建立相同Socket连接,这个连接的报文会被误解,从而丢弃。因此建立新连接的端口一般和上次不同。

但是如果是服务端重新建立连接就有所不同,因为服务端使用的端口一般是固定的。

  • 1个MSL确保四次挥手中主动关闭方最后的ACK报文最终能达到对端
  • 1个MSL确保对端没有收到ACK重传的FIN报文可以到达

有方法可以跨越这个限制,但是违反了TCP规范

防止FIN_WAIT_2保持连接

客户端在FIN_WAIT_2状态时,可能一直保持连接,只要服务端不发送FIN=1的包,所以为了防止这种情况,服务器可能采取一种倒计时,超出这个计时,则进入CLOSED状态

此操作实际上违背协议规范

平静时间

如果在2MSL等待状态的主机出现故障,TCP会在MSL秒内重新启动,此时需要再经过MSL秒后才能建立连接,称为平静时间,否则可能造成上次连接未发送的包没有被废弃,反而被接收

异常终止

连接可能被异常终止,通过RST字段来实现

  • 客户端尝试与服务器未对外提供服务的端口建立TCP连接,服务器将会直接向客户端发送reset报文
  • 客户端和服务器的某一方在交互的过程中发生异常,该方系统将向对端发送TCP reset报文,告之对方释放相关的TCP连接
  • 接收端收到TCP报文,但是发现该TCP连接并不在其已建立的TCP连接列表内,则其直接向发送端发送reset报文(半打开)
  • 在交互的双方中的某一方长期未收到来自对方的确认报文,则其在超出一定的重传次数或时间后,会主动向对端发送reset报文释放该TCP连接
  • 有些应用开发者在设计应用系统时,会利用reset报文快速释放已经完成数据交互的TCP连接

优点

  • 可以发送reset报文快速终止连接
  • RST接收方会区分时异常关闭还是正常关闭,应用程序使用API提供异常关闭的处理方式

Connection reset by peer

防止半打开

如果一方已经关闭,而另外一方还没有关闭,这样的情况称为半打开。可以引入心跳机制,以检测半打开状态,检测到了发送RST重新建立连接

同时打开

有那么极小的可能性,双方都可能使服务端的时候,同时打开了TCP连接,比如A用8888连接B的8888时,B也用8888端口连接A的8888端口。同时打开需要4次报文,最后会建立1条连接。

同时关闭

有同时打开,那么就可能有双方同时关闭,也就是双方同时发送FIN=1

最后双方会同时进入TIME_WAIT

积压值

最大连接数就是这个饭店最大能容纳的顾客人数,积压值(backlog)就是门外允许排队的最大长度
对于新的连接请求

  • 如果还有空间,则建立连接
  • 如果没有空间,废弃不做任何处理(不发送RST),客户端最终会超时

linux内核中会维护两个队列

  • 未完成队列:接收到一个SYN建立连接请求,处于SYN_RCVD状态
  • 已完成队列:已完成TCP三次握手过程,处于ESTABLISHED状态

当有一个SYN到来请求建立连接时,就在未完成队列中新建一项。当三次握手过程完成后,就将套接口从未完成队列移动到已完成队列。

半连接队列和全连接队列

在TCP三次握手的时候,Linux内核会维护两个队列,分别是:

  • 半连接队列,也称SYN队列;
  • 全连接队列,也称accepet队列;

服务端收到客户端发起的SYN请求后,内核会把该连接存储到半连接队列,并向客户端响应SYN+ACK,接着客户端会返回ACK,服务端收到第三次握手的ACK后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到accept队列,等待进程调用accept函数时把连接取出来。

不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回RST包。

  • 如果半连接队列满了,并且没有开启tcp_syncookies,则会丢弃;
  • 若全连接队列满了,且没有重传SYN+ACK包的连接请求多于1个,则会丢弃;
  • 如果没有开启tcp_syncookies,并且max_syn_backlog减去当前半连接队列长度小于(max_syn_backlog >> 2),则会丢弃;

TCP交互数据流

要处理TCP的数据,就要看TCP的数据有什么,TCP主要有2类数据

  • 块数据:如文件数据,邮件数据等等
  • 交互数据:如指令

块数据大小大,而交互数据很小,所以处理起来需要有不同算法

TCP包在IP数据包的负载里面,它的头信息最少也需要20字节,因此TCP数据包的最大负载是1480 - 20 = 1460字节

Nagle算法

交互数据一般产生:20字节IP头部+20字节TCP头部+1字节数据,局域网上称为微小分组。但是在广域网上,会增加阻塞的风险,所以需要有方法应对。Nagle算法用于是减少广域网的小分组数目,从而减小网络拥塞的出现。

该算法要求TCP连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认达到之前不能发送其他的小分组。TCP需要收集这些少量的分组,并在ACK到来时以一个分组的方式发送出去。其中小分组的定义是小于MSS的任何分组

1
2
3
4
5
6
7
8
9
10
11
if there is new data to send
if the window size >= MSS and available data is >= MSS
send complete MSS segment now
else
if there is unconfirmed data still in the pipe
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
end if
  • 对于MSS的片段直接发送
  • 如果有没有被确认的data在缓冲区内,先将待发送的数据放到buffer中直到被发送的数据被确认
  • 两种情况置位,就直接发送数据,实际上如果是小包,但是没有未被确认的分组,就直接发送数据。

延迟ACK

如果TCP对每个数据包都发送一个ACK确认,那么只是一个单独的数据包为了发送一个ACK代价比较高,所以TCP会延迟一段时间,如果这段时间内有数据发送到对端,则捎带发送ACK,如果在延迟ACK定时器触发时候,发现ACK尚未发送,则立即单独发送

  • 避免糊涂窗口综合症
  • 发送数据的时候将ack捎带发送,不必单独发送ack
  • 如果延迟时间内有多个数据段到达,那么允许协议栈发送一个ack确认多个报文段

坏处

写-写-读,即通过多个写小片数据向对端发送单个逻辑的操作,两次写数据长度小于MSS,当第一次写数据到达对端后,对端延迟ACK,不发送ACK,而本端因为要发送的数据长度小于MSS,所以Nagle算法起作用,数据并不会立即发送,而是等待对端发送的第一次数据确认ack;这样的情况下,需要等待对端超时发送ack,然后本段才能发送第二次写的数据,从而造成延迟

TCP粘包问题

TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的。加上Nagle算法,就可能发送粘包问题。

  • 发送方原因
    • Nagle算法
  • 发送方解决方法
    • 关闭Nagle算法
  • 接收方原因
    • 写入缓存,一并读取
  • 接收方解决方法
    • 应用层处理

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

滑动窗口

TCP头里有一个字段叫Window,又叫Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来

数据分为4类

  • 已经发送并且对端确认
  • 已经发送但未收到确认数据
  • 允许发送但尚未防的数据
  • 未发送暂不允许

同理,接收方分3类

  • 已接收
  • 未接收准备接收
  • 未接收并未准备接收

窗口的大小在传输过程中是可能变化的

后退N帧协议

接收方只能对已经接受的n个分组发出确认。发送方无法知道第n之后分组的下落,而只好把n后面的分组都再重传一次。这就叫做Go-back-N (回退N ),表示需要再退回来重传己发送过分组。

零窗口

在接收方窗口大小变为0的时候,发送方就不能再发送数据了。但是当接收方窗口恢复的时候发送方要怎么知道那?在这个时候TCP会启动一个零窗口(TCP Zero Window)定时探测器,向接收方询问窗口大小,当接收方窗口恢复的时候,就可以再次发送数据。

糊涂窗口综合症

如果我们的接收方太忙了,来不及取走Receive Windows里的数据,那么,就会导致发送方越来越小。到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的window,而我们的发送方会义无反顾地发送这几个字节。

TCP+IP头有40个字节,为了几个字节,要达上这么大的开销,这太不经济了。以太网来说,MTU是1500字节,除去TCP+IP头的40个字节,真正的数据传输可以有1460,这就是所谓的MSS(Max Segment Size)。如果你的网络包可以塞满MTU,那么你可以用满整个带宽,如果不能,那么你就会浪费带宽。所以糊涂窗口综合症就是,本来那么多可用的空间,结果只用了一点

  • 如果这个问题是由Receiver端引起的,那么就会使用 David D Clark’s 方案。在receiver端,如果收到的数据导致window size小于某个值,可以直接ack(0)回sender,这样就把window给关闭了,也阻止了sender再发数据过来,等到receiver端处理了一些数据后windows size 大于等于了MSS,或者,receiver buffer有一半为空,就可以把window打开让send 发送数据过来。
  • 如果这个问题是由Sender端引起的,那么就会使用著名的 Nagle’s algorithm。这个算法的思路也是延时处理,他有两个主要的条件:1)要等到 Window Size>=MSS 或是 Data Size >=MSS,2)收到之前发送数据的ack回包,他才会发数据,否则就是在攒数据。

拥塞控制

拥塞控制的算法其实还有其他很多

TCP的超时与重传

对于每个TCP连接,有4个定时器

  • 重传定时器
  • 坚持定时器
  • 保活定时器
  • 2MSL定时器

重传有2种方式

  • 一种是仅重传timeout的包。
  • 一种是重传timeout后所有的数据

往返时间

往返时间(RTT),TCP每发送一个报文段,就对这个报文段数值一次计时器,只要计时器设置的重传时间到但还没有收到确认,就要重传这一报文段

新的RTT=(1a)×(旧的RTT)+a×(新的RTT)\text{新的RTT} = (1 - a) \times (\text{旧的RTT}) + a \times (\text{新的RTT})

超时重传时间应略大于上面得出的加权平均往返时间RTTRTT(RFC2988推荐aa值为1/81/8

RTO(重传时间)一般为RTO的倍数

Karn算法

上面的经典算法会造成以下问题

  • 情况(a)是ack没回来,所以重传。如果你计算第一次发送和ACK的时间,那么,明显算大了。
  • 情况(b)是ack回来慢了,但是导致了重传,但刚重传不一会儿,之前ACK就回来了。如果你是算重传的时间和ACK回来的时间的差,就会算短了。

Karn算法忽略重传,不把重传的RTT做采样。但是,这样一来,又会引发一个大BUG:如果在某一时间,网络闪动,突然变慢了,产生了比较大的延时,这个延时导致要重传所有的包(因为之前的RTO很小),于是,因为重传的不算,所以,RTO就不会被更新,这是又是一个问题。于是Karn算法用了一个取巧的方式:只要一发生重传,就对现有的RTO值翻倍

Jacobson/Karels算法

上诉方法都是加权平均,也就是惯性平均。这样会平滑掉比较大的波动。Jacobson/Karels这个算法引入了最新的RTT的采样和平滑过的SRTT的差距做因子来计算

SRTT=SRTT+α(RTTSRTT)DevRTT=(1β)×DevRTT+β×RTTSRTTRTO=μ×SRTT+θDevRTTα=0.125,β=0.25,μ=1,θ=4\begin{aligned} \text{SRTT} &= \text{SRTT} + \alpha (\text{RTT} - \text{SRTT}) \\ \text{DevRTT} &= (1 - \beta) \times \text{DevRTT} + \beta \times \vert\text{RTT} - \text{SRTT}\vert \\ \text{RTO} &= \mu \times \text{SRTT} + \theta * \text{DevRTT} \\ &\alpha = 0.125, \beta = 0.25, \mu = 1, \theta = 4 \end{aligned}

由算法可以知道 RTO 的值会根据每次测量的 RTT 值变化而变化,基本要点是 TCP 监视每个连接的性能,由每一个 TCP 的连接情况推算出合适的 RTO 值,根据不同的网络情况,自动修改 RTO 值,以适应负责的网络变化。

慢开始

慢开始指主机由小到大逐渐增大发送窗口,即增大拥塞窗口的数值。

初始拥塞窗口(cwnd)设置为1个最大报文段(SMSS)的数值。慢开始在每收到一个对新的报文段的确认后,cwnd增加一倍SMSS的数值(也就是翻倍)。

1
1 -> 2 -> 4 -> 8
  • N是刚收到确认的报文段所确认的字节数
  • 拥塞窗口每次的增加量要小于SMSS

具体:

  1. 初始化窗口为1,所有发送M1报文段
  2. 收到确认号之后,发送M2-M3两个报文段
  3. 因为拥塞窗口增大了,后面的轮次也是这样翻倍增加的
  4. 那么发送到网络的数据就会急剧增加,容易出现拥塞,因此需要慢开始门限(ssthresh)状态变量。

拥塞避免算法

拥塞避免算法用于处理丢失分组,该算法假定由于分组收到损坏引起的丢失非常少(远小于1%),所以一旦发送丢失,就意味着发生了拥塞。

  • 发生超时(拥塞会丢弃数据包,且不回复)
  • 接受到重复确认

当拥塞发生时,我们希望降低分组进入网络的传输速率。所以会使用慢开始来达到这一点,所以实际中这2个算法通常一起使用。
上方的慢开始门限(ssthresh)。当拥塞发生的时候,ssthresh被设置为当前窗口的一半和接收方通过窗口中的最小值。如果是超时引起的拥塞,则cwnd被设置为1个报文段

  • cwnd < ssthresh时,使用慢开始算法
  • cwnd > ssthresh时,使用拥塞避免算法
  • cwnd = ssthresh时,忙开始或者拥塞避免算法

拥塞避免算法,每次会增加1个cwnd大小,而慢开始是指数方式增长

拥塞状态算法

等到RTO超时,重传数据包

  • sshthresh = cwnd / 2
  • cwnd重置为1
  • 进入慢启动过程

快速重传

快重传是指,如果发送端接收到3个以上的重复ACK,不需要等到重传定时器溢出就重新传递。假设在分组3的似乎丢失,那么接下来的ACK确认都会是确认收到2组为止,所以会产生重复的ACK。

快速回复

快速回复指的是,重传的时候不要一下把cwnd设置为1,而是按照下面的算法

  • 收到3个重复的ACK时,将ssthresh设置为当前拥塞窗口cwnd的一半,重传丢失的报文段,设置cwndssthresh加上3倍的报文段大小
  • 每次收到一个重复的ACK时,cwnd增加一个报文段大小,并发送一个分组
  • 当下一个确认新数据的ACK达到时,设置cwndssthresh。这个ACK应该是在进行重传后的一个往返时间内对步骤1中重传的确认。

因为收到重复的ACK不仅仅告诉我们一个分组丢失了,由于接收方只有在收到另一个报文段时才会产生重复的ACK,所以还告诉我们该报文段已经进入了接收方的缓存。也就是说,在收发两端之间仍然有流动的数据,而我们不想执行慢启动来突然减少数据流

重新分组

TCP超时并重传时,不一定要重传相同的报文段,相反,TCP允许进行重新分组而发送一个较大的报文段(不超过MSS)。这样有助于提升性能。

SACK

这种方式需要在TCP头里加一个SACK的东西

这样可用知道哪些数据收到了,哪些没有收到

坚持定时器

假设接收方发送零窗口后,后续的ACK报文丢失了,会导致双方进入等待,进入死锁。所以需要一个坚持定时器来向周期性的向接收方查询。以便发现接受窗口是否已经增发,这样的报文叫做窗口探查。

保活定时器

TCP建立连接后,客户端和服务端若不发送任何信息,也不重启崩溃,这个连接可以一直保持下去。但是,许多时候,服务端希望知道客户端是否还存活,就使用保活定时器。

保活不是TCP规范,因为出现误判,耗费带宽。保活更应该在应用层进行

我们假设服务端探测客户端的情况

  • 客户端正常运行,则计时器重置
  • 客户端崩溃没有回应,服务器累计发送10个75s的探查,最后关闭连接
  • 客户端崩溃且已经重新启动,回应服务端RST
  • 客户端正常运行,但是服务端不可达,和崩溃一样处理

2MSL定时器

四次挥手客户端一共有三次状态:应用程序关闭,发送完第一个FIN之后进入 FIN_WAIT1,之后接收到来自服务器的ACK进入 FIN_WAIT2。接收到来自服务器的FIN进入 TIME_WAIT 状态(又称2MSL等待状态),使用2MSL定时器进行计时。

TCP攻击

SYN Flood

发送大量虚假IP、端口的SYN报文给主机建立TCP连接,服务端回复SYN-ACK后,这边是不可能有回复的。从而建立大量无效半连接。耗光资源。

防御方法:

  • 虚假源
    • TCP代理
      • 都在防火墙建立半连接,防火墙可能撑不住
    • TXP源检测
      • 先回应一个错误的SYN-ACK包,如果对方发送RST,则源为真,加入白名单
      • 也可以(反DDOS设备)回应一个正确的SYN-ACK包,如果有回应,发送RST重新与真正服务器建立连接
    • 丢弃首包
      • 对方会重连

FIN/RST Flood

攻击者利用僵尸网络发送大量的变源变端口FIN/RST报文攻击。正常用户和服务器连接时,攻击者发送一个伪造的FIN/RST包给服务端,这个伪造的FIN/RST包源地址是正常用户的地址,服务端收到后就会断开与正常用户的连接,随后正常用户发送的数据服务器会认为是网络错误而忽略掉

防御原理:当FIN/RST报文速率超过阈值时,启动会话检查。

  • 如果Anti-DDoS设备检查到FIN/RST报文没有命中会话,直接丢弃报文。
  • 如果Anti-DDoS设备检查到FIN/RST报文命中会话,则根据会话创建原因和会话检查结果来判断该报文是否通过。
    • 如果会话是由SYN或SYN-ACK报文创建的,则允许该FIN/RST报文通过。
    • 如果会话是由其他报文创建的(例如ACK报文),则进一步检查报文序列号是否正确,序列号正确的报文允许通过,不正确的报文则被丢弃。

TCP连接耗尽攻击

连接耗尽攻击是指攻击者通过僵尸网络,向服务器发起大量的TCP连接,耗尽服务器的TCP连接资源。

  • 完成三次握手后,不发送任何报文,一直维持这些TCP连接。
  • 完成三次握手后,立刻发送FIN或RST报文,释放本端连接,同时快速发起新的连接。
  • 连接过程中呈现给服务器端很小的TCP windows size,导致服务器TCP协议栈资源耗尽。
  • 发送大量TCP重传请求,以很小的流量即可导致被攻击网络上行链路拥塞。

防御原理:Anti-DDoS设备对目的IP地址的新建连接速率和并发连接数分布进行统计,当新建连接速率或并发连接数大于阈值时,则触发对源IP地址的相应检查,当检查发现异常时,将异常源IP地址加入黑名单,切断其TCP流量。

  • 源IP地址新建连接速率检查:启动源IP地址新建连接速率检查后,如果某个源IP地址在检查周期内发起的TCP新建连接数大于阈值,则将该源IP地址判定为攻击源。
  • 源IP地址并发连接数检查:启动源IP地址并发连接数检查后,如果某个源IP地址的TCP并发连接数大于阈值,则将该源IP地址判定为攻击源。
  • 慢速连接速率检查:启动慢速连接速率检查后,统计同一源IP地址对同一目的IP地址的连接次数,在各统计时间间隔内,如果连续多次连接数相同,则判定为TCP慢速连接攻击。
  • 异常会话检查:如果在检查周期内,某个源IP地址发起的TCP异常会话的连接数大于阈值时,则将该源IP地址判定为攻击源。判定TCP异常会话依据如下:
    • 空连接检查:如果在检查周期内,在某条TCP连接上通过的报文数小于阈值,则判定该连接为异常连接。
    • 重传会话检查:当某条TCP连接上重传报文数量大于阈值时,则判定该连接为异常连接。
    • 慢启动连接检查:当某条TCP连接上通过的报文窗口小于阈值时,则判定该连接为异常连接。

TCP分片攻击

正常的网络流量中很少出现TCP分片报文,如果网络中TCP分片报文增多,则很可能正受到DDoS攻击。攻击者向攻击目标发送大量的TCP分片报文,通常会造成以下危害:

  • 大量的TCP分片报文消耗带宽资源,造成被攻击者的响应缓慢甚至无法正常回应。
  • 网络设备或服务器收到大量的TCP分片报文,会进行分片重组,这样会导致网络设备或服务器的性能降低,甚至无法正常工作。

防御原理:TCP分片分为首分片和后续分片,Anti-DDoS设备只对首分片执行防御动作,如果首分片异常被丢弃了,后续分片因找不到首分片的会话会直接被后续转发流程丢弃。Anti-DDoS设备基于目的地址对TCP首分片报文速率进行统计,当TCP首分片报文速率超过阈值时,按照以下处理方式:

  • 首先检查报文源IP地址是否命中白名单
    • 如果没有命中白名单,则将该源IP所有发送的TCP分片报文直接丢弃。
    • 如果命中白名单,则报文允许通过。
  • 对于真实源发送的分片报文攻击,Anti-DDoS设备还支持对分片报文限速。

TCP异常报文攻

TCP报文标志位包括URGACKPSHRSTSYNFIN六位,攻击者通过发送非法TCP flag组合的报文,对主机造成危害。

防御原理:检查TCP报文的各个标志位URG、ACK、PSH、RST、SYN、FIN,如果标志位异常,则认为是TCP异常报文。当TCP异常报文的速率大于告警阈值时,将所有TCP异常报文全部丢弃,并记录攻击日志。

  • 6个标志位全为1。
  • 6个标志位全为0。
  • SYN和FIN位同时为1。
  • SYN和RST位同时为1。
  • FIN和RST位同时为1。
  • PSH、FIN和URG位同时为1。
  • 仅FIN位为1。
  • 仅URG位为1。
  • 仅PSH位为1。
  • SYN/RST/FIN标记位为1的分片报文。
  • 带有载荷的SYN、SYN-ACK报文。

UDP攻击

UDP 是一种无连接的协议,不需建立连接来传输数据。Udp flood攻击在短时间那模拟随机的源端口地址向随机的目的端口发送大量的udp包,服务器要处理判断是否是正常的udp包,如果不是正常的udp包,服务器要进行回应处理,造成服务器不能处理其他udp的请求

反射攻击

通过构造大量源地址是受害者IP的包,请求NTP服务器,可以达到放大攻击流量的效果。NTP在以前可以查询历史记录,于是发送很小的查询记录的包,会返回很大的回复包,从而达到放大效果。

  • snmp reflect flood
  • ntp reflect flood
  • dns reflect flood

本质:利用一些协议的请求数据包和响应数据包比例不平衡

防御方法:因为利用协议放大,这些包都有特性,利用动态指纹学习或特征过滤。或者进行限速

  • 报文载荷具有明显特征。
  • 报文负载内容完全一致。

UDP分片攻击

攻击者向攻击目标发送大量的UDP分片报文,通常会造成以下危害。

  • 一般攻击效果是消耗网络带宽资源,严重时造成链路拥塞。
  • 大量UDP分片报文会导致具有会话重组功能的网络设备性能急剧降低。
  • 大量变源变端口的UDP分片报文会导致依靠会话转发的网络设备性能降低,甚至会话耗尽导致网络瘫痪。
  • 如果攻击报文达到服务器开放的UDP业务端口,服务器检查报文的正确性需要消耗计算资源,造成服务器响应缓慢甚至无法正常回应。

防御原理:UDP分片分为首分片和后续分片,Anti-DDoS设备只对首分片执行防御动作,如果首分片异常被丢弃了,后续分片因找不到首分片的会话会直接被后续转发流程丢弃。UDP首分片防御方法和UDP flood防御方法一致。

防火墙针对UDP分片攻击:支持指纹学习和限流两种防御方式

ICMP攻击

攻击者短时间内发送大量的ICMP报文到被攻击目标,导致依靠会话转发的网络设备会话耗尽引起网络瘫痪,如果采用超大报文攻击也会导致网络链路拥塞。

疯狂PING

基本上不会有业务使用ICMP,禁止ICMP流量即可,或者进行限流

参考