在之前的章節中,我們用了偷吃步的方法,將 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-numbers
flag 取得)
同樣地,我們需要新增規則允許 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 位址。
至於怎麼解決,我們下一個章節再繼續。