当客户端输入 mysql -h x.x.x.x -u root -p 时,服务端在做什么(中)¶
Conventions
- 每一个代码块的顶部都有它所属的文件(相对)路径,如果代码块属于某个函数(方法),那么顶部会有函数(方法)的声明。
- 代码块中的对象,如果很重要,会在行尾补充该对象的声明。
- 我使用的MySQL 版本是 8.0.41。你看到这篇时,可能有了更新的版本,比如 8.0.42,区别不大的。
身份认证¶
sql/sql_connect.cc | |
---|---|
thd_prepare_connection
方法很简短,一眼就看到第 893 行的 login_connection
是关键。
login_connection
方法也很简短,但如果每个方法都完整列出来,代码块会占很大的篇幅,所以接下来的几个方法,直接展示调用链,因为真的不重要,重要的肯定会贴出来的。
sql/sql_connect.cc | |
---|---|
sql/sql_connect.cc | |
---|---|
sql/auth/sql_authentication.cc | |
---|---|
sql/auth/sql_authentication.cc | |
---|---|
- st_mysql_auth *auth
login_connection
-> check_connection
-> acl_authenticate
-> do_auth_once
-> authenticate_user
这么一路调用下来,走到上面这行停住。
auth
是指向结构类型的指针,表示身份认证插件,authenticate_user
是该结构的成员,对应类型 int (*)(MYSQL_PLUGIN_VIO *vio,MYSQL_SERVER_AUTH_INFO *info)
,这是指向身份认证函数的指针类型。
不同的身份认证插件会有不同的实现,我们只看默认插件 caching_sha2_password
的实现。caching_sha2_password
在文件 sql/auth/sha2_password.cc
中。caching_sha2_password_authenticate
函数是身份认证函数在该插件中的具体实现,也就是说 authenticate_user
指向了这个函数,所以接下来我们会看这个函数。
Tip
MySQL 有 3 种身份认证插件,分别是 mysql_native_password
、sha256_password
、caching_sha2_password
。
握手¶
TCP 的握手大家肯定都听过,这儿讲的是 TCP 握手之后,MySQL 实现的服务端与客户端的握手。下面我们通过 Wireshark 查看这个过程。
1、2、3 号包是 TCP 的握手,4 号和 6 号是 MySQL 的握手。
4 号包是 MySQL 服务端发出的,说明握手是服务端发起。上图中左下方有包内容,可以看到服务端通过握手传递了版本号、协议版本、盐、身份认证的插件等信息,对应下面函数:
sql/auth/sha2_password.cc | |
---|---|
write_packet
是指向函数的指针,指向下面的函数 server_mpvio_write_packet
。
关键在第 3241 行 send_server_handshake_packet
函数,函数名翻译成中文就是发送服务端握手包,对应 Wireshark 截图中的 4 号包。
sql/auth/sha2_password.cc | |
---|---|
---
title: "MySQL Server Greeting Packet"
---
packet-beta
0-23: "Packet Length: 80"
24-31: "Packet Number: 0"
32-39: "Protocol: 10"
40-143: "Version: 8.0.41-debug"
144-175: "Thread ID: 8"
176-247: "Salt: C@o\\x13\\x14IeR"
248-263: "Server Capabilities: ffff"
264-271: "Server Language: utf8mb4"
272-287: "Server Status: 0002"
288-303: "Extended Server Capabilities: dfff"
304-311: "Authentication Plugin Length: 21"
312-391: "Unused: 00 00 00 00 00 00 00 00 00 00"
392-495: "Salt: tB0.9HRb,Zj/"
496-671: "Authentication Plugin: caching_sha2_password"
完成发送服务端握手包后,函数返回到 caching_sha2_password_authenticate
。接着,往下走到第 962 行的 read_packet
函数,这也是指向函数的指针,指向 sql/auth/sql_authentication.cc
的 server_mpvio_read_packet
函数,这个函数稍稍有点长,不贴出来了。我们只需要关心,在该函数中调用了下面的 parse_client_handshake_packet
函数,即解析客户端握手包,这个包对应 Wireshark 截图中的 6 号包。
static size_t parse_client_handshake_packet(THD *thd, MPVIO_EXT *mpvio,
uchar **buff, size_t pkt_len) {
// ignore
/*
If client requested SSL then we must stop parsing, try to switch to SSL,
and wait for the client to send a new handshake packet.
The client isn't expected to send any more bytes until SSL is initialized.
*/
if (protocol->has_client_capability(CLIENT_SSL)) { // line 2882
// ignore
if (sslaccept(*(context.get()), protocol->get_vio(), // line 2900
protocol->get_net()->read_timeout, &errptr)) {
DBUG_PRINT("error", ("Failed to accept new SSL connection"));
return packet_error;
}
DBUG_PRINT("info", ("Reading user information over SSL layer"));
int rc = protocol->read_packet(); // line 2907
pkt_len = protocol->get_packet_length();
}
// ignore
char *user = get_string(&end, &bytes_remaining_in_packet, &user_len); // line 2999
// ignore
passwd =
get_length_encoded_string(&end, &bytes_remaining_in_packet, &passwd_len); // line 3011
在解析客户端端握手包过程中,如果发现客户端支持 TLS(第 2882 行),那么会进行 TLS 握手(第 2900 行),TLS 握手完成后客户端会发送用户名和密码,服务端阻塞读取(第 2907 行),然后解析(第 2999 和 3011 行)得到用户名和密码。
小结¶
MySQL 服务端和客户端在 TCP 握手完成后,并没有立刻进行 TLS 握手,而是先做了基础信息的交换,比如版本号、是否支持 TLS 等信息,这是 MySQL 自身实现的握手,如果支持 TLS,那么才进行 TLS 握手,全部完成后,客户端发送用户名和密码,服务端接收到后解析。
下一篇,讲讲服务端如何验证用户名和密码。