TCP三次握手

一句话:在TCP连接建立过程中,客户端首先向服务端发送SYN报文,服务端接收到SYN报文后会回复一个SYN+ACK报文,客户端再回复一个ACK报文,这样就完成了TCP连接的建立。

详细过程

刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。

缩写

SYN: Synchronize Sequence Numbers,同步序列号

ISN: Initial Sequence Number,初始同步序列号

第一次握手

客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN

此时客户端处于 SYN_SENT 状态。

第二次握手

服务器收到客户端的 SYN 报文之后,回复一个 ACK 报文作为应答,把客户端的 ISN + 1 作为 ACK 的值,表示已经收到了客户端的 SYN,约定从数据流从这个数开始。并且指定自己的初始化序列号 ISN

此时服务器处于 SYN_REVD 的状态。

第三次握手

客户端收到 SYN 报文之后,回复一个 ACK 报文,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 ACK ,并约定数据流从这个数开始。

此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,变为 ESTABLISHED 状态,双方就建立起了连接。

发送第一个 SYN 的一端将执行主动打开(active open),接收这个 SYN 并发回下一个 ACK 的另一端执行被动打开(passive open)

为什么需要三次握手

弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。

三次握手的目的是建立可靠的连接,两次握手无法达到这个目的。

第一次握手:客户端发送网络包,服务端收到了。

这样服务端就能得出结论:客户端的发送能力是正常的。

第二次握手:服务端发包,客户端收到了。

这样客户端就能得出结论:服务端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。

如果这个阶段就视为连接建立成功,那么:

  1. 后续服务端发送的数据,客户端可能收不到,这是不可靠连接

  2. 服务端成功建立连接是会为连接创建 fd 并放入全连接队列,但如果客户端的接收能力有问题,那么服务端的资源是浪费的。

第三次握手:客户端发包,服务端收到了。

这样服务端就能得出结论:客户端的接收能力正常。

因此,需要三次握手才能确认双方的接收与发送能力是否正常。

半连接队列

服务器第一次收到客户端的 SYN 之后,会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,称为半连接队列。

虽然叫队列,但半连接队列其实是个 hash表

当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

服务器发送完 ACK 包,如果未收到客户确认包,则会进行退避式重传(间隔变大)。如果重传次数超过配置的最大重传次数,则会将该连接信息从半连接队列中删除。

SYN攻击

服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到 SYN 洪泛攻击。SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 则回复确认包,并等待 Client 确认,由于源地址不存在,因此 Server 需要不断重发直至超时,这些伪造的 SYN 包将长时间占用半连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。

检测 SYN 攻击非常容易,当你在服务器上看到大量的半连接状态的连接时,特别是源 IP 地址是随机的,基本上可以断定这是一次 SYN 攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

线上这种情况有四个 TCP 参数可以调,第一个是:tcp_synack_retries 可以用来减少重试次数;第二个是:tcp_max_syn_backlog,可以增大 SYN 连接数;第三个是:tcp_abort_on_overflow 处理不过来干脆就直接拒绝连接了。第四个方法是开启 cookies 功能,使用 cookie,不使用半连接队列

ISN 是固定的吗?

是动态随机生成的。

当一端为建立连接而发送它的 SYN 时,它会为连接设一个初始序号。ISN 随时间而变化,因此每个连接都将具有不同的 ISNISN 可以看作是一个 32 bit 的计数器,每 4ms1

这样选择序号的目的在于:

  1. 这个号要作为以后的数据通信的序号,以保证收到的数据不会因为网络上的传输的问题而乱序。如果序号固定,就无法分辨顺序了

  2. 增加安全性。如果序号固定,攻击者很容易猜出后续的确认号

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

其实第三次握手的时候,是可以携带数据的(HTTP3 就是这么做的)。但是,第一次、第二次握手不可以携带数据

假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,重复发 SYN 报文的话,会大量浪费服务器的资源。

而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。

TCP状态机

最后附上一张 TCP 状态机的图。

参考

猿人谷 - 关于三次握手和四次挥手

Last updated