2

# 前言
  在近期的mysql学习过程中,一直想做的一件事就是分析下mysql的协议。了解Mysql协议也是深入了解Mysql的过程。本章的内容由于篇幅过大,会拆分成两章来讲述。

Mysql版本:8.0.20

抓包工具:Wireshark

1.Mysql协议分析

  在分析mysql之前我们首先要把mysql的ssl关掉,抓包过程中TLS解析比较麻烦。

1.1 关闭Mysql SSL

  可以通过配置my.cnf文件加入skip_ssl指令关闭ssl,配置如下:

[mysqld]
skip_ssl

图111.png
<center>图1-1-1 查询SSL关闭</center>

  关闭SSL之后重启mysql服务,可以通过“show global variables like 'have_ssl%';” 查看ssl关闭情况,如图1-1-1。

  通过客户端直接连接mysql的话可能会报“ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.”错误,此时可以通过加入“--connect-expired-password” 参数进行访问。默认情况下Mysql8.0.20会采用caching_sha2_password插件进行密码交互,会存在缓存。此参数是密码的沙箱模式缓存过期。Mysql8.0以下版本采用mysql_native_password插件进行交互。

  其次如果要抓包的话,连接客户端需要使用参数“--protocol=tcp”进行访问,这样方便抓包。

1.2 Mysql 抓包

  抓包之前我们明确一下我们要抓包的内容,抓包内容如下:

$bin/mysql -h localhost -u root -p'123456' --protocol=tcp
mysql>use t1;
mysql>select * from t1;

1.2.1 Mysql 抓包

  语句中我们只有两条语句,执行过程如图1-2-1:
图121.png
<center>图1-2-1 查询过程</center>
  我们直接使用Wireshark进行抓包,抓包格式如下:

No.SourceSportDportProtocolLengthInfo
1localhost569333306TCP6856933 → 3306 [SYN] Seq=0 Win=65535 Len=0 MSS=16344 WS=64 TSval=282542188 TSecr=0 SACK_PERM=1
2localhost330656933TCP683306 → 56933 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=16344 WS=64 TSval=282542188 TSecr=282542188 SACK_PERM=1
3localhost569333306TCP5656933 → 3306 [ACK] Seq=1 Ack=1 Win=408256 Len=0 TSval=282542188 TSecr=282542188
4localhost330656933TCP56[TCP Window Update] 3306 → 56933 [ACK] Seq=1 Ack=1 Win=408256 Len=0 TSval=282542188 TSecr=282542188
5localhost330656933MySQL134Server Greeting proto=10 version=8.0.20
6localhost569333306TCP5656933 → 3306 [ACK] Seq=1 Ack=79 Win=408192 Len=0 TSval=282542188 TSecr=282542188
7localhost569333306MySQL268Login Request user=root
8localhost330656933TCP563306 → 56933 [ACK] Seq=79 Ack=213 Win=408064 Len=0 TSval=282542188 TSecr=282542188
9localhost330656933MySQL62Auth Switch Request
10localhost569333306TCP5656933 → 3306 [ACK] Seq=213 Ack=85 Win=408192 Len=0 TSval=282542188 TSecr=282542188
11localhost330656933MySQL67Response OK
12localhost569333306TCP5656933 → 3306 [ACK] Seq=213 Ack=96 Win=408192 Len=0 TSval=282542188 TSecr=282542188
13localhost569333306MySQL93Request Query
14localhost330656933TCP563306 → 56933 [ACK] Seq=96 Ack=250 Win=408000 Len=0 TSval=282542188 TSecr=282542188
15localhost330656933MySQL139Response
16localhost569333306TCP5656933 → 3306 [ACK] Seq=250 Ack=179 Win=408064 Len=0 TSval=282542188 TSecr=282542188
17localhost569333306MySQL78Request Query
18localhost330656933TCP563306 → 56933 [ACK] Seq=179 Ack=272 Win=408000 Len=0 TSval=282558403 TSecr=282558403
19localhost330656933MySQL113Response
20localhost569333306TCP5656933 → 3306 [ACK] Seq=272 Ack=236 Win=408064 Len=0 TSval=282558403 TSecr=282558403
21localhost569333306MySQL63Request Use Database
22localhost330656933TCP563306 → 56933 [ACK] Seq=236 Ack=279 Win=408000 Len=0 TSval=282558403 TSecr=282558403
23localhost330656933MySQL74Response OK
24localhost569333306TCP5656933 → 3306 [ACK] Seq=279 Ack=254 Win=408000 Len=0 TSval=282558403 TSecr=282558403
25localhost569333306MySQL75Request Query
26localhost330656933TCP563306 → 56933 [ACK] Seq=254 Ack=298 Win=408000 Len=0 TSval=282558404 TSecr=282558404
27localhost330656933MySQL214Response
28localhost569333306TCP5656933 → 3306 [ACK] Seq=298 Ack=412 Win=407872 Len=0 TSval=282558407 TSecr=282558407
29localhost569333306MySQL72Request Query
30localhost330656933TCP563306 → 56933 [ACK] Seq=412 Ack=314 Win=407936 Len=0 TSval=282558407 TSecr=282558407
31localhost330656933MySQL152Response
32localhost569333306TCP5656933 → 3306 [ACK] Seq=314 Ack=508 Win=407744 Len=0 TSval=282558409 TSecr=282558409
33localhost569333306MySQL67Request Show Fields
34localhost330656933TCP563306 → 56933 [ACK] Seq=508 Ack=325 Win=407936 Len=0 TSval=282558409 TSecr=282558409
35localhost330656933MySQL275Response
36localhost569333306TCP5656933 → 3306 [ACK] Seq=325 Ack=727 Win=407552 Len=0 TSval=282558415 TSecr=282558415
37localhost569333306MySQL64Request Show Fields
38localhost330656933TCP563306 → 56933 [ACK] Seq=727 Ack=333 Win=407936 Len=0 TSval=282558415 TSecr=282558415
39localhost330656933MySQL140Response
40localhost569333306TCP5656933 → 3306 [ACK] Seq=333 Ack=811 Win=407488 Len=0 TSval=282558415 TSecr=282558415
41localhost569333306MySQL64Request Show Fields
42localhost330656933TCP563306 → 56933 [ACK] Seq=811 Ack=341 Win=407936 Len=0 TSval=282558415 TSecr=282558415
43localhost330656933MySQL140Response
44localhost569333306TCP5656933 → 3306 [ACK] Seq=341 Ack=895 Win=407360 Len=0 TSval=282558416 TSecr=282558416
45localhost569333306MySQL77Request Query
46localhost330656933TCP563306 → 56933 [ACK] Seq=895 Ack=362 Win=407936 Len=0 TSval=282563656 TSecr=282563656
47localhost330656933MySQL166Response
48localhost569333306TCP5656933 → 3306 [ACK] Seq=362 Ack=1005 Win=407296 Len=0 TSval=282563658 TSecr=282563658
49localhost569333306MySQL61Request Quit
50localhost330656933TCP563306 → 56933 [ACK] Seq=1005 Ack=367 Win=407872 Len=0 TSval=282585990 TSecr=282585990
51localhost569333306TCP5656933 → 3306 [FIN, ACK] Seq=367 Ack=1005 Win=407296 Len=0 TSval=282585990 TSecr=282585990
52localhost330656933TCP563306 → 56933 [ACK] Seq=1005 Ack=368 Win=407872 Len=0 TSval=282585990 TSecr=282585990
53localhost330656933TCP563306 → 56933 [FIN, ACK] Seq=1005 Ack=368 Win=407872 Len=0 TSval=282585990 TSecr=282585990
54localhost569333306TCP5656933 → 3306 [ACK] Seq=368 Ack=1006 Win=407232 Len=0 TSval=282585990 TSecr=282585990

<center>表1-2-2 抓包数据</center>
  如表1-2-2所示,“No.”编号 1~3 为TCP的三次握手。“No.”编号 51~54为TCP的四次挥手部分。其余部分为Mysql的通信数据部分。
Mysql协议通信 1.jpg
<center>图1-2-3 数据包交互流程</center>
  根据表1-2-2中抓包数据,我们可以得到如图1-2-3,我们的一个数据包交互流程。

  在分析数据包时,会有Request Command的Server Command编码该编码在/mysql-8.0.20/include/my_command.h文件中:

enum enum_server_command {
  COM_SLEEP,      /** 0  */
  COM_QUIT,       /** 1 退出,可以看page_protocol_com_quit */
  COM_INIT_DB,    /** 2 初始化数据库, 可以看page_protocol_com_init_db */
  COM_QUERY,      /** 3 查询,可以看 page_protocol_com_query */
  COM_FIELD_LIST, /** 4 列举字段,可以看 page_protocol_com_field_list */
  COM_CREATE_DB, /** 5 创建数据库,可以看 ::dispatch_command */
  COM_DROP_DB,   /** 6 删除数据库,可以看 ::dispatch_command */
  COM_REFRESH,   /** 7 刷新,可以看 page_protocol_com_refresh */
  COM_DEPRECATED_1, /** 8 &lt; Deprecated, used to be COM_SHUTDOWN */
  COM_STATISTICS,   /** 9 查询服务端内部统计,可以看 page_protocol_com_statistics */
  COM_PROCESS_INFO, /** 10 查看processs list 可以看 page_protocol_com_process_info */
  COM_CONNECT,      /** 11 当服务器被拒绝,连接 */
  COM_PROCESS_KILL, /** 12 process kill,可以看 page_protocol_com_process_kill */
  COM_DEBUG,        /** 13 debug,可以看 page_protocol_com_debug */
  COM_PING,         /** 14 ping,可以看page_protocol_com_ping */
  COM_TIME,         /** 15 当服务器被拒绝  */
  COM_DELAYED_INSERT, /** 16 功能已删除  */
  COM_CHANGE_USER,    /** 17 修改用户,可以看 page_protocol_com_change_user */
  COM_BINLOG_DUMP,    /** 18 binlog dump,可以看 page_protocol_com_binlog_dump */
  COM_TABLE_DUMP,     /** 19 dumptable */
  COM_CONNECT_OUT,    /** 20 服务器中的内部命令 */
  COM_REGISTER_SLAVE, /** 21 在主服务器上注册一个从服务器。应该在使用COM_BINLOG_DUMP请求binlog事件之前发送 。 */
  COM_STMT_PREPARE,   /** 22 根据传递的查询字符串创建准备好的语句,例如“SELECT CONC
41 54 28 3f 2c 20 3f 29    20 41 53 20 63 6f 6c 31    AT(?, ?) AS col1”,可以看 page_protocol_com_stmt_prepare */
  COM_STMT_EXECUTE, /** 23 要求服务器执行由标识的准备好的语句stmt-id,可以看 page_protocol_com_stmt_execute */
  
  COM_STMT_SEND_LONG_DATA, /** 24 发送列的数据。重复发送,将数据追加到参数中。可以看 page_protocol_com_stmt_send_long_data */
  COM_STMT_CLOSE, /** 25  取消分配准备好的语句、可以看 page_protocol_com_stmt_close */
  COM_STMT_RESET, /** 26 重置通过COM_STMT_SEND_LONG_DATA 命令累积的准备好的语句的数据, 如果使用COM_STMT_EXECUTE打开了该游标,则关闭该游标、可以看 page_protocol_com_stmt_reset */
  COM_SET_OPTION, /** 27 设置参数,比如启用和禁用当前连接的功CLIENT_MULTI_STATEMENTS、可以设置MYSQL_OPTION_MULTI_STATEMENTS_ON和MYSQL_OPTION_MULTI_STATEMENTS_OFF,可以看 page_protocol_com_set_option */
  COM_STMT_FETCH, /** 28 在COM_STMT_EXECUTE之后从现有结果集中获取行 。可以看。 page_protocol_com_stmt_fetch */
  /**
    Currently refused by the server. See ::dispatch_command.
    Also used internally to mark the session as a "daemon",
    i.e. non-client THD. Currently the scheduler and the GTID
    code does use this state.
    These threads won't be killed by `KILL`

    @sa Event_scheduler::start, ::init_thd, ::kill_one_thread,
    ::Find_thd_with_id
  */
  COM_DAEMON,  /** 29 服务器中的内部命令 */
  COM_BINLOG_DUMP_GTID,  /** 30 如果binlog-filename为空,则服务器将发送第一个已知二进制日志的二进制日志流。 */
  COM_RESET_CONNECTION, /** 31 重置会话状态;比COM_CHANGE_USER它不会关闭并重新打开连接,并且不会重新进行身份验证 更轻巧。可以看 page_protocol_com_reset_connection */
  COM_CLONE, /** 32 克隆 */
  /* 别忘了在sql中更新 sql_parse.cc 的const char *command_name[] 中   */

  /* 最后一个 */
  COM_END /** 33 不是真正的命令. */
};

1.2.2 Mysql 授权版本信息数据包分析

  我们首先从数据包第5条开始看,如图1-2-4。
图124.png
<center>图1-2-4 Server Greeting数据包</center>
  图1-2-4内容可以清晰看到我们Mysql Protocol协议体结构,结构如表1-2-5。

TypeNameDescription
int<3>payload_length有效数据包的长度。数据包中超过组成数据包头的4个字节的字节数。
int<1>sequence_idSequence ID
string<var>payload有效的数据包

<center>表1-2-5 数据包结构</center>
  数据结构可以参照官方文档:
https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_packets.html 。我们可以看到图1-2-3中Mysql Protocol结构,payload_length长度为74,sequence_id为1。Server Greeting中可以获取到mysql到版本号,协议,线程ID,Salt等等,该数据包是服务端回复客户端数据。

1.2.3 Mysql 授权数据包分析

  接下来我们继续看一下,下一个Mysql数据协议包,登陆数据包。
图126.png
<center>图1-2-6 Login Request </center>
  如图1-2-6客户端往服务端发送了一个Login Request,Login Request 对应Username为root,对应的Client Auth Plugin插件名称为caching_sha2_password。

图127.png
<center>图1-2-7 Auth Switch Request</center>
  身份验证开关数据包。

图128.png
<center>图1-2-8 Response OK</center>
  授权ok数据包。
图129.png
<center>图1-2-9 查询@@version_comment</center>
  在使用use t1之前,先调用了“select @@version_comment”,查询对应版本备注。Command指令为3,对应1.2.1小节中的COM_QUERY参数。

1.2.4 Mysql 选择数据库数据包分析

图1210.png

**<center>图1-2-10 SELECT DATABASE</center>** 

  查询当前数据库,当前在那个数据实例下。,Command指令为3,对应1.2.1小节中的COM_QUERY参数。

图1211.png
<center>图1-2-11 use database </center>
  Command指令为2,对应1.2.1小节中的COM_INIT_DB参数。初始化数据空间。

图1212.png
<center>图1-2-12 show databases 和 show tables </center>
  use t1; 数据包中除了上面数据包以外还会发起几个数据包,分别是show databases、show tables、以及Show Fields。几个数据包。Show Fields总数据包对应demo1、t1、t2三张表。也就是show tables返回数据包返回的表。

1.2.5 Mysql 查询数据包分析

图1213.png
<center>图1-2-13 select查询t1表</center>
  如图1-2-13,Command指令为3,对应1.2.1小节中的COM_QUERY参数,Statement对应“select * from t1”,客户端往服务端去送查询数据包。

图1214.png
<center>图1-2-14 select查询返回(一) 字段</center>
  如图1-2-14中,Number of fileds为显示的字段个数。 MySQL Protocol为返回显示字段名称,其中包含数据包长度,数据库名称,表明此,类型等,服务端回复的数据包。

图1215.png
<center>图1-2-15 select查询返回(二)数据 </center>
  如图1-2-15中返回字段对应的数据,总共返回了3行数据。和图1-2-14为同一个数据包。

1.2.6 Mysql 退出数据包分析

图1216.png
<center>图1-2-16 退出数据包 </center>
  根据图1-2-16所示、“No”号为49的Mysql发送了一个Request Quit。Command的值等于1,1刚好就是对应1.2.1小节中的COM_QUIT参数。往服务端发送了退出指令。

2.Mysql协议源码分析

  经过之前数据包分析之后,我又进一步研究了一下源码。把源码的研究过程,整理成了图 2-1-1。根据图中内容可以通过gdb或lldb下一些断点调试下。
图211.png
<center>图2-1-1 通信协议</center>

2.1 客户端部分解析

  在客户端中,我们主要看一下run_plugin_auth函数,其他的的内容可以根据图2-1-1 进行调试。

run_plugin_auth函数原型如下:

/**
  插件驱动程序的客户端身份验证。
  @注意,mysql_real_connect和mysql_change_用户都使用此选项

  @param mysql       mysql
  @param data        插件中的身份验证数据,握手包
  @param data_len    数据长度
  @param data_plugin 为数据准备的插件如果是mysql_change_ user()则为0
  @param db          要使用的初始数据库,可以是0

  @retval 0 ok
  @retval 1 error
*/
int run_plugin_auth(MYSQL *mysql, char *data, uint data_len,
                    const char *data_plugin, const char *db) {
  DBUG_TRACE;
  mysql_state_machine_status status;
  mysql_async_auth ctx;
  memset(&ctx, 0, sizeof(ctx));

  ctx.mysql = mysql; //mysql结构、内部包含连接信息,如:host、user、passwd、unix_socket、server_version等等
  ctx.data = data; //验证数据
  ctx.data_len = data_len; //数据长度
  ctx.data_plugin = data_plugin;  //插件数据mysql8.0.20默认为caching_sha2_password
  ctx.db = db;   //初始化数据库
  ctx.non_blocking = false;
  ctx.state_function = authsm_begin_plugin_auth;

  do {
    status = ctx.state_function(&ctx); //调用插件处理函数
  } while (status != STATE_MACHINE_FAILED && status != STATE_MACHINE_DONE);

  return status == STATE_MACHINE_FAILED; 
}

  根据如上代码我们可以看到run_plugin_auth函数是调用授权插件,授权插件Mysql8.0.20是默认使用caching_sha2_password,图2-1-2。
图212.png
<center>图2-1-2 run_plugin_auth函数调试</center>
  caching_sha2_password在authsm_begin_plugin_auth函数中设置,ctx->auth_plugin对应的内容中的caching_sha2_password_plugin_name宏,/mysql-8.0.20/sql-common/client.cc 文件中。

#define caching_sha2_password_plugin_name "caching_sha2_password"

图213.png
<center>图2-1-3 caching_sha2_password_auth_client函数调用</center>
  在运行run_plugin_auth函数后,调用插件处理函数最终会调用caching_sha2_password_auth_client进行授权处理。caching_sha2_password_auth_client函数也是在authsm_begin_plugin_auth函数中设置,对应ctx->auth_plugin。

总结

(1)Mysql 8.0.20 采用caching_sha2_password插件进行密码验证。Mysql8.0之前是使用mysql_native_password插件进行密码验证。

(2)caching_sha2_password密码验证方式有缓存。

(3)“use 数据库; ”语句会发多条查询包。

  • 1.select DATABASE()
  • 2.use t1
  • 3.show databases
  • 4.show tables

c_rain
23 声望8 粉丝