添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
使用 Python 和 Wireshark 理解 MySQL 客户端/服务器协议:第 1 部分

原文链接: https://www.turing.com/blog/understanding-mysql-client-server-protocol-using-python-and-wireshark-part-1/

背景介绍

MySQL 客户端 / 服务端 协议有很多应用场景。例如:

那么, MySQL 客户端 / 服务端 协议到底是什么东西呢?

MySQL 客户端 / 服务端 协议是一种被接受的约定(规则)。通过这些规则,客户端和服务器可以实现“对话”并能够相互理解。客户端通过带有特殊套接字的 TCP 连接连接到 MySQL 的服务端,向服务端发送特殊数据包并从服务端接收返回数据。这个连接有会有两个阶段:

下面通过一幅图来描述一下这两个阶段:

数据包介绍

每个数据包都包含有价值的数据类型。每个数据包的最大长度是 16M 。如果数据包的长度超过 16M ,这个数据包就会被切分成多个数据块(每个 16M 的块)。首先我们来看一下协议的数据类型。 MySQL 的客户端和服务端有两种数据类型:

请看官方文档: https://dev.mysql.com/doc/internals/en/basic-types.html

整数类型

整数类型也被分成两部分:

固定长度的整数类型占用: 1 2 3 4 6 或者 8 字节。例如,如果我们想用 int<3> 数据类型描述数字 2 ,那么我们可以这样写成十六进制格式: 02 00 00 。或者如果我们想用 int<2> 数据类型描述数字 2 ,那么我们可以写成这样十六进制格式: 02 00

长度编码的整数类型占用: 1 3 4 或者 9 字节。在长度编码的整数类型之前有 1 个字节,这一个字节用来检测整数的长度,我们必须要检查这第一个字节。

如果第一个字节的等于 0xfb ,那么就不需要读取下一个字节了,它等于 MySQL NULL 值,如果等于 0xff 则表示它未定义。

例如,要将 fd 03 00 00 ... 转换为普通整数,我们必须读取第一个字节,它是 0xfd 。根据上述规则,我们必须读取接下来的 3 个字节并将其转换为普通整数,其值在十进制数系统中为 2 。所以长度编码的整数数据类型的值为 2

字符串类型

字符串类型同样被分割成为几部分:

使用 Wireshark 抓包数据

我们启动 Wireshark 工具来嗅探网络数据,通过 IP 条件来过滤 MySQL 的数据包(在我的环境中,服务端 IP 是: 54.235.111.67 )。接下来我们尝试使用我本地的 MySQL 本地客户端工具来连接到 MySQL 服务器。

正如在 TCP 连接到服务器后看到的内容,我们有几个来自服务器的 MySQL 数据包,首先是握手数据包。

让我们深入研究这个数据包并描述每个字段含义:

3 个字节是数据包长度:

接下来 1 字节是数据包序号:

剩余字节为 MySQL 客户端 / 服务端 协议的握手数据包的有效载荷:

我们来详细描述一下握手数据包每一个属性的内容:

  • 协议号 - Int<1> (Protocol number – Int<1>)
  • 服务端版本号 - String (Server version – String)
  • 线程 id - Int<4> (Thread id – Int<4>)
  • 盐值1 - String (Salt1 – String)
  • 服务端能力 - Int<2> (Server capabilities – Int<2>)
  • 服务端语言 - Int<1> (Server language – Int<1>)
  • 服务端状态 - Int<2> (Server Status – Int<2>)
  • 扩展服务端能力 - Int<2> (Extended Server Capabilities – Int<2>)
  • 身份认证插件长度 - Int<1> (Authentication plugin length – Int<1>)
  • 保留字节 - 10 字节 (Reserved bytes – 10 bytes)
  • 盐值2 - String (Salt2 – String)
  • 身份验证插件字符串 - String (Authentication plugin string – String)

服务器语言是整数数值,下表将帮助我们通过整数值选择合适的语言:

在我的测试环境中服务端语言的整数数值是 16 进制的 0x08 (在十进制数值中也是 8 )。从上表中我们可以看到 8 的等价内容是 latin1_swedish_ci ,所以我们知道服务器的默认语言是 latin1_swedish_ci

服务器能力和服务器状态同样是整数类型,但是通过读取这些整数的每个 BIT ,我们可以了解服务器的能力和状态。下图描述了服务器功能和状态位:

在握手数据包客户端准备将 登录请求包 发送到服务器进行身份验证。现在让我们研究一下登录请求包:

  • 3 字节描述了载荷的长度;
  • 接下来的 1 字节是数据包序号;
  • 客户端能力 - Int<2> / 与服务器功能相同
  • 客户端扩展能力 - Int<2> / 与服务器扩展能力功能相同
  • 最大数据包 - Int<4> / 描述了数据包的最大长度
  • 字符类型 - Int<1> / 在我的例子中它是 16 进制的 0x21 (十进制数字系统是 33 ),从表中我们可以看到它是 utf8_general_ci ,这里将服务器的默认字符集从 latin1_swedish_ci 设置为 utf8_general_ci
  • 用户名 - String
  • 密码 - String
  • 客户端身份验证插件字符串 - String

正如你所看到的密码是被加密的。为了加密密码,我们可以使用 SHA1 MD5 算法,以及从服务器发送的先前握手包中的 salt1 salt2 字符串作为盐值,为密码内容进行加密。

最后如果我们验证成功,我们会从服务器获得 OK 数据包;否则我们会得到 ERR 数据包。

  • 3 字节是数据包的长度;
  • 1 字节时数据包的序号;
  • 受影响行数 - Int<1>
  • 服务器状态 - Int<2>
  • 警告信息 - Int<2>

就这样我们已经完成了理论知识的学习,现在是时候开始开始进行实践了。在本文的第二部分,我们将不使用外部模块或库从头开始编写我们自己的 MySQL 本地客户端。

感谢您的阅读,本文由 董宗磊的博客 版权所有。如若转载,请注明出处:董宗磊的博客( https://dongzl.github.io/2022/04/24/02-Understanding-MySQL-Client-Server-Protocol-Using-Python-Wireshark-Part-1/
使用 TestContainers 工具以 Docker 编程方式在 Go 语言中实现数据库集成测试
使用 Python 和 Wireshark 理解 MySQL 客户端/服务器协议:第 2 部分