github.com/XiaoMi/Gaea@v1.2.5/backend/direct_connection_en.md (about) 1 # Direct Connection in Backend package 2 3 ## Code Explanation 4 5 ### The first step 6 7 > The first step is to send the initial handshake packet from MariaDB to Gaea. 8 9 There are some details about the initial handshake packet in [the official document](https://mariadb.com/kb/en/connection/) , and please see the details below. 10 11 <img src="./assets/image-20220315221559157.png" alt="image-20220315221559157" style="zoom:100%;" /> 12 13 The actual packet demonstrates how this handshake works, and please see details below. 14 15 | packet | exmaple | 16 | ------------------------------- | ------------------------------------------------------------ | 17 | int<1> protocol version | Protocol Version 10 | 18 | string<NUL> server version | MariaDB version is <br /><br />[]uint8{<br />53, 46, 53, 46, 53,<br />45, 49, 48, 46, 53,<br />46, 49, 50, 45, 77,<br />97, 114, 105, 97, 68,<br />66, 45, 108, 111, 103<br />}<br /><br />Converting the array to ASCII, the result is "5.5.5-10.5.12-MariaDB-log". | 19 | int<4> connection id | Connection ID is []uint8{16, 0, 0, 0}.<br /><br />After reversing the array, it becomes []uint8{0, 0, 0, 16} that equals to []uint32{16}. | 20 | string<8> scramble 1st part | The first part of the scramble:<br /><br />MariaDB utilizes the scramble for secure password authentication.<br /><br />The scramble is 20 bytes of data; the first part occupies 8 bytes, []uint8{81, 64, 43, 85, 76, 90, 97, 91}. | 21 | string<1> reserved byte | It occupies 1 byte, []uint8{0}. | 22 | int<2> server capabilities | The first part of the capability occupies 2 bytes, []uint8{254, 247}. | 23 | int<1> server default collation | The charset of MariaDB in the current exameple is 33.<br /><br />After checking<br />[document](https://mariadb.com/kb/en/supported-character-sets-and-collations/) <br />or<br />using a command "SHOW CHARACTER SET LIKE 'utf8'",<br />finding out that number 33 means "utf8_general_ci". | 24 | int<2> status flags | The status of MariaDB in the current exameple is []uint8{2, 0}.<br/><br />Reversing from the status flags to []uint8{0, 2} and then converting them to binary, []uint{0b000000000, 0b00000010}.<br /><br />After referring to "Gaea/mysql/constants.go", the result means "Autocommit." | 25 | int<2> server capabilities | The second part of the capability occupies 2 bytes, []uint8{255, 129}. | 26 27 Calculate the whole capability 28 29 ``` 30 Gathering two parts of the capability and combining them, the result is []uint8{254, 247, 255, 129}. 31 32 After Converting the result to binary, the result becomes []uint8{0b10000001, 0b11111111, 0b11110111, 0b11111110}. 33 34 After that, refer to [the website](https://mariadb.com/kb/en/connection/) and ensure some details without difficulty. 35 36 For example, the first element of the capability is 0, which means the packet came from MariaDB to Gaea. 37 ``` 38 39 The next table follows on from the previous one. 40 41 | item | value | 42 | ------- | ------------------------------------------------------------ | 43 | packet | if (server_capabilities & PLUGIN_AUTH)<br/> int<1> plugin data length <br/> else<br/> int<1> 0x00 | 44 | example | skip 1 byte | 45 46 The next table follows on from the previous one. 47 48 | item | value | 49 | ------- | ---------------- | 50 | packet | string<6> filler | 51 | example | skip 6 bytes | 52 53 The next table follows on from the previous one. 54 55 | item | value | 56 | ------- | ------------------------------------------------------------ | 57 | packet | if (server_capabilities & CLIENT_MYSQL)<br/> string<4> filler <br/> else<br/> int<4> server capabilities 3rd part .<br /> MariaDB specific flags /* MariaDB 10.2 or later */ | 58 | example | skip 4 bytes | 59 60 The next table follows on from the previous one. 61 62 | item | value | 63 | ------- | ------------------------------------------------------------ | 64 | packet | if (server_capabilities & CLIENT_SECURE_CONNECTION)<br/> string<n> scramble 2nd part . Length = max(12, plugin data length - 9)<br/> string<1> reserved byte | 65 | example | The scramble is 20 bytes of data; the second part occupies 12 (20-8=12) bytes, []uint8{34, 53, 36, 85, 93, 86, 117, 105, 49, 87, 65, 125}. | 66 67 The next table follows on from the previous one. 68 69 | item | value | 70 | ------- | ------------------------------------------------------------ | 71 | packet | if (server_capabilities & PLUGIN_AUTH)<br/> string<NUL> authentication plugin name | 72 | example | Gaea discards the rest of the data in the packet because there is no use for "PLUGIN_AUTH". | 73 74 combine the whole data of the scramble: 75 76 ``` 77 The first part of the scramble is []uint8{81, 64, 43, 85, 76, 90, 97, 91} 78 The second part of the scramble is []uint8{34, 53, 36, 85, 93, 86, 117, 105, 49, 87, 65, 125} 79 80 After combining them, the final result is []uint8{81, 64, 43, 85, 76, 90, 97, 91, 34, 53, 36, 85, 93, 86, 117, 105, 49, 87, 65, 125}. 81 ``` 82 83 ### The second step 84 85 > The second step is to calculate the auth base on the scramble, combined with two parts of the scramble. 86 87 There are some details about the auth formula in [the official document](https://dev.mysql.com/doc/internals/en/secure-password-authentication.html) . 88 89 ``` 90 some formulas for the auth: 91 92 SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) ) 93 stage1 = SHA1( password ) 94 stage1Hash = SHA1( stage1 ) = SHA1( SHA1( password ) ) 95 scramble = SHA1( scramble <concat> SHA1( stage1Hash ) ) // the first new scramble 96 scramble = stage1 XOR scramble // the second new scramble 97 ``` 98 99 Assume 100 101 - The password for a secure login process in MariaDB is 12345. 102 - The auth base on the scramble, combined with two parts of the scramble, is []uint8{81, 64, 43, 85, 76, 90, 97, 91, 34, 53, 36, 85, 93, 86, 117, 105, 49, 87, 65, 125}. 103 The result that converted from decimal to hexadecimal is []uint8{51, 40, 2B, 55, 4c, 5a, 61, 5b, 22, 35, 24, 55, 5d, 56, 75, 69, 31, 57, 41, 7d}. 104 It is the same as 51402B554c5A615b223524555d5675693157417d. 105 106 Regarding the stage1 formula, Linux Bash calculates the result and compares it. 107 108 ```bash 109 # stage1 = SHA1( password ) 110 111 # calculate stage1 112 $ echo -n 12345 | sha1sum | head -c 40 # convert password 12345 to stage1 113 8cb2237d0679ca88db6464eac60da96345513964 # stage1 114 ``` 115 116 As regards the stage1Hash formula, Linux Bash calculates the result and compares it. 117 118 ```bash 119 # stage1Hash = SHA1( stage1 ) = SHA1( SHA1( password ) ) 120 121 $ echo -n 12345 | sha1sum | xxd -r -p | sha1sum | head -c 40 122 00a51f3f48415c7d4e8908980d443c29c69b60c9 # stage1hash 123 124 $ echo -n 8cb2237d0679ca88db6464eac60da96345513964 | xxd -r -p | sha1sum | head -c 40 125 00a51f3f48415c7d4e8908980d443c29c69b60c9 # stage1hash 126 ``` 127 128 Linux Bash concatenates the scramble and stage1Hash into the string concat. 129 130 ```bash 131 # scramble is 51402B554c5A615b223524555d5675693157417d, the first half part. 132 # stage1Hash is 00a51f3f48415c7d4e8908980d443c29c69b60c9, the second half part. 133 134 # calculate "20-bytes random data from server" <concatenate> SHA1( SHA1( password ) ) 135 $ echo -n 51402B554c5A615b223524555d5675693157417d 00a51f3f48415c7d4e8908980d443c29c69b60c9 | sed "s/ //g" 136 51402B554c5A615b223524555d5675693157417d00a51f3f48415c7d4e8908980d443c29c69b60c9 # concat 137 ``` 138 139 In terms of the first new scramble, Linux Bash calculates the result and compares it. 140 141 ```bash 142 # scramble = SHA1( concat ) = SHA1( scramble <concatenate> SHA1( stage1Hash ) ) 143 144 $ echo -n 51402B554c5A615b223524555d5675693157417d00a51f3f48415c7d4e8908980d443c29c69b60c9 | xxd -r -p | sha1sum | head -c 40 145 0ca0f764a59d1cdb10a87f0155d61aa54be1c71a # The first new scramble 146 ``` 147 148 In the case of the second new scramble, Linux Bash calculates the result and compares it. 149 150 ```bash 151 # scramble = stage1 XOR scramble 152 153 $ stage1=0x8cb2237d0679ca88db6464eac60da96345513964 # stage1 154 $ scramble=0x0ca0f764a59d1cdb10a87f0155d61aa54be1c71a # The first new scramble 155 $ echo $(( $stage1^$scramble )) 156 -7792437067003134338 # insufficient precision 157 158 $ stage1=0x8cb2237d0679ca88db6464eac60da96345513964 159 $ scramble=0x0ca0f764a59d1cdb10a87f0155d61aa54be1c71a 160 $ printf "0x%X" $(( (($stage1>>40)) ^ (($scrambleFirst>>40)) )) 161 0xFFFFFFFFFF93DBB3 # insufficient precision 162 163 # Linux Bash divides stage1 and the first new scramble into four parts and individually makes four bitwise XOR operations. 164 $ printf "0x%X" $(( ((0x8cb2237d06)) ^ ((0x0ca0f764a5)) )) 165 $ printf "%X" $(( ((0x79ca88db64)) ^ ((0x9d1cdb10a8)) )) 166 $ printf "%X" $(( ((0x64eac60da9)) ^ ((0x7f0155d61a)) )) 167 $ printf "%X" $(( ((0x6345513964)) ^ ((0xa54be1c71a)) )) 168 0x8012D419A3E4D653CBCC1BEB93DBB3C60EB0FE7E # correct 169 170 # scramble is []uint8{ 80, 12, D4, 19, A3, E4, D6, 53, CB, CC, 1B, EB, 93, DB, B3, C6, 0E, B0, FE, 7E} // hexadecimal 171 # decimal 172 # scramble 为 []uint8{128, 18, 212, 25, 163, 228, 214, 83, 203, 204, 27, 235, 147, 219, 179, 198, 14, 176, 254, 126} // the same as the result in Gaea 173 ``` 174 175 The correct result, auth, is the same as Gaea's. 176 177 <img src="./assets/image-20220318183833245.png" alt="image-20220318183833245" style="zoom:70%;" /> 178 179 ### The third step 180 181 > The second step is to reply to MariaDB after receiving the initial handshake packet. 182 183 There are some details about the response packet in [the official document](https://mariadb.com/kb/en/connection/) , and please see the details below. 184 185 <img src="./assets/image-20220318083633693.png" alt="image-20220318083633693" style="zoom:100%;" /> 186 187 The actual packet demonstrates how this response works, and please see details below. 188 189 | capability | binary | decimal | 190 | ---------------------------- | ------------------ | ------- | 191 | mysql.ClientProtocol41 | 0b0000001000000000 | 512 | 192 | mysql.ClientSecureConnection | 0b1000000000000000 | 32768 | 193 | mysql.ClientLongPassword | 0b0000000000000001 | 1 | 194 | mysql.ClientTransactions | 0b0010000000000000 | 8192 | 195 | mysql.ClientLongFlag | 0b0000000000000100 | 4 | 196 | | | | 197 | sum | | | 198 | Gaea's capability | 0b1010001000000101 | 41477 | 199 200 Calculate the new capability that coordinates with MariaDB's. 201 202 ``` 203 With regard to the first step, the dc object's capability is 0b10000001111111111111011111111110, which equals 2181036030 in decimal. 204 205 Obviously, it does not support "mysql.ClientLongPassword". 206 207 SpeedCrunch calculator takes both Gaea's capability and the capability to make a bitwise AND operation. The result is the mutual capability. 208 Gaea's capability & dc.capability = []uint32{41477} & []uint32{2181036030} = []uint32{41476} 209 ``` 210 211 <img src="./assets/image-20220319002738908.png" alt="image-20220319002738908" style="zoom:70%;" /> 212 213 | packet | exmaple | 214 | --------------------------------- | ------------------------------------------------------------ | 215 | int<4> client capabilities | Gaea reverses from the mutual capability []uint32{41476} to []uint8{4, 162, 0, 0} when sending the packet to MariaDB.<br /><img src="./assets/image-20220319113026919.png" alt="image-20220319113026919" style="zoom:50%;" /> | 216 | int<4> max packet size | It occupies 4 bytes, []uint8{0, 0, 0, 0}. | 217 | int<1> client character collation | After checking [document](https://mariadb.com/kb/en/supported-character-sets-and-collations/) , finding out that number 46 means "utf8mb4_bin". | 218 | string<19> reserved | It occupies 19 bytes, []uint8{<br /> 0, 0, 0, 0, 0,<br /> 0, 0, 0, 0, 0,<br /> 0, 0, 0, 0, 0,<br /> 0, 0, 0, 0,<br /> } | 219 220 The next table follows on from the previous one. 221 222 | item | value | 223 | ------- | ------------------------------------------------------------ | 224 | packet | if not (server_capabilities & CLIENT_MYSQL)<br/> int<4> extended client capabilities <br/>else<br/> string<4> reserved | 225 | example | **CLIENT_MYSQL** means this packet belongs to Gaea, True.<br /><br />It occupies 4 bytes, []uint8{0, 0, 0, 0}, because the formula, <br />**not (server_capabilities & CLIENT_MYSQL)**<br />, is False. | 226 227 The next table follows on from the previous one. 228 229 | item | value | 230 | ------- | ------------------------------------------------------------ | 231 | packet | string<NUL> username | 232 | example | xiaomi is an account to log in MariaDB. However, it needs one byte to terminate it.<br />It will occupy 7 bytes, []uint8{120, 105, 97, 111, 109, 105, 0} | 233 234 With respect to the second new scramble, Linux Bash calculates the result and compares it. 235 236 ```bash 237 $ echo -n xiaomi | od -td1 238 0000000 120 105 97 111 109 105 239 0000006 240 ``` 241 242 The next table follows on from the previous one. 243 244 | item | value | 245 | ------- | ------------------------------------------------------------ | 246 | packet | if (server_capabilities & PLUGIN_AUTH_LENENC_CLIENT_DATA)<br/> string<lenenc> authentication data <br/>else if (server_capabilities & CLIENT_SECURE_CONNECTION)<br/> int<1> length of authentication response<br/> string<fix> authentication response (length is indicated by previous field) <br/>else<br/> string<NUL> authentication response null ended | 247 | example | Gaea supports CLIENT_SECURE_CONNECTION in this case.<br /><br />In the second step, the auth is []uint8{128, 18, 212, 25, 163, 228, 214, 83, 203, 204, 27, 235, 147, 219, 179, 198, 14, 176, 254, 126}.<br />However, Gaea has to send the length of the auth first.<br /><br />It will occupy 21 bytes, []uint8{20, 128, 18, 212, 25, 163, 228, 214, 83, 203, 204, 27, 235, 147, 219, 179, 198, 14, 176, 254, 126} | 248 249 The next table follows on from the previous one. 250 251 | item | value | 252 | ------- | ------------------------------------------------------------ | 253 | packet | if (server_capabilities & CLIENT_CONNECT_WITH_DB)<br/> string<NUL> default database name | 254 | example | Gaea deals with capabilities that are **mysql.ClientProtocol41**, **mysql.ClientSecureConnection**, **mysql.ClientTransactions**, and **mysql.ClientLongFlag** in the dc connection. <br /><br />Gaea ignores this one. | 255 256 The next table follows on from the previous one. 257 258 | item | value | 259 | ------- | ------------------------------------------------------------ | 260 | packet | if (server_capabilities & CLIENT_PLUGIN_AUTH)<br/> string<NUL> authentication plugin name | 261 | example | Gaea deals with capabilities that are **mysql.ClientProtocol41**, **mysql.ClientSecureConnection**, **mysql.ClientTransactions**, and **mysql.ClientLongFlag** in the dc connection. <br /><br />Gaea ignores this one. | 262 263 The next table follows on from the previous one. 264 265 | item | value | 266 | ------- | ------------------------------------------------------------ | 267 | packet | if (server_capabilities & CLIENT_CONNECT_ATTRS)<br/> int<lenenc> size of connection attributes<br/> while packet has remaining data<br/> string<lenenc> key<br/> string<lenenc> value | 268 | example | Gaea deals with capabilities that are **mysql.ClientProtocol41**, **mysql.ClientSecureConnection**, **mysql.ClientTransactions**, and **mysql.ClientLongFlag** in the dc connection. <br /><br />Gaea ignores this one. | 269 270 ## Testing 271 272 > There is an explanation of what I consider about in Unit Test. 273 274 ### Considering about Anonymous Function 275 276 The function of the unit test whose name is "response after handshake," containing an anonymous function. 277 278 The variables in the anonymous function inside the test will take the address of other variables and bring them inside the function. 279 280 I consider about it again and again. It seems correct. 281 282 The problem that I am concerned about is that the anonymous function takes the wrong values. 283 284 ```go 285 // 交握第二步 Step2 286 t.Run("Response after Handshake", func(t *testing.T) { 287 var connForSengingMsgToMariadb = mysql.NewConn(mockGaea.GetConnWrite()) 288 dc.conn = connForSengingMsgToMariadb 289 dc.conn.StartWriterBuffering() 290 291 customFunc := func() { 292 err := dc.writeHandshakeResponse41() 293 require.Equal(t, err, nil) 294 err = dc.conn.Flush() 295 require.Equal(t, err, nil) 296 err = mockGaea.GetConnWrite().Close() 297 require.Equal(t, err, nil) 298 } 299 300 fmt.Println(mockGaea.CustomSend(customFunc).ArrivedMsg(mockMariaDB)) 301 }) 302 ``` 303 304 ## Check Result 305 306 The result is hexadecimal when I use the Linux command and tools on other websites to calculate sha1sum. 307 308 However, the IDE tool produces the result in decimal. 309 310 ### Linux command 311 312 Linux Bash generates the sha1shum. 313 314 <img src="./assets/image-20220314214316673.png" alt="image-20220314214316673" style="zoom:80%;" /> 315 316 ### Website 317 318 The tool on [the website](https://coding.tools/tw/sha1) calculates the sha1shum. 319 320 <img src="./assets/image-20220314215924425.png" alt="image-20220314215924425" style="zoom:80%;" /> 321 322 ### Broken Point 323 324 The IDE uses broken point to take a look at the stage1 variable. 325 326 <img src="./assets/image-20220314220921338.png" alt="image-20220314220921338" style="zoom:100%;" /> 327 328 ### Comparison 329 330 The table below compares the two results. 331 332 One comes from taking broken point, and the other comes from tools on the website. 333 334 Thus I am sure the result is correct. 335 336 | Position | Binary | Decimal | Hexadecimal | 337 | :------: | :------: | :-----: | :---------: | 338 | 0 | 10001100 | 140 | 8c | 339 | 1 | 10110010 | 178 | b2 | 340 | 2 | 00100011 | 35 | 23 | 341 | 3 | 01111101 | 125 | 7d | 342 | 4 | 00000110 | 6 | 06 | 343 | 5 | 01111001 | 121 | 79 | 344 | 6 | 11001010 | 202 | ca | 345 | 7 | 10001000 | 136 | 88 | 346 | 8 | 11011011 | 219 | db | 347 | 9 | 01100100 | 100 | 64 | 348 | 10 | 01100100 | 100 | 64 | 349 | 11 | 11101010 | 234 | ea | 350 | 12 | 11000110 | 198 | c6 | 351 | 13 | 00001101 | 13 | 0d | 352 | 14 | 10101001 | 169 | a9 | 353 | 15 | 01100011 | 99 | 63 | 354 | 16 | 01000101 | 69 | 45 | 355 | 17 | 01010001 | 81 | 51 | 356 | 18 | 00111001 | 57 | 39 | 357 | 19 | 01100100 | 100 | 64 |