我又一次被震惊了。。。
时间:2021-09-17 15:44:35
手机看文章
扫描二维码
随时随地手机看文章
[导读]大家好,我是小林。昨晚有位读者问了我这么个问题:大概意思是,一个已经建立的TCP连接,客户端中途宕机了,而服务端此时也没有数据要发送,一直处于establish状态,客户端恢复后,向服务端建立连接,此时服务端会怎么处理?看过我的图解网络的读者都知道,TCP连接是由「四元组」唯...
大家好,我是小林。昨晚有位读者问了我这么个问题:大概意思是,一个已经建立的 TCP 连接,客户端中途宕机了,而服务端此时也没有数据要发送,一直处于 establish 状态,客户端恢复后,向服务端建立连接,此时服务端会怎么处理?看过我的图解网络的读者都知道,TCP 连接是由「四元组」唯一确认的。然后这个场景中,客户端的IP、服务端IP、目的端口并没有变化,所以这个问题关键要看客户端发送的 SYN 报文中的源端口是否和上一次连接的源端口相同。1. 客户端的 SYN 报文里的端口号与历史连接不相同如果客户端恢复后发送的 SYN 报文中的源端口号跟上一次连接的源端口号不一样,此时服务端会认为是新的连接要建立,于是就会通过三次握手来建立新的连接。那旧连接里处于 establish 状态的服务端最后会怎么样呢?
-> tcp_v4_do_rcv
-> tcp_rcv_established
-> tcp_validate_incoming
-> tcp_send_ack
我们只关注 tcp_validate_incoming 函数是怎么处理 SYN 报文的,精简后的代码如下:从上面的代码实现可以看到,处于 establish 状态的服务端,在收到报文后,首先会判断序列号是否在窗口内,如果不在,则看看 RST 标记有没有被设置,如果有就会丢掉。然后如果没有 RST 标志,就会判断是否有 SYN 标记,如果有 SYN 标记就会跳转到 syn_challenge 标签,然后执行 tcp_send_challenge_ack 函数。tcp_send_challenge_ack 函数里就会调用 tcp_send_ack 函数来回复一个携带了正确序列号和确认号的 ACK 报文。
killcx 工具的工作原理,如下图。它伪造客户端发送 SYN 报文,服务端收到后就会回复一个携带了正确「序列号和确认号」的 ACK 报文(Challenge ACK),然后就可以利用这个 ACK 报文里面的信息,伪造两个 RST 报文:
- 如果服务端发送了数据包给客户端,由于客户端的连接已经被关闭了,此时客户的内核就会回 RST 报文,服务端收到后就会释放连接。
- 如果服务端一直没有发送数据包给客户端,在超过一段时间后, TCP 保活机制就会启动,检测到客户端没有存活后,接着服务端就会释放掉该连接。
- 丢掉 SYN 报文?
- 回复 RST 报文?
- 回复 ACK 报文?
RFC 文档解释
rfc793 文档里的第 34 页里,有说到这个例子。原文的解释我也贴出来给大家看看。我就不瞎翻译了,意思和我在前面用中文说的解释差不多。
- When the SYN arrives at line 3, TCP B, being in a synchronized state,
and the incoming segment outside the window, responds with an
acknowledgment indicating what sequence it next expects to hear (ACK
100).- TCP A sees that this segment does not acknowledge anything it
sent and, being unsynchronized, sends a reset (RST) because it has
detected a half-open connection.- TCP B aborts at line 5.
- TCP A willcontinue to try to establish the connection;
源码分析
处于 establish 状态的服务端如果收到了客户端的 SYN 报文时,内核会调用这些函数:tcp_v4_rcv-> tcp_v4_do_rcv
-> tcp_rcv_established
-> tcp_validate_incoming
-> tcp_send_ack
我们只关注 tcp_validate_incoming 函数是怎么处理 SYN 报文的,精简后的代码如下:从上面的代码实现可以看到,处于 establish 状态的服务端,在收到报文后,首先会判断序列号是否在窗口内,如果不在,则看看 RST 标记有没有被设置,如果有就会丢掉。然后如果没有 RST 标志,就会判断是否有 SYN 标记,如果有 SYN 标记就会跳转到 syn_challenge 标签,然后执行 tcp_send_challenge_ack 函数。tcp_send_challenge_ack 函数里就会调用 tcp_send_ack 函数来回复一个携带了正确序列号和确认号的 ACK 报文。
如何关闭一个 TCP 连接?
我这里问题大家这么一个问题,如何一个 TCP 连接?可能大家第一反应是「杀掉进程」不就行了吗?是的,这个是最粗暴的方式,杀掉客户端进程和服务端进程影响的范围会有所不同:- 在客户端杀掉进程的话,就会发送 FIN 报文,来断开这个客户端进程与服务端建立的所有 TCP 连接,这种方式影响范围只有这个客户端进程所建立的连接,而其他客户端或进程不会受影响。
- 而在服务端杀掉进程影响就大了,此时所有的 TCP 连接都会被关闭,服务端无法继续提供访问服务。
killcx 工具的工作原理,如下图。它伪造客户端发送 SYN 报文,服务端收到后就会回复一个携带了正确「序列号和确认号」的 ACK 报文(Challenge ACK),然后就可以利用这个 ACK 报文里面的信息,伪造两个 RST 报文:
- 用 Challenge ACK 里的确认号伪造 RST 报文发送给服务端,服务端收到 RST 报文后就会释放连接。
- 用 Challenge ACK 里的序列号伪造 RST 报文发送给客户端,客户端收到 RST 也会释放连接。