⛏️

SSHポートフォワーディングでMinecraftサーバを公開する

2022/12/01に公開約4,000字1件のコメント

はじめに

SSH のポートフォワーディング機能を使い,リバースプロキシのようにして外部にサーバを公開します。

サーバを立てるときに,オリジンを直接インターネットに晒したくない… or ポート開放が何らかの理由でできない…,VPNサーバを立てるのも面倒… などというときにどうぞ。つい最近この方法を知ったので,Minecraft サーバを一例として記録を残しておきます(すぐ忘れるので…)。

これは TDU CPSLab Advent Calendar 2022 の 1日目の記事です。初っ端から遅刻してすみません。日付が変わってからが本番ですよね? 2日目の記事はこちらからどうぞ!

やりたいこと・概要

  • 踏み台サーバの 25565 番ポートに来た通信を ゲームサーバの 25565 番ポートに流す
  • ゲームサーバは直接インターネットに公開したくない
    • あるいは,何らかの理由でそれが不可能
  • VPN サーバなどを使わず,簡単に構築したい
    • 今回はリモートポートフォワードを使用する

環境

  • ゲームサーバ
    • CentOS Stream 9
    • ローカルネットワーク内で動作(インターネットには出られるが,外部から直接の接続は不可)
  • 踏み台サーバ
    • Ubuntu 22.04 LTS
    • Oracle Cloud Infrastructure で動作(任意のポートを開放し,インターネットから接続可能)

(今回の内容では,CentOS / Ubuntu 間でのコマンドの差異は特にない…はず)

準備

ゲームサーバ

Minecraftサーバを構築し,使用するポートを確認しておく

バニラでも Spigot でも構いません。
今回は Docker (iztg/minecraft-server) を使って Spigot を用意しました。

使用するポートを確認しておきます。Minecraft Java Edition のサーバは,デフォルトで 25565 を使用します。
プラグインなどを使用する場合は,それらも確認しておきます。
(例えば Dynmap8123 ですが,本番環境ではリバースプロキシとして Apache, Nginx などを利用して 80 and/or 443 で公開することをおすすめします。)

踏み台サーバ

sshd の設定をする

まず,ゲームサーバからログインできることを確認ておきます。
次に,/etc/ssh/sshd_config の以下の部分を編集します。
no の場合,踏み台サーバの localhost でしか当該ポートにアクセスできません。yes とすると,全ての接続元から当該ポートへのアクセスを許可します。)

踏み台サーバ /etc/ssh/sshd_config
- GatewayPorts no
+ GatewayPorts yes

設定が済んだら,sshd を再起動します。エラーメッセージが出たらよく読んで対処してください(だいたいスペルミスが原因だったりする)。

踏み台サーバ
$ sudo systemctl restart sshd

使いたいポートを開放しておく

使用しているクラウド,VPS,ルータ等の設定を確認して設定します。
必要のないポートを開放しないように注意します。
疎通確認のために,nc コマンドを使用して任意のポートを LISTEN させることができます。

実際に試す

以下のコマンドを実行すると,トンネル接続が確立されます。

ゲームサーバ
$ ssh -fNR 25565:localhost:25565 踏み台のログインユーザ:踏み台のアドレス

この例では両者同じポートになっていますが,例えば,30000:localhost:25565 とすると,インターネットから踏み台の 30000 に来た通信が localhost の 25565 に届くようになります(つまり,公開するポートを 30000 に変更できます)。
localhost 以外のホストを指定することも可能です。

~/.ssh/config で設定しておけば,通常の SSH 接続の際と同様に,接続先の指定を簡単にできます。

特権ポート(≒ Well Known Ports, System Ports)を使う場合は,root 権限が必要です。

オプションについて

option description
-f バックグラウンドで実行する
-N 接続時にリモートコマンド(/bin/bash など)を実行しない
-R 接続先ホストの指定された TCP ポートまたは Unix ソケットをローカル側にフォワードする

オプションなどのさらに詳しい説明は $ man ssh を実行すると確認できます。

ゲームサーバ:プロセスを確認する

上記の通り,-f オプションを付けると,バックグラウンドで実行されます。
以下のコマンドで,動作しているプロセスを確認できます。

ゲームサーバ
$ ps -aux | grep ssh
# (略)
user  1141687  0.0  0.1  11696  4932  ?  Ss  02:39   0:00 ssh -fNR 25565:localhost:25565 fumidai
# (略)

この場合,プロセスID: 1141687 で動作していることが分かります。

踏み台:使用中のポートを確認する

接続が確立されると,踏み台サーバでポートを LISTEN します。
以下のコマンドで確認します。

踏み台サーバ
$ sudo lsof -i | grep sshd
# (略)
sshd  2269  user  7u  IPv4  30782  0t0  TCP *:25566 (LISTEN)
sshd  2269  user  9u  IPv6  30783  0t0  TCP *:25566 (LISTEN)
# (略)

接続を切断するには

上記で調べたプロセスIDを指定して kill します(入力ミスに注意。自分で確認したものに読み替えてください)。

ゲームサーバ
$ kill 1141687

ゲームに参加する!

Minecraft Java Edition を起動し,Multiplayer > Direct Connect / Add Server から踏み台のアドレス(必要に応じてポートも)を指定して参加します。正常にゲームに入れればOK!

接続できなった場合は,どこかでエラーが発生していないか,ポート開放ができていて外部から接続できるようになっているか…などを確認して対処します。
※ 統合版はそのままでは今回の方法に対応しません(統合版は UDP を使っており,SSH ポートフォワードは UDP に非対応なため)

その他

接続が途切れるのを防ぐ

放置していたら接続が途切れることがあります。
「client_loop: send disconnect: broken pipe」などでググれば対処法が出てきます。
さらに確実にしたければ,autossh をサービス化して使用するなどの方法があるようです。

遅延は?

少なくとも普通に Minecraft をプレイする場合は,全く気になりませんでした。
ゲーム上で表示される ping 値は,10〜15ms 程度でした。(直接接続の場合は1桁)

おわりに

踏み台サーバ経由で簡単にサーバを外部に公開してみました。
今回はリモートポートフォワードのみを使用しましたが,ローカルポートフォワードもあります。記事は無限にネットに転がっていると思いますので,是非調べてみてください…。

何か誤りなどがあれば,コメントなどで教えていただけると,とても助かります。
(そういえば,この方法を本番環境で使うことって一般的なんでしょうか…?)

Discussion

注釈でも書かれているようにGatewayPorts yesはすべてのリモートポートフォワーディングに影響する上、クライアントから動作を抑制する事が出来ない危険な設定なので使わないほうがいいでしょう。

それよりはGatewayPorts clientspecifiedにし、以下のようにBind Addressとして0.0.0.0を指定する方がデフォルトの動作を変えずに対象のポートフォワーディングのみ外部からの接続を受け付けるように出来るので安全です。

$ ssh -fNR 0.0.0.0:25565:localhost:25565 踏み台のログインユーザ:踏み台のアドレス
ログインするとコメントできます