前言
因为最近要把一些容器的端口屏蔽掉
但是发现如果单纯去屏蔽 filter 的 INPUT 链的话
并不起作用
因此对 Docker 的网络模式进行一番探索
bridge 模式
就是桥接模式
主要是介绍一下这个模式
因为这个网络模式是 docker 默认的
也就是如果我想启动一个容器,例如
docker run -itd -p 6379:6379 redis
上面命令默认就使用的是 bridge 的网络模式
而桥模式是如何将端口的流量映射出来的呢?
这时候就轮到 iptables 上场了
首先看一下 nat 表的 iptables
[root@vbox ~]# iptables -vnL -t nat
Chain PREROUTING (policy ACCEPT 55 packets, 3331 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 87 packets, 6603 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 87 packets, 6603 bytes)
pkts bytes target prot opt in out source destination
55 3331 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30010
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30009
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30008
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30007
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30006
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30005
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30004
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30003
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30002
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30001
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:30000
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:7070
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:6060
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:443
0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:80
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30010 to:172.17.0.2:30010
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30009 to:172.17.0.2:30009
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30008 to:172.17.0.2:30008
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30007 to:172.17.0.2:30007
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30006 to:172.17.0.2:30006
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30005 to:172.17.0.2:30005
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30004 to:172.17.0.2:30004
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30003 to:172.17.0.2:30003
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30002 to:172.17.0.2:30002
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30001 to:172.17.0.2:30001
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:30000 to:172.17.0.2:30000
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:7070 to:172.17.0.2:7070
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:6060 to:172.17.0.2:6060
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 to:172.17.0.2:443
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
首先看 PREROUTING 链,所有入口的流量都被指向了 DOCKER 的链
端口映射,比如我的端口有 80:80 的映射需求
它对应的就是 Docker 链中的 0 0 DNAT tcp — !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80 这一行
直接将 80 使用 DNAT 指向了网桥中的容器 ip
而这个 ip 的路由是
[root@vbox ~]# ip route
default via 10.0.2.2 dev enp0s3
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
也就是 172.17.0.0 的路由指向了 docker0 这个网桥
此时流量的目的 ip 已经不是主机本身的 ip
因此不会走 Filter 表中的 INPUT 和 OUTPUT 链
如上图,流量走的是最下面的一条路线
而不是从上面走过
而每创建一个 bridge 模式的容器
宿主机上都会出现一个名叫 veth 开头的网卡
如果他们之间想相互通信则通过链接 docker0 的网桥实现网络互通 t
不过即使 veth 网卡和容器内的 eth0 网卡是对应关系,但是他们的 MAC 并不相同(采用了 veth pair 技术,逻辑上相当于同一条线上的两个网卡)
原理如图
而根据 iptables 表
流量会被分发到 Filter 表的 forward 链中
让我们简单看一下 FORWARD 链里到底有什么
[root@vbox ~]# iptables -vn -L FORWARD
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
337 30773 DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
337 30773 DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
81 14642 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
256 16131 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
0 0 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
FORWARD 链的最上面会先将所有流量指向到 DOCKER-USER,再到 DOCKER-ISOLATION-STAGE-1
DOCKER-USER 这个链就是 docker 提供给用户,用来自定义操作的,这个链有特殊的配置,即使重启 docker 该链也不会被清理(放其他链里有被清理的风险)
DOCKER-ISOLATION-STAGE- 1 这个链则是负责去做容器之间的网络隔离
那么很明显,如我们想做端口屏蔽,在流量进来的这几个链中都可以做,比如 DOCKER、DOCKER-USER、DOCKER-ISOLATION-STAGE- 1 甚至 Filter 中的 FORWARD,但是 DOCKER 留给我们使用的是 DOCKER-USER
屏蔽端口
比如屏蔽 3306 端口,注意要放在 return 之前,要不然流量就返回了,无法达到效果
iptables -I DOCKER-USER -p tcp --dport 3306 -j DROP