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プロトコルを使用したときの接続の流れは以下の様な流れを通ります。
最初にハンドシェイクパケットをサーバが投げて接続が開始される
SSLを使用する場合はSSL接続のための処理を行う
クライアントがハンドシェイクパケットを受けてレスポンスを返す
サーバ側はクライアントから渡された認証用メソッドがあるかを調べる(無かったら接続を閉じる)
認証を行い、問題がなければ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_version 1byte MySQLプロトコルのバージョン
server_version 可変長で終端に0x00 人間が読める形のバージョン表記
connection_id 4byte コネクションID
auth_plugin_data_part_1 8byte パスワードをhash化するための値その1
filter_01 1byte(0x00) フィルター
capability_flag_1 2byte サーバがサポートしている機能
character_set 1byte サーバのデフォルトのキャラクターセット
status_flags 2byte サーバのステータス
capability_flags_2 2byte サーバがサポートしている機能その2
auth_plugin_data_len 1byte auth_plugin-dataの長さ
reserved 10byte(0x00) 予約されているバイト数
auth-plugin-data-part-2 (auth_plugin_data_len - 8)byte パスワードをhash化するための値その2
auth_plugin_name 可変長で終端に0x00 認証に使うプラグインの名前
今回の例で行くと、[0000]の行の5列目の値(payloadの最初の値)からスタートしているのですが、protocol_version
が0A
であることからMySQLプロトコルのバージョンが10であることがわかります。ターミナル上で5.7.20の直前で改行されていたのは0x0A(改行コード)が入ってたためだというのがわかると思います。
server_version
は35 2E 37 2E 32 30 00
で 5.7.20
(最後に0x00)が入っていることがわかります。connection_id
は0B 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 FF
とFF 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を操作してみるのは楽しいので、一度試してみてはいかがでしょうか?