Moby 网桥网络 ICC 禁用场景:发布端口时 iptables 规则全景解析(usernet-portmap-noicc)
本篇基于 Moby(Docker Engine)仓库中自动生成的 iptables 参考文档 integration/network/bridge/iptablesdoc/generated/usernet-portmap-noicc.md,完整解读“禁用容器间通信(ICC)的用户自定义网桥网络 + 发布端口”这一场景下,daemon 在 filter 表与 nat 表中实际生成的每一条规则,并对照 daemon/libnetwork/drivers/bridge 下的源码,说明每条规则由哪个函数、在何时写入,帮助读者读懂 com.docker.network.bridge.enable_icc=false 选项背后的完整网络隔离机制。
一、场景复现:禁用 ICC 的网桥网络与端口发布
该文档描述的测试场景等价于以下两条命令:
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.name=bridge1:指定网桥接口名(常量定义见 bridge_linux.go 与 labels.go)。com.docker.network.bridge.enable_icc=false:关闭容器间通信。该选项标签在源码中定义为EnableICC = "com.docker.network.bridge.enable_icc"(labels.go),解析发生在驱动读取网络选项的分支case EnableICC(bridge_linux.go),最终存入NetworkConfig.EnableICC字段。--subnet 192.0.2.0/24 --gateway 192.0.2.1:文档场景固定使用 TEST-NET 网段 192.0.2.0/24,与集成测试中的docNetworks/docGateways一致(iptablesdoc_linux_test.go)。-p 8080:80:容器 c1(获得地址 192.0.2.2)的 80 端口发布到宿主机 8080 端口。
总索引文档 index.md 同时说明:ip6tables 规则与 iptables 规则遵循同样的模式,因此文档只展示 IPv4 规则。
二、文档的生成机制:真实 daemon + golden 测试
generated/usernet-portmap-noicc.md 是生成文件(文件头部标注 DO NOT EDIT)。按 index.md 与 iptablesdoc_linux_test.go 的说明,其生成流程为:
TestBridgeIptablesDoc为每个场景(section)创建独立网络命名空间,在其中启动一个真实 dockerd(--swarm-default-advertise-addr等参数按场景追加);- 本场景对应的 section 定义为
name: "usernet-portmap-noicc.md"、noICC: true,即通过bridge.EnableICC: "false"选项创建 bridge1 网络并运行发布 80/tcp→8080 的容器(iptablesdoc_linux_test.go,创建逻辑在 createBridgeNetworks); - 先用
iptables -Z清零计数,再执行固定命令集采集输出(iptCmds):
iptables -vL --line-numbers -t filter # LFilter4(带行号的 filter 列表)
iptables -S -t filter # SFilter4(filter 命令行形式)
iptables -vL --line-numbers -t nat # LNat4
iptables -S -t nat # SNat4
- 将输出代入 templates/usernet-portmap-noicc.md 中的
{{index . "LFilter4"}}等模板变量,与generated/目录下的 golden 文件做 diff,不一致则测试失败;规则变更后需检查 diff、更新模板描述,再用TESTFLAGS='-update'重新生成参考文档。
需要注意的两个前提(同样来自 index.md 与测试代码):
- 该文档仅供开发参考:Docker 的 iptables/ip6tables 规则结构在不同版本间会变化,不是稳定接口;
- 测试在 firewalld 运行、rootless 模式或 nftables 后端下跳过(TestBridgeIptablesDoc),因此文档中的规则对应 iptables 后端。另据 index.md,daemon 重启后网络重建顺序不确定,filter-FORWARD 链不会被清空,规则排列顺序可能与初次创建时不同。
三、filter 表:完整规则与链的职责
场景落地后,filter 表如下(原文档完整内容):
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
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 OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain DOCKER (2 references)
num pkts bytes target prot opt in out source destination
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)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER all -- any docker0 anywhere anywhere
2 0 0 DOCKER all -- any bridge1 anywhere anywhere
Chain DOCKER-CT (1 references)
num pkts bytes target prot opt in out source destination
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)
num pkts bytes target prot opt in out source destination
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
Chain DOCKER-INTERNAL (1 references)
num pkts bytes target prot opt in out source destination
Chain DOCKER-USER (1 references)
num pkts bytes target prot opt in out source destination
对应的命令行形式(iptables -S -t filter):
-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
各链职责(与 index.md 中的说明一致):
- filter-INPUT / filter-OUTPUT 不被 Docker 使用:来自宿主物理网络的包会被路由进网桥网络,从而命中 filter-FORWARD 链;
- DOCKER-USER:预留给用户自定义规则的入口,本场景中为空;
- DOCKER-FORWARD:Docker 自己的转发决策链,所有容器相关转发逻辑都在此分流;
- DOCKER-CT:为各网桥(docker0、bridge1)放行 conntrack 状态为 RELATED/ESTABLISHED 的回程流量;
- DOCKER-BRIDGE:把目的指向某个网桥接口的包送入 DOCKER 链做逐端口放行/默认丢弃判定;
- DOCKER-INTERNAL:仅
--internal网络使用(setupInternalNetworkRules会在此写入 DROP INCOMING/OUTGOING 规则),本场景为空链; - DOCKER:逐端口 ACCEPT 规则 + 网络默认 DROP 规则。
四、逐条解读关键规则
4.1 DOCKER 链:端口发布放行与网络级默认丢弃
| 规则 | 内容 | 来源 |
|---|---|---|
| 1 | -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp --dport 80 -j ACCEPT |
setPerPortForwarding(port.go),容器创建时插入链首 |
| 2 | ! -i docker0 -o docker0 -j DROP |
setDefaultForwardRule(network.go),docker0 的默认丢弃规则 |
| 3 | ! -i bridge1 -o bridge1 -j DROP |
setDefaultForwardRule,bridge1 的默认丢弃规则 |
- 规则 1 放行“非来自 bridge1 自身”的、目的为容器地址 192.0.2.2:80 的 TCP 包——这正是外部(或其他网络)访问发布端口的转发路径;该规则是容器创建时追加的,晚于网络初始化阶段生成的其余规则,且插入链首、位于网络 DROP 规则之前(对照 usernet-portmap.md 中的说明)。
- 规则 2/3 是“默认丢弃”:
setDefaultForwardRule的注释表明,它阻止远端主机通过宿主机地址设置路由后,直接访问未发布端口。! -i bridge1 -o bridge1意味着“进入和离开都经过 bridge1”(即网桥内部流量)被默认 DROP;而本场景中网桥内部流量实际由 DOCKER-FORWARD 的规则 5 先行处理(见下节)。 - DOCKER-BRIDGE 链把
-o docker0、-o bridge1的包分别送入 DOCKER 链,所以“目的地址在本网桥网段”的包会先尝试逐端口 ACCEPT,落空后命中对应 DROP。
4.2 DOCKER-FORWARD 链:ICC 隔离的核心位置
DOCKER-FORWARD 的 6 条规则中,前 4 条与 ICC=true 场景完全相同(conntrack 放行 → internal 链 → 网桥分发 → docker0 入向放行)。差异集中在规则 5、6:
5 DROP all -- bridge1 bridge1 anywhere anywhere # 丢弃网桥内部通信
6 ACCEPT all -- bridge1 !bridge1 anywhere anywhere # 放行其余出向流量
- 规则 5(DROP bridge1→bridge1):由
setIcc添加。在 network.go 中,当iccEnable为 false 且处于创建(insert)阶段时,dropRule(-i bridge1 -o bridge1 -j DROP)被追加到DOCKER-FORWARD链;同时旧版本遗留的 ICC ACCEPT 规则会被删除(moby 28.0.0 及更早版本曾把 ICC 规则直接放在 FORWARD 链,当前代码会清理这些遗留规则)。 - 规则 6(ACCEPT bridge1→!bridge1):即源码中的
outRuleNoICC(-i bridge1 ! -o bridge1 -j ACCEPT,标签 "ACCEPT NON_ICC OUTGOING"),由setupNonInternalNetworkRules在n.config.ICC为 false 的分支中写入(network.go)。它的作用与注释一致:放行容器出向流量,但排除目的仍是本网桥的包——后者已被规则 5 DROP。
原文档对这一差异的官方表述(保留原文语义):
By comparison with ICC=true: 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 bysetupIPTablesInternalaccepts any other outgoing packet.
注意文档撰写时的函数名 setupIPTablesInternal 在当前的 network.go 中已演进为 setupNonInternalNetworkRules(index.md 也提示其中的代码链接可能过期,仅作为定位线索)。
4.3 与 ICC=true 场景的对照
同一测试目录下的 usernet-portmap.md(ICC 默认开启)中,DOCKER-FORWARD 链最后一条是:
5 ACCEPT all -- bridge1 any anywhere anywhere # 放行一切出向流量(含网桥内部)
而 ICC=false 场景用两条规则(DROP 自身网桥 + ACCEPT 其余)替代了这条单一放行规则。其余链(DOCKER、DOCKER-BRIDGE、DOCKER-CT、端口发布相关的 nat DNAT/MASQUERADE)在两个场景中完全一致——这正是 ICC 选项的设计边界:它只改变“容器间”流量的转发决策,不影响对外端口发布与 SNAT。
五、nat 表:端口发布的 DNAT 与 SNAT
nat 表完整内容(原文档):
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER all -- any any anywhere !loopback/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
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)
num pkts bytes target prot opt in out source destination
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:宿主机外部地址的入站包、以及本地进程发出的目标为本地地址(
ADDRTYPE match dst-type LOCAL,OUTPUT 排除 loopback)的包,都先交给 nat-DOCKER 链做 DNAT 判定; - DNAT 规则:
! -i bridge1 -p tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80,把宿主机 8080 端口的 TCP 流量改写为容器地址 192.0.2.2 的 80 端口,由setPerPortForwarding(port.go)在端口发布时写入;! -i bridge1确保网桥内部流量不被 DNAT 干扰; - POSTROUTING 的 MASQUERADE:对 192.0.2.0/24 子网(bridge1)、172.17.0.0/16(docker0)的出网流量做源地址伪装。在 network.go 的
setupNonInternalNetworkRules中,当未指定HostIP时使用 MASQUERADE(按路由表下一跳选择源地址);指定了则改用 SNAT。
值得强调:nat 表在 ICC=false 与 ICC=true 两个场景下没有任何差异,这从源码结构上也可印证——setIcc 只操作 filter 表的 DOCKER-FORWARD 链(network.go),端口发布规则独立于 ICC 配置。
六、补充:EnableICC 在驱动生命周期中的其他落点
除 iptables 规则外,从源码结构看,EnableICC 还参与以下行为(bridge_linux.go):
- Join/Leave 阶段的 veth 链接:
Join在!network.config.EnableICC时调用d.link(network, endpoint, true)(L1468-L1470),Leave时以false对称拆除(L1527-L1531),说明 ICC 关闭的网络对容器 veth 链路采用不同的接线方式; - 链路级 netfilter 调用的启用条件:
enableBrNfCallIptables := !config.EnableICC || d.config.EnableProxy(L853),即关闭 ICC 的网桥会启用br-nf-call-iptables,保证网桥内部流量确实经过 iptables 的 FORWARD 路径——这也是规则 5 的 DROP 能够生效的前提; - 网络持久化:
EnableICC参与网络配置的序列化/反序列化(bridge_store.go),daemon 重启恢复网络时按原配置重建规则。
七、要点小结
| 观察点 | ICC=true | ICC=false(本文档场景) |
|---|---|---|
| DOCKER-FORWARD 出向规则 | 1 条:-i bridge1 -j ACCEPT |
2 条:-i bridge1 -o bridge1 -j DROP + -i bridge1 ! -o bridge1 -j ACCEPT |
| 写规则函数 | setupNonInternalNetworkRules 的 ICC 分支 |
同左 + setIcc 的 DROP 分支 |
| 端口发布(filter ACCEPT / nat DNAT / MASQUERADE) | 相同 | 相同 |
| 效果 | 网桥内容器可互访 | 同网桥容器互访被丢弃,容器仍可通过宿主机对外通信 |
阅读建议:本文档(及 generated/ 目录下的 9 个场景文档)是理解 Moby 网桥 iptables 体系的“规则快照”,配套模板位于 templates/,实现逻辑集中在 daemon/libnetwork/drivers/bridge/internal/iptabler/;由于规则结构不是稳定接口,将其用于生产排障时应以本机 iptables -S -t filter 实际输出为准。
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 StartedRust0624
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