MySQL Connection Phase Packets

Initial Handshake Packet

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
1              [0a] protocol version
string[NUL]    server version
4              connection id
string[8]      auth-plugin-data-part-1
1              [00] filler
2              capability flags (lower 2 bytes)
  if more data in the packet:
1              character set
2              status flags
2              capability flags (upper 2 bytes)
  if capabilities & CLIENT_PLUGIN_AUTH {
1              length of auth-plugin-data
  } else {
1              [00]
  }
string[10]     reserved (all [00])
  if capabilities & CLIENT_SECURE_CONNECTION {
string[$len]   auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8))
  if capabilities & CLIENT_PLUGIN_AUTH {
string[NUL]    auth-plugin name
  }

字段详解:

  1. protocol_version (1):值是0x0a,协议版本号10
  2. server_version (string.NUL): 服务器版本号,字符串格式
  3. connection_id (4):连接ID
  4. auth_plugin_data_part_1 (string.fix_len):固定长度为8个字节
  5. filler_1 (1):占位符,值为0x00
  6. capability_flag_1 (2):权能值标示,低字节2位
  7. character_set (1):字符集合
  8. status_flags (2):Protocol::StatusFlags (optional)
  9. capability_flags_2 (2):权能值标示,高字节2位
  10. auth_plugin_data_len (1) :length of the combined auth_plugin_data, if auth_plugin_data_len is > 0
  11. auth_plugin_name (string.NUL) :name of the auth_method that the auth_plugin_data belongs to

握手包golang实现示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Pack used to pack the greeting packet.
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeV10
func (g *Greeting) Pack() []byte {
    // greeting buffer
    buf := common.NewBuffer(256)
    capLower := uint16(g.Capability)
    capUpper := uint16(uint32(g.Capability) >> 16)

    // 1: [0a] protocol version
    buf.WriteU8(g.protocolVersion)

    // string[NUL]: server version
    buf.WriteString(g.serverVersion)
    buf.WriteZero(1)

    // 4: connection id
    buf.WriteU32(g.ConnectionID)

    // string[8]: auth-plugin-data-part-1
    buf.WriteBytes(g.Salt[:8])

    // 1: [00] filler
    buf.WriteZero(1)

    // 2: capability flags (lower 2 bytes)
    buf.WriteU16(capLower)

    // 1: character set
    buf.WriteU8(sqldb.CharacterSetUtf8)

    // 2: status flags
    buf.WriteU16(g.status)

    // 2: capability flags (upper 2 bytes)
    buf.WriteU16(capUpper)

    // Length of auth plugin data.
    // Always 21 (8 + 13).
    buf.WriteU8(21)

    // string[10]: reserved (all [00])
    buf.WriteZero(10)

    // string[$len]: auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8))
    buf.WriteBytes(g.Salt[8:])
    buf.WriteZero(1)

    // string[NUL]    auth-plugin name
    pluginName := "mysql_native_password"
    buf.WriteString(pluginName)
    buf.WriteZero(1)
    return buf.Datas()
}

Protocol::HandshakeResponse41:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
4              capability flags, CLIENT_PROTOCOL_41 always set
4              max-packet size
1              character set
string[23]     reserved (all [0])
string[NUL]    username
  if capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA {
lenenc-int     length of auth-response
string[n]      auth-response
  } else if capabilities & CLIENT_SECURE_CONNECTION {
1              length of auth-response
string[n]      auth-response
  } else {
string[NUL]    auth-response
  }
  if capabilities & CLIENT_CONNECT_WITH_DB {
string[NUL]    database
  }
  if capabilities & CLIENT_PLUGIN_AUTH {
string[NUL]    auth plugin name
  }
  if capabilities & CLIENT_CONNECT_ATTRS {
lenenc-int     length of all key-values
lenenc-str     key
lenenc-str     value
   if-more data in 'length of all key-values', more keys and value pairs
  }

字段详解:

  1. capability_flags (4):客户端权能值标示
  2. max_packet_size (4) :客户端发给服务端最大命令包大小
  3. character_set (1) :字符集
  4. username (string.fix_len):用户名,使用字符集表示字符
  5. auth-response (string.NUL):根据认证方法生成的认证数据(登录密码加密)
  6. database (string.NUL) :初始数据库,使用字符集表示字符
  7. auth plugin name (string.NUL):生成认证数据的认证方法名称,是UTF-8字符串(mysql_native_password/caching_sha2_password)

握手响应包解析golang实现示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// UnPack parses the handshake sent by the client.
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse41
func (a *Auth) UnPack(payload []byte) error {
    var err error
    buf := common.ReadBuffer(payload)

    if a.clientFlags, err = buf.ReadU32(); err != nil {
        return fmt.Errorf("auth.unpack: can't read client flags")
    }
    if a.clientFlags&sqldb.CLIENT_PROTOCOL_41 == 0 {
        return fmt.Errorf("auth.unpack: only support protocol 4.1")
    }
    if a.maxPacketSize, err = buf.ReadU32(); err != nil {
        return fmt.Errorf("auth.unpack: can't read maxPacketSize")
    }
    if a.charset, err = buf.ReadU8(); err != nil {
        return fmt.Errorf("auth.unpack: can't read charset")
    }
    if err = buf.ReadZero(23); err != nil {
        return fmt.Errorf("auth.unpack: can't read 23zeros")
    }
    if a.user, err = buf.ReadStringNUL(); err != nil {
        return fmt.Errorf("auth.unpack: can't read user")
    }
    if (a.clientFlags & sqldb.CLIENT_SECURE_CONNECTION) > 0 {
        if a.authResponseLen, err = buf.ReadU8(); err != nil {
            return fmt.Errorf("auth.unpack: can't read authResponse length")
        }
        if a.authResponse, err = buf.ReadBytes(int(a.authResponseLen)); err != nil {
            return fmt.Errorf("auth.unpack: can't read authResponse")
        }
    } else {
        if a.authResponse, err = buf.ReadBytes(20); err != nil {
            return fmt.Errorf("auth.unpack: can't read authResponse")
        }
        if err = buf.ReadZero(1); err != nil {
            return fmt.Errorf("auth.unpack: can't read authResponse")
        }
    }
    if (a.clientFlags & sqldb.CLIENT_CONNECT_WITH_DB) > 0 {
        if a.database, err = buf.ReadStringNUL(); err != nil {
            return fmt.Errorf("auth.unpack: can't read dbname")
        }
    }
    if (a.clientFlags & sqldb.CLIENT_PLUGIN_AUTH) > 0 {
        if a.pluginName, err = buf.ReadStringNUL(); err != nil {
            return fmt.Errorf("auth.unpack: can't read pluginName")
        }
    }
    if a.pluginName != DefaultAuthPluginName {
        return fmt.Errorf("invalid authPluginName, got %v but only support %v", a.pluginName, DefaultAuthPluginName)
    }
    return nil
}

参考