深圳幻海软件技术有限公司 欢迎您!

TCP 四次挥手,你熟了!那意外情况呢?恶意攻击呢?单端跑路呢?

2023-02-27

一、序当我们聊到TCP协议的时候,聊的最多的就是三次握手与四次挥手,但是你有没有想过,三次握手或者四次挥手时,如果发生异常了,是如何处理的?又是由谁处理的?TCP作为一个靠谱的协议,在传输数据的前后,需要在双端之间建立连接,并在双端各自维护连接的状态。TCP并没有多么神奇,在面对着多变的网络情况,也

一、序

当我们聊到 TCP 协议的时候,聊的最多的就是三次握手与四次挥手,但是你有没有想过,三次握手或者四次挥手时,如果发生异常了,是如何处理的?又是由谁处理的?

TCP 作为一个靠谱的协议,在传输数据的前后,需要在双端之间建立连接,并在双端各自维护连接的状态。TCP 并没有多么神奇,在面对着多变的网络情况,也只能通过不断的重传和各种算法来保证可靠性。

当数据传输完成,需要断开连接的时候,TCP 会通过四次握手来完成双端的断连,并回收各自的资源。那么今天就来继续聊聊,TCP 四次挥手过程中,如果出现异常,单端跑路等问题,是如何处理的。

二、TCP 四次挥手

2.1 简单理解四次挥手

虽然是在说四次挥手的异常情况,在此之前,我们还是先来简单了解一下 TCP 的四次挥手。

当数据传输完成,需要断开连接的时候,TCP 会采取四次挥手的方式,来安全的断开连接。

为什么握手需要三次,而挥手需要四次呢?

本质上来说,双端都需要经过一次「分手」的过程,来保证自己和对端的状态正确。本着友好协商的态度,你先提出的分手,也要把最大的善意給对方,不能打了对方一个措手不及。你说不玩了就不玩了,那以后谁还敢和你玩。

下面这张图,是比较经典的 TCP 四次挥手的消息和双端状态的变化。

 

我们解释一下这张图:

1. 初始时双端还都处于 ESTABLISHED 状态并传输数据,某端可以主动发起「FIN」包准备断开连接,在这里的场景下,是客户端发起「FIN」请求。在发出「FIN」后,客户端进入 FIN-WAIT-1 状态。

2. 服务端收到「FIN」消息后,回复「ACK」表示知道了,并从 ESTABLISHED 状态进入 CLOSED-WAIT 状态,开始做一些断开连接前的准备工作。

3. 客户端收到之前「FIN」的回复「ACK」消息后,进入 FIN-WAIT-2 状态。而当服务端做好断开前的准备工作后,也会发送一个「FIN,ACK」的消息給客户端,表示我也好了,请求断开连接,并在发送消息后,服务端进入 LAST-ACK 状态。

4. 客户端在收到「FIN,ACK」消息后,会立即回复「ACK」表示知道了,并进入 TIME_WAIT 状态,为了稳定和安全考虑,客户端会在 TIME-WAIT 状态等待 2MSL 的时长,最终进入 CLOSED 状态。

5. 服务端收到客户端回复的「ACK」消息后,直接从 LAST-ACK 状态进入 CLOSED 状态。

正常的经过四次挥手之后,双端都进入 CLOSED 状态,在此之后,双端正式断开了连接。

2.2 TCP 挥手的异常情况

四次挥手的正常发包和应答过程,我们已经简单了解了,接下来就继续看看,四次挥手过程中,出现的异常情况。

三次握手的正常发包和应答,以及双端的状态扭转我们已经讲了,接下来就来看看在这三次握手的过程中,出现的异常情况。

1. 断开连接的 FIN 包丢了。

我们前面一直强调过,如果一个包发出去,在一定时间内,只要没有收到对端的「ACK」回复,均认为这个包丢了,会触发超时重传机制。而不会关心到底是自己发的包丢了,还是对方的「ACK」丢了。

所以在这里,如果客户端率先发的「FIN」包丢了,或者没有收到对端的「ACK」回复,则会触发超时重传,直到触发重传的次数,直接关闭连接。

对于服务端而言,如果客户端发来的「FIN」没有收到,就没有任何感知。会在一段时间后,也关闭连接。

2. 服务端第一次回复的 ACK 丢了。

此时因为客户端没有收到「ACK」应答,会尝试重传之前的「FIN」请求,服务端收到后,又会再重传「ACK」。

而此时服务端已经进入 CLOSED-WAIT 状态,开始做断开连接前的准备工作。当准备好之后,会回复「FIN,ACK」,注意这个消息是携带了之前「ACK」的响应序号的。

只要这个消息没丢,客户端可以凭借「FIN,ACK」包中的响应序号,直接从 FIN-WAIT-1 状态,进入 TIME-WAIT 状态,开始长达 2MSL 的等待。

3. 服务端发送的 FIN,ACK 丢了。

服务端在超时后会重传,此时客户端有两种情况,要么处于 FIN-WAIT-2 状态(之前的 ACK 也丢了),会一直等待;要么处于 TIME-WAIT 状态,会等待 2MSL 时间。

也就是说,在之后的一小段时间内客户端还在,客户端在收到服务端发来的「FIN,ACK」包后,也会回复一个「ACK」应答,并做自己的状态切换。

4. 客户端最后回复的 ACK 丢了。

客户端在回复「ACK」后,会进入 TIME-WAIT 状态,并开始长达 2MSL 的等待,服务端因为没有收到「ACK」的回复,会重试一段时间,直到服务端重试超时后主动断开。

或者等待新的客户端接入后,收到服务端重试的「FIN」消息后,回复「RST」消息,在收到「RST」消息后,复位服务端的状态。

5. 客户端收到 ACK 后,服务端跑路了。

客户端在收到「ACK」后,进入了 FIN-WAIT-2 状态,等待服务端发来的「FIN」包,而如果服务端跑路了,这个包永远都等不到。

在 TCP 协议中,是没有对这个状态的处理机制的。但是协议不管,系统来凑,操作系统会接管这个状态,例如在 Linux 下,就可以通过 tcp_fin_timeout 参数,来对这个状态设定一个超时时间。

需要注意的是,当超过 tcp_fin_timeout 的限制后,状态并不是切换到 TIME_WAIT,而是直接进入 CLOSED 状态。

参考:https://blog.huoding.com/2016/09/05/542

6. 客户端收到 ACK 后,客户端自己跑路了。

客户端收到「ACK」后直接跑路,服务端后续在发送的「FIN,ACK」就没有接收端,也就不会得到回复,会不断的走 TCP 的超时重试的机制,此时服务端处于 LAST-ACK 状态。

那就要分 2 种情况分析:

  1. 在超过一定时间后,服务端主动断开。
  2. 收到「RST」后,主动断开连接。

「RST」消息是一种重置消息,表示当前错误了,应该回到初始的状态。如果客户端跑路后有新的客户端接入,会在此发送「SYN」以期望建立连接,此时这个「SYN」将被忽略,并直接回复「FIN,ACK」消息,新客户端在收到「FIN」消息后是不会认的,并且会回复一个「RST」消息。

参考:https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux

三、小结时刻

今天我们聊了 TCP 在四次挥手阶段出现错误时的一些处理策略。

大多数情况下,TCP 协议本身已经保证了可靠性,例如超时重传,而有些时候,又需要操作系统来接管一些状态,例如 tcp_fin_timeout 等。

关于 TCP 三次握手的异常情况,在之前的文章中所有讲解,有需要可以点击蓝字阅读。

【本文为51CTO专栏作者“张旸”的原创稿件,转载请通过微信公众号联系作者获取授权】

 

戳这里,看该作者更多好文