主人看到 navicat 和 MySQL 在那嘻嘻哈哈, 眉来眼去的, 好不快乐, 忽然也想自己写个程序, 直接去访问 MySQL, 虽然现在已经有很多现成的中间件可以直接拿来用了, 程序只要负责写 sql 语句就行了, 但是主人想要自己通过 MySQL 协议直接和 MySQL 通讯, 一窥究竟. 于是主人找到 MySQL 说: 亲爱的 MySQL, 我以前和你交流总要通过第三方的驱动在中间传话, 总感觉我们之间还有一个隔阂, 有些话也不方便说, 我现在有些心里话想直接和你交流... 你说行吗?
MySQL 说: 当然行啊, MySQL 受宠若惊, 要和我打交道有多种方法比如: TCP/IP,TLS/SSL,Unix Sockets,Shared Memory,Named pipes 等, 那我们就用 TCP/IP 的方吧. 用 tcp 协议就绕不开三次握手连接和四次握手断开, 所以呢你和我连接的第一件事就是三次握手连接.
主人尴尬的笑了笑, tcp 的三次握手听到听说过很多次, 但是从没有真正的理解...
MySQL 从身后丢过来一个便签: 这里有篇文章可以参考下: https://www.cnblogs.com/zhanyd/p/9877762.html
主人谢道, 还是你体贴, 刚开始 navicat 和你连接的时候, 我是输入了主机地址, 用户名, 密码的, 你们之间是怎么验证的呢?
MySQL 说: 好问题, 所有的客户端和我连接首先都要先经过我的认证, 我和客户端一次正常的交互过程如下:
1. 三次握手建立 TCP 连接.
2. 建立 MySQL 连接, 也就是认证阶段.
服务端 -> 客户端: 发送握手初始化包 (Handshake Initialization Packet).
客户端 -> 服务端: 发送验证包 (Client Authentication Packet).
服务端 -> 客户端: 认证结果消息.
3. 认证通过之后, 客户端开始与服务端之间交互, 也就是命令执行阶段.
客户端 -> 服务端: 发送命令包 (Command Packet).
服务端 -> 客户端: 发送回应包 (OK Packet, or Error Packet, or Result Set Packet).
4. 断开 MySQL 连接.
客户端 -> 服务器: 发送退出命令包.
5. 四次握手断开 TCP 连接.
我专门搞了个认证报文格式, 我会按照以下的格式给客户端发送数据, 然后客户端要根据这里面的内容给我返回验证包, 然后我判断是否有权限登录:
官方的文档是这样子滴:
感觉不直观, 在网上找到一个更直观的图:
具体解释如下:
protocol_version (1) -- 0x0a protocol_version
第一个字节表示协议版本号
server_version (string.NUL) -- human-readable server version
服务器版本号, 字符串遇到 Null 结束
connection_id (4) -- connection id
服务器线程 id
auth_plugin_data_part_1 (string.fix_len) -- [len=8] first 8 bytes of the auth-plugin data
第一部分 8 个字节的挑战随机数, 后面还有第二部分
filler_1 (1) -- 0x00
填充位 0x00
capability_flag_1 (2) -- lower 2 bytes of the Protocol::CapabilityFlags (optional)
服务器权能标志 (低位 2 个字节)
- character_set (1) -- default server character-set, only the lower 8-bits Protocol::CharacterSet (optional)
- This "character set" value is really a collation ID but implies the character set; see the Protocol::CharacterSet description.
字符编码
status_flags (2) -- Protocol::StatusFlags (optional)
服务器状态
capability_flags_2 (2) -- upper 2 bytes of the Protocol::CapabilityFlags
服务器权能标志 (高位 2 个字节)
auth_plugin_data_len (1) -- length of the combined auth_plugin_data, if auth_plugin_data_len is> 0
挑战随机数的长度
string[10] reserved (all [00])
10 个字节的保留位, 都是 00
auth_plugin_data_part_2
挑战随机数的第二部分, 通常是 12 字节
挑战随机数结束标志 00
auth_plugin_name (string.NUL) -- name of the auth_method that the auth_plugin_data belongs to
认证插件的名称, null 结尾 (这部分上面的图表里没有加进去)
主人听完后, 跃跃欲试, 很想验证下 MySQL 说的是不是真的, 于是他找到了密友 Wiresshark, 让他监听下 navicat 和 MySQL 之间的认证包, Wiresshark 很快就完成了任务, 把结果呈上来了:
具体先看服务器发送过来的第一个包:
主人一看, 居然和 MySQL 说的一模一样, 好神奇...
MySQL 笑道: 那当然, 我还能骗你不成. 我发给客户端收到后, 客户端就要返回认证包给我验证啦, 是驴是马我一眼就能认出来了哦, 客户端返回给我要遵循以下的格式:
- Fields
- capability_flags (4) -- capability flags of the client as defined in Protocol::CapabilityFlags
客户端权能标志
max_packet_size (4) -- max size of a command packet that the client wants to send to the server
报文的最大字节数
character_set (1) -- connection's default character set as defined in Protocol::CharacterSet.
字符集编码
username (string.fix_len) -- name of the SQL account which client wants to log in -- this string should be interpreted using the character set indicated by character set field.
用户名
auth-response (string.NUL) -- opaque authentication response data generated by Authentication Method indicated by the plugin name field
用户认证信息, 即密码明文和挑战随机数加密后的 token
database (string.NUL) -- initail database for the connection -- this string should be interpreted using the character set indicated by character set field.
数据库名称
auth plugin name (string.NUL) -- the Authentication Method used by the client to generate auth-response value in this packet. This is an UTF-8 string.
认证方法
主人抓包的结果:
MySQL 收到了主人发过来的认证包: 主人, 经过验证用户名密码都是正确的, 可以登录了, 我要返回一个 ok 报文, 告诉你操作成功了哦, 报文结构如下:
- header:
- OK: header = 0 and length of packet> 7
header=0 并且报文长度 > 7 表示当前是 ok 报文
EOF: header = 0xfe and length of packet < 9
header=0xfe 并且报文长度 < 9 表示当前是 eof 报文
主人抓包的结果:
header = 0, 表示这个是个 ok 报文, status_flags(server status)= 02 表名设置自动提交成功.
主人很高兴: 这是不是说明, 我和你的连接成功了呀?
MySQL: 恭喜你连接成功了, 我们走出了第一步, 接下来你就可以发送命令让我执行了哟.
来源: https://www.cnblogs.com/zhanyd/p/9895241.html