要補上的三件事#
上一章把問題拆出來了,這一章要實際動手:
- 開啟
ip_forward,讓 host 願意幫別人轉送封包 - 在 iptables 的 POSTROUTING chain 加上 MASQUERADE 規則,把來源 IP 改寫成 host 的對外 IP
- 確認 FORWARD chain 不會擋下這些封包
iptables 是 netfilter 在使用者層的設定工具,所有 NAT 規則最後都會落到 kernel 的 netfilter 框架上執行。
第一步:打開 ip_forward#
臨時生效:
sudo sysctl -w net.ipv4.ip_forward=1要開機後仍生效,寫進設定檔:
echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-forward.conf
sudo sysctl -p /etc/sysctl.d/99-forward.conf確認:
cat /proc/sys/net/ipv4/ip_forward值應為 1。
第二步:認識 iptables 的 chain#
iptables 有幾個跟轉送相關的常見 chain:
- PREROUTING:封包剛進入,路由判斷之前
- INPUT chain:目標是本機的封包
- FORWARD chain:要被轉送出去的封包
- OUTPUT chain:本機自己產生、要送出去的封包
- POSTROUTING:路由判斷之後、實際送出之前
NAT 只會發生在兩個位置:
- PREROUTING:適合做 DNAT(目的地位址轉換),「外面進來的封包,先把目的 IP 改掉」
- POSTROUTING:適合做 SNAT / MASQUERADE(來源位址轉換),「要出去的封包,最後一刻把來源 IP 改掉」
容器出網用到的是後者。
第三步:加上 MASQUERADE 規則#
SNAT 與 MASQUERADE 的差別:
- SNAT 要明確指定改寫後的來源 IP,適合來源 IP 固定的場景
- MASQUERADE 會自動取用「該封包要出去那張介面」當下的 IP,適合 IP 可能變動的場景(如撥接、DHCP)
加上一條規則,把來自 10.0.0.0/24 而且不是要送回 br0 的封包做 MASQUERADE:
sudo iptables -t nat -A POSTROUTING \
-s 10.0.0.0/24 ! -o br0 -j MASQUERADE檢查規則:
sudo iptables -t nat -L POSTROUTING -n -v第四步:確認 FORWARD chain 通#
預設 FORWARD chain 的 policy 可能是 DROP,加上明確的 ACCEPT 比較安全:
sudo iptables -A FORWARD -i br0 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o br0 -m conntrack \
--ctstate RELATED,ESTABLISHED -j ACCEPT兩條的意思:
- 從
br0進、eth0出的新連線通通放行 - 從
eth0進、br0出,只放行已建立連線的回程封包(避免外部任意主機主動連進 namespace)
驗證一次#
回到 namespace 試 ping:
sudo ip netns exec ns1 ping -c 3 8.8.8.8預期這次會有回覆。同時在 host 用 tcpdump 觀察 eth0:
sudo tcpdump -i eth0 -nn icmp預期看到的封包樣貌:
- 出去的 echo request 來源 IP 已經被改寫成 host 在
eth0上的 IP(不再是10.0.0.1) - 回來的 echo reply 目的 IP 是 host 在
eth0上的 IP,被 netfilter 反向 NAT 還原成10.0.0.1後送進br0
NAT 的反向還原由 conntrack 模組記住對應關係,不需要再寫一條反向規則。
docker0 的 iptables 規則長什麼樣#
跑了 Docker 後執行:
sudo iptables -t nat -L -n -v
sudo iptables -L -n -v會看到 Docker 自動建立了一堆規則,重點包括:
nat表的POSTROUTING:對172.17.0.0/16出docker0以外介面的封包做 MASQUERADEfilter表的FORWARD:跳到DOCKER-USER、DOCKER-ISOLATION-STAGE-1、DOCKER等自定義 chainDOCKERchain:放著個別容器的 port 對應規則(下一章再講)
換句話說,Docker 做的事就是這一章手動做過的內容自動化版本,再加上幾層隔離規則。
至此完成的能力#
- namespace 之間互通:bridge 解決
- namespace 看得到 bridge:ip route + bridge IP 解決
- namespace 連得到外網:ip_forward + MASQUERADE 解決
還缺最後一塊:「外部要怎麼連回 namespace 裡的服務」,這就是下一章的 port publishing。
延伸閱讀#
man 8 iptablesman 8 iptables-extensions- netfilter.org 文件