Skip to content

Commit

Permalink
update network docs
Browse files Browse the repository at this point in the history
  • Loading branch information
fengzhao committed Nov 7, 2024
1 parent eab4abf commit 47d6fb2
Showing 1 changed file with 133 additions and 17 deletions.
150 changes: 133 additions & 17 deletions docs/foundmental/1.MySQL协议分析与抓包概述.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,72 @@ MySQL的客户端和服务端交互是以数据包(Packet) 为单位进行的,



MySQL 客户端和服务端的交互过程主要分为两个阶段:**连接建立阶段(Connection Phase)和命令执行阶段(Command Phase)**
MySQL 客户端和服务端的交互过程主要分为两个阶段:**连接建立阶段(或者有的人也叫握手认证阶段,官网文档叫Connection Phase)和命令执行阶段(Command Phase)**



握手认证阶段在客户端与服务器建立连接后进行(即在三次握手后进行),整体交互过程如下:

1)服务器 ===> 客户端:握手初始化消息;由 MySQL 服务器主动发送一个认证的握手数据包;

2)客户端 ===> 服务端:登陆认证消息;客户端收到握手数据包后,将用户的信息(用户名、密码、数据库等信息)打包发送给 MySQL 服务器;

3)服务端 ===> 客户端:认证结果消息;MySQL 服务端认证,返回认证结果给客户端;



认证成功:如果服务器通过认证,则发送一个 Ok_pack 包给客户端;

认证失败:如果服务器没有通过认证,,则发送一个 error_pack 包;



> 注意:此处握手和三次握手不是一回事;


**问题一:MySQL 客户端与服务器连接过程中,进行了 TCP/IP 的三次握手后,为什么还需要握手认证阶段,这两次握手分别起着什么样的作用?**

进行 TCP/IP 三次握手是为了确保客户端和服务器的传输层正确建立TCP连接,并协商传输层使用的端口和通信协议。通过三次握手,客户端和服务器能够确认双方的发送和接收能力,并避免由于网络问题导致的连接错误。然而,仅仅进行 TCP/IP 三次握手并不能保证客户端的身份是可信的。因此,在建立了可靠的连接之后,还需要进行握手认证阶段。在这个阶段,客户端需要向服务器提供身份验证信息,如用户名、密码等,以证明自己的身份。服务器会验证客户端提供的身份验证信息,并决定是否允许该客户端建立连接。**这个所谓的握手阶段,是MySQL协议内中定义的的握手**



**问题二:在握手认证阶段,为什么服务器会主动发送一个认证的握手数据包?**

**连接建立阶段主要的工作是:身份认证建立连接**。这个握手数据包包含了服务器用于身份验证的信息,如:协议版本号、服务器版本号、服务器权能标志等(即握手初始化报文中的信息),**其中最主要是加密随机数(Salt加盐)**。这些信息用于告诉客户端服务器的身份和所使用的协议版本,以便客户端能够进行正确的身份验证和后续的通信。

在握手过程中,客户端也会发送自己的身份验证信息,如:用户名、数据库名和密码(把明文密码通过盐值来加密后,再发回给服务器)等,来证明自己的身份。客户端和服务器之间会进行双向的身份验证,确保连接的安全性。



> When you connect to a MySQL server, you should use a password. The password is not transmitted as cleartext over the connection.




客户端收到`random string`后,使用hash对密码加密,过程如下:

- 第一步,将密码hash,得到hash值`hash_stage1`; `eg.hash_stage1=sha1("password")`;
- 第二步,二次hash,得到`hash_stage2`; `eg. hash_stage2=sha1(hash_stage1)`;
- 第三步,将密码二次hash得到的值与`random string`进行hash,得到`hash_stage3`; `eg. hash_stage3=sha1("randomstring",hash_stage2)`;
- 第四步,异或处理准备发送给服务器端,得到`reply=xor(hash_stage1,hash_stage3)`;



服务器端收到reply后同样进行hash运算,过程如下:

- 第一步,将保存的hash形式的密码`hashpassword``random string`进行hash,得到`server_hash_stage1=sha1("randomstring","hashpassword")`;

- 第二步,将客户端发送的reply与刚才得到的hash值进行异或运算,得到`xor_value; eg. xor_value=xor(reply,server_hash_stage1)`;

- 第三步,将得到的异或值进行hash,得到`server_hash_stage2``eg. server_hash_stage2=sha1(server_hash_stage1)`;

- 第四步,验证,将最后得到的hash值`server_hash_stage2`与保存的密码`hashpassword`进行比较。`eg. server_hash_stage2==hashpassword`,相等则验证通过。




**连接建立阶段主要的工作是:身份认证建立连接。** 在此阶段有一个握手认证过程:服务端会生成一些服务信息,包括协议号、server 版本、线程 id、加密随机数(Salt加盐)和服务端能力 flag 等信息。客户端则会生成认证信息,包含客户端能力 flag、用户名、加密密码和连接数据库名字等信息。然后,服务端返回认证结果,如果验证正确,则返回 OK 包,否则是异常包。



Expand Down Expand Up @@ -259,16 +320,52 @@ MySQL通信是否开启TLS加密,是由客户端和服务端共同协商是否

##### 客户端命令请求报文

客户端可以发送各种不同类型的命令包,payload中的第一个byte描述`command-type`这个type在include/my_command.h中以enum_server_command枚举方式定义
客户端可以发送各种不同类型的命令包,`payload`中的第一个byte描述`command-type`这个type在 `include/my_command.h`中以`enum_server_command`枚举方式定义.

命令:用于标识当前请求消息的类型,例如切换数据库(0x02)、查询命令(0x03)等。命令值的取值范围及说明如下表(参考MySQL源代码/include/mysql_com.h头文件中的定义。
命令用于标识当前请求消息的类型,例如切换数据库(0x02)、查询命令(0x03)等。命令值的取值范围及说明如下表(参考MySQL源代码/include/mysql_com.h头文件中的定义。




在官网中,把这些类型分为`Text Protocol`,`Utility Commands`,`Prepared Statements`,`Stored Programs`

其中,最主要的是 `COM_QUERY` 消息报文,这个是最常见的请求消息类型,当用户执行SQL语句时发送该消息。
其中,**最主要的命令包是 `COM_QUERY` 消息报文,这个是最常见的请求消息类型,当客户端提交SQL语句时就是这个类型。**

这个包中的第一个字节:用于标识当前请求消息的类型,例如切换数据库(0x02)、查询命令(0x03)等。命令值的取值范围及说明如下表(参考 MySQL 源代码 /include/mysql_com.h 头文件中的定义)

| 类型值 | 命令 | 功能 | 关联函数 |
| ------ | ----------------------- | -------------------------- | ------------------------- |
| 0x00 | COM_SLEEP | (内部线程状态) | (无) |
| 0x01 | COM_QUIT | 关闭连接 | mysql_close |
| 0x02 | COM_INIT_DB | 切换数据库 | mysql_select_db |
| 0x03 | COM_QUERY | SQL查询请求 | mysql_real_query |
| 0x04 | COM_FIELD_LIST | 获取数据表字段信息 | mysql_list_fields |
| 0x05 | COM_CREATE_DB | 创建数据库 | mysql_create_db |
| 0x06 | COM_DROP_DB | 删除数据库 | mysql_drop_db |
| 0x07 | COM_REFRESH | 清除缓存 | mysql_refresh |
| 0x08 | COM_SHUTDOWN | 停止服务器 | mysql_shutdown |
| 0x09 | COM_STATISTICS | 获取服务器统计信息 | mysql_stat |
| 0x0A | COM_PROCESS_INFO | 获取当前连接的列表 | mysql_list_processes |
| 0x0B | COM_CONNECT | (内部线程状态) | (无) |
| 0x0C | COM_PROCESS_KILL | 中断某个连接 | mysql_kill |
| 0x0D | COM_DEBUG | 保存服务器调试信息 | mysql_dump_debug_info |
| 0x0E | COM_PING | 测试连通性 | mysql_ping |
| 0x0F | COM_TIME | (内部线程状态) | (无) |
| 0x10 | COM_DELAYED_INSERT | (内部线程状态) | (无) |
| 0x11 | COM_CHANGE_USER | 重新登陆(不断连接) | mysql_change_user |
| 0x12 | COM_BINLOG_DUMP | 获取二进制日志信息 | (无) |
| 0x13 | COM_TABLE_DUMP | 获取数据表结构信息 | (无) |
| 0x14 | COM_CONNECT_OUT | (内部线程状态) | (无) |
| 0x15 | COM_REGISTER_SLAVE | 从服务器向主服务器进行注册 | (无) |
| 0x16 | COM_STMT_PREPARE | 预处理SQL语句 | mysql_stmt_prepare |
| 0x17 | COM_STMT_EXECUTE | 执行预处理语句 | mysql_stmt_execute |
| 0x18 | COM_STMT_SEND_LONG_DATA | 发送BLOB类型的数据 | mysql_stmt_send_long_data |
| 0x19 | COM_STMT_CLOSE | 销毁预处理语句 | mysql_stmt_close |
| 0x1A | COM_STMT_RESET | 清除预处理语句参数缓存 | mysql_stmt_reset |
| 0x1B | COM_SET_OPTION | 设置语句选项 | mysql_set_server_option |
| 0x1C | COM_STMT_FETCH | 获取预处理语句的执行结果 | mysql_stmt_fetch |




##### 回包报文
Expand Down Expand Up @@ -382,23 +479,42 @@ Sever 告诉客户端自己的版本、分配的连接 ID、服务器的能力

MySQL 采用的是挑战-响应方式来进行身份认证。由服务端生成加密用的盐给客户端,提高安全性。

| 名称 | 类型 | 长度 | 含义 |
| ---------------------------- | ------ | ------ | ------------------------------------------------------------------------------------------------------------ |
| Protocol | int | 1 byte | MySQL 应用协议的版本号参考 MySQL 源代码/include/mysql_version.h 头文件定义 |
| Version | string | 不确定 | MySQL Server 的版本号,MYSQL_SERVER_VERSION 宏定义决定(参考MySQL源代码/include/mysql_version.h头文件定义) |
| Thread ID | int | 4 byte | 建立连接所分配的线程 id |
| Salt | | | 挑战字符串,相当于用于加密密码的盐,分为两部分,长度分别为 8 字节和 12 字节 |
| Server Capabilities | | | 服务器支持的功能,int 为 32 位,1 支持,0 不支持,最多支持 32 种功能。这里占 2 个字节,为 int 的前 16 位(参考MySQL源代码/include/mysql_com.h中的宏定义) |
| Server Language | | | 服务器编码,通常为 utf8mb4 |
| Server Status | | | 服务器状态,通常为 SERVER_STATUS_AUTOCOMMIT,表示自动提交事务 |
| 名称 | 类型 | 长度 | 含义 |
| ---------------------------- | ------ | ------ | ------------------------------------------------------------ |
| Protocol | int | 1 byte | MySQL 应用协议的版本号。这个版本号用于确保客户端和服务器段能够正确地解析和通信。参考 MySQL 源代码/include/mysql_version.h 头文件定义 |
| Version | string | 不确定 | MySQL Server 的版本号,以便客户端能够根据服务器版本的不同采取不同的处理方式或使用不同的协议特性。服务器版本信息通常是一个字符串,包含了 MySQL 的版本号、构建日期、操作系统等信息。参考 `MYSQL_SERVER_VERSION` 宏定义决定(参考MySQL源代码/include/mysql_version.h头文件定义) |
| Thread ID | int | 4 byte | 建立连接所分配的线程 id |
| Salt | | | 挑战字符串(随机的),相当于用于加密密码的盐值,分为两部分,长度分别为 8 字节和 12 字节 |
| Server Capabilities | | | 服务器支持的功能,int 为 32 位,1 支持,0 不支持,最多支持 32 种功能。这里占 2 个字节,为 int 的前 16 位(参考MySQL源代码/include/mysql_com.h中的宏定义) |
| Server Language | | | 服务器编码,通常为 utf8mb4 |
| Server Status | | | 服务器状态,通常为 SERVER_STATUS_AUTOCOMMIT,表示自动提交事务 |
| Extended Server Capabilities | | | 服务器支持的拓展功能,int 为 32 位,1 支持,0 不支持,最多支持 32 种功能。这里占 2 个字节,为 int 的后 16 位 |
| Authentication Plugin Length | | | ?好像指的是挑战字符串的长度,没搞懂为什么是 21 而不是 20 |
| Unused | | | 未使用的部分 |
| Authentication Plugin | string | | 密码采用的加密方法,通常为 mysql_native_password |
| Authentication Plugin Length | | | ?好像指的是挑战字符串的长度,没搞懂为什么是 21 而不是 20 |
| Unused | | | 未使用的部分 |
| Authentication Plugin | string | | 密码采用的加密方法,通常为 mysql_native_password |



**Server Capabilities**



| 标记位名称 | 标志位 | 说明 | |
| ---------------------- | ------ | ---- | ---- |
| CLIENT_LONG_PASSWORD | | | |
| CLIENT_FOUND_ROWS | | | |
| CLIENT_LONG_FLAG | | | |
| CLIENT_CONNECT_WITH_DB | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |



Expand Down

0 comments on commit 47d6fb2

Please sign in to comment.