Wireshark网络分析之追踪TiDB官方Bug
问题描述
在我们的小程序首页,用户点击不同的区域,因不同的区域是有不同的内容,但诡异的是返回了同样的信息。
技术实现,包括前后端的逻辑都是非常简单的:后端的不同区域分配的id值不同,前端传入参数带上这个id,以便得到不同的结果。
发生问题之后,我们将注意力集中到后端具体的请求接口,最终的逻辑就是一句SQL语句
SELECT * FROM tbl_challenge WHERE (id=?)
这条语句真的是简单的不能再简单了:根据id,返回不同的记录
明明输入的id=1和id=2,但结果都返回了id=1的记录。
奇哉怪哉。
问题分析
解决该问题之前,首先要做的就是复现该问题。
我们写了一个单元测试,立马复现,代码如下:
func TestTiDBQueryId(t *testing.T) {
t.Run("test query id", func(t *testing.T) {
var info1 = &TblCropInfo{}
selectSql := `SELECT * FROM tbl_crop_info WHERE (id=?)`
err := libs.GetMysqlDb(DressUpDb).Raw(selectSql, 2).QueryRow(&info1)
assert.Nil(t, err)
var info2 = &TblCropInfo{}
selectSql = `SELECT * FROM tbl_crop_info WHERE (id=?)`
err = libs.GetMysqlDb(DressUpDb).Raw(selectSql, 4).QueryRow(&info2)
assert.Nil(t, err)
assert.Equal(t, info1, info2)
})
}
结果返回的都是id=2的数据
那么好,既然复现了。我们需要确定这个问题到底是我们client的问题,还是TiDB服务器的问题。
考虑到TiDB已经被广泛应用,我们首先怀疑自己的代码或者依赖的包有问题。
因为业务逻辑非常简单,很快排除了代码的问题。
那如何确定第三方client包的问题?
第一种方法就是运行代码,进行Debug,一步一步追踪代码的逻辑。
优点是「代码之下,毫无秘密」,缺点是代码较为复杂、层层叠叠,很容易迷失在调用的过程中。
不过幸好,我们还好第二种方法。
第二种方法就是直接抓包。看看我们发出去的请求包中的id值是否为想要赋予的值。
最终确定通过wireshark抓包的方式进行问题的追踪。(因为我自己也比较拿手)
抓包分析
我们的问题在于:代码中给定不同的id,却返回了同一条数据记录。
实验的目的是验证:我们的client发送给TiDB server两个请求中id值是否相等。
为此,我们将这一任务分成两部分:首先抓到请求包,然后对抓包的包进行分析。
进行抓包
- 根据目的
IP,确定网卡接口
为此,我们需要route命令,本质上是查看路由表。根据目的ip,路由表会告诉我们发出信息会经过本机的哪一个网卡接口.
比如我们想要抓取请求百度的包,那它经过哪个网卡呢?
🐂🍺 ~ route -n get www.baidu.com
route to: 110.242.68.4
destination: default
mask: default
gateway: 192.168.10.1
interface: en0
flags: <UP,GATEWAY,DONE,STATIC,PRCLONING>
recvpipe sendpipe ssthresh rtt,msec rttvar hopcount mtu expire
0 0 0 0 0 0 1500 0
没错,如上所展示的,经过en0。那么由此可以确定,wireshark主界面应该选择en0接口,抓取到百度的包。
确定送请求到TiDB server的网卡信息,类似上面百度的例子,只需要我们将TiDB server的ip地址替换掉www.baidu.com即可。
- 进行抓包,然后运行代码,得到抓包文件。
// 代码类似如下
func TestTiDBQueryId(t *testing.T) {
t.Run("test query id", func(t *testing.T) {
var info1 = &TblCropInfo{}
selectSql := `SELECT * FROM tbl_challenge WHERE (id=?)`
err := libs.GetMysqlDb(DressUpDb).Raw(selectSql, 2).QueryRow(&info1)
assert.Nil(t, err)
var info2 = &TblCropInfo{}
selectSql = `SELECT * FROM tbl_challenge WHERE (id=?)`
err = libs.GetMysqlDb(DressUpDb).Raw(selectSql, 4).QueryRow(&info2)
assert.Nil(t, err)
assert.Equal(t, info1, info2)
})
}
分析包
当我们开始分析包的时候,我们需要知道TiDB采用的通信协议。根据官网与 MySQL 兼容性对比的信息:
TiDB 高度兼容 MySQL 5.7 协议、MySQL 5.7 常用的功能及语法。MySQL 5.7 生态中的系统工具(PHPMyAdmin、Navicat、MySQL Workbench、mysqldump、Mydumper/Myloader)、客户端等均适用于 TiDB。
我们得出结论,TiDB采用MySQL作为其应用层协议。
然后开始看包吧。
因为我们的TiDB server采用6033端口提供服务,所以首先我们在过滤器输入tcp.port == 6033,查看执行此次代码得到的相关包。
大致浏览一下,我们期待能在抓包文件看见MySQL协议,结果却没有,但出人意料的是,我们看见了X11协议。
那么,X11协议是个什么东西?
X11 is version 11 of the X Window System protocol.
我们已知TiDB采用MySQL协议进行数据传输,那么出现的X11可能是解析出错了。
我们查看wireshark官网中,对X11协议的描述:

没错,wireshark应该是解析错误。
什么,wireshark还会解析出错?
应用层以下,每一层的协议都有专门的字段显示上层协议是什么,具体见本人拙作网络协议中为什么要有协议类型的字段
应用层协议,wireshark基本上靠端口号+其余的一些信息进行判断。所以就存在出错的可能喽。
此时,我们可以按照wireshark官方的提示,将之解析为MySQL协议。
选中一个X11包,右键,继续点击Decode As

修改Current列为MySQL即可
点击ok,然后回到主界面,此时发现到处都是MySQL协议啦。

我们想要找到执行具体SQL时的通信,在过滤执行tcp.port == 6033 and tcp contains "SELECT * FROM tbl_challenge "

我们发现执行这两句的src-port均为54090,可以确定是在同一个TCP连接中执行了这两个SQL语句。
标记📌这两个SQL预处理包然后Follow TCP Stream

首先查看第一个select语句发送的id数据

如我们观察到的,id=1
然后查看第二个select语句,发送的id数据

我的天,id=30001
也就是说,我们的代码没问题、使用的第三方client包没问题,因为我们真真切切的发送了不同的id值。期望得到不同的记录,结果TiDB server居然给我们返回了相同的数据!
终于找到这次事件的元凶了。
(其实发现client包有问题也没问题,那将是一个金闪闪的PR。)
抓包总结
重要的是明确此次实验的目标:我们需要通过数据包确定,两次select语句传送的id值是否不同。
为此,我们首先需要将运行的代码对TiDB server的请求抓取下来。
其次,经过抽丝剥茧,我们需要找到两次select语句,然后确定对应的id值。
最终我们发现,我们发出去的id值是不同的,但是返回的信息是相同的。
问题直指TiDB官方。
问题的解决
解决其实非常简单,我们将该问题以及问题排查过程发送给云服务厂商,他们回复称升级一下TiDB版本就可,后面升级了版本。我们再次验证,发现没有问题。
bingo!
参考
-
《Wireshark数据包分析实战》 -
TiDB官网 -
Wireshark官网
转载自:https://juejin.cn/post/7171085921608007693