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

网络 网络管理
排查下服务端的日志,发现只有 D4 阶段的业务日志打印,D5 阶段的日志没有。初步结论:设备端没有发送 D5 阶段的数据包。

一、背景

最近在预发布环境上遇到一个特别诡异的问题,事情大概是这样的:

设备在生产时需要走一个注册的过程,里面涉及到和服务端进行 TCP 通信获取配置文件、发送密钥等操作,但是生产进度会卡在70%。

流程如下图所示。

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

数据通信方式:TCP。

图片图片

配置文件长这样,key=value 形式存储。

name=rabbit
B2=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, data。

  • 0x1234abcd : 起始数据
  • lenght: 业务数据长度
  • type: 请求类型
  • data:业务数据

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

图片图片

说明:

  • 指定的业务数据的长度的值必须和后面的业务数据报文的长度相等(比如D4阶段的配置文件的数据,D5 阶段的密钥数据的长度),否则会执行报错,这也是导致 D5 阶段未正确执行的根本原因。
  • D4 阶段的配置文件的数据的长度为 0x00 0x00 0x03 0xF4 ,转成十进制就是 1011。
  • 服务端在读取 D4 阶段报文时,先读取 4 字节的配置文件数据长度length。然后读取1 字节的请求类型type,最后再只读取 1011 字节的数据data,如果业务数据的长度不等于 1011 就会报错!
  • D4阶段总共读取了 1016 字节数据。然后执行 D4 阶段的逻辑。
  • 接着读取 D5 阶段的 4字节的报文起始数据,然后是 4 字节的业务数据的长度(十六进制 0x00 0x00 0x01 0x00 转成十进制是 256),这里总共读取了 1024 字节的数据,刚刚达到了服务端读取数据的最大长度1024,就会分成下一次读取。如下图所示,完整读取了业务数据的长度。

图片图片

  • 然后读取 1字节的请求类型数据,最后是 256 字节的密钥数据。

3.6.2 name=rabbit-TD 时的报文(不能正常生产)

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

图片图片

说明:

  • D4 阶段的配置文件的数据的长度lenth为 0x00 0x00 0x03 0xF6 ,转成十进制就是 1014。
  • 服务端在读取 D4 阶段报文时,先读取 4 字节的配置文件数据长度,然后读取1 字节的请求类型,最后再只读取 1014 字节的数据,这里总共读取了 1019 字节数据。然后执行 D4 阶段的逻辑。这前面的步骤都没有问题。
  • 接着读取 D5 阶段的 4字节的报文起始数据,已经读取了 1023 字节的数据。
  • 再读业务数据的长度 lenth,先读取了 1字节,刚好达到服务端读取数据的最大长度 1024,分成下一次读取。问题就出现在这里,业务数据的长度被分开了!

日志的内容如下:

图片图片

  • 下一次读取时,会直接读 4 个字节的数据,作为读取业务数据的长度,这里产生了错位,因业务数据的长度length已经被读取了一个字节,就只能往后读取 4 个字节。
  • 如下图所示:本来 D5 阶段的业务数据的长度应该是 256 字节,但是因为错位往后读取了一位,把请求类型type的 1 个字节读取了,最后就是 0x00,0x01,0x00,0x02,转成十进制就是 65538,但是 D5 阶段的业务数据只有 256 字节。这就导致传的业务数据的长度和传的业务数据报文长度不一致,所以服务端解析的 D5 的数据报文有问题。如下图所示:

图片图片

日志内容如下:

图片图片

  • 结合上面的说明,来一张完整的报文数据图:

图片

2.7、真相大白

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

三、解决方案

3.1、方案一

原因就是前面读取的 length 的 1 个字节没有和后续读取的 length 的三个字节合成长度字段 length 的值,那么只要保证第二次读取长度字段length的时候把之前的 1 个字节拿到即可。

3.2、方案二

还有一个卡 Bug 的方案:将 D4 阶段的配置文件增加一点内容,保证配置文件的内容 = 1014 + 1 =1015 即可,或大于等于 1014+5 = 1019,目的就是把长度字段完整的四个字节卡到 1024 后面,或者把起始数据的四个字节也卡在 1024 后面。

验证了两种情况:name 为 Rabbit-TDDDDDDD 和 Rabbit-TDD 是正常生产的。下面是 Rabbit-TDD 的情况,正好将 D4 的数据 + D5 的起始数据卡满了 1024 字节。

如下图所示:

  • 左边是出 Rabbit-TD 的日志,系统报错。1023-4-5=1024 或者这样算 1014+5+5=1024。
  • 右边是 Rabbit-TDD的日志,右边正常执行。1024-4+4=1024 或者这样算 1015+5+4=1024。

图片图片

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

1024 字节 = 1015(配置文件报文内容) + 4(配置文件报文长度) + 1(请求类型) + 4(D5报文起始数据)。

1024 字节 = 1019(配置文件报文内容) + 4(配置文件报文长度) + 1(请求类型)= 1024 字节。

还有两个疑问:

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

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

责任编辑:武晓燕 来源: 悟空聊架构
相关推荐

2022-05-16 08:42:26

Pandasbug

2019-04-29 07:53:11

TCP数据包TCP网络编程

2022-06-08 08:14:27

Dubbo数据包源代码

2021-12-29 19:20:41

数据GitHub服务器

2014-06-10 09:16:53

数据包

2021-05-12 00:07:27

TCPIP协议

2014-07-09 09:43:59

2011-01-18 13:50:20

路由跟踪tcptracerou

2022-05-27 07:03:04

JDK场景线程

2020-01-10 09:20:03

手机ISOJDK

2020-07-07 14:49:17

中断轮询数据包

2021-09-13 08:41:52

职场互联网自闭

2009-09-14 17:08:02

WebFormView

2023-03-27 00:13:26

数据包Kubernete网络

2021-04-22 07:47:47

JavaJDKMYSQL

2022-11-30 09:18:51

JavaMyBatisMQ

2017-10-10 15:14:23

BUGiOS 11苹果

2018-01-18 09:05:05

存储数据包分层

2015-01-23 10:04:56

bug程序员

2016-09-28 14:00:56

点赞
收藏

51CTO技术栈公众号