本文已参与「新人创作礼」活动,一起开启掘金创作之路。
问题背景
该问题案例来自于群友分享,咨询 TCP 连接问题,反映在完成 FIN 交互之后,TCP 连接却未正常关闭。看起来比较奇怪,也算是一个不常见的案例吧,简析分享一下。
问题信息
提问截图信息如下
乍看之下,确实如问题描述一样,在 No.8 - 10 双方完成 TCP 四次挥手后,仍然产生了 No.11 - 13 交互数据包,像是 TCP 连接未正常关闭。
提供的跟踪文件简要信息如下,专家信息除了 TCP Keep-Alive 、TCP ZeroWindow 之外并无其他特别的告警信息。
λ capinfos test.pcapng
File name: test.pcapng
File type: Wireshark/... - pcapng
File encapsulation: Ethernet
File timestamp precision: microseconds (6)
Packet size limit: file hdr: (not set)
Number of packets: 13
File size: 1996 bytes
Data size: 1059 bytes
Capture duration: 4.542063 seconds
First packet time: 2022-03-17 14:51:32.055157
Last packet time: 2022-03-17 14:51:36.597220
Data byte rate: 233 bytes/s
Data bit rate: 1865 bits/s
Average packet size: 81.46 bytes
Average packet rate: 2 packets/s
Strict time order: True
Capture hardware: Intel(R) xxx
Capture oper-sys: 64-bit Windows 7 xxx
Capture application: Dumpcap (Wireshark) 3.4.12 (v3.4.12-0-g398502390084)
Number of interfaces in file: 1
Interface #0 info:
Name = \Device\NPF_{xxx}
Description = 本地连接
Encapsulation = Ethernet (1 - ether)
Capture length = 65535
Time precision = microseconds (6)
Time ticks per second = 1000000
Time resolution = 0x06
Operating system = 64-bit Windows 7 xxx
Number of stat entries = 1
Number of packets = 13
Number of resolved IPv4 addresses in file: 2
λ
问题分析
首先是 No.8-10 数据包,展开 TCP Seq、Next Seq 和 ACK 分析,可以确认的是一次标准的 TCP 四次挥手过程,完全正常。
其次是 No.11 数据包 TCP Keep-Alive,为什么 Wireshark 在此会判断这个 ACK 为 TCP Keep-Alive 数据包,主要基于以下规则判断。
Set when the segment size is zero or one, the current sequence number is one byte less than the next expected sequence number, and none of SYN, FIN, or RST are set.
No.11 相较 No.9 ,它的 Seq Num 为 150 正好小于 No.9 Next Seq 151 一个字节,加上其他条件也满足,因此标记成了 TCP Keep-Alive 数据包。
但实际在 TCP 四次挥手看起来已经完成之后,为什么服务器还会发出所谓的 TCP Keep-Alive 数据包进行保活连接呢,岂不是个矛盾。。
最后是 No.12 数据包 TCP ZeroWindow,一样类似的原因,基于以下规则判断
Set when the receive window size is zero and none of SYN, FIN, or RST are set.
No.12 的 Win 明显为 0 ,很自然的加上其他条件也满足,因此标记成了 TCP ZeroWindow 数据包。
综上所述,No.8 - 12 看起来确确实实就是 client 和 server 的一系列交互,wireshark 也根据实际数据包字段进行了相关解析和判断,那么问题出在哪?服务器为什么会有如此怪异的 No.11 发送行为,是否是它的问题?
让我们扩展字段全面分析一下,增加 IP ID 一列,看看数据包排序,在这我们发现了问题端倪。
什么情况 ?No.11 的 IP ID 36508 反而比 No.9 的 IP ID 36509 要小,这不就是乱序嘛???可是为什么 Wireshark 没有标记成 Out-Of-Order 呢,而是 TCP Keep-Alive ,难道是 Wireshark 的问题。
在此当然不是,只能说在这个很特殊的案例中,No.11 乱序的位置非常巧,TCP Keep-Alive 的完整规则如下,也就是说在满足 TCP Keep-Alive 的条件下,它会替代掉 Out-Of-Order,意味着优先。
Set when the segment size is zero or one, the current sequence number is one byte less than the next expected sequence number, and none of SYN, FIN, or RST are set.
Supersedes “Fast Retransmission”, “Out-Of-Order”, “Spurious Retransmission”, and “Retransmission”.
所以真正的问题是数据包乱序,重新排列的数据包 No.1-11 如下,才是正常的交互过程。
而乱序数据包 No.11 的到来,造成处于 TIME_WAIT 状态下的 client 回复了一个 Win=0 的 ACK 数据包,而 server 很自然的以 RST 回应。
问题总结
虽然总说数据包的世界什么现象都有可能发生,但是绝大多数的现象还是有其存在的必然原因的。