問題重述#

接著上一章的設定,namespace ns1 裡的位址是 10.0.0.1,default gateway 指到 br010.0.0.254,host 也有正常的對外網路。然而:

sudo ip netns exec ns1 ping -c 3 8.8.8.8

仍然 ping 不到。這一章要回答:封包走到哪一步死掉的?

用 tcpdump 跟著封包#

最可靠的辦法是沿著封包的路徑佈下 tcpdump 觀察點。

觀察點 1:namespace 內的介面#

sudo ip netns exec ns1 tcpdump -i veth-c1 -nn icmp

預期會看到從 10.0.0.1 > 8.8.8.8 的 ICMP echo request 一直送出,但完全沒有對應的 echo reply 回來。

觀察點 2:host 上的 bridge#

sudo tcpdump -i br0 -nn icmp

同樣只會看到 request,沒有 reply。

觀察點 3:host 對外實體介面(例如 eth0#

sudo tcpdump -i eth0 -nn icmp

這裡才是關鍵。常見會出現以下兩種其中一種狀況:

  • 完全看不到任何封包出去
  • 看得到封包出去,但來源 IP 仍然是 10.0.0.1(私有位址)

為什麼會這樣#

問題其實不只一個,而是疊在一起。

1. host 預設不幫別人轉送封包#

Linux kernel 預設不會把不是「自己產生」的封包再轉出去。這由 sysctl 控制:

cat /proc/sys/net/ipv4/ip_forward

如果值是 0,封包到了 host 之後會直接被丟掉,連 eth0 都看不到。這時 tcpdump 結果會是「br0 看得到、eth0 看不到」。

2. 即使開了 forwarding,來源 IP 是私有位址#

假設手動把 ip_forward 設成 1,封包會被轉出 eth0,但來源 IP 仍是 10.0.0.1。這帶來兩個致命問題:

  • 上游路由器看到 10.0.0.1 這種 private IP,照 RFC 1918 規定多半直接丟棄
  • 就算對端真的收到了 ICMP echo request,回應的目的 IP 是 10.0.0.1,這個 IP 在公網上根本不存在,封包永遠回不來

這就是「封包出得去、回不來」的本質。

3. RPF(Reverse Path Filtering)#

第三層保險叫做 RPF(Reverse Path Filtering,反向路徑過濾)。Linux 在收到封包時會檢查:

  • 「如果我要回這個來源 IP,我會從哪張介面送回去?」
  • 如果答案跟封包進來的介面不一致,就丟掉

相關 sysctl:

cat /proc/sys/net/ipv4/conf/all/rp_filter
cat /proc/sys/net/ipv4/conf/eth0/rp_filter

當封包來源是 10.0.0.1 但從 eth0 進來,host 的路由表會說「10.0.0.0/24 應該走 br0」,介面對不起來,封包會被 RPF 丟掉。

把症狀對應回工具#

整理一下三層症狀:

  • br0 有看到 request、eth0 沒看到 → ip_forward = 0,沒轉送
  • eth0 有看到 request、來源 IP 是私有位址、沒有 reply → 來源 IP 不可路由
  • 看似什麼都對,但 reply 進來後立刻消失 → 可能是 RPF 在作用

tcpdump 的 -i any 可以一次抓多張介面,但會看不出方向;除錯時建議分介面抓。

為什麼 Docker 跑起來就直接能上網#

因為 Docker 在啟動時會自動處理:

  • net.ipv4.ip_forward 設為 1
  • 在 iptables(netfilter 上層工具)裡加上 NAT(Network Address Translation)規則,把容器的私有來源 IP 改寫成 host 的對外 IP
  • 同時加上 FORWARD chain 規則允許轉送

這些設定隱藏在 dockerd 啟動流程裡,沒有手動拼過一遍的話,很容易誤以為「容器自然就會出網」。

下一步#

下一章要做的事,正是手動補上這三件事:

  • 開啟 ip_forward
  • 在 iptables 的 POSTROUTING chain 加上 MASQUERADE 規則
  • 確保 FORWARD chain 不會把封包擋下來

做完之後,namespace 內 ping 公網應該就會通了。

延伸閱讀#

  • man 7 packet
  • Linux kernel 文件:Documentation/networking/ip-sysctl.rst
  • man 8 tcpdump