前言
本文主要介绍 client 与 server 连接与认证过程,包括client和server之间的网络交互情况
使用 mysql 5.6.16官方版本分析
认证流程分析
client 建立连接的认证过程如下图
- server 监听端口
- client 向server建立TCP连接
- server 向client发送挑战码报文(报文详细内容在下文中有分析)
- client 使用挑战码加密密码,将加密后的密码包含在回包中,发送给server
- server 根据client的回包,验证密码的有效性,给client发送ok包或error包
client 给 server 的回包,和 server 的ok包内容在下面的代码栈里有体现
server 给 client 的挑战码报文下下文中有详细分析
和其他c语言调用方式一样,mysql client 也是调用 mysql_read_connect 建立连接
mysql_real_connect 实际是 client.c 里面的 CLI_MYSQL_REAL_CONNECT 函数
server监听请求
main
mysqld_main
handle_connections_sockets
/* 监听socket请求 */
poll
mysql_socket_accept
create_new_thread
client 向 server 建立连接请求
main
sql_connect
sql_read_connect
mysql_read_connect
vio_socket_connect
inline_mysql_socket_connect
/*向server发送连接请求*/
connect
/* 等待server回包 */
cli_safe_read
my_net_read
/* client 主要认证过程 */
run_plugin_auth
native_password_auth_client
/* 使用挑战码加密密码 */
scramble
client_mpvio_write_packet
/*
拼接client的返回包,包含:
1. client 能力位
2. 最大报文长度
3. 字符集
4. 用户名
5. 挑战码加密后的密码
6. database name (如果能力位包含 CLIENT_CONNECT_WITH_DB
7. client auth plugin name (如果能力位包含 CLIENT_PLUGIN_AUTH
*/
send_client_reply_packet
/* 发送认证包 */
my_net_write
handle_one_connection
do_handle_one_connection
thd_prepare_connection
login_connection
check_connection
/* 检查host或IP是否能匹配某个用户 */
acl_check_host
acl_authenticate
do_auth_once
native_password_authenticate
/*给 client 发送挑战码*/
server_mpvio_write_packet
/* 发送 handshake 包 */
send_server_handshake_packet
/* */
server_mpvio_read_packet
/* 接收 client 报文 */
my_net_read
/* 解析client报文 */
parse_client_handshake_packet
/* 检查密码有效性 */
check_scramble
Protocol::end_statement
Protocol::send_ok
/*
拼接 ok 报文,包含:
1. 0 标志位,占一字节
2. affected_rows
3. last_insert_id
4. server_status
5. warning_count
6. message
建立连接的时候,一般只有 server_status 有内容,其他都是0或者空
*/
net_send_ok
/* 发送 ok 报文 */
my_net_write
挑战码报文
挑战码是一段随机字符串,server 发送给client用于加密client输入的密码,client给server的回包中包含挑战码加密后的密码,从而避免了网络传输明文密码
static bool send_server_handshake_packet(MPVIO_EXT *mpvio,
const char *data, uint data_len)
{
/* 申请 1 + 60 + 20 + 64 = 145 byte */
char *buff= (char *) my_alloca(1 + SERVER_VERSION_LENGTH + data_len + 64);
char scramble_buf[SCRAMBLE_LENGTH];
/* end 是写报文的指针*/
char *end= buff;
/* 报文第一位写 protocol_version */
*end++= protocol_version;
/*
第二位开始写 server_version
笔者编译的时候带了suffix,version一共24字节,以5.6.16开头
加'/0'一共25字节
*/
end= strnmov(end, server_version, SERVER_VERSION_LENGTH) + 1;
/* 4个字节保存 thread_id */
int4store((uchar*) end, mpvio->thread_id);
end+= 4;
/*
发送头8个字节挑战码,用于兼容旧版客户端
后12个字节写在后面,会被旧版客户端忽略
*/
end= (char*) memcpy(end, data, SCRAMBLE_LENGTH_323);
end+= SCRAMBLE_LENGTH_323;
/* 第一段挑战码结尾 */
*end++= 0;
/*
保存低16位能力位
能力位作用见官方文档:
https://dev.mysql.com/doc/internals/en/capability-flags.html
*/
int2store(end, mpvio->client_capabilities);
/* charset 信息 */
end[2]= (char) default_charset_info->number;
/*
server_status
server_status 是 automatic, in trans 等状态信息
每个位的含义在 include/mysql_com.h 中,以 SERVER_STATUS_ 开头的宏定义
*/
int2store(end + 3, mpvio->server_status[0]);
/* 能力位的高16位 */
int2store(end + 5, mpvio->client_capabilities >> 16);
/* 挑战码长度 */
end[7]= data_len;
DBUG_EXECUTE_IF("poison_srv_handshake_scramble_len", end[7]= -100;);
/* 10字节0,应该是保留位 */
memset(end + 8, 0, 10);
/* 高低共4字节能力位,1字节charset,2字节server_status,1字节挑战码长度,10字节0,共18位 */
end+= 18;
/* 其余部分的挑战码 */
end= (char*) memcpy(end, data + SCRAMBLE_LENGTH_323,
data_len - SCRAMBLE_LENGTH_323);
end+= data_len - SCRAMBLE_LENGTH_323;
/* auth plugin name,一般是 mysql_native_password,21字节 */
end= strmake(end, plugin_name(mpvio->plugin)->str,
plugin_name(mpvio->plugin)->length);
当前内容版权归 阿里云RDS-数据库内核组 或其关联方所有,如需对内容或内容相关联开源项目进行关注与资助,请访问 阿里云RDS-数据库内核组 .