前言

本文主要介绍 client 与 server 连接与认证过程,包括client和server之间的网络交互情况

使用 mysql 5.6.16官方版本分析

认证流程分析

client 建立连接的认证过程如下图

  1. server 监听端口
  2. client 向server建立TCP连接
  3. server 向client发送挑战码报文(报文详细内容在下文中有分析)
  4. client 使用挑战码加密密码,将加密后的密码包含在回包中,发送给server
  5. server 根据client的回包,验证密码的有效性,给client发送ok包或error包

client 给 server 的回包,和 server 的ok包内容在下面的代码栈里有体现

server 给 client 的挑战码报文下下文中有详细分析

image.png

和其他c语言调用方式一样,mysql client 也是调用 mysql_read_connect 建立连接

mysql_real_connect 实际是 client.c 里面的 CLI_MYSQL_REAL_CONNECT 函数

server监听请求

  1. main
  2. mysqld_main
  3. handle_connections_sockets
  4. /* 监听socket请求 */
  5. poll
  6. mysql_socket_accept
  7. create_new_thread

client 向 server 建立连接请求

  1. main
  2. sql_connect
  3. sql_read_connect
  4. mysql_read_connect
  5. vio_socket_connect
  6. inline_mysql_socket_connect
  7. /*向server发送连接请求*/
  8. connect
  9. /* 等待server回包 */
  10. cli_safe_read
  11. my_net_read
  12. /* client 主要认证过程 */
  13. run_plugin_auth
  14. native_password_auth_client
  15. /* 使用挑战码加密密码 */
  16. scramble
  17. client_mpvio_write_packet
  18. /*
  19. 拼接client的返回包,包含:
  20. 1. client 能力位
  21. 2. 最大报文长度
  22. 3. 字符集
  23. 4. 用户名
  24. 5. 挑战码加密后的密码
  25. 6. database name (如果能力位包含 CLIENT_CONNECT_WITH_DB
  26. 7. client auth plugin name (如果能力位包含 CLIENT_PLUGIN_AUTH
  27. */
  28. send_client_reply_packet
  29. /* 发送认证包 */
  30. my_net_write
  1. handle_one_connection
  2. do_handle_one_connection
  3. thd_prepare_connection
  4. login_connection
  5. check_connection
  6. /* 检查host或IP是否能匹配某个用户 */
  7. acl_check_host
  8. acl_authenticate
  9. do_auth_once
  10. native_password_authenticate
  11. /*给 client 发送挑战码*/
  12. server_mpvio_write_packet
  13. /* 发送 handshake 包 */
  14. send_server_handshake_packet
  15. /* */
  16. server_mpvio_read_packet
  17. /* 接收 client 报文 */
  18. my_net_read
  19. /* 解析client报文 */
  20. parse_client_handshake_packet
  21. /* 检查密码有效性 */
  22. check_scramble
  23. Protocol::end_statement
  24. Protocol::send_ok
  25. /*
  26. 拼接 ok 报文,包含:
  27. 1. 0 标志位,占一字节
  28. 2. affected_rows
  29. 3. last_insert_id
  30. 4. server_status
  31. 5. warning_count
  32. 6. message
  33. 建立连接的时候,一般只有 server_status 有内容,其他都是0或者空
  34. */
  35. net_send_ok
  36. /* 发送 ok 报文 */
  37. my_net_write

挑战码报文

挑战码是一段随机字符串,server 发送给client用于加密client输入的密码,client给server的回包中包含挑战码加密后的密码,从而避免了网络传输明文密码

  1. static bool send_server_handshake_packet(MPVIO_EXT *mpvio,
  2. const char *data, uint data_len)
  3. {
  4. /* 申请 1 + 60 + 20 + 64 = 145 byte */
  5. char *buff= (char *) my_alloca(1 + SERVER_VERSION_LENGTH + data_len + 64);
  6. char scramble_buf[SCRAMBLE_LENGTH];
  7. /* end 是写报文的指针*/
  8. char *end= buff;
  9. /* 报文第一位写 protocol_version */
  10. *end++= protocol_version;
  11. /*
  12. 第二位开始写 server_version
  13. 笔者编译的时候带了suffix,version一共24字节,以5.6.16开头
  14. 加'/0'一共25字节
  15. */
  16. end= strnmov(end, server_version, SERVER_VERSION_LENGTH) + 1;
  17. /* 4个字节保存 thread_id */
  18. int4store((uchar*) end, mpvio->thread_id);
  19. end+= 4;
  20. /*
  21. 发送头8个字节挑战码,用于兼容旧版客户端
  22. 后12个字节写在后面,会被旧版客户端忽略
  23. */
  24. end= (char*) memcpy(end, data, SCRAMBLE_LENGTH_323);
  25. end+= SCRAMBLE_LENGTH_323;
  26. /* 第一段挑战码结尾 */
  27. *end++= 0;
  28. /*
  29. 保存低16位能力位
  30. 能力位作用见官方文档:
  31. https://dev.mysql.com/doc/internals/en/capability-flags.html
  32. */
  33. int2store(end, mpvio->client_capabilities);
  34. /* charset 信息 */
  35. end[2]= (char) default_charset_info->number;
  36. /*
  37. server_status
  38. server_status 是 automatic, in trans 等状态信息
  39. 每个位的含义在 include/mysql_com.h 中,以 SERVER_STATUS_ 开头的宏定义
  40. */
  41. int2store(end + 3, mpvio->server_status[0]);
  42. /* 能力位的高16位 */
  43. int2store(end + 5, mpvio->client_capabilities >> 16);
  44. /* 挑战码长度 */
  45. end[7]= data_len;
  46. DBUG_EXECUTE_IF("poison_srv_handshake_scramble_len", end[7]= -100;);
  47. /* 10字节0,应该是保留位 */
  48. memset(end + 8, 0, 10);
  49. /* 高低共4字节能力位,1字节charset,2字节server_status,1字节挑战码长度,10字节0,共18位 */
  50. end+= 18;
  51. /* 其余部分的挑战码 */
  52. end= (char*) memcpy(end, data + SCRAMBLE_LENGTH_323,
  53. data_len - SCRAMBLE_LENGTH_323);
  54. end+= data_len - SCRAMBLE_LENGTH_323;
  55. /* auth plugin name,一般是 mysql_native_password,21字节 */
  56. end= strmake(end, plugin_name(mpvio->plugin)->str,
  57. plugin_name(mpvio->plugin)->length);