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