tcpdpをPROXY protocolに対応させた

というわけで、勉強も兼ねてtcpdpをPROXY protocolに対応させてみました。

今回想定したユースケース

私自身がPROXY protocolのユースケースを持っているわけではなかったので、まずは「TCPパケットをいじらないユースケース」を想定して実装しました。

PROXY protocolを有効にして通信しているサーバの間に入ってパケットを取得する イメージです。

例えばフロントサーバにHAProxy、バックエンドにMariaDBとします。

まず、MariaDBでPROXY protocolを受け入れられるように設定します。

[mysqld]
proxy-protocol-networks=::1, 0.0.0.0/0 ,localhost

tcpdp proxy を使う場合

proxyモードの場合は以下のような構成で考えます。

+------------------------+   +-------------------------------------+
| proxy.example.internal |   | db.example.internal                 |
|                        |   |                                     |
| HAProxy:3306+---------------->tcpdp proxy:33306+--->MariaDB:3306 |
|                        |   |                                     |
+------------------------+   +-------------------------------------+

HAProxyは以下のように tcpdp が待ち受けるポートをbackendにします

global
  quiet

defaults
  mode tcp

frontend proxy
  bind *:3306
  default_backend tcpdp-proxy

backend tcpdp-proxy
  server tcpdp db.example.com:33306 send-proxy-v2

tcpdp はproxyモードで33306localhost:3306をプロキシするようにして起動します。

$ tcpdp proxy -l 0.0.0.0:33306 -r localhost:3306 -d mysql --proxy-protocol --stdout

この状態でmysqlコマンドで proxy.example.internal:3306 に接続し、クエリを発行すると以下のように proxy_protocol_src_addrmysqlコマンドを発行したIPアドレスとポートが取得できます。

{"query":"INSERT INTO t1 VALUES (NULL,1804289383,'mxvtvmC9127qJNm06sGB8R92q2j7vTiiITRDGXM9ZLzkdekbWtmXKwZ2qG1llkRw5m9DHOFilEREk3q7oce8O3BEJC0woJsm6uzFAEynLH2xCsw1KQ1lT4zg9rdxBL')","seq_num":0,"command_id":3,"conn_id":"bhjpukel0s151gfqpo1g","client_addr":"127.0.0.1:56571","proxy_listen_addr":"127.0.0.1:33306","proxy_client_addr":"127.0.0.1:56572","remote_addr":"127.0.0.1:3306","proxy_protocol_src_addr":"172.24.0.1:37948","proxy_protocol_dst_addr":"172.24.0.3:3306","character_set":"utf8","username":"root","conn_seq_num":12,"direction":"->","ts":"2019-02-16T14:27:45.318+0900"}

proxy. db. それぞれ 127.0.0.1 になっているのは Docker でローカルに構築しているからです。わかりにくいので例としてみてください

tcpdp probe を使う場合

probeモードの場合は以下のような構成で考えます。

+------------------------+   +---------------------------+
| proxy.example.internal |   | db.example.internal       |
|                        |   |                           |
| HAProxy:3306+-----------------+->MariaDB:3306          |
|                        |   |  ^                        |
+------------------------+   |  |                        |
                             | ++------------+           |
                             | | tcpdp probe |           |
                             | +-------------+           |
                             +---------------------------+

HAProxyは以下のように tcpdp が待ち受けるポートをbackendにします

global
  quiet

defaults
  mode tcp

frontend proxy
  bind *:3306
  default_backend mariadb

backend mariadb
  server db db.example.internal:3306 send-proxy-v2

tcpdp はprobeモードでネットーワークインターフェースにアタッチします

$ tcpdp probe -i eth0 -t 3306 -d mysql --proxy-protocol --stdout

この状態でmysqlコマンドで proxy.example.internal:3306 に接続し、クエリを発行すると以下のように proxy_protocol_src_addrmysqlコマンドを発行したIPアドレスとポートが取得できます。

{"ts":"2019-02-18T00:35:31.925+0900","src_addr":"127.0.0.1:53786","dst_addr":"127.0.0.1:3306","query":"INSERT INTO t1 VALUES (NULL,866596855,'naQuzhMt1IrZIJMkbLAKBNNKKK2sCknzI5uHeGAgQuDd5SLgpN0smODyc7qorTo1QaI5qLl97qmCIzl0Mds81x7TxpIoJyqlY0iEDRNKA1PS0AKEn5NhuMAr3KgEIM')","seq_num":0,"command_id":3,"interface":"lo0","probe_target_addr":"3306","conn_id":"bhknugkrtr39kl5h32ng","mss":16344,"proxy_protocol_src_addr":"172.24.0.1:49854","proxy_protocol_dst_addr":"172.24.0.2:3306","character_set":"utf8","username":"root","database":"mysqlslap"}

こちらも同様にわかりにくいので例としてみてください

今回想定しなかったユースケース

今回想定しなかったユースケースとして、

PROXY protocolに対応していないRDBMSの前段に tcpdp proxy でプロキシサーバをたてて、upstreamのRDBMSにはPROXY protocolヘッダを外したパケットを流す

というものもありました。

これを実装するとMySQLPostgreSQLでもPROXY protocolで接続元の情報を取得できるようになります。 (今回MariaDBを採用した理由もPROXY protocolに対応しているからでした)

ただ、そこまで必要なのかがわからなかったので、パケットをいじらない実装をしました。

こちらも要望があれば検討しようと思っています。

というわけで

休日の空いた時間にチマチマと進めていたのですが、PROXY protocol、勉強になりました!

あと、今回はじめてHAProxyを触ったのですが便利ですねコレ。これも良い経験になりました。