サイドルーター環境でポート転送が効かない問題を解決する

サイドルーター環境でポート転送が効かない問題を解決する

サイドルーターを導入すると、メインルーターのポート転送が効かなくなることがあります。ゲートウェイをサイドルーターに変えることで通信経路が変わり、メインルーターを前提とした転送が正常に動かなくなるためです。ゲートウェイはアドレスを変換して内部ネットワークの通信を外部へ転送し、各端末は外部との通信にこれを必要とします。この仕組みを理解すると、ポート転送の問題を解決しやすくなります。

はじめに

前の記事の構成を設定したあと、メインルーターでポート転送を設定していて、転送先のマシンがサイドルーターをゲートウェイにしている場合、ポート転送が効かなくなっているはずです。これはClashの振り分けが原因ではありません。ゲートウェイをサイドルーターにすると、メインルーターのポート転送が効かなくなります。

仕組み(長いので読み飛ばしてもOK)

ゲートウェイの役割は、要するにNATによるアドレス変換で、内部ネットワークの通信を外部へ転送することです。内部から外部への通信は必ずゲートウェイを通るため、各マシンには外部と通信するためのゲートウェイアドレスを設定する必要があります。

実装を単純化して考えると、ゲートウェイは自動のポート転送のようなものです。5タプルで一意に識別される各アクティブ接続について、内向きと外向きのポートを1組管理します。たとえば自分のマシンがGoogleのサーバーに接続し、ゲートウェイが管理するポートの組が(32384, 14122)なら、Google宛ての通信はすべてゲートウェイの32384番ポートへ送られ、そこから14122番ポート経由でGoogleへ転送されます。Googleからのパケットも逆向きに同じ転送を通ります。NATが1段だけとは限りません。回線にグローバルIPが割り当てられていなければ、自宅のゲートウェイの上流でもNATが行われます。最終的にGoogleと通信するゲートウェイは、必ずグローバルIPを持っています(インターネット上で通信できるのはグローバルIPを持つマシンだけだからです)。

サイドルーターもゲートウェイとして動作するため、NATを行います。つまり、自分のマシンと外部との通信は実際には2段のNATを通ります。

text
你的机器 <——> 旁路由(NAT) <——> 主路由(NAT) <——> 外部机器

メインルーターにポート転送を設定し、内部マシンのゲートウェイはサイドルーターにしている場合、グローバルIPから内部へアクセスすると、往路は次のようになります。

text
外部机器 ----> 主路由网关 ----> 你的机器

一方、復路は次のようになります。

text
你的机器 ----> 旁路由 ----> 主路由 ----> 外部机器

外部マシンが接続を開始した時点では、自分のマシンはメインルーターのゲートウェイとしか接続を確立していません。ところが返りの通信はサイドルーターへ送られます。サイドルーターにはその接続に対応するポートの組がないため、パケットを破棄してしまい、ポート転送が失敗します。

解決方法(ここだけ読めばOK)

解決策は簡単です。NATが2段あるので、ポート転送も2段に設定すればよいだけです。

  1. メインルーターで、サイドルーター宛てのポート転送を設定します。
  2. サイドルーターで、目的のマシン宛てのポート転送を設定します。

これで往路も復路も次の経路になり、接続が成立します。

text
你的机器 <——> 旁路由(NAT) <——> 主路由(NAT) <——> 外部机器

ポート転送の設定方法はシステムによって異なります。iKuaiとOpenWrtにはGUIがあるので、ここでは省略します。前の記事ではサイドルーターにDebianを使いましたが、次の方法はiptablesを使うシステム全般で利用できます。以下のコマンドでiptablesを設定します。例では転送先マシンを「内部」、ゲートウェイのマシンを「外部」と呼んでいます。

shell
iptables -t nat -I PREROUTING -p tcp -d <外部 IP> --dport <外部端口> -j DNAT --to-destination <内部 IP>:<内部端口>
iptables -t nat -I POSTROUTING -p tcp --dport <内部端口> -d <外部 IP> -j SNAT --to-source <内部 IP>

これでiptablesの設定ができます。

前の記事のclean.shにあるiptables -t nat -Fは、ユーザー定義のルーティングルールをすべて消去するため、転送も無効になります。実行後は、上のコマンドを追加で実行してください。