Understanding Docker network through iptables

Docker 為主流的容器化技術之一,而 Docker 則是使用 iptables 做 provide network isolation。 這篇來了解 Docker 預設的 iptables 規則是什麼。

之前有寫過 iptables guide,知道 iptables 底層是使用 Netfilter 模組作封包的控制。

官方也有相關的資料可以參考 Docker and iptables

On Linux, Docker manipulates iptables rules to provide network isolation. While this is an implementation detail and you should not modify the rules Docker inserts into your iptables policies, it does have some implications on what you need to do if you want to have your own policies in addition to those managed by Docker.

思路

  1. 建立虛擬機,確認初始 network interface 與 iptables
  2. 安裝 Docker 並確認 network interface 與 iptables
  3. 建置 nginx 容器,測試封包走向

環境與版本

OS:

1root@ip-172-31-37-164:/home/ubuntu# cat /etc/os-release
2NAME="Ubuntu"
3VERSION="20.04.2 LTS (Focal Fossa)"
4・・・・・・

Docker:

 1root@ip-172-31-37-164:/home/ubuntu# docker version
 2Client: Docker Engine - Community
 3 Version:           20.10.7
 4 API version:       1.41
 5・・・・・・
 6
 7Server: Docker Engine - Community
 8 Engine:
 9  Version:          20.10.7
10  API version:      1.41 (minimum version 1.12)
11・・・・・・
12 containerd:
13  Version:          1.4.6
14  GitCommit:        d71fcd7d8303cbf684402823e425e9dd2e99285d
15 runc:
16  Version:          1.0.0-rc95
17  GitCommit:        b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
18 docker-init:
19  Version:          0.19.0
20  GitCommit:        de40ad0

確認初始的 iptables rules

在一個新的虛擬機中,一開始的網路介面有 lo 跟 eth0。 其中 lo 為這虛擬機的 LOOPBACK 使用; 而 eth0 則為可廣播的網路介面使用。

 1root@ip-172-31-37-164:/home/ubuntu# ip a
 21: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
 3    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 4    inet 127.0.0.1/8 scope host lo
 5       valid_lft forever preferred_lft forever
 6    inet6 ::1/128 scope host
 7       valid_lft forever preferred_lft forever
 82: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
 9    link/ether 06:3d:47:e2:09:ba brd ff:ff:ff:ff:ff:ff
10    inet 172.31.37.164/20 brd 172.31.47.255 scope global dynamic eth0
11       valid_lft 3514sec preferred_lft 3514sec
12    inet6 fe80::43d:47ff:fee2:9ba/64 scope link
13       valid_lft forever preferred_lft forever

新的虛擬機中,一開始是沒有配置 Chain、Table 的。

 1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t nat
 2Chain PREROUTING (policy ACCEPT 6 packets, 328 bytes)
 3num   pkts bytes target     prot opt in     out     source               destination
 4
 5Chain INPUT (policy ACCEPT 6 packets, 328 bytes)
 6num   pkts bytes target     prot opt in     out     source               destination
 7
 8Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 9num   pkts bytes target     prot opt in     out     source               destination
10
11Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
12num   pkts bytes target     prot opt in     out     source               destination
1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t filter
2Chain INPUT (policy ACCEPT 58 packets, 5352 bytes)
3num   pkts bytes target     prot opt in     out     source               destination
4
5Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
6num   pkts bytes target     prot opt in     out     source               destination
7
8Chain OUTPUT (policy ACCEPT 48 packets, 6066 bytes)
9num   pkts bytes target     prot opt in     out     source               destination

安裝 Docker 並且確認 iptables rules

確認網路介面

Docker 安裝完成後,看到新增了一個網路介面 docker0。 其 docker0 的網路介面 ip 位置可以看到被分配到 172.17.0.1/16 的 private ip。而 router 部分則看到 172.17.0.0/16 網段都 link src 172.17.0.1。

 1# ip address
 2root@ip-172-31-37-164:/home/ubuntu# ip a
 3・・・・・・
 43: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
 5    link/ether 02:42:d3:81:15:1c brd ff:ff:ff:ff:ff:ff
 6    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
 7       valid_lft forever preferred_lft forever
 8
 9# ip route
10root@ip-172-31-37-164:/home/ubuntu# ip r
11default via 172.31.32.1 dev eth0 proto dhcp src 172.31.37.164 metric 100
12172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
13172.31.32.0/20 dev eth0 proto kernel scope link src 172.31.37.164
14172.31.32.1 dev eth0 proto dhcp scope link src 172.31.37.164 metric 100
15
16# ip route show table local
17root@ip-172-31-37-164:/home/ubuntu# ip r show table local
18broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
19local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
20local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
21broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
22broadcast 172.17.0.0 dev docker0 proto kernel scope link src 172.17.0.1
23local 172.17.0.1 dev docker0 proto kernel scope host src 172.17.0.1
24broadcast 172.17.255.255 dev docker0 proto kernel scope link src 172.17.0.1
25broadcast 172.31.32.0 dev eth0 proto kernel scope link src 172.31.37.164
26local 172.31.37.164 dev eth0 proto kernel scope host src 172.31.37.164
27broadcast 172.31.47.255 dev eth0 proto kernel scope link src 172.31.37.164

確認 iptables 的 NAT Table

下面列出了 nat table 的 Chain。看到新增了多個 Chain DOCKER,而這 Chain 被 references 在 PREROUTING、OUTPUT 中。 而我們知道 iptables 的規則是從上至下順序執行,直至匹配的的規則為止,否則執行預設 policy。

下面說明 nat table 的 Chain:

  1. PREROUTING num 1 是指如果進來的封包目的地址 match LOCAL 都跳到 Chain DOCKER,這邊要注意 LOCAL 並不是指本地,可以使用 "ip route show table local" 命令來確認哪些 ip 為 LOCAL
    1. DOCKER num 1 指如果封包是從 docker0 網路介面進來的,則結束 Chain DOCKER 然後返回原來的 Chain 繼續跑規則
  2. OUTPUT num 1 是指如果出去封包目的地址 match LOCAL 都跳到 Chain DOCKER,除了 127.0.0.0/8 網段
    1. DOCKER num 1 指如果封包是從 docker0 網路介面進來的,則結束 Chain DOCKER 然後返回原來的 Chain 繼續跑規則
  3. POSTROUTING num 1 是指如果出去封包不是 docker0 網路介面和 ip 來源是 172.17.0.0/16 時,不做修改
 1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t nat
 2Chain PREROUTING (policy ACCEPT 6 packets, 328 bytes)
 3num   pkts bytes target     prot opt in     out     source               destination
 41    14138  743K DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL
 5
 6Chain INPUT (policy ACCEPT 6 packets, 328 bytes)
 7num   pkts bytes target     prot opt in     out     source               destination
 8
 9Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
10num   pkts bytes target     prot opt in     out     source               destination
111        4   240 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL
12
13Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
14num   pkts bytes target     prot opt in     out     source               destination
151       34  2073 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
16
17Chain DOCKER (2 references)
18num   pkts bytes target     prot opt in     out     source               destination
191       19  1140 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0

確認 iptables 的 Filter Table

可以看到新增了多個 Chain DOCKERDOCKER-ISOLATION-STAGE-1DOCKER-ISOLATION-STAGE-2DOCKER-USER,而這些 Chain 都被 references 在 FORWARD 中。 可以看到 Chain FORWARD 的預設 policy 已經被改為 DROP,而我們知道 iptables 的規則是從上至下順序執行,直至匹配的的規則為止,否則執行預設 policy。

看到 FORWARD 的規則:

  1. FORWARD num 1 規則是跳到 DOCKER-USER 鏈中
    1. DOCKER-USER num 1 規則是 RETURN 封包,回 FORWARD 中繼續直執行其他的規則
  2. FORWARD num 2 規則是跳到 DOCKER-ISOLATION-STAGE-1 鏈中
    1. DOCKER-ISOLATION-STAGE-1 num 1 封包又跳到 DOCKER-ISOLATION-STAGE-2 鏈中
      1. DOCKER-ISOLATION-STAGE-2 num 1 丟棄所有 docker0 網路介面出去的封包
      2. DOCKER-ISOLATION-STAGE-2 num 2 RETURN 所有封包繼續執封包繼續執行 DOCKER-ISOLATION-STAGE-1 的其他規則
    2. DOCKER-ISOLATION-STAGE-1 num 2 RETURN 所有封包繼續執封包繼續執行 FORWARD 的其他規則
  3. FORWARD num 3 規則為允許由 docker0 網路介面出去的封包,但封包狀態是 RELATED,ESTABLISHED
  4. FORWARD num 4 規則為當封包是 docker0 網路介面出去的,則跳到 DOCKER 鏈中
    1. DOCKER 暫無規則
  5. FORWARD num 5FORWARD num 6 為允許經由 docker0 網路介面近來的所有封包進出
  6. 不在上述規則中的封包全部都 drop
 1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t fillter
 2Chain INPUT (policy ACCEPT 385 packets, 28200 bytes)
 3num   pkts bytes target     prot opt in     out     source               destination
 4
 5Chain FORWARD (policy DROP 0 packets, 0 bytes)
 6num   pkts bytes target     prot opt in     out     source               destination
 71        0     0 DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
 82        0     0 DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
 93        0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
104        0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
115        0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
126        0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0
13
14Chain OUTPUT (policy ACCEPT 304 packets, 59638 bytes)
15num   pkts bytes target     prot opt in     out     source               destination
16
17Chain DOCKER (1 references)
18num   pkts bytes target     prot opt in     out     source               destination
19
20Chain DOCKER-ISOLATION-STAGE-1 (1 references)
21num   pkts bytes target     prot opt in     out     source               destination
221        0     0 DOCKER-ISOLATION-STAGE-2  all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
232        0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
24
25Chain DOCKER-ISOLATION-STAGE-2 (1 references)
26num   pkts bytes target     prot opt in     out     source               destination
271        0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
282        0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
29
30Chain DOCKER-USER (1 references)
31num   pkts bytes target     prot opt in     out     source               destination
321        0     0 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

建置 nginx 容器,測試封包走向

啟動 container nginx

1docker run --name nginx -p 80:80  -d nginx

查看本機 listen port

看到 port 80 被 docker-proxy 監聽著

1root@ip-172-31-37-164:/home/ubuntu# netstat -tlnp
2Active Internet connections (only servers)
3Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
4tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      253681/docker-proxy
5tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      370/systemd-resolve
6tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      576/sshd: /usr/sbin
7tcp6       0      0 :::80                   :::*                    LISTEN      253686/docker-proxy
8tcp6       0      0 :::22                   :::*                    LISTEN      576/sshd: /usr/sbin

確認網路變化

網路介面新增了 veth3ce1bed

1root@ip-172-31-37-164:~# ip link
21: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
3    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
42: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
5    link/ether 06:3d:47:e2:09:ba brd ff:ff:ff:ff:ff:ff
618: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
7    link/ether 02:42:55:24:1f:ed brd ff:ff:ff:ff:ff:ff
822: veth3ce1bed@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
9    link/ether ea:ba:b9:7b:48:ea brd ff:ff:ff:ff:ff:ff link-netnsid 1

還有將新增的網路介面 veth3ce1bed 橋接在 docker0 上。

1root@ip-172-31-37-164:~# brctl show
2bridge name     bridge id               STP enabled     interfaces
3docker0         8000.024255241fed       no              veth3ce1bed

iptables NAT table 新增了兩條規則。

  1. POSTROUTING 封包來源為封包目的為 172.17.0.2 地址為裝封包內容 tcp dpt:80
  2. DOCKER num 2 規則為所有非 docker0 網路介面近來且 tcp dpt:80 的封包,改到 172.17.0.2:80
1iptables -L -n -v --line-numbers -t nat
2
3Chain POSTROUTING (policy ACCEPT 266 packets, 17090 bytes)
4num   pkts bytes target     prot opt in     out     source               destination
52        0     0 MASQUERADE  tcp  --  *      *       172.17.0.2           172.17.0.2           tcp dpt:80
6
7Chain DOCKER (2 references)
8num   pkts bytes target     prot opt in     out     source               destination
92      136  7472 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.17.0.2:80
1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t filter
2
3Chain DOCKER (1 references)
4num   pkts bytes target     prot opt in     out     source               destination
51      123  6692 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:80

測試封包走向

在不同 client 端嘗試連上 nginx 服務,觀察封包所經過的網路規則。

監控 iptalbes 的 pkts 變化。

1root@ip-172-31-37-164:/home/ubuntu# watch iptables -L -n -v --line-numbers -t nat
2root@ip-172-31-37-164:/home/ubuntu# watch iptables -L -n -v --line-numbers -t filter

監控 docker0 網路介面的封包裝況。

1tcpdump -i docker0 -q -v tcp port 80
comments powered by Disqus