首页
/ Moby 网桥网络 ICC 禁用场景:发布端口时 iptables 规则全景解析(usernet-portmap-noicc)

Moby 网桥网络 ICC 禁用场景:发布端口时 iptables 规则全景解析(usernet-portmap-noicc)

2026-09-06 12:48:31作者:范垣楠Rhoda

本篇基于 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.golabels.go)。
  • com.docker.network.bridge.enable_icc=false:关闭容器间通信。该选项标签在源码中定义为 EnableICC = "com.docker.network.bridge.enable_icc"labels.go),解析发生在驱动读取网络选项的分支 case EnableICCbridge_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.mdiptablesdoc_linux_test.go 的说明,其生成流程为:

  1. TestBridgeIptablesDoc 为每个场景(section)创建独立网络命名空间,在其中启动一个真实 dockerd(--swarm-default-advertise-addr 等参数按场景追加);
  2. 本场景对应的 section 定义为 name: "usernet-portmap-noicc.md"noICC: true,即通过 bridge.EnableICC: "false" 选项创建 bridge1 网络并运行发布 80/tcp→8080 的容器(iptablesdoc_linux_test.go,创建逻辑在 createBridgeNetworks);
  3. 先用 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
  1. 将输出代入 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 setPerPortForwardingport.go),容器创建时插入链首
2 ! -i docker0 -o docker0 -j DROP setDefaultForwardRulenetwork.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"),由 setupNonInternalNetworkRulesn.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 by setupIPTablesInternal accepts 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 端口,由 setPerPortForwardingport.go)在端口发布时写入;! -i bridge1 确保网桥内部流量不被 DNAT 干扰;
  • POSTROUTING 的 MASQUERADE:对 192.0.2.0/24 子网(bridge1)、172.17.0.0/16(docker0)的出网流量做源地址伪装。在 network.gosetupNonInternalNetworkRules 中,当未指定 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.EnableProxyL853),即关闭 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 实际输出为准。

登录后查看全文
热门项目推荐
相关项目推荐