iptablesでポートフォワードを試してみたいと思います。
手元で試したいのでちょっと構成は特殊ですが以下です。
- LAN(192.168.0.0/24)内にスマホとノートPCがあります。
- ノートPC上にはwebサーバーをたて、4000番ポートで待ち受けさせます。
- 図の簡略化のためLAN内のルーターは省略しています。
達成したいことはスマホのブラウザからノートPC(192.168.0.9)にアクセスした際に、
4000番ポートのサーバに応答させることです。
TODO
パケットの流れを下図でおさらいしながらTODOを洗い出します。
参考:Iptablesチュートリアル 1.2.2 Chapter 6.1
スマホブラウザのGETリクエストのパケットはDestination IPが192.168.0.9、Destination Portが80で、図の「NETWORK」から流れてきます。
サーバーは図の「Local Process」です。
よって、以下を実施すればリクエストのパケットはサーバーに到達できます。
- 図の「nat PREROUTING」でDestination IPを127.0.0.1に、Destination Portを4000に書き変える
※宛先がローカルホストのため、Routing Decisionでパケットは図の「INPUT」の流れに振り分けられます。 - 図の「filter INPUT」でそのパケットの通過を許可する
検証
初期状態は以下です。
# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
デフォルトポリシーの設定
ホワイトリスト方式にします。
# iptables -P INPUT DROP
# iptables -P FORWARD DROP
ノートPC上のWEBサーバーへのアクセスを許可
TODOの順序と逆になりますが、まずノートPCのブラウザでサーバにアクセスできるようにします。
# iptables -A INPUT -d 127.0.0.1 -p tcp --dport 4000 -j ACCEPT
# iptables -A INPUT -s 127.0.0.1 -p tcp --sport 4000 -j ACCEPT
ローカルなのでややこしいですが、設定とパケットは以下のように対応します
# iptables -A INPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 4000 -j ACCEPT
上記がブラウザからサーバーへ向かうパケットを許可
# iptables -A INPUT -s 127.0.0.1/32 -p tcp -m tcp --sport 4000 -j ACCEPT
上記がサーバーからブラウザへ向かうパケットを許可
スマホからのhttpアクセスをノートPC上のサーバーへ振る
スマホ(192.168.0.2)からのリクエストのdesinationをNAPT(Network Address Port Translation)します。
# iptables -t nat -A PREROUTING -s 192.168.0.2 -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:4000
LAN内のスマホ以外からもアクセスさせるには以下のようにします。
# iptables -t nat -A PREROUTING ! -s 192.168.0.9 -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:4000
DNATターゲットを使うことでDestination IPとDestination Portの書き変えを行っています。
。
。
。
。
。
上記で動きそうですが動きませんでした。
- 「iptables redirect outside requests to 127.0.0.1」
- https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt(リンク先でroute_localnetを検索)
外部からlocalhostへはデフォルトでは繋がらないようです。
localhostは外界から隔絶されているべきだからでしょう。
今回は手元で検証するためなので設定を変更します。
# sysctl -w net.ipv4.conf.eth0.route_localnet=1
※eth0はスマホからのパケットが通るNICの名前に読み替えること
これでアクセスできました!
「サーバーからの応答パケットのSNATをする必要はないのか?」という疑問をもちましたか?僕は持ちました。
結論は必要ありません。DNAT, SNATは一方向の変換を定義してやれば逆方向は自動でおこないます。
※参考: DNATターゲット
設定の確認
最終的な設定は以下です。
root@X1-Carbon-6th:~# iptables-save
# Generated by iptables-save v1.6.1 on Sat Dec 28 14:15:30 2019
*nat
:PREROUTING ACCEPT [167:28771]
:INPUT ACCEPT [10:624]
:OUTPUT ACCEPT [314:29752]
:POSTROUTING ACCEPT [314:29752]
-A PREROUTING -s 192.168.0.2/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination 127.0.0.1:4000
COMMIT
# Completed on Sat Dec 28 14:15:30 2019
# Generated by iptables-save v1.6.1 on Sat Dec 28 14:15:30 2019
*filter
:INPUT DROP [3377:278628]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [3560:414379]
-A INPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 4000 -j ACCEPT
-A INPUT -s 127.0.0.1/32 -p tcp -m tcp --sport 4000 -j ACCEPT
COMMIT
# Completed on Sat Dec 28 14:15:30 2019
root@X1-Carbon-6th:~# sysctl -n net.ipv4.conf.enx7cc3a186ea62.route_localnet
1
片付け
片付けるときは以下に気をつけてください。
- table毎に-Fオプションでルールを消すこと、ポリシーを元に戻すこと
- 今回の特殊対応した設定をsysctlで戻すこと
# iptables -F && iptables -F -t nat
# iptables -P INPUT ACCEPT && iptables -P FORWARD ACCEPT
# sysctl -w net.ipv4.conf.enx7cc3a186ea62.route_localnet=0
まとめ
手元の環境でポートフォワードを試しました。
パケットの流れの図がとても優秀で、見ながら考えれば十分に設定ができることを確認しました。
「いいね」よろしくお願いします。