【Network】TCP 三次握手(TCP Three-way Handshake)

Posted by 西维蜀黍 on 2019-05-03, Last Modified on 2024-09-25

TCP 三次握手(TCP Three-way Handshake)

TCP 是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。在 TCP/IP 协议中,TCP 协议提供可靠的连接服务,连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换 TCP 窗口大小信息。

第一次握手 - 建立连接

客户端发送连接请求报文段,将 Control Flag 中的 SYN 位置为 1,Sequence Number(发送序号)为 x;然后,客户端进入 SYN_SENT 状态,等待服务器的确认。

SYN 报文段是指,Control Flag 中的 SYN 位为 1 的 TCP 报文段。

第二次握手 - 服务器收到 SYN 报文段

服务器收到客户端的 SYN 报文段,需要对这个 SYN 报文段进行确认,设置 Acknowledgment Number(确认序号)为 x+1(即 Sequence Number+1);同时,自己还要发送 SYN 请求信息(即将 SYN 位为 1),Sequence Number(发送序号)为 y;服务器端将上述所有信息放到一个报文段(即 SYN+ACK 报文段)中,一并发送给客户端,此时服务器进入 SYN_RECV 状态

类似地,ACK 报文段是指 Control Flag 中的 ACK 位为 1 的 TCP 报文段。而 SYN+ACK 报文段是指 Control Flag 中的 SYN 位和 ACK 位均为 1 的 TCP 报文段。

第三次握手 - 客户端收到服务器的 SYN+ACK 报文段

客户端收到服务器发送的 SYN+ACK 报文段后,将 Acknowledgment Number(确认序号)设置为 y+1,向服务器发送 ACK 报文段,这个报文段发送完毕以后,客户端进入 ESTABLISHED 状态。当服务器端接收到这个 ACK 报文段后,服务器端进入 ESTABLISHED 状态,完成 TCP 三次握手。

而如果,客户端向服务器发送 ACK 报文段没有达到服务器端(或者延迟了),服务器会重新发送一个第二次握手阶段中的 SYN+ACK 报文段。

SYN 攻击

在三次握手过程中,Server 发送 SYN+ACK 之后,收到 Client 的 ACK 之前的 TCP 连接称为半连接(half-open connect),此时 Server 处于 SYN_RCVD 状态 **,当收到 ACK 后,Server 转入 ESTABLISHED 状态 **。

SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 回复确认包,并等待 Client 的确认,由于源地址是不存在的,因此,Server 需要不断重发直至超时,这些伪造的 SYN 包将产时间占用未连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。

SYN 攻击时一种典型的 DDOS 攻击,检测 SYN 攻击的方式非常简单,即当 Server 上有大量半连接状态且源 IP 地址是随机的,则可以断定遭到 SYN 攻击了,使用如下命令可以让之现行:

netstat -nap | grep SYN_RECV

如何防御 SYN 攻击?

SYN 攻击不能完全被阻止,除非将 TCP 协议重新设计。我们所做的是尽可能的减轻 SYN 攻击的危害,常见的防御 SYN 攻击的方法有如下几种:

  • 缩短超时(SYN Timeout)时间
  • 增加最大半连接数
  • 过滤网关防护
  • SYN cookies 技术

三次握手时可能发生的丢包问题

TCP 规定:对有数据的 TCP segment 在接收方收到数据后,必须向发送方发送一个 ACK 数据包以表示确认。

以主机 A 尝试与 B 进行连接为例,我们来分别讨论一下,在进行三次握手时,不同握手阶段的握手数据包丢失时的情况。

第一次握手时丢包

第一个包,即 Client 发给 Server 的 SYN 数据报丢包了,没有到达 B。此后,Client 会周期性超时重传,直到收到 Server 的确认(ACK+SYN 数据报)。

第二次握手时丢包

** 第二个包,即 Server 发给 Client 的 SYN +ACK 数据报丢包了,没有到达 Client **。此后, Server 会周期性超时重传,直到收到 Client 的确认(ACK 数据报)。

这就需要一个超时时间让 Server 将这个连接断开,否则这个连接就会一直占用 Client 的 SYN 连接队列中的一个位置,大量这样的连接就会将 Server 的 SYN 连接队列耗尽,让正常的连接无法得到处理。

目前,Linux 下默认会进行 5 次重发 SYN-ACK 包,重试的间隔时间从 1s 开始,下次的重试间隔时间是前一次的双倍,5 次的重试时间间隔为 1s, 2s, 4s, 8s, 16s,总共 31s,第 5 次发出后还要等 32s 都知道第 5 次也超时了。所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP 才会把断开这个连接。

由于,SYN 超时需要 63 秒,那么就给攻击者一个攻击服务器的机会,攻击者在短时间内发送大量的 SYN 包给 Server (俗称 SYN flood 攻击),用于耗尽 Server 的 SYN 队列。对于应对 SYN 过多的问题,linux 提供了几个 TCP 参数:tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 来调整应对。

第三次握手时丢包

第三个包,即 Client 发给 Server 的 ACK 数据报丢包了,没有到达 Server。

Client 发完 ACK,单方面认为 TCP 为 ESTABLISHED 状态,而 Server 显然认为 TCP 仅处于 SYN_SENT 状态。

因此,具体存在以下三种情况:

  1. 假定在此后一段时间双方都没有数据发送,Server 会周期性超时重传,直到收到 Client 的确认。且收到之后,Server 的 TCP 连接也为 ESTABLISHED 状态,因而可以双向发包。
  2. 假定此时 Client 有数据发送,Server 收到 Client 的 Data + ACK 后,自然会切换为 ESTABLISHED 状态,并接受 Client 的 Data。
  3. 假定 Server 有数据发送,但是因为数据发送不了(由于 Server 未接收到 Client 发来的 ACK 数据报),因此会一直会周期性超时重传 SYN + ACK,直到收到 Client 的确认后,才可以发送数据。

Why Three

三次握手是相对于两次握手而言的。

两次握手是指,当客户端期望与服务端建立连接时,客户端向服务端发送一个 SYN 报文段,服务端收到后,向客户端发送一个 ACK 报文段。当客户端收到后,双方都会认为这个连接建立成功了。

不采用两次握手,而使用三次握手,来作为连接建立完成的标志,是为了避免已失效的客户端连接请求报文段(因为网络延迟问题)突然又传送到了服务端,因而建立了一个不被期望的客户端 - 服务端连接的情况

已失效的连接请求报文段

“已失效的连接请求报文段” 的产生在这样一种情况:客户端发出的第一个连接请求报文段(SYN 报文段)并没有丢包(丢包是指,因为某些原因被中途经过的某个网络节点将这个包丢弃了),而是在某个网络结点长时间地滞留了,以至于延误(delay)到连接释放以后的某个时间点才到达服务端。

本来这是一个早已失效的报文段,对于客户端而言,也不希望再次建立与服务端的连接了。

但服务端收到此过时的连接请求报文段后,就误认为是客户端再次发出的一个新的连接请求。于是就向客户端发出确认报文段(ACK 报文段),同意建立连接。假设不采用 “三次握手”,而采用两次握手,那么这时,只要服务端发出确认报文段(ACK 报文段)后,服务端就会认为新的连接已经建立完成了。

而事实上,这个建立完成的连接,并不是被客户端期望的,因此客户端不会利用这个连接,因而也不会向服务端通过这个连接来发送任何数据。

前面也提到了,由于服务端认为新的连接已经建立完成了,因而会一直苦苦地等待数据,最终服务端的资源就白白浪费掉了。

采用三次握手后:

而采用三次握手后,被延误的连接请求报文段(SYN 报文段)到达了服务端后,服务端因无法判断这个报文段是正常的请求还是被延误的请求,因而自然会向客户端发出确认报文段(SYN+ACK 报文段)。

然而,对于客户端而言,它能够判断这个连接请求报文段(SYN 报文段)是自己之前发送的,但是因为被延误(delay)了,因此自然没有意义,所以不会向服务端发送对应的 ACK 报文段。

服务端通过依赖一个超时计时机制,由于在较短时间后没有得到客户端发送的 ACK 报文段,因而就知道客户端并没有真正要求建立连接,最终不会认为这个连接建立完成了。

因此,三次握手避免了在这种场景下,服务端的资源被白白浪费掉的情况。

换一个角度的分析

这个问题的本质是,通信信道不可靠,但是通信双方仍然需要就某个问题达成一致。

而要解决这个问题,无论你在消息中包含什么信息,三次通信是理论上的最小值。 所以三次握手不是 TCP 本身的要求,而是为了满足 "在不可靠信道上可靠地传输信息" 这一需求所导致的。

请注意这里的本质需求,信道不可靠,数据传输要可靠。 三次达到了,那后面你想接着握手也好,发数据也好,跟进行可靠信息传输的需求就没关系了。 因此,如果信道是可靠的,即无论什么时候发出消息,对方一定能收到,或者你不关心是否要保证对方收到你的消息,那就能像 UDP 那样直接发送消息就可以了。

Reference