使用 iptables 進行連接埠轉送 (port forwarding)

在 GNU/Linux 系統上面,只有具有 root 權限的程式才能直接使用 1024 以下的 port。有時候我們並不想讓 server 程式以 root 權限執行,但是又想要讓其他人可以從 1024 以下的 port 連到它,這時就可以利用內建的防火牆 — iptables
來達成這樣的功能。

舉個例子來說,現在我們想要讓一個一般使用者權限的 http server 可以從 port 80
連上,也就是要可以在瀏覽器裡面直接輸入 localhost 或是對外的 ip 位置,就能連到該 server。我們以 python http server 做測試,輸入以下指令,建立測試檔案及啟動 http server:

mkdir http_root
cd http_root
echo "http server test" > index.html
python -m SimpleHTTPServer 8000

由於 http server 是以一般使用者權限執行,它只能使用 1024 以上的 port,在這裡我們使用 port 8000。現在開啟瀏覽器,輸入 localhost:8000,應該就可以看到 http server test 的訊息,表示
http server 已經成功啟動在 port 8000。此時若直接在瀏覽器中輸入 localhost,瀏覽器會連到 port 80 (預設的 http port),但是這個時候 http server 並沒有 listen port 80,所以會顯示「找不到網頁」的訊息。

接下來我們要利用 iptables 來將連接到 port 80 的連線轉送到 port 8000。
iptables 有內建連接埠轉送 (port forwarding) 的功能,輸入以下指令便可將 port 80 導到 port 8000:

sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8000
sudo iptables -t nat -A OUTPUT -p tcp -d localhost --dport 80 -j REDIRECT --to-ports 8000

第一行的 PREROUTING 規則會在封包進到介面卡的時候執行,也就是在從其他電腦連線進來的時候才會將外部要連到 port 80 (--dport) 的連線轉送到 port 8000
(--to-ports)。但是如果從本機 (localhost) 連線的話,就不會用到 PREROUTING,所以要再加上一個 OUTPUT 的規則,把從本機連到自己 (-d localhost)
port 80 的連線轉送到 port 8000。上面的範例是轉送 TCP port,如果要轉送的是 UDP port 的話,只要將以上函數裡面的 -p tcp 改成 -p udp 即可。另外,iptables 必須用 root 權限執行,所以這裡加上了
sudo

接下來要做的事是讓系統允許 port forwarding:

sudo sysctl -w net.ipv4.ip_forward=1

做完這一步之後,應該在瀏覽器裡面輸入 localhost 就會看到 http server test
的訊息了。

這些 iptables 和 sysctl 做的設定只會在目前的使用階段有效,重新開機之後設定就會消失了。我們可以把這些步驟寫成一個 script,讓之後可以很快地開啟 port forwarding

#!/bin/sh

# 檢查 root 權限
if [ `id -u` -ne 0 ]; then
   echo "這個 script 需要 root 權限!"
   exit 1
fi

# 把 port forwarding 定義成函數
remap_tcp_port() {
   iptables -t nat -A PREROUTING -p tcp --dport $2 -j REDIRECT --to-ports $1
   iptables -t nat -A OUTPUT -p tcp -d localhost --dport $2 -j REDIRECT --to-ports $1
}

# 允許 port forwarding
sysctl -w net.ipv4.ip_forward=1

# 設定要轉送的 port (呼叫上面定義的函數)
remap_tcp_port 8000 80