TCP Bug 的数据包看吐了! 真·卡了一个1024的

一、背景

最近在预颁布环境上遇到一个特意诡异的疑问,事件大略是这样的:

设施在消费时须要走一个注册的环节,外面触及到和服务端启动 TCP 通讯失掉性能文件、发送密钥等操作,然而消费进展会卡在70%。

流程如下图所示。

大家不用细看外面的原理,只用看 D4 阶段和 D5 阶段即可。

数据通讯方式:TCP。

性能文件长这样,key=value 方式存储。

name=rabbitB2=asdf21...

当性能文件中的 name 字段为 rabbit 时,设施反常消费,当性能文件中的 name 字段为 rabbit-TD 时,设施就无法消费成功,消费进展会卡在 70%。

从现象来看,不确定是设施端没有口头 D5 阶段,还是服务端没有处置成功处置 D5 阶段。

二、排查环节

2.1、审核代码

审核下设施端和服务端的代码,有没有对 name 这个字段的长度做一些限度。

论断:设施端和服务端并没有对性能文件的字段长度做限度。

2.2、检查服务端日志

排查下服务端的日志,发现只要 D4 阶段的业务日志打印,D5 阶段的日志没有。

初步论断:设施端没有发送 D5 阶段的数据包。

2.3、服务端抓包

思绪:抓个包看下服务端有没有收到 D5 阶段的数据包。

在服务端经过 microsoft network monitor 抓包工具抓包,而后将抓包文件放到 wireshark 中排查。

下图是设施端和服务端的 TCP 通讯数据。

可以看到设施向服务端发送了性能文件(D4阶段),服务端发送了一个 ACK 照应。

在TCP(传输控制协定)通讯中,当客户端发送一条TCP信息给服务端时,服务端通常会发送一个ACK(确认)照应来标明它曾经成功接纳到了这条信息。这是基于TCP的牢靠传输机制,确保数据能够正确无误地从发送方传输到接纳方。

TCP经常使用序列号和确认号来成功牢靠传输。发送方会为每个发送的字节调配一个序列号,接纳方在收到数据后会发送一个ACK确认,确认号示意接纳方希冀接纳的下一个字节的序列号。假设发送方在必定期间内没有收到ACK确认,它会从新发送数据。(来自 AI)

初步论断:服务端发送了 D4 阶段的 ACK 照应。设施端没有发送 D5 阶段的数据包

留意:这个论断在前面的排查环节中被颠覆。

2.4、设施端抓包

思绪:抓个包看下服务端有没有发送 D5 阶段的数据包。经过如下命令在设施端抓个包:

#tcpdump -i fetho host 192.168.1.253

抓到的数据包如下所示:

经过上图的抓包结果可以看到最后一个阶段是 D4 和 D5,它俩其实是将数据包兼并在一同发送的(这个是我后来才发现的,也是 1024 卡 Bug 发生的源头)

也就是说 D4 和 D5 其实是一个阶段,并没有分开发。

而后设施端不时在期待服务端前往性能文件(P6 阶段)。

初步论断:设施端口头了 D5 阶段,服务端没有口头 P6 阶段,服务端有疑问。

2.5、再查服务端的数据包

这就难堪了,设施端明明口头了 D5 阶段,然而服务端看起来没有收到 D5 的数据包。

从新再看下最后一条数据包,报文内容如下图所示:

关上 D4 阶段的数据报文,可以看到数据外面是蕴含有 D4 阶段的性能文件内容以及D5阶段的文件内容,过后我看到这个报文是懵的:

我看之前的接口文档上写的是 D4 和 D5 阶段分开发送数据?怎样又合在一同发了?

要素:设施端将 D4和D5 的数据包延续写到 socket 中的。

初步论断:服务端没有正确处置 D4 和 D5 合体的数据包。

那怎样办?只能在服务端多加点日志打印看看 D5 的数据包为什么没有正确处置呢。

2.6、剖析数据包

3.6.1 name=rabbit 时的报文(可反常消费)

每个阶段发送一次性报文都是依照这样的格局启动发送:0x1234abcd, length, type,>

说明:

3.6.2 name=rabbit-TD时的报文(不能反常消费)

当性能文件中的 name 字段为 rabbit-TD 时,报文 D4 和 D5 合体后的报文内容如下:

说明:

日志的内容如下:

日志内容如下:

2.7、水落石出

因读取的数据报文到达1024 字节时,将业务数据的长度这四个字节做了切割,前面1024字节蕴含长度字段的第一个字节,长度字段的前面3个字节和恳求类型的 1个字节组成了长度字段的 4 个字节,也就是错位多读取了前面一个字节的内容,最后算出来长度的值为 65538,不等于前面的业务数据的 256 字节,造成服务端的程序报错,所续代码就没有口头了。

三、处置打算

3.1、打算一

要素就是前面读取的 length 的 1 个字节没有和后续读取的 length 的三个字节分解长度字段 length 的值,那么只需保障第二次读取长度字段length的时刻把之前的 1 个字节拿到即可。

3.2、打算二

还有一个卡 Bug 的打算:将 D4 阶段的性能文件参与一点内容,保障性能文件的内容 = 1014 + 1 =1 即可,或大于等于 1014+5 = 1019,目标就是把长度字段完整的四个字节卡到 1024 前面,或许把起始数据的四个字节也卡在 1024 前面。

验证了两种状况:name 为 Rabbit-TDDDDDDD 和 Rabbit-TDD 是反常消费的。上方是 Rabbit-TDD 的状况,正好将 D4 的数据 + D5 的起始数据卡满了 1024 字节。

如下图所示:

再来给大家算一遍如何卡 Bug 的,系统能反常运转。

1024 字节 = 1(性能文件报文内容) + 4(性能文件报文长度) + 1(恳求类型) + 4(D5报文起始数据)。

1024 字节 = 1019(性能文件报文内容) + 4(性能文件报文长度) + 1(恳求类型)= 1024 字节。

还有两个不懂:

- D4 阶段的起始数据为啥没有算到 1024 字节中,这里我也没弄懂 Socket的数据是怎样分开、兼并发送的。

- 服务端为什么是读取 1024 字节就会分红下次读取?技术栈是 mina 框架,出疑问的是 windows server 2003,而win10上没重现这个疑问。

您可能还会对下面的文章感兴趣: