# 个人服务器组网与防护实践(二):理解 Docker、iptables 与 UFW 的关系
上一篇打通了私有网络,随后遇到一个现象:Docker 发布的 Redis 端口,即使没有在 UFW 中放行,也可以访问。
原因在于数据包经过的路径:宿主机服务通常走 INPUT,Docker bridge 的发布端口经 DNAT 后走 FORWARD。只配置 UFW 入站规则,并不足以限制后一类流量。
本篇梳理判断规则所需的原理,下一篇给出实际防护脚本。
1. 几个组件各自做什么
- Netfilter:Linux 内核中的报文处理框架,在收包、路由、转发等位置提供处理入口。
- iptables:配置相关规则的命令行工具,负责匹配、过滤和地址转换等。
- UFW:简化防火墙规则管理的工具,入站、出站、路由规则分别作用于不同路径。
- Docker:为 bridge 网络自动配置网桥、虚拟网卡、路由和防火墙规则。
本文限定 Linux 上使用 iptables 后端的 Docker bridge 网络:
iptables --version中出现nf_tables,可能只是 iptables 使用 nft 兼容层,不等于 Docker 已切换原生 nftables 后端。- Docker 原生 nftables 后端不沿用本文的
DOCKER-USER方案。 - host、macvlan、rootless 等网络模式不能直接套用本文的数据路径。
2. 表、链与规则顺序
2.1 表与链
本场景主要查看两张表:
filter:过滤报文,内置链包括INPUT、FORWARD、OUTPUT。nat:地址转换,内置链包括PREROUTING、INPUT、OUTPUT、POSTROUTING。
其他常见表:mangle 用于修改报文属性,raw 可用于连接跟踪豁免等处理;本方案不需要修改它们。
常见路径位置:
PREROUTING:入站路由判断之前。INPUT:交给宿主机本地进程。FORWARD:由宿主机转发到其他目标。OUTPUT:宿主机本地产生的报文。POSTROUTING:完成路由判断、离开主机之前。
同名自定义链可出现在不同表中。例如 nat/DOCKER 处理端口转换,filter/DOCKER 参与访问放行;查看时要指定表。
2.2 规则顺序和动作
规则在链内按顺序检查,匹配后的行为取决于目标动作:
ACCEPT:在当前过滤处理处接受报文;后续其他处理环节仍可能影响它。DROP:丢弃,不返回明确拒绝响应。REJECT:拒绝并返回相应错误。RETURN:退出当前自定义链,回到调用链继续处理。- 跳转到另一条链:进入该链,返回后再继续。
因此,DOCKER-USER 中的 RETURN 只是结束本链检查,之后仍要通过 Docker 的其他规则。
常用命令参数:
bash
# -S 输出规则语法,便于确认条件和跳转顺序。
sudo iptables -S FORWARD
sudo iptables -S DOCKER-USER
# -n 不解析地址,-v 显示计数,--line-numbers 显示规则序号。
sudo iptables -L DOCKER-USER -n -v --line-numbers
-A:追加到链尾。-I 链名 1:插入链首。-C:检查相同规则是否存在。-D:删除匹配规则或指定序号的规则。-w:等待 xtables 锁释放,不是等待某条链创建。
3. 四类常见数据路径
以下是理解本场景的简化路径,实际还可能涉及其他表、连接跟踪和 Docker 版本差异。
3.1 外部访问宿主机服务
text
网卡 → PREROUTING → 路由判断 → INPUT → 宿主机进程
- 例如 apt 安装的 Nginx、宿主机 Redis、SSH。
- UFW 的
allow in规则用于此类本机入站访问。 - 容器访问宿主机 Redis,同样需要考虑服务监听地址及宿主机入站规则;来源可能是自定义 Docker 网段。(即需要宿主机6379/tcp ALLOW IN 172.17.0.0/16)
3.2 外部访问 Docker 发布端口
以 40002:80 为例:
text
网卡 → nat/PREROUTING → nat/DOCKER
→ DNAT:宿主机:40002 改为 容器IP:80
→ 路由判断 → filter/FORWARD → DOCKER-USER
→ Docker 后续转发规则 → 容器
- DNAT 后目标变成容器,报文走
FORWARD,不会按宿主机服务的路径进入INPUT。 - Docker 的转发放行可能先于 UFW 的相关转发限制生效,单独添加
ufw deny 40002不能可靠封住发布端口。 - 这仍是内核按规则处理报文,并非 Docker 跳过了整个防火墙。
DOCKER-USER是 Docker 为用户预留的转发过滤入口,位于其常规容器放行规则之前;它不处理全部宿主机流量。
3.3 容器访问外部服务
text
容器 → 网桥 → FORWARD → POSTROUTING/MASQUERADE → 外网
回包 → 连接跟踪还原地址 → FORWARD → 容器
MASQUERADE是一种源地址转换,将出站源地址改为出口地址。- conntrack 记录连接及 NAT 映射,后续报文据此处理;NAT 表通常在连接的首包建立转换关系。
ESTABLISHED,RELATED用于匹配已有连接和相关流量。若将物理网卡入站转发一律丢弃而不保留回包,可能导致容器无法正常访问外部服务。
3.4 容器之间通信
- 同一用户自定义 bridge 网络内,可使用服务名或网络别名访问容器端口,无需发布到宿主机。
- 不同 bridge 网络默认有隔离;可让需要通信的容器加入共同网络,或明确设计其他访问路径。
- 网桥、路由、Docker 内置 DNS 和过滤规则共同参与通信,不能将 Docker 所有网络能力都归结为 iptables。
- 隔离链的名称和组织随版本变化,不应固定认定只有四条 Docker 链。
4. DNAT 后如何匹配宿主机端口
映射 40002:80 后,进入 DOCKER-USER 的报文目标端口已经是 80。此时直接匹配 --dport 40002 通常不会命中。
使用 conntrack 查询该连接原始方向的目标端口:
bash
# 只查看具有该条件的规则是否存在,不添加规则。
sudo iptables -C DOCKER-USER -i enp1s0 -p tcp \
-m conntrack --ctdir ORIGINAL --ctorigdstport 40002 -j RETURN
--ctdir ORIGINAL:匹配连接的原始请求方向。--ctorigdstport 40002:匹配本机连接跟踪记录中的原始目标端口。- 如果前面还有家庭路由器做 NAT,这里看到的是路由器转发到小主机的端口,不一定是路由器公网端口。
- conntrack 扩展匹配有额外开销,本方案用于个人小主机的少量端口规则。
5. WireGuard 为什么仍需要防火墙规则
WireGuard 流量分为两层:
- 外层加密报文访问云主机
51820/UDP。 - 解密后的内层报文,从
wg0进入,源地址例如10.10.0.3。
内层报文按目标继续分流:
- 访问宿主机自身服务:走
INPUT,由本机入站规则控制。 - 经云主机访问另一 Peer:走
FORWARD,需要 IP 转发和转发规则。 - 访问 Docker 发布端口:经 DNAT 后走 Docker 转发路径。
因此,开放 UDP 51820 并不等于开放隧道内全部服务;而某个 Docker 端口无需 UFW 入站放行就能访问,也不能证明其他宿主机服务具有相同行为。
6. 排查与配置原则
bash
# 发布地址和端口映射。
docker ps --format 'table {{.Names}}\t{{.Ports}}'
# 发布端口的 DNAT 规则。
sudo iptables -t nat -S DOCKER
# 转发链入口、自定义规则和命中计数。
sudo iptables -S FORWARD
sudo iptables -L DOCKER-USER -n -v --line-numbers
# 转发开关与 UFW 当前配置。
sysctl net.ipv4.ip_forward
sudo ufw status verbose
- 先区分宿主机服务、bridge 发布端口和 host 网络,再判断报文路径。
- 保留 Docker 自动管理 iptables,在
DOCKER-USER添加本场景所需限制。 - 不清空 Docker 链,也不为解决 UFW 问题直接设置
"iptables": false;这会破坏 Docker 自动维护的 NAT 和过滤机制。 - 不需要跨主机访问的端口,可只发布到
127.0.0.1;需要访问的端口,再结合绑定地址、防火墙和认证配置。 127.0.0.1属于当前网络命名空间,容器里的回环地址指向容器自己;不能简单归因于某条“禁止容器访问宿主机回环”的规则。- IPv4 与 IPv6 分别检查,IPv4 规则不能保护
:::发布的 IPv6 入口。
理解这些路径后,防护分工就明确了:宿主机服务检查 UFW 入站规则,Docker 发布端口检查转发规则,公网入口再结合云安全组或家庭路由器控制。
GitHub 登录
Gitee 登录
QQ 登录
请围绕文章内容交流,勿发布广告、违法信息或他人隐私。