本篇文章使用 Ubuntu 22.04
防火牆
防火牆是透過設定一些有順序的規則,來管制進出封包的機制。這邊引用了鳥哥筆記的圖片來輔助,可以看到封包在進入防火牆這道關卡的流程會經過規則 1、規則 2 、 … ,過程具有順序性。
其中 iptables
是 Linux 系統上一個極為關鍵的防火牆工具,在進入 networking 這個大坑之前請教前輩的時候,他們也都說這個指令必學(嘎嘎頂XD)。
iptables 與 Netfilter
iptables 和 Netfilter 有很大的關係,Netfilter 框架是 Linux kernel 提供的一個模組,用來管理進出主機的 packet flow,而 iptables 作為一個命令列介面,透過直接與 Netfilter 互動 (呼叫 Netfilter 的 hooks) 以攔截並處理幾乎所有進出主機的封包:
- INPUT:進入本機的封包(如 ssh、http 請求)
- OUTPUT:由本機送出的封包(如 ping、curl)
- FORWARD:經過本機轉送到其他主機的封包(通常在 router/gateway 上常見)
iptables 架構:三大核心元素
Tables
用來分類與組織不同的防火牆規則。每張表會依據它的處理目的被分類,換句話說,不同表會針對不同類型的封包處理決策做優化。
- Filter 表:處理封包是否允許通過的決策(最常用的表)。
- NAT 表:負責網路位址轉換(如 SNAT、DNAT),影響封包的路由行為。
- Mangle 表:用來修改封包的特殊資訊(如 IP header 的 TTL 值等)。
- Raw 表:提供處理狀態追蹤(state tracking)之前的控制點,常用於設定某些封包不被連線追蹤系統處理(NOTRACK)。
1 | sudo iptables-save |
這行指令我是在 VirtualBox 的 Ubuntu terminal 使用,輸出如下(#
號是我個人註解說明),顯示了主機上目前的 iptables 規則(filter 表與 nat 表),兩張表主要是由 Docker 自動產生與管理的。
1 | *nat |
- filter
- INPUT:控制進入主機的封包
- OUTPUT:控制由主機送出的封包
- FORWARD:控制經由主機轉送的封包(e.g. container 間流量),與下列 nat table 相關性較高。
- nat
- PREROUTING:在進行路由判斷之前所要進行的規則(DNAT/REDIRECT)
- POSTROUTING:封包離開主機前,進行 SNAT/MASQUERADE 規則處理
- OUTPUT:本機發出的封包,進行 NAT 處理
Chains
鏈定義封包在傳遞過程中「何時」會被檢查,並且「在哪裡」做出處理決策。其實這篇筆記的第一張圖,也就是規則執行流程也屬於一個 chain。
在執行指令 sudo iptables-save
後的輸出內容中,表下面的幾行都是 chain 的定義,例如對於 nat 表而言,:PREROUTING
、:INPUT
都是 chain,以下是輸出與說明:
- PREROUTING:封包進入主機前(未經路由)處理
- INPUT:封包要送進本機的 NAT 前處理(較少使用)
- OUTPUT:本機發出的封包,進行 NAT 處理
- POSTROUTING:封包離開主機前,進行 SNAT/MASQUERADE 處理
- DOCKER:Docker 自定義鏈,處理 DNAT 導向 container
1 | *nat |
Targets
當封包經過某條鏈時,它會依照鏈內的規則逐一比對。如果符合某條規則的條件,則會執行該規則設定的 target。常見的 terminating targets 如下:
- ACCEPT:接受封包。
- DROP:丟棄封包,不回應對方,使對方無法知道主機是否存在。
- REJECT:拒絕封包,並明確通知對方連線被拒絕。
如果要透過指令檢查每個 chain 中有哪些規則,可以用
1 | iptables -L -n |
1 | Chain INPUT (policy ACCEPT) |
如果要透過指令檢查特定 chain 中有哪些規則,可以用
1 | sudo iptables -t nat -L <CHAIN_NAME> -v -n |
例如當我執行 sudo iptables -t nat -L DOCKER -v -n
,就會顯示 nat 表中 DOCKER 這條 chain 裡目前的所有規則,包含封包數、來源/目的、interface 等資訊如下:
1 | Chain DOCKER (2 references) |
封包如何進行規則處理?
從鳥哥筆記的圖裡可以發現網路封包在進入伺服器前,會經過好幾組規則,每個鏈裡面都有自己的一組規則,每個表又會有很多鏈。
NAT 網路位址轉換:SNAT/DNAT 的設定
透過指令sudo iptables -t nat -L -v -n
,可以從 nat 表中 chains 的規則理解使用了哪幾種 NAT 類型
1 | Chain PREROUTING (policy ACCEPT 10 packets, 1074 bytes) |
從上述結果可以看到我的 VM 目前用了以下兩種 NAT 類型,但沒有 SNAT、DNAT 等 NAT 類型:
NAT 類型 | 出現位置 | 說明 |
---|---|---|
MASQUERADE |
POSTROUTING 鏈 | 將 container 對外封包偽裝成主機 IP(動態 SNAT) |
RETURN |
DOCKER 鏈 | 結束處理、跳出 DOCKER 鏈(非 NAT 類型,但重要邏輯) |
透過 MASQUERADE
的 NAT,讓 VM 內容器可以主動存取到外部網路的連線,但是外部網路無法存取到容器。這樣的運作可以搭配這張圖做解釋:
圖中元件 | 對應 VirtualBox / Docker 環境 |
---|---|
INTERNET |
外部網路(如主機對外網卡、路由器、真實 Internet) |
eth0 |
Ubuntu VM 的 NAT 或橋接網卡(連到外部) |
eth1 |
Ubuntu VM 的 Docker bridge(例如 docker0) |
Firewall: MASQUERADE |
Ubuntu VM + iptables(設定 MASQUERADE 的地方) |
LAN |
多個 Docker container,模擬區域網路使用者 |
當某個容器(例如 IP = 10.1.1.101)想要上網時,封包首先從 container 發出 → 透過 docker0 傳到 Ubuntu VM 的 eth1(圖中 LAN → eth1),接著 iptables 對這個封包執行 MASQUERADE,把 來源 IP 改為 Ubuntu VM 的 eth0(即 172.19.19.130),改寫後的封包送出 Internet,外部網站將回應送回 172.19.19.130 → VM 收到後,由 conntrack 追蹤反向轉換 → 傳回 container
MASQUERADE vs SNAT, DNAT
MASQUERADE 和 SNAT 的功能是一樣的,他們都會改寫封包的來源 IP 位址,讓內部主機的封包在送出到外網時,看起來是由 NAT 主機送出的。不過不一樣的是 MASQUERADE 不需要手動指定來源 IP,它會自動使用封包要送出的介面(如 eth0)的 IP。在這樣的情況,SNAT 適合用於固定 public IP 的伺服器,在 Docker 等動態網路場景,MASQUERADE 是預設且常用的選擇。
iptables 實作 - 用 Docker 建立小型 LAN 環境
- 建立 Docker 自訂網路,子網
10.10.0.0/24
、Gateway(主機對內橋接 IP):10.10.0.1
1
2
3
4sudo docker network create \
--subnet=10.10.0.0/24 \
--gateway=10.10.0.1 \
mynet - 建立兩個 Container 當作內部主機
1
2
3
4
5
6
7
8
9# 建立 container c1,IP: 10.10.0.2
sudo docker run -dit --rm \
--name c1 --net mynet --ip 10.10.0.2 \
busybox sh
# 建立 container c2,IP: 10.10.0.3
sudo docker run -dit --rm \
--name c2 --net mynet --ip 10.10.0.3 \
busybox sh - 測試內部連線,讓 c1 ping 到 c2
1
sudo docker exec -it c1 ping -c 3 10.10.0.3
1
2
3
4
5
6
7
8PING 10.10.0.3 (10.10.0.3): 56 data bytes
64 bytes from 10.10.0.3: seq=0 ttl=64 time=0.318 ms
64 bytes from 10.10.0.3: seq=1 ttl=64 time=0.119 ms
64 bytes from 10.10.0.3: seq=2 ttl=64 time=0.138 ms
--- 10.10.0.3 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.119/0.191/0.318 ms - 應用 iptables 規則測試,首先要在主機 (這邊是在 VM) 新增防火牆規則。這邊會把 DROP 規則插入在規則第一條,避免導向 DOCKER chain,裡面預設就是放行 container 內部互通。
1
sudo iptables -I FORWARD 1 -s 10.10.0.2 -d 10.10.0.3 -j DROP
- 再次測試,結果如下
1
2
3
4PING 10.10.0.3 (10.10.0.3): 56 data bytes
--- 10.10.0.3 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss - 觀察封包與連線追蹤,查看連線追蹤紀錄(conntrack)
1
2unknown 2 6 src=10.10.0.1 dst=224.0.0.22 [UNREPLIED] src=224.0.0.22 dst=10.10.0.1 mark=0 use=1
conntrack v1.4.5 (conntrack-tools): 1 flow entries have been shown. - 刪除規則
1
sudo iptables -D FORWARD -s 10.10.0.2 -d 10.10.0.3 -j DROP
- 再次實驗
1
2
3
4
5
6
7tcp 6 33 TIME_WAIT src=10.0.2.15 dst=91.189.91.98 sport=58758 dport=80 src=91.189.91.98 dst=10.0.2.15 sport=80 dport=58758 [ASSURED] mark=0 use=1
udp 17 21 src=10.10.0.1 dst=224.0.0.251 sport=5353 dport=5353 [UNREPLIED] src=224.0.0.251 dst=10.10.0.1 sport=5353 dport=5353 mark=0 use=1
udp 17 20 src=127.0.0.1 dst=127.0.0.53 sport=54935 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=54935 mark=0 use=1
udp 17 28 src=10.0.2.15 dst=185.125.190.57 sport=40618 dport=123 src=185.125.190.57 dst=10.0.2.15 sport=123 dport=40618 mark=0 use=1
unknown 2 80 src=10.10.0.1 dst=224.0.0.22 [UNREPLIED] src=224.0.0.22 dst=10.10.0.1 mark=0 use=1
udp 17 20 src=10.0.2.15 dst=10.0.2.3 sport=56977 dport=53 src=10.0.2.3 dst=10.0.2.15 sport=53 dport=56977 mark=0 use=1
conntrack v1.4.5 (conntrack-tools): 6 flow entries have been shown.
iptables 實作 - 查看封包流向與 iptables 關係
在鳥哥那張圖裡面可以看到封包流向,這邊做個簡單的實驗驗證封包經過 PREROUTING → 路由判斷 → INPUT
。
- 加入規則:
sudo iptables -I FORWARD 1 -s 10.10.0.2 -d 10.10.0.3 -j DROP
docker exec c1 ping -c 3 10.10.0.3
sudo iptables -L FORWARD -v -n --line-numbers
可以看到結果如下,pkts
表示封包數量,他從 0 變成 3,表示剛剛那三個封包都有成功進入到 FORWARD chain,而這是因為封包經過 PREROUTING 並被判斷「不是送給本機」,就會經過 FORWARD1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21Chain FORWARD (policy DROP 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 3 252 DROP all -- * * 10.10.0.2 10.10.0.3
2 12 1008 DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
3 12 1008 DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
4 10 840 ACCEPT all -- * br-13eaf78191b4 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
5 2 168 DOCKER all -- * br-13eaf78191b4 0.0.0.0/0 0.0.0.0/0
6 0 0 ACCEPT all -- br-13eaf78191b4 !br-13eaf78191b4 0.0.0.0/0 0.0.0.0/0
7 2 168 ACCEPT all -- br-13eaf78191b4 br-13eaf78191b4 0.0.0.0/0 0.0.0.0/0
8 0 0 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
9 0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
10 0 0 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
11 0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
12 0 0 ACCEPT all -- * br-ed05190a252d 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
13 0 0 DOCKER all -- * br-ed05190a252d 0.0.0.0/0 0.0.0.0/0
14 0 0 ACCEPT all -- br-ed05190a252d !br-ed05190a252d 0.0.0.0/0 0.0.0.0/0
15 0 0 ACCEPT all -- br-ed05190a252d br-ed05190a252d 0.0.0.0/0 0.0.0.0/0
16 0 0 ACCEPT all -- * br-e4d2a678019e 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
17 0 0 DOCKER all -- * br-e4d2a678019e 0.0.0.0/0 0.0.0.0/0
18 0 0 ACCEPT all -- br-e4d2a678019e !br-e4d2a678019e 0.0.0.0/0 0.0.0.0/0
19 0 0 ACCEPT all -- br-e4d2a678019e br-e4d2a678019e 0.0.0.0/0 0.0.0.0/0
References
- 25+ Most Common iptables Commands with Examples
- How does the default NAT network work exactly with Virtual box?
- https://forums.virtualbox.org/viewtopic.php?t=112051
- Using Masquerading with Iptables for Network Address Translation (NAT)
- VirtualBox connection from host to VM through Nat and NatNetwork
- Difference between SNAT and Masquerade
- Connect to the internet via Virtualbox Guest VM