📖

HAProxy で複数の Minecraft サーバへのプロキシを作る

に公開

概要

  • 単一の HAProxy コンテナで、複数のドメインの Minecraft サーバへのプロキシをしたい場合の設定例
    • HAProxy を ひとつのグローバルIP しか持たない VPS などに置いていて、かつ複数のバックエンドサーバに異なるホスト名でプロキシさせたい、というような状況を想定
  • この記事は HAProxy + Tailscale で公開する自宅 Minecraft サーバ を読んでいることを前提にしています

haproxy.conf の設定

プロキシサーバ xxx.xxx.xxx.xxx にて、mc1.example.net と mc2.example.net への接続を受け付け、それぞれ 192.168.2.2, 192.168.2.3 へ振り分けたいとします。
haproxy.conf を以下のように設定します。

defaults
  mode tcp
  timeout connect 10s
  timeout client 300s
  timeout server 300s
  log global

frontend mc-proxy
  mode tcp
  bind xxx.xxx.xxx.xxx:25565

  tcp-request inspect-delay 3s
  # Handshake パケットであれば通す
  tcp-request content accept if { req.payload(0,1) -m bin 00 }

  # パケットの Payload の 4バイト目から32バイト目までの値(ホスト名)をチェック
  # ホスト名に一致する値があれば、各ホストのフラグを立てる
  acl is_mc1 req.payload(4,32) -m sub mc1.example.net
  acl is_mc2 req.payload(4,32) -m sub mc2.example.net

  # フラグによって転送するバックエンドサーバを切り替える
  use_backend mc1_backend if is_mc1
  use_backend mc2_backend if is_mc2
  default_backend fallback_backend

backend mc1_backend
  mode tcp
  server mc1 192.168.2.2:25565 check

backend mc2_backend
  mode tcp
  server mc2 192.168.2.3:25565 check

# 意図しないホスト名へのアクセスは切断する
backend fallback_backend
  mode tcp
  tcp-request content reject

上記の設定が済んだら、HAProxy を再起動すればOKです。
#Docker で HAProxy を動かしている場合、構成によってはイメージのリビルドが必要になります。

振り分けの仕組み

Minecraft Java Edition のパケットの資料をざっと斜め読みしただけなので、理解が誤っているかもしれませんが……。

  • Minecraft のクライアントは、TCP セッションが開いた直後に Handshake パケットを送信する
    • パケットの Payload の0-1バイトが 00 であれば Handshake パケットである
    • haproxy.conf の req.payload(0,1) -m bin 00 で判定
  • Handshake パケットのペイロードの4-32バイトに接続先ホスト名が含まれる
    • HAProxy がここをチェックする時間を確保する必要がある
    • haproxy.conf の tcp-request inspect-delay 3s でわざと遅延を発生させている
    • haproxy.conf の req.payload(4,32) -m sub mc1.example.net でホスト名を判定する
  • 接続先ホスト名によって転送先バックエンドを切り替える
  • 以降の通信は TCP セッション内で行われる

参考資料

Discussion