Moby 桥接网络实战:ICC(容器间通信)禁用时 iptables 规则的完整解析
本文基于 Moby 仓库中 TestBridgeIptablesDoc 集成测试生成的文档,完整讲解"用户自定义桥接网络 + 禁用 ICC + 发布端口"这一场景下 Docker Engine 在 iptables 中写入的全部 filter 与 nat 规则。读完后,你将能够逐条读懂 iptables -L 输出中 DOCKER 系列链的每一条规则,理解 com.docker.network.bridge.enable_icc=false 在底层如何把"同桥容器互访"与"对外发布端口"分开处理,并知道如何用仓库自带的测试复现、核对这些规则。
场景与等价命令
该文档对应的测试场景定义在 index 清单 中:创建一个名为 bridge1 的桥接网络(网段 192.0.2.0/24、网关 192.0.2.1),设置 enable_icc=false,并在其中运行一个发布 8080:80 端口的容器。等价的 CLI 操作为:
docker network create \
-o com.docker.network.bridge.name=bridge1 \
-o com.docker.network.bridge.enable_icc=false \
--subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1
docker run --network bridge1 -p 8080:80 --name c1 busybox
其中 com.docker.network.bridge.enable_icc 是 bridge 驱动的网络选项标签,常量定义见 labels.go,驱动在 bridge_linux.go 中用 strconv.ParseBool 解析该值,写入网络配置 ncfg.EnableICC。
注意:仓库文档 index.md 明确提示,Docker 的 iptables 规则结构不属于稳定接口,会跨版本变化,本文内容仅用于理解当前源码行为的开发参考。
filter 表:DOCKER-FORWARD 链中的两条关键规则
测试捕获的完整 filter 表如下(摘自 生成的文档,该文件由测试自动比对生成):
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER-USER all -- any any anywhere anywhere
2 0 0 DOCKER-FORWARD all -- any any anywhere anywhere
Chain DOCKER (2 references)
1 0 0 ACCEPT tcp -- !bridge1 bridge1 anywhere 192.0.2.2 tcp dpt:http
2 0 0 DROP all -- !docker0 docker0 anywhere anywhere
3 0 0 DROP all -- !bridge1 bridge1 anywhere anywhere
Chain DOCKER-BRIDGE (1 references)
1 0 0 DOCKER all -- any docker0 anywhere anywhere
2 0 0 DOCKER all -- any bridge1 anywhere anywhere
Chain DOCKER-CT (1 references)
1 0 0 ACCEPT all -- any docker0 anywhere anywhere ctstate RELATED,ESTABLISHED
2 0 0 ACCEPT all -- any bridge1 anywhere anywhere ctstate RELATED,ESTABLISHED
Chain DOCKER-FORWARD (1 references)
1 0 0 DOCKER-CT all -- any any anywhere anywhere
2 0 0 DOCKER-INTERNAL all -- any any anywhere anywhere
3 0 0 DOCKER-BRIDGE all -- any any anywhere anywhere
4 0 0 ACCEPT all -- docker0 any anywhere anywhere
5 0 0 DROP all -- bridge1 bridge1 anywhere anywhere
6 0 0 ACCEPT all -- bridge1 !bridge1 anywhere anywhere
对应的等价 iptables 命令(原文档以 <details> 折叠展示):
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-BRIDGE
-N DOCKER-CT
-N DOCKER-FORWARD
-N DOCKER-INTERNAL
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-FORWARD
-A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER ! -i docker0 -o docker0 -j DROP
-A DOCKER ! -i bridge1 -o bridge1 -j DROP
-A DOCKER-BRIDGE -o docker0 -j DOCKER
-A DOCKER-BRIDGE -o bridge1 -j DOCKER
-A DOCKER-CT -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-CT -o bridge1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-FORWARD -j DOCKER-CT
-A DOCKER-FORWARD -j DOCKER-INTERNAL
-A DOCKER-FORWARD -j DOCKER-BRIDGE
-A DOCKER-FORWARD -i docker0 -j ACCEPT
-A DOCKER-FORWARD -i bridge1 -o bridge1 -j DROP
-A DOCKER-FORWARD -i bridge1 ! -o bridge1 -j ACCEPT
逐段解读:
- FORWARD 入口:内核转发路径上只有两条跳转规则,先入
DOCKER-USER(留给用户自定义),再入DOCKER-FORWARD。所有 Docker 相关的转发裁决都在后者的子链中完成。 - DOCKER-CT:对发往
docker0/bridge1的RELATED,ESTABLISHED连接放行,保证容器发起的外联流量(如curl出公网)的回包能回来。该规则由 network.go 中的ctRule编程写入。 - DOCKER-BRIDGE → DOCKER:只有目的地指向某座桥的入站流量才进入对应桥的
DOCKER链做端口映射/默认丢弃裁决。DOCKER链第 1 条规则正是-p 8080:80映射产生的 ACCEPT(目标192.0.2.2:80),第 3 条! -i bridge1 -o bridge1 -j DROP则是该网络的默认丢弃规则,实现于 setDefaultForwardRule。 - DOCKER-FORWARD 第 5、6 条(本场景的核心差异):
- 规则 5:
-i bridge1 -o bridge1 -j DROP—— 把从bridge1进入又回到bridge1的包(即同桥容器互访)丢弃; - 规则 6:
-i bridge1 ! -o bridge1 -j ACCEPT—— 放行bridge1上容器发往其他网络/宿主的所有出站流量。
- 规则 5:
与 ICC=true 场景的对比
启用 ICC 的同一场景(同样发布 8080:80)见 usernet-portmap.md。两者 filter 表几乎完全相同,唯一区别在 DOCKER-FORWARD 链:ICC=true 时只有一条规则 5 -A DOCKER-FORWARD -i bridge1 -j ACCEPT(出站全放行);而 ICC=false 时,这条单一放行规则被**规则 5(DROP 同桥流量)+ 规则 6(ACCEPT 其余出站)**两条规则取代。原文档对此的表述是:
DOCKER-FORWARD rules 6 and 7 replace the accept rule for outgoing packets.
- Rule 6, added by
setIcc, drops any packet sent from the internal network to itself.- Rule 7, added by
setupIPTablesInternalaccepts any other outgoing packet.
(原文档中的规则编号按当时链内顺序编号;在本文展示的当前生成结果中对应 DOCKER-FORWARD 的第 5、6 条。)
源码级实现:setIcc 与出站放行规则
在 iptabler/network.go 中,非 internal 网络的规则编排入口是 setupNonInternalNetworkRules:
setIcc写入 DROP(L311 调用,函数体 L363-L403)。它构造-i bridgeIface -o bridgeIface -j的规则:ICC=false且为"插入"模式时追加 DROP 规则(对应 DOCKER-FORWARD 规则 5);ICC=true时则不写任何同桥规则(对外网络场景下同桥流量由出站 ACCEPT 覆盖),并顺带清理旧版本遗留的规则。注意 DROP 规则是无条件写在 DOCKER-FORWARD 链上的,位置在DOCKER-BRIDGE跳转之后,因此不会误伤已裁决的入站 DNAT 流量。outRuleNoICC写入 ACCEPT(L322-L358):ICC=false时追加-i bridge1 ! -o bridge1 -j ACCEPT(对应规则 6);ICC=true时追加的是不带! -o判断的-i bridge1 -j ACCEPT。源码注释特别说明:这两条规则是"合并后的新写法",针对 ICC 的旧式独立规则已不再需要,且旧版本(moby 28.0.0 及更早)直接挂在 filter-FORWARD 链上的同名规则会在升级时被动删除(见 L331-L336、L346-L351)。
对于 --internal 网络,路径不同:setupInternalNetworkRules(L437-L480)会先写 DOCKER-INTERNAL 链的入站/出站 DROP,再调用 setIcc(icc, internal=true, ...)——internal 网络因为没有任何出站 ACCEPT 规则,ICC=true 时必须由 setIcc 显式写入同桥 ACCEPT,否则容器之间也无法通信。
nat 表:发布端口不受 ICC 禁用影响
原文档同时给出了该场景的 nat 表(与 ICC=true 时完全一致,可见 ICC 只作用于 filter 层的转发裁决):
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
1 0 0 DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
1 0 0 DOCKER all -- any any anywhere !loopback/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
1 0 0 MASQUERADE all -- any !bridge1 192.0.2.0/24 anywhere
2 0 0 MASQUERADE all -- any !docker0 172.17.0.0/16 anywhere
Chain DOCKER (2 references)
1 0 0 DNAT tcp -- !bridge1 any anywhere anywhere tcp dpt:http-alt to:192.0.2.2:80
等价命令:
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 192.0.2.0/24 ! -o bridge1 -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER ! -i bridge1 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80
各规则的作用:
- PREROUTING/OUTPUT → DOCKER:只有目的地是本机的流量才进入
DOCKER链做 DNAT;OUTPUT侧排除 loopback(避免宿主自身流量被劫持,loopback 绑定端口的场景见 usernet-portmap-lo.md)。 - nat-Docker DNAT 规则:
! -i bridge1保证来自桥本身的包不再次 DNAT;-p 8080 → 192.0.2.2:80即端口发布映射,由用户态代理(userland proxy)与 iptables 双通道配合工作。 - POSTROUTING MASQUERADE:
192.0.2.0/24的容器流量出桥时做源地址伪装(MASQUERADE 从路由表选源地址,实现于 L286-L290),与默认桥docker0的172.17.0.0/16各自独立。
用仓库测试复现并校验规则
上述文档并非手写,而是由集成测试 TestBridgeIptablesDoc 自动生成并做 golden 比对:
- 测试为每个"章节"(本文对应的
gen_usernet-portmap-noicc.md)创建一个独立网络命名空间(networking.NewL3Segment+l3.AddHost),在命名空间内启动一个 daemon,用 client 创建enable_icc=false的网络并运行容器(createBridgeNetworks 中通过network.WithOption(bridge.EnableICC, "false")传入选项); - 随后
iptables -Z清零计数器,分别执行iptables -vL --line-numbers -t filter、iptables -S -t filter、nat/raw 表对应命令(命令表见 iptCmds),并用正则抹掉包/字节计数(CI 中 OUTPUT 链偶发计数波动); - 结果套用 templates/usernet-portmap-noicc.md 模板(即本文的源文档,
{{index . "LFilter4"}}等占位符被填充为真实 iptables 输出),若与 generated/ 下的黄金文件不一致则测试失败。规则变更时需修改对应模板描述,再用TESTFLAGS='-update'重新生成。
运行前提(与测试内 skip 条件一致):需要 iptables 后端(非 nftables 后端)、非 rootless、且 firewalld 未运行——因为 firewalld 重载会清空 iptables 规则,此时文档无法如实反映 daemon 行为。另外,按 index.md 的说明:daemon 重启时 bridge 驱动初始化阶段会删除其自定义链、再随网络恢复重建,重建顺序与原始创建顺序无关,因此重启后规则排列可能不同;firewalld 环境下 daemon 通过 dbus 监听其重载事件并重建规则。nftables 后端的同场景规则则记录在 nftablesdoc 生成文档 中,可作为对照。
小结与注意事项
enable_icc=false的隔离效果完全体现在 filter 表 DOCKER-FORWARD 链上:-i bridge -o bridge -j DROP(由setIcc写入)+-i bridge ! -o bridge -j ACCEPT(由setupNonInternalNetworkRules写入)这对规则,同桥互访被丢弃而外部出站与入站 DNAT 均不受影响;- nat 表与 ICC 设置无关,端口发布映射(DNAT)和出向 MASQUERADE 行为与 ICC=true 场景一致;
- 该规则结构是随源码演进的实现细节而非稳定接口,跨版本升级(尤其 28.0.0 前后)时 DOCKER-FORWARD 链内的规则位置与数量可能变化,daemon 内已内置对旧版本遗留规则的清理逻辑;
- 若需在自己的环境中核对规则,可直接执行文档中的
iptables -S命令对照,或运行TestBridgeIptablesDoc集成测试观察 diff。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00