試して学ぶiptablse、ポートフォワード

iptablesでポートフォワードを試してみたいと思います。

手元で試したいのでちょっと構成は特殊ですが以下です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369713/bfd4a285-5e7b-fa62-d23a-591a264f1c26.png)

- 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](https://www.asahi-net.or.jp/~aa4t-nngk/ipttut/output/ipttut_all.html#TRAVERSINGGENERAL)

<img src="https://www.asahi-net.or.jp/~aa4t-nngk/ipttut/output/images/tables_traverse.jpg" width="50%">

スマホブラウザのGETリクエストのパケットはDestination IPが192.168.0.9、Destination Portが80で、図の「NETWORK」から流れてきます。
サーバーは図の「Local Process」です。

よって、以下を実施すればリクエストのパケットはサーバーに到達できます。

1. 図の「nat PREROUTING」でDestination IPを127.0.0.1に、Destination Portを4000に書き変える  
   ※宛先がローカルホストのため、Routing Decisionでパケットは図の「INPUT」の流れに振り分けられます。
1. 図の「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://unix.stackexchange.com/questions/111433/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ターゲット](https://www.asahi-net.or.jp/~aa4t-nngk/ipttut/output/ipttut_all.html#DNATTARGET)

## 設定の確認

最終的な設定は以下です。

~~~
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
~~~

# まとめ

手元の環境でポートフォワードを試しました。
パケットの流れの図がとても優秀で、見ながら考えれば十分に設定ができることを確認しました。

**「いいね」よろしくお願いします。**:wink: