在之前的章節中,我們用了偷吃步的方法,將 iptables 的 FORWARD 預設行為改成 ACCEPT。然而,當我們在上一章將 FORWARD 的預設行為改回 DROP 後,先前建立的網路連線卻斷掉了。這是怎麼回事呢?
測試網路連線:ns1 到 ns0
sudo ip netns exec ns1 ping -c 1 172.18.0.2
# output
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
--- 172.18.0.2 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
測試網路連線:ns1 到外網
sudo ip netns exec ns1 ping -c 1 8.8.8.8
# output
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
完全斷線了!為什麼會這樣呢?原因就在於 iptables 的預設行為的差異。
iptables 的預設行為,主要體現在三個內建 chains (INPUT, OUTPUT, FORWARD) 的 policy 上:
ACCEPT (預設允許):採黑名單模式,只有明確被拒絕的流量才會被阻擋,其餘都允許通過。DROP (預設丟棄):採白名單模式,只有明確被允許的流量才能通過,其餘都會被丟棄。
所以,當我們把 FORWARD 的 policy 改成 DROP 後,所有未被明確允許的流量,包含容器之間的通訊,都被阻擋了。我們必須重新設定 iptables 規則,才能讓網路恢復正常。
為了找出問題所在,我們比較了安裝 Docker 前後 iptables 規則的差異。
Docker 安裝前:
sudo iptables -t filter -nvL --line-numbers
# output
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination
Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination
Docker 安裝後:
sudo iptables -t filter -nvL --line-numbers
# output
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
Chain FORWARD (policy DROP 1 packets, 84 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1      664 2569K DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
2      664 2569K DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
3      340 2547K ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
4        0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
5      171 11618 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
6        0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
Chain DOCKER (1 references)
num   pkts bytes target     prot opt in     out     source               destination
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1      171 11618 DOCKER-ISOLATION-STAGE-2  all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
2      664 2569K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
2      171 11618 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
Chain DOCKER-USER (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1      664 2569K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
sudo iptables-save -t filter
# output
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [1:84]
:OUTPUT ACCEPT [0:0]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
安裝 Docker 後,多了許多 iptables 規則和 chains,這些都是 Docker 為我們自動建立的,用於管理容器網路。
這些 chains 主要有兩個目的:1. 安全隔離容器與外部網路;2. 集中管理容器流量。Docker 會自動建立必要的 rules,例如允許容器所需的 RELATED、ESTABLISHED 流量,以確保容器間以及容器與外部網路的正常通訊。
DOCKER
-A FORWARD -o docker0 -j DOCKER 表示從主機對外發送的封包,在通過 docker0 介面前,會先經過 DOCKER 鏈進行檢查。DOCKER-ISOLATION-STAGE-1、DOCKER-ISOLATION-STAGE-2
docker0 與其他 Docker 橋接網路)之間的流量隔離。STAGE-1:先判斷封包來源與目的地是否為相同的 Docker 橋接網路,若否,則跳轉到 STAGE-2。STAGE-2:直接丟棄不允許跨橋接網路的流量。DOCKER-USER
DOCKER-USER 中新增自訂規則。-A DOCKER-USER -j RETURN,表示若無自訂規則,則直接返回 FORWARD 鏈。Docker 未建立容器時,這些鏈並無任何規則。讓我們繼續分析 FORWARD chain 的規則。
讓我們更仔細看看 FORWARD chain 中新增的規則:
# iptables
num   pkts   bytes  target                       prot opt in     out       source         destination
1      664   2569K  DOCKER-USER                 all  --  *      *         0.0.0.0/0      0.0.0.0/0
2      664   2569K  DOCKER-ISOLATION-STAGE-1    all  --  *      *         0.0.0.0/0      0.0.0.0/0
3      340   2547K  ACCEPT                      all  --  *      docker0   0.0.0.0/0      0.0.0.0/0    ctstate RELATED,ESTABLISHED
4        0       0  DOCKER                      all  --  *      docker0   0.0.0.0/0      0.0.0.0/0
5      171   11618  ACCEPT                      all  --  docker0 !docker0 0.0.0.0/0      0.0.0.0/0
6        0       0  ACCEPT                      all  --  docker0 docker0  0.0.0.0/0      0.0.0.0/0
# iptables-save
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -j DOCKER-USER
DOCKER-USER chain,讓使用者可以在該 chain 裡自行新增或覆蓋 Docker 預設的規則。-A FORWARD -j DOCKER-ISOLATION-STAGE-1
DOCKER-ISOLATION-STAGE-1,這個 chain 主要用來隔離不同 Docker bridge 或者不同介面之間的流量。DOCKER-USER,若無規則攔截就 RETURN 回 FORWARD,接著又跳到 DOCKER-ISOLATION-STAGE-1 做容器網路隔離檢查。-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
DOCKER chain 做後續處理。DOCKER chain 是 Docker 自動新增的 chain,用來存放針對特定 Port / Protocol 的允許規則 (例如 docker run -p 8080:80 時會插入 DNAT、FORWARD 規則等)。-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
! -o docker0),就直接允許。-A FORWARD -i docker0 -o docker0 -j ACCEPT
簡單來說
DOCKER-USER 和 DOCKER-ISOLATION-STAGE-* chain 是 Docker 特別設計來讓使用者自訂規則、並且在容器與外部之間做網路隔離與控制。DOCKER chain 是設計對特定容器 Port / Protocol 做控制轉發。現在我們需要為自定義的 docker1 橋接網路新增相應的 iptables 規則,才能讓 ns1 正常連線到 ns0 和外網。
我們新增一條規則,類似於原來的第 6 條規則,但將網路介面替換為 docker1:
sudo iptables -t filter -A FORWARD -i docker1 -o docker1 -j ACCEPT
測試連線:
sudo ip netns exec ns1 ping -c 1 172.18.0.2
# output
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
64 bytes from 172.18.0.2: icmp_seq=1 ttl=127 time=0.060 ms
--- 172.18.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.060/0.060/0.060/0.000 ms
提醒
若新增的規則錯誤,可以使用以下指令刪除:sudo iptables -t filter -D FORWARD 7(7為該條目在FORWARD表中的編號,使用iptables命令的--line-numbersflag 取得)
同樣地,我們需要新增規則允許 docker1 網路上的容器訪問外部網路:
sudo iptables -t filter -A FORWARD -i docker1 ! -o docker1 -j ACCEPT
sudo iptables -t filter -A FORWARD -o docker1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
測試連線:
sudo ip netns exec ns1 ping -c 1 8.8.8.8
# output
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=6.83 ms
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 6.834/6.834/6.834/0.000 ms
思考
若只新增第 5 條規則,會發生什麼事?
因為第 5 條規則只允許從docker1進入,且目的網卡非docker1的流量通過。但在連線交握時,封包的進出方向會互換,因此無法符合規則,流量將被丟棄。
在本章中,我們更加深入探討 iptables 的運作原理,並成功讓容器間以及從容器到外網的流量得以順利傳輸。那麼,如何從外網存取容器呢?
ns1 的 IP 位址為 172.18.0.3,對於外網而言,其他主機的 Private IP 位址顯然無法作為封包的目的位址。即使使用主機的 Public IP 位址作為目的位址,也無法精確得知容器內的 IP 位址。
至於怎麼解決,我們下一個章節再繼續。