要補上的三件事#

上一章把問題拆出來了,這一章要實際動手:

  • 開啟 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/16docker0 以外介面的封包做 MASQUERADE
  • filter 表的 FORWARD:跳到 DOCKER-USERDOCKER-ISOLATION-STAGE-1DOCKER 等自定義 chain
  • DOCKER chain:放著個別容器的 port 對應規則(下一章再講)

換句話說,Docker 做的事就是這一章手動做過的內容自動化版本,再加上幾層隔離規則。

至此完成的能力#

  • namespace 之間互通:bridge 解決
  • namespace 看得到 bridge:ip route + bridge IP 解決
  • namespace 連得到外網:ip_forward + MASQUERADE 解決

還缺最後一塊:「外部要怎麼連回 namespace 裡的服務」,這就是下一章的 port publishing。

延伸閱讀#

  • man 8 iptables
  • man 8 iptables-extensions
  • netfilter.org 文件