プロトコル振り分け
複数のサービスで同じ公開ポートを共用します(たとえば Web サイト、VPN、SSH、リモートデスクトップがすべて 443 番を使う場合)。Gately はクライアントが接続後に最初に送るデータからプロトコルを判別し、TLS ならハンドシェイクのドメイン名(SNI)と ALPN を、HTTP なら Host を確認して、接続全体を対応するバックエンドへそのまま中継します。ここではどのプロトコルも復号・終端しないため、TLS 証明書はバックエンド側に置いたままで使えます。haproxy の req_ssl_sni ルールと sslh を合わせた役割を果たします。
ドメインによる振り分け(SNI 振り分け)はその一例で、TLS の振り分けルートを追加してドメインを入力するだけです。
各ルールには次の設定があります。
- 待ち受け:プロトコル(TCP のみ。tcp はデュアルスタック、tcp4 / tcp6 は単一のアドレスファミリー)、待ち受けアドレスとポート(
443、443,8443、ポート範囲に対応) - 振り分けルート:各ルートに、プロトコル、名前、ターゲットアドレス(複数の場合は順に試行し、フェイルオーバーします)、任意のターゲットポート(空欄は待ち受けポートと同じ)、PROXY プロトコルの設定(送信しない、v1、v2)を指定します。PROXY の設定をルートごとに持つのは、必須のバックエンドと拒否するバックエンドがあるためです。
- デフォルトターゲット:ルール自身のターゲットアドレス、ターゲットポート、PROXY プロトコルの設定です。どのルートにも一致しない場合や、プロトコルを判別できない場合に使います。
- 判別タイムアウト:クライアントから判別できるデータが届くまで待つ秒数です。0 で既定の 5 秒になります。
判別できるプロトコル
| ルートのプロトコル | 判別方法 | 追加の照合 |
|---|---|---|
| TLS | TLS ハンドシェイクレコード(ClientHello) | ドメイン(SNI)、ALPN |
| HTTP | リクエスト行のメソッド名と HTTP/ バージョン。CONNECT、WebDAV のメソッド、HTTP/2 の平文直接接続(h2c)を含む | ドメイン(Host ヘッダー) |
| SSH | クライアントのバージョン文字列 SSH- | — |
| RDP リモートデスクトップ | TPKT ヘッダーと X.224 接続要求 | — |
| SOCKS4 / SOCKS5 | SOCKS4、SOCKS4a の要求と SOCKS5 のメソッドネゴシエーション | — |
| OpenVPN(TCP) | クライアントのハードリセットパケット。tls-auth、tls-crypt、tls-crypt-v2 を含む | — |
| XMPP | XML 宣言または <stream:stream | — |
| MQTT | CONNECT パケット(3.1、3.1.1、5.0) | — |
| PostgreSQL | スタートアップメッセージ、SSLRequest、GSSENCRequest | — |
| Minecraft(Java 版) | ハンドシェイクパケット、1.6 以前のサーバーリスト ping | サーバーアドレス |
| RTSP | リクエスト行のバージョン RTSP/1.0 または RTSP/2.0(ネットワークカメラ、NVR) | — |
| SIP | リクエスト行のバージョン SIP/2.0(TCP) | — |
| Redis | RESP 配列で送られるコマンド | — |
| AMQP | プロトコルヘッダー AMQP(0-9-1 と 1.0、RabbitMQ など) | — |
| SMB | NetBIOS セッションメッセージ内の SMB1、SMB2、SMB3 のネゴシエート要求 | — |
| Git | git:// プロトコルの最初の pkt-line(git daemon) | — |
| IRC | クライアントが最初に送る CAP、PASS、NICK、USER のいずれかのコマンド | — |
| ADB | Android デバッグブリッジの CNXN メッセージ(adb connect) | — |
| BitTorrent | 暗号化されていないピアハンドシェイク | — |
| 待機タイムアウト | 判別タイムアウト内にクライアントが何も送らない | — |
照合の順序
TLS、HTTP、Minecraft のルートはドメインで照合します(Minecraft はクライアントが入力したサーバーアドレスをハンドシェイクから読み取り、Forge、BungeeCord、TCPShield が付け加えたデータは無視します)。完全一致(vpn.example.com)が最優先で、次に最も長いワイルドカード(*.example.com、任意の階層に一致)、次に *(ドメインを含むすべての接続)、最後にドメイン未指定のルート(SNI や Host のない接続も含め、そのプロトコルのすべての接続)の順です。大文字と小文字は区別されません。保存時にドメインは小文字にそろえ、国際化ドメインは punycode に変換します。ポート、パス、@ を含む書き方は拒否します。
TLS のルートには ALPN も指定できます。クライアントが提示する ALPN にそのいずれかが含まれる場合にだけ一致します。同じドメインでは ALPN を指定したルートが優先され、ALPN が一致しない場合はより広いドメインの照合に進みます。たとえば acme-tls/1 を TLS-ALPN-01 証明書検証に応答するプログラムへ、postgresql を PostgreSQL 17 の直接 TLS 接続へ送れます。同じプロトコル内では、1 つのドメイン(と同じ ALPN)を登録できるルートは 1 つだけです。その他のプロトコルはそれぞれ 1 ルートまでです。
「待機タイムアウト」は、VNC、MySQL、FTP、SMTP、POP3、IMAP のようにサーバー側が先にデータを送るプロトコル用です。これらのクライアントは接続後にサーバーからの送信を待つため、Gately には何も届かず、どのプロトコルかを見分けられません。そのため「待機タイムアウト」ルートへ送れるのは 1 つのポートにつき 1 種類だけで、これらの接続は判別タイムアウトが過ぎてから転送されます。判別タイムアウトは小数点以下 1 桁まで指定できます。TLS、SSH、HTTP などのクライアントはハンドシェイク後数ミリ秒で最初のパケットを送るため、「待機タイムアウト」ルートがある場合は 0.3~1 秒で足ります。VNC などの接続が待つのもその時間だけです。
次の接続はデフォルトターゲットに送られます。一致するルートがない場合、データが上の表のどのプロトコルにも該当しない場合(たとえば Shadowsocks、VMess、暗号化を有効にした BitTorrent のように特徴のないプロトコルや、MongoDB、DNS over TCP のように最初のパケットが長さフィールドだけで他のプロトコルと紛らわしいプロトコル)、ClientHello を解析できない場合、「待機タイムアウト」ルートがなくクライアントが何も送らない場合です。デフォルトターゲットがない場合、これらの接続は切断され、「拒否」として集計されます。判別が終わる前に切断したクライアントは破棄されます。
補足
- TCP のみです。HTTP/3(QUIC)や WireGuard などの UDP トラフィックは別のルールで扱ってください。
- SIP、Redis、AMQP、IRC などの暗号化版は TLS 上で動作します。TLS ルートでドメインまたは ALPN によって振り分けてください。
- ClientHello は複数の TLS レコードに分割されていてもよく、最大 64 KB まで受け付けます。HTTP、RTSP、SIP のリクエスト行は最大 8 KB で、HTTP では Host を探すためにリクエストヘッダーを最大 32 KB まで読み取ります。
- 判別は接続ごとに 1 回だけです。同じ HTTP キープアライブ接続の後続リクエストは、最初に選ばれたバックエンドへ送られます。
- ルールのカードには、各ルートとデフォルトターゲットが受け付けた接続数が表示されます。実行ログのデバッグレベルには、接続ごとに判別したプロトコル、SNI、ALPN または Host が記録されます。
- ルールの変更(メモ、ルート、デフォルトターゲット、送信元 IP リストなど)をホット更新すると、プロトコル・ドメイン・ALPN が変わらないルートはヒット数を引き継ぎます。移動、名前の変更、ターゲットの変更でも引き継ぎます。新しいルートと、プロトコル・ドメイン・ALPN が変わったルートは 0 から数えます。ルールを無効にしてから再度有効にするか再起動すると、すべて 0 から数え直します。
- ルート、デフォルトターゲット、判別タイムアウトの変更はすぐに反映されます。待ち受けは止まらず、確立済みの接続もそのまま維持されます。
- 各ルールに送信元 IP の許可リストまたはブラックリスト(Web サービスの購読 IP リストも参照可)を設定でき、プロトコル判別の前に判定します。条件を満たさない接続はデータを読み取らずに閉じ、ルートのヒット数にも数えません。侵入防御のブロックが優先されます。リストを編集しても待ち受けは中断せず、ルールカードに拒否回数を表示します。使い方はポート転送と同じです。
例:haproxy の置き換え
次のような haproxy の設定は、
frontend https
bind 192.0.2.10:443
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
use_backend ocserv if { req_ssl_sni -i tz.example.com }
default_backend web
backend ocserv
server ocserv 127.0.0.1:443 send-proxy-v2
backend web
server web 127.0.0.1:8443 send-proxy-v2Gately では 1 つのルールになります。
| 設定 | 値 |
|---|---|
| プロトコル | TCP |
| 待ち受けアドレス / ポート | 192.0.2.10 / 443 |
ルート ocserv | プロトコル TLS、ドメイン tz.example.com、ターゲット 127.0.0.1、ポート 443、PROXY v2 |
| デフォルトターゲット | 127.0.0.1、ターゲットポート 8443、PROXY v2 |
バックエンドが 127.0.0.1:443 で待ち受けている場合と衝突しないよう、待ち受けアドレスにはサーバーのパブリックアドレスを指定してください。Web サイトを Gately 自身で配信するには、127.0.0.1:8443 で Web サービス のリスナーを追加し、「PROXY プロトコルを受信」を有効にして、信頼する PROXY 送信元に 127.0.0.0/8 を追加します。ポートを待ち受けられるプログラムは 1 つだけなので、ルールを有効にする前に haproxy を停止してください。
例:sslh の置き換え
次のような sslh の起動引数は、
sslh --listen 0.0.0.0:443 --timeout 2 \
--ssh 127.0.0.1:22 --openvpn 127.0.0.1:1194 --tls 127.0.0.1:8443Gately では 1 つのルールになります。
| 設定 | 値 |
|---|---|
| プロトコル | TCP |
| 待ち受けアドレス / ポート | 空欄 / 443 |
| 判別タイムアウト | 2 |
ルート ssh | プロトコル SSH、ターゲット 127.0.0.1、ポート 22 |
ルート vpn | プロトコル OpenVPN、ターゲット 127.0.0.1、ポート 1194 |
ルート web | プロトコル TLS、ドメインは空欄、ターゲット 127.0.0.1、ポート 8443 |
ルート vnc(任意) | プロトコル「待機タイムアウト」、ターゲット 127.0.0.1、ポート 5900 |
sslh はタイムアウトした接続をデフォルトで SSH に渡します。Gately は SSH- バージョン文字列で SSH をすぐに判別するため、タイムアウトした接続は VNC のようにサーバーが先に送るサービスに回せます。さらに RDP、SOCKS、HTTP などのルートを追加したり、ドメインごとに TLS を別々のバックエンドへ振り分けたりできます。
ポート転送からのアップグレード
プロトコル振り分けは、以前はポート転送ルールの「SNI 振り分け」モードでした。アップグレード後の初回起動時に、ポート転送にある SNI 振り分けルールは自動的にプロトコル振り分けモジュールへ移ります。各ルールのキー、名前、有効状態、グループ、すべてのルートはそのまま引き継がれ、元のルートはすべて TLS ルートになります。ポート転送モジュールを無効にしていた場合はプロトコル振り分けモジュールも無効になり、ポート転送の権限を持つ API トークンにはプロトコル振り分けにも同じレベルの権限が付与されます。
移行後は /api/protoroute/rules でこれらのルールを管理してください。ルートのフィールド名は sniRoutes から routes に変わり、mode フィールドは使われなくなりました。/api/portforward/rules に振り分けルールを送信すると拒否されます。監視メトリクスも gately_protoroute_* に変わりました。詳しくは監視を参照してください。
デプロイのヒント
プロトコル振り分けはホストのポートを直接待ち受けるため、Docker でデプロイする場合は --network host を推奨します。
グループと並べ替え
ルールには「グループ」を設定できます。グループ分け、並べ替え、検索の使い方はポート転送と同じです。検索ボックスでは名前・グループ・待ち受けポート・ターゲット・ドメイン・ルートのプロトコルで絞り込めます。