MySQL道普請便り

第78回MySQLプロトコルのハンドシェイクパケットを眺めてみる

MySQLを触ったことがある人なら、一度はMySQLのクライアントを一から全部作ってみたくなることがあると思います! そういうときのために、今回は、MySQLプロトコルを用いてMySQLに接続するために必要なハンドシェイクパケットの見方を説明していきたいと思います。

MySQLプロトコルとは?

MySQLプロトコルは、MySQLサーバとMySQLクライアントを接続するためのプロトコルです。このプロトコルを使ってConnector/Jといったコネクタ類やMySQL Proxy、マスタースレーブのレプリケーションなどに利用されています。このプロトコルにはSSLを用いた暗号化や圧縮といった機能も含まれていますが、今回は紹介しません。

このプロトコルは、コネクションを張るフェーズとコマンドを実行するフェーズの2つのフェーズに分かれていて、今回説明を行うのはコネクションを張るフェーズに関する部分です。

MySQLプロトコルに関する詳細なドキュメントは、公式のソースコードドキュメントのMySQL Client/Server Protocolに書かれています。

また、少々古くなっていると書かれていますが、ライフサイクル等の実行のイメージやパケットやプロトコルの詳細を知るためには、dev.mysql.comにあるChapter 14 MySQL Client/Server Protocolを眺めてみるのも良いでしょう。

MySQLプロトコルの接続の流れ

MySQLプロトコルを使用したときの接続の流れは以下の様な流れを通ります。

  1. 最初にハンドシェイクパケットをサーバが投げて接続が開始される
  2. SSLを使用する場合はSSL接続のための処理を行う
  3. クライアントがハンドシェイクパケットを受けてレスポンスを返す
  4. サーバ側はクライアントから渡された認証用メソッドがあるかを調べる(無かったら接続を閉じる)
  5. 認証を行い、問題がなければMySQLサーバ接続される

上記のようなフローを抜けて、接続が行われていきます。今回はそのうち1.の「最初のハンドシェイクパケット」について説明を行っていきます。

MySQLプロトコルのハンドシェイクパケット

今回はMySQLサーバをlocalhostに立てた状態でncatコマンドを利用して、ハンドシェイクパケットとしてどのようなパケットが流れてくるのかを確かめてみます。今回検証に使ったMySQLのバージョンは、5.7.20です。

$ ncat -x dump localhost 3306
J
5.7.20
      oP~Z*.G��!��~1XN~N[yeb;mysql_native_password

最後は入力待ちになってしまうので、Ctrl+Cを押して抜けましょう。

この時点で5.7.20というバージョン情報らしき文字や、mysql_native_passwordといった、なんとなく見覚えがある名前が見えているかと思います。そして今回使用したncatの-xオプションにより、dumpというファイルがカレントディレクトリに作成されていると思うので、こちらをバイナリを綺麗に表示してくれるstringsコマンドを利用してdumpの中身を表示してみます。

表示した結果は以下の通りになります。

$ strings  dump
[0000]   4A 00 00 00 0A 35 2E 37   2E 32 30 00 0B 00 00 00   J....5.7 .20.....
[0010]   6F 07 50 7E 5A 2A 2E 47   00 FF FF 21 02 00 FF C1   o.P~Z*.G ...!....
[0020]   15 00 00 00 00 00 00 00   00 00 00 7E 31 58 4E 7E   ........ ...~1XN~
[0030]   4E 5B 17 79 65 62 3B 00   6D 79 73 71 6C 5F 6E 61   N[.yeb;. mysql_na
[0040]   74 69 76 65 5F 70 61 73   73 77 6F 72 64 00         tive_pas sword.

読み方としては、左から右に16個の1byteの値が入っていて、それを表示したものが並んでいます。右側にはそれをascii文字列として処理したときの値が入っています。

表示されているハンドシェイクパケットの値は、このままだと一部を除いてただのバイト列なので読み解くのは難しいですが、それぞれ以下の表のような値が入ることになっています。また、可変長の文字列の区切りには0x00が使われています。

名前長さ説明
protocol_version1byteMySQLプロトコルのバージョン
server_version可変長で終端に0x00人間が読める形のバージョン表記
connection_id4byteコネクションID
auth_plugin_data_part_18byteパスワードをhash化するための値その1
filter_011byte(0x00)フィルター
capability_flag_12byteサーバがサポートしている機能
character_set1byteサーバのデフォルトのキャラクターセット
status_flags2byteサーバのステータス
capability_flags_22byteサーバがサポートしている機能その2
auth_plugin_data_len1byteauth_plugin-dataの長さ
reserved10byte(0x00)予約されているバイト数
auth-plugin-data-part-2(auth_plugin_data_len - 8)byteパスワードをhash化するための値その2
auth_plugin_name可変長で終端に0x00認証に使うプラグインの名前

今回の例で行くと、[0000]の行の5列目の値(payloadの最初の値)からスタートしているのですが、protocol_version0AであることからMySQLプロトコルのバージョンが10であることがわかります。ターミナル上で5.7.20の直前で改行されていたのは0x0A(改行コード)が入ってたためだというのがわかると思います。

server_version35 2E 37 2E 32 30 005.7.20(最後に0x00)が入っていることがわかります。connection_id0B 00 00 00です。auth_plugin_data_part_1には6F 07 50 7E 5A 2A 2E 47が入っています。その次のfilter_01には固定値で00が入っているはずなので、実際に自分で試してみる時にずれていないことの確認にも使うことができます。

capability_flag_1はは少し特殊で、ちょっと先の値のcapability_flag_2と組み合わせて計算されます。それぞれFF FFFF C1が該当します。

character_setは、21なので10進数表記に戻すと33なのでutf8_general_ciが選択されていることがわかります。character setを検索したい場合は、SHOW COLLATION;で使える文字セットが一覧で確認できるので、今回と違う値が表示された場合は設定が何になっているのか確認してみると良いと思います。

status_flagsは、02 00なのでSERVER_STATUS_NO_BACKSLASH_ESCAPESが有効になっていることがここからわかります。

auth-plugin_data_lenは、15なので10進数表記に戻すと21なのでトータルで21バイトが使われることがわかります。つまりauth-plugin-data-part-2の長さは21-8=13バイトになることがわかります。最後にauth_plugin_nameが入り、今回はmysql_native_passwordプラグインが利用されていることがわかります。

これらのサーバから与えられた情報から必要な値を取り出して、HandshareResponse41を作成することで認証を続けていくことができます。

まとめ

今回はMySQLプロトコルの概要と、MySQLサーバと接続するために必要な一番始めのハンドシェイクパケットを眺めて解説してみました。これにより、MySQLプロトコルでMySQLサーバと通信を始める時にどのような情報が伝わってくるのかが解るようになりました。

普段MySQLを触っていてMySQLプロトコルを意識をすることはほぼ無いとは思いますが、自分でライセンスを定めてクライアントを配布したい場合や、プロトコル上で特殊な処理をさせたい場合などには、役に立つこともあるのではないかと思います。何よりパケットを通してMySQLを操作してみるのは楽しいので、一度試してみてはいかがでしょうか?

おすすめ記事

記事・ニュース一覧