iptables でローカルの上位ポートに転送しているときにそのポートへの直接アクセスを禁止する

下記の様な方法でウェルノウンポートへのアクセスを別の上位ポートへ転送したときに、上位ポートへの直接のアクセスを禁止する方法。

 - [80番ポートへ届いたパケットをiptablesでローカルの上位ポートに転送する - Qiita](http://qiita.com/kawaz/items/ed0030cb29c7d0497b63 "80番ポートへ届いたパケットをiptablesでローカルの上位ポートに転送する - Qiita")

## 準備

3000 ポートでサーバを立てます。

```
$ nc -k -l 3000
```

iptables で 80 ポートに届いたパケットを 3000 ポートへ REDIRECT します。

```
$ sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000
```

別ホストから 80 ポートと 3000 ポートに接続すると、どちらでも接続できます。

```
$ date | nc -n -v 192.0.2.123 80
Connection to 192.0.2.123 80 port [tcp/*] succeeded!

$ date | nc -n -v 192.0.2.123 3000
Connection to 192.0.2.123 3000 port [tcp/*] succeeded!
```

## 失敗その１

サーバのバインドアドレスを 127.0.0.1 にしてみました。

```
$ nc -k -l 127.0.0.1 3000
```

80 ポートと 3000 ポートの両方で繋がらなくなりました。

```
$ date | nc -n -v 192.0.2.123 80
nc: connect to 192.0.2.123 port 80 (tcp) failed: Connection refused

$ date | nc -n -v 192.0.2.123 3000
nc: connect to 192.0.2.123 port 3000 (tcp) failed: Connection refused
```

REDIRECT したからといって eth0 に来たパケットを lo に転送しているわけではないのでダメなのだと思います。

## 失敗その２

INPUT チェインで 3000 ポートをフィルタしてみました。

```
$ sudo iptables -F
$ sudo iptables -F -t nat

$ sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000
$ sudo iptables -A INPUT -p tcp --dport 3000 -j LOG
$ sudo iptables -A INPUT -p tcp --dport 3000 -j REJECT --reject-with tcp-reset
```

両方のポートで繋がらなくなりました。

```
$ date | nc -n -v 192.0.2.123 80
nc: connect to 192.0.2.123 port 80 (tcp) failed: Connection refused

$ date | nc -n -v 192.0.2.123 3000
nc: connect to 192.0.2.123 port 3000 (tcp) failed: Connection refused
```

filter テーブルの INPUT よりも nat テーブルの PREROUTING の方が先なので当たり前でした。

## 成功その１

nat テーブルの PREROUTING よりも前にフィルタすればいいのですが filter テーブルで PREROUTING よりも先に適用されるチェインはありません。

なので mangle テーブルの PREROUTING でマークして、マークされたパケットを filter テーブルの INPUT で REJECT します。

```
sudo iptables -F
sudo iptables -F -t nat
sudo iptables -F -t mangle

sudo iptables -t mangle -A PREROUTING -p tcp --dport 3000 -j MARK --set-mark 0x01
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000
sudo iptables -A INPUT -p tcp --dport 3000 -m mark --mark 0x01 -j REJECT --reject-with tcp-reset
```

80 ポートでだけ繋がるようになりました。

```
$ date | nc -n -v 192.0.2.123 80
Connection to 192.0.2.123 80 port [tcp/*] succeeded!

$ date | nc -n -v 192.0.2.123 3000
nc: connect to 192.0.2.123 port 3000 (tcp) failed: Connection refused
```

## 成功その２

REJECT ではなく DROP なら mangle テーブルの PREROUTING チェインでできました。

```
sudo iptables -F
sudo iptables -F -t nat
sudo iptables -F -t mangle

sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000
sudo iptables -t mangle -A PREROUTING -p tcp --dport 3000 -j DROP
```

パケットがドロップされるのでタイムアウトまで待たされますけどね。

```
$ date | nc -n -v -w 3 192.0.2.123 80
Connection to 192.0.2.123 80 port [tcp/*] succeeded!

$ date | nc -n -v -w 3 192.0.2.123 3000
nc: connect to 192.0.2.123 port 3000 (tcp) timed out: Operation now in progress
```

## 成功その３

nat テーブルの PREROUTING でリッスンしていない適当なポートに REDIRECT すれば、REJECT と同じような動作になります。

```
sudo iptables -F
sudo iptables -F -t nat
sudo iptables -F -t mangle

sudo iptables -t nat -A PREROUTING -p tcp --dport 3000 -j REDIRECT --to-ports 1
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3000
```

1 ポートでリッスンしていないことが前提になりますけど。

```
$ date | nc -n -v 192.0.2.123 80
Connection to 192.0.2.123 80 port [tcp/*] succeeded!

$ date | nc -n -v 192.0.2.123 3000
nc: connect to 192.0.2.123 port 3000 (tcp) failed: Connection refused
```

## さいごに

「成功その１」が一番妥当っぽい気がします。

「成功その２」は mangle テーブルで DROP とかしてもいいんですかね？

「成功その３」はちょっとワイルドすぎると思います。