問題重述#
接著上一章的設定,namespace ns1 裡的位址是 10.0.0.1,default gateway 指到 br0 的 10.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