石家庄快餐美食交流组

TCP三次握手背的滚瓜乱熟,那意外丢包呢?故意不回复 ACK 呢?

只看楼主 收藏 回复
  • - -
楼主
万能卡片

我们在学习 tcp 建连和断连时,都是一个标准的流程,但是网络是多变的,很多时候并不像教科书那样标准,那么今天就来聊聊 tcp 三次握手出现异常的时候,是如何处理的。

一、序

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

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

建立连接前,tcp 会通过三次握手来保证双端状态正确,然后就可以正常传输数据了,在数据传输完毕后,又通过四次挥手来保证双端合理的断开连接并回收各自的资源。

我们在学习 tcp 建连和断连时,都是一个标准的流程,但是网络是多变的,很多时候并不像教科书那样标准,那么今天就来聊聊 tcp 三次握手出现异常的时候,是如何处理的。

[[312105]]

二、tcp 三次握手

1. 简单理解三次握手

虽然是说三次握手的异常情况,我们还是先来了解一下三次握手。

在通过 tcp 传输数据时,第一步就是要先建立一个连接。tcp 建立连接的过程,就是我们常说的三次握手。

我们经常将三次握手,描述成「请求 → 应答 → 应答之应答」。

至于 tcp 握手为什么是三次,其实就是要让双端都经历一次「请求 → 应答」的过程,来确认对方还在。网络情况是多变的,双端都需要一次自己主动发起的请求和对方回复的应答过程,来确保对方和网络是正常的。

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

我们先来解释一下这张图:

  • 在初始时,双端处于 close 状态,服务端为了提供服务,会主动监听某个端口,进入 listen 状态。
  • 客户端主动发送连接的「syn」包,之后进入 syn-sent 状态,服务端在收到客户端发来的「syn」包后,回复「syn,ack」包,之后进入 syn-rcvd 状态。
  • 客户端收到服务端发来的「syn,ack」包后,可以确认对方存在,此时回复「ack」包,并进入 established 状态。
  • 服务端收到最后一个「ack」包后,也进入 established 状态。

正常的三次握手之后,双端都进入 established 状态,在此之后,就是正常的数据传输过程。

2. tcp 握手的异常情况

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

(1) 客户端第一个「syn」包丢了。

如果客户端第一个「syn」包丢了,也就是服务端根本就不知道客户端曾经发过包,那么处理流程主要在客户端。

而在 tcp 协议中,某端的一组「请求-应答」中,在一定时间范围内,只要没有收到应答的「ack」包,无论是请求包对方没有收到,还是对方的应答包自己没有收到,均认为是丢包了,会触发超时重传机制。

所以此时会进入重传「syn」包。根据《tcp/ip详解卷ⅰ:协议》中的描述,此时会尝试三次,间隔时间分别是 5.8s、24s、48s,三次时间大约是 76s 左右,而大多数伯克利系统将建立一个新连接的最长时间,限制为 75s。

也就是说三次握手第一个「syn」包丢了,会重传,总的尝试时间是 75s。

(2) 服务端收到「syn」并回复的「syn,ack」包丢了。

此时服务端已经收到了数据包并回复,如果这个回复的「syn,ack」包丢了,站在客户端的角度,会认为是最开始的那个「syn」丢了,那么就继续重传,就是我们前面说的「错误 1 流程」。

而对服务端而言,如果发送的「syn,ack」包丢了,在超时时间内没有收到客户端发来的「ack」包,也会触发重传,此时客户端处于 syn_rcvd 状态,会依次等待 3s、6s、12s 后,重新发送「syn,ack」包。

而这个「syn,ack」包的重传次数,不同的操作系统下有不同的配置,例如在 linux 下可以通过 tcp_synack_retries 进行配置,默认值为 5。如果这个重试次数内,仍未收到「ack」应答包,那么服务端会自动关闭这个连接。

同时由于客户端在没有收到「syn,ack」时,也会进行重传,当客户端重传的「syn」收到后,会立即重新发送「syn,ack」包。

(3) 客户端最后一次回复「syn,ack」的「ack」包丢了。

如果最后一个「ack」包丢了,服务端因为收不到「ack」会走重传机制,而客户端此时进入 established 状态。

多数情况下,客户端进入 established 状态后,则认为连接已建立,会立即发送数据。但是服务端因为没有收到最后一个「ack」包,依然处于 syn-rcvd 状态。

那么这里的关键,就在于服务端在处于 syn-rcvd 状态下,收到客户端的数据包后如何处理?

这也是比较有争议的地方,有些资料里会写到当服务端处于 syn-rcvd 状态下,收到客户端的数据包后,会直接回复 rts 包响应,表示服务端错误,并进入 close 状态。

但是这样的设定有些过于严格,试想一下,服务端还在通过三次握手阶段确定对方是否真实存在,此时对方的数据已经发来了,那肯定是存在的。

所以当服务端处于 syn-rcvd 状态下时,接收到客户端真实发送来的数据包时,会认为连接已建立,并进入 established 状态。

那么实际情况,为什么会这样呢?

当客户端在 established 状态下,开始发送数据包时,会携带上一个「ack」的确认序号,所以哪怕客户端响应的「ack」包丢了,服务端在收到这个数据包时,能够通过包内 ack 的确认序号,正常进入 established 状态。

(4) 客户端故意不发最后一次「syn」包。

前面一直在说正常的异常逻辑,双方都还算友善,按规矩做事,出现异常主要也是因为网络等客观问题,接下来说一个恶意的情况。

如果客户端是恶意的,在发送「syn」包后,并收到「syn,ack」后就不回复了,那么服务端此时处于一种半连接的状态,虽然服务端会通过 tcp_synack_retries配置重试的次数,不会无限等待下去,但是这也是有一个时间周期的。

如果短时间内存在大量的这种恶意连接,对服务端来说压力就会很大,这就是所谓的 syn flood 攻击。

这就属于安全攻防的范畴了,今天就不讨论了,有兴趣可以自行了解。

三、小结时刻

今天我们聊了 tcp 在建立连接的三次握手阶段,出现异常,如何处理的一些事儿。

大多数情况下,都是依赖超时重传来保证 tcp 的可靠性,但是重传的次数,状态的转换,这些细节,就是本文的主题。

 


举报 | 1楼 回复

友情链接