部落格好读版
前言
在上一章提到,安装 Docker 后,Docker 会自动修改 iptables 和 NAT 规则,这可能导致我们手动配置的网路连线出现冲突或无法正常运作的情况。接下来我们来验证这一点。
安装 Docker 后再验证
现在安装完 Docker 后,我们进行 Ping 测试,检查网络连线是否正常:
# 从 ns1 ping 位于 ns0 的 veth0 IP
$ sudo ip netns exec ns1 ping -c 2 172.18.0.2
###
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
^C
--- 172.18.0.2 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1010ms
# 从 Host ping 位于 ns0 的 veth0 IP
$ ping -c 2 172.18.0.2
###
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
^C
--- 172.18.0.2 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1012ms
从结果来看,我们被拒于门外,验证了 Docker 的安装修改了 iptables。
iptables 是什么
iptables 是一个用来管理网路封包过滤和 NAT 的工具,基于 Linux 内核的 netfilter 模组运作。简单来说,它可以控制哪些网路流量被允许或阻挡,并处理地址转换 (NAT)。其主要概念包括以下几个部分:
表(Tables)
- filter:管理封包的允许或阻挡(预设的处理)。
- nat:进行地址转换,适用于封包的来源和目的地址变更(如 SNAT、DNAT)。
- mangle:对封包内容进行修改,通常用于特殊处理(如变更 TOS)。
- raw:设定跳过连线追踪,用于特定封包不进行追踪。
链(Chains)
- INPUT:进入本机的封包。
- OUTPUT:从本机发出的封包。
- FORWARD:路由转发的封包。
- PREROUTING:处理到达网卡前的封包。
- POSTROUTING:处理即将发出的封包。
规则(Rules)每条规则定义匹配条件(如来源 IP、目的端口)及动作(如 ACCEPT、DROP、LOG)。
匹配(Match)与目标(Target)
- 匹配条件:依据协议、IP、端口等过滤封包。
- 目标动作:指定如何处理匹配的封包。
它运作的流程如下:
iptables 内建各表格与链的相关性 - 鸟哥
mangle 这个表很少用,省略它进一步精简如下:
iptables 内建各表格与链的相关性(简图) - 鸟哥
filter table 的变化
还记得上一章中,我们準备了 network-test-1, network-test-2 两台机器,我们只在前者安装 Docker。下面我们将用这两台机器查询 iptable 资料,作为对照组,这样能更清楚地了解 Docker 对网络环境的影响。
使用以下指令查询 iptable:
# 使用 -t flag 可以指定 table, 预设是 filter
sudo iptables -L --line-number
安装 Docker 前,执行 iptables 结果如下:
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 后,执行 iptables 结果如下:
Chain INPUT (policy ACCEPT)
num target prot opt source destination
Chain FORWARD (policy DROP)
num target prot opt source destination
1 DOCKER-USER all -- anywhere anywhere
2 DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere
3 ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
4 DOCKER all -- anywhere anywhere
5 ACCEPT all -- anywhere anywhere
6 ACCEPT all -- anywhere anywhere
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
Chain DOCKER (1 references)
num target prot opt source destination
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
num target prot opt source destination
1 DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere
2 RETURN all -- anywhere anywhere
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
num target prot opt source destination
1 DROP all -- anywhere anywhere
2 RETURN all -- anywhere anywhere
Chain DOCKER-USER (1 references)
num target prot opt source destination
1 RETURN all -- anywhere anywhere
iptables -L 指令省略了不少细节,让我们改用 iptables-save 指令,可以呈现较完整的资讯:
# iptables-save 预设会将所有 table 汇出,所以要使用 -t flag 过滤 filter table
sudo iptables-save -t filter
安装 Docker 前,执行 iptables-save 结果如下:
# Generated by iptables-save v1.8.8 (nf_tables) on Tue Nov 26 11:16:39 2024
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
# Completed on Tue Nov 26 11:16:39 2024
安装 Docker 后,执行 iptables-save 结果如下:
# Generated by iptables-save v1.8.8 (nf_tables) on Tue Nov 26 11:13:50 2024
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
: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
# Completed on Tue Nov 26 11:13:50 2024
分析
在安装 Docker 之前和之后,iptables 的主要变化如下:
接下来我们详细分析这些变化:
安装 Docker 前:
- 三个预设链(INPUT、FORWARD、OUTPUT)均设为 ACCEPT,表示所有封包均允许通过。
- 无其他自定义规则或链,属于「默认开放」状态。
- 缺乏细粒度控制,没有针对性过滤或封包管理。
安装 Docker 后:
FORWARD 链的策略从 ACCEPT 改为 DROP
- 改变:强制封包必须匹配 Docker 规则才能被转发。
-
原因:
- 加强网路隔离,避免无限制的封包转发。
- 仅允许来自容器的相关流量进行通信。
新增 DOCKER 链
- 改变:容器的网路规则集中在 DOCKER 链管理。
-
原因:
- 简化管理,动态添加和删除容器相关规则。
- 确保容器与宿主机或外部网路的正常通信。
新增 DOCKER-ISOLATION-STAGE-1 和 STAGE-2
- 改变:为不同的 bridge 网络隔离流量。
-
原因:
- 保证来自不同 Docker 网桥(如 docker0)的容器之间默认不互相通信。
- 用于安全隔离,避免未经授权的内部通信。
新增 DOCKER-USER 链
- 改变:在 FORWARD 前引入一层用户自定义规则。
-
原因:
- 提供灵活性,允许用户配置自定义防火墙规则,且不影响 Docker 自身规则。
新增 Rule
- FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT:确保容器发起的连线能接收外部回应流量。
- FORWARD -o docker0 -j DOCKER:将容器相关的流量传递到 DOCKER 链。
- FORWARD -i docker0 ! -o docker0 -j ACCEPT:允许容器发往外部的流量。
- FORWARD -i docker0 -o docker0 -j ACCEPT:允许同一网桥内部的容器通信。
FORWARD 链在修改后,已经变为预设 DROP,然而我们 ping 来源和目的介面是自建的 docker1,而不是安装 Docker 时预设的 docker0,所以封包在这个阶段被丢掉了。
修改 FORWARD 让封包可以过桥
需要注意的是,将策略修改为 ACCEPT 可能会使所有的转发流量自动被允许,这在安全性方面可能会带来风险,特别是在公开网络中使用时。建议在更改策略后,根据实际需求进一步增加更细緻的防火墙规则来保护网络安全。
使用以下指令修改预设策略:
sudo iptables --policy FORWARD ACCEPT
查询 iptables FORWARD 链的策略:
$ sudo iptables -L
###
[...]
Chain FORWARD (policy ACCEPT)
[...]
再次测试 bridge 是否畅通:
# 从 ns1 ping 位于 ns0 的 veth0 IP
$ sudo ip netns exec ns1 ping -c 2 172.18.0.2
###
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.061 ms
64 bytes from 172.18.0.2: icmp_seq=2 ttl=127 time=0.050 ms
--- 172.18.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1065ms
rtt min/avg/max/mdev = 0.050/0.055/0.061/0.005 ms
容器之间的连线已经畅通。
不过容器与 Host 的问题依旧:
# 从 Host ping 位于 ns0 的 veth0 IP
$ ping -c 2 172.18.0.2
###
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
^C
--- 172.18.0.2 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1004ms
如果是对外网的连线呢?
# 找到 Host 的 enX0 ip
$ ip -br addr
###
[...]
enX0 UP 172.31.39.53/20 metric 512 fe80::49d:2ff:fe88:529d/64
[...]
######
# 从 ns1 ping 位于 Host 的 enX0 ip
$ sudo ip netns exec ns1 ping -c 2 172.31.39.53
###
ping: connect: Network is unreachable
######
# 从 ns1 ping 位于外部的 www.google.com
$ sudo ip netns exec ns1 ping -c 2 www.google.com
###
ping: www.google.com: Name or service not known
看起来,容器内对容器外的连线还没有导通。
观察封包流向
在 Linux 中,我们可以使用 tcpdump 网路封包分析工具,用来捕捉网路介面的封包,进行观察。这次我们先用它,观察容器间的封包流向。
首先,分别準备好四组指令和终端,分别监控 veth0, veth0-br, veth1, veth1-br 四个网路介面:
# 在 ns0 监控 veth0 的 ICMP 封包
sudo ip netns exec ns0 tcpdump -i veth0 -nn icmp
# 在 host 监控 veth0-br 的 ICMP 封包
sudo tcpdump -i veth0-br -nn icmp
# 在 host 监控 veth1-br 的 ICMP 封包
sudo tcpdump -i veth1-br -nn icmp
# 在 ns1 监控 veth1 的 ICMP 封包
sudo ip netns exec ns1 tcpdump -i veth1 -nn icmp
我们开启第五个终端,执行以下指令测试:
# 从 ns1 ping 位于 ns0 的 veth0 IP
sudo ip netns exec ns1 ping -c 1 172.18.0.2
结果如下:
我们透过录下来的资讯和时间戳记,排序后可以归纳出,封包的走向是这样的:
小结
本章我们验证了 Docker 对于 iptables 的影响,并用偷吃步的方式让容器间的封包可以正常传递,但依然没有解决容器对外的封包传递问题。这部分的内容就留到下一章。我们下次见。
参考
- https://linux.vbird.org/linux_server/centos6/0250simple_firewall.php
- https://linux.vbird.org/linux_server/rocky9/0180firewall.php