(二)Go-编写MySQL数据库驱动之MySQL协议解析Handshake Packet

· 1437字 · 3分钟 · 阅读量

准备:

Handshake Packet格式 🔗

Handshake packet是由Server向Client发送的初始化包,因为所有从Server向Client端发送的包都是一样的格式,前面的四个字节是包头,其中前三位代表Handshake packet数据长度,第四位是包序列号。下面是Handshake packet具体内容的格式:

MySQL官网参考地址

相对包内容的位置 长度(字节) 名称 描述
0 1 协议版本(Protocol) 协议版本的版本号,通常为10(0x0A)协议版本的版本号,通常为10(0x0A)
1 len = strlen (server_version) + 1(0x00表示结束) 数据库版本(Version) 长度为数据库版本字符串的长度+上标示结束的的一个字节
len + 1 4 线程ID(Thread ID) 此次连接MySQL Server启动的线程ID
len + 5 8 + 1(0x00表示结束) 密码盐值(第一部分)(Salt) 用于后续账户密码验证
len + 14 2 数据库提供的功能(Server Capabilities) 用于与客户端协商通讯方式
len + 16 1 编码格式(Server Language) 标识数据库目前的编码方式
len + 17 2 服务器状态(Server Status) 用于表示服务器状态,比如是否是事务模式或者自动提交模式
len + 19 2 扩展的协议,数据库提供的功能(Extended Server Capabilities) 用于与客户端协商通讯方式
len + 21 1 身份验证插件长度(Authentication Plugin Length) 身份验证插件长度
len + 22 10 保留字节(Unused) 未来可能会用到,预留字节
len + 32 12 + 1(0x00表示结束) 密码盐值(第二部分)(Salt) 用于后续账户密码验证
len + 45 21 + 1(0x00表示结束) 身份验证插件(Authentication Plugin) 密码加密方式

使用的MySQL 5.7.26增加了Extended Server CapabilitiesAuthentication Plugin Length字段,占用了len+19~len+21位的保留字节,现在预留字节还剩余10个字节。其它低版本MySQL预留字节剩余13个。

报文结构:

Img

Img

点击Server Greeting proto=10 version=5.7.26可以看到从Frame物理层到MySQL Protocol应用层各层的具体内容。点击MySQL Protocol可以看到具体的十六进制的内容,以及它的ASCII码。

Wireshark抓包软件中的Packet LengthPacket Number中的值74、0分别就是payload_length sequence_idServer Greeting内是具体的包内容payload

MySQL完整的Handshake Packet包内容4a0000000a352e372e3236003a0000006d6d5c126f20082f00fff7c00200ff81150000000000000000000065446a040c41620a5a0a653f006d7973716c5f6e61746976655f70617373776f726400

根据以上信息解析的某次连接数据库的Handshake packet包payload内容,仅供参考:

&{10 5.7.26  56 [94 14 72 8 12 22 51 99] 63487 192 2 33279 21            [20 92 85 1 34 62 25 56 111 43 14 64]  mysql_native_password }

Go代码实现:

func (HandshakePacket *HandshakePacket) Handshake(packet []byte, username, pwd string) []byte {

	protocolPacket := []byte{0x00, 0x00}
	copy(protocolPacket, packet[0:1])
	HandshakePacket.Protocol = binary.LittleEndian.Uint16(protocolPacket)

	idx := bytes.IndexByte(packet[1:], 0x00)
	dbVer := packet[1:idx]
	HandshakePacket.Version = string(dbVer)

	idx = idx + 2

	HandshakePacket.ThreadId = binary.LittleEndian.Uint32(packet[idx : idx+4])

	HandshakePacket.Salt1 = packet[idx+4 : idx+4+8]

	HandshakePacket.ServerCapabilities = binary.LittleEndian.Uint16(packet[idx+4+8+1 : idx+4+8+1+2])

	languagePacket := []byte{0x00, 0x00}
	copy(languagePacket, packet[idx+4+8+1+2:idx+4+8+1+2+1])
	HandshakePacket.ServerLanguage = binary.LittleEndian.Uint16(append(languagePacket, 0x00))

	HandshakePacket.ServerStatus = binary.LittleEndian.Uint16(packet[idx+4+8+1+2+1 : idx+4+8+1+2+1+2])

	HandshakePacket.ExtendedServerCapabilities = binary.LittleEndian.Uint16(packet[idx+4+8+1+2+1+2 : idx+4+8+1+2+1+2+2])

	pluginLengthPacket := []byte{0x00, 0x00}
	copy(pluginLengthPacket, packet[idx+4+8+1+2+1+2+2:idx+4+8+1+2+1+2+2+1])
	HandshakePacket.AuthPluginLength = binary.LittleEndian.Uint16(pluginLengthPacket)

	HandshakePacket.Unused = string(packet[idx+4+8+1+2+1+2+2+1 : idx+4+8+1+2+1+2+2+1+10])

	salt2Idx := bytes.IndexByte(packet[idx+4+8+1+2+1+2+2+1+10:], 0x00)

	HandshakePacket.Salt2 = packet[idx+4+8+1+2+1+2+2+1+10 : idx+4+8+1+2+1+2+2+1+10+salt2Idx]

	HandshakePacket.AuthenticationPlugin = string(packet[idx+4+8+1+2+1+2+2+1+10+len(HandshakePacket.Salt2):])
	HandshakePacket.echo()
	return GetAuthPacket(append(HandshakePacket.Salt1, HandshakePacket.Salt2...), username, pwd)
}