首页
/ Moby 桥接网络实战:ICC(容器间通信)禁用时 iptables 规则的完整解析

Moby 桥接网络实战:ICC(容器间通信)禁用时 iptables 规则的完整解析

2026-09-06 13:54:46作者:昌雅子Ethen

本文基于 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/bridge1RELATED,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 上容器发往其他网络/宿主的所有出站流量。

与 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 setupIPTablesInternal accepts any other outgoing packet.

(原文档中的规则编号按当时链内顺序编号;在本文展示的当前生成结果中对应 DOCKER-FORWARD 的第 5、6 条。)

源码级实现:setIcc 与出站放行规则

iptabler/network.go 中,非 internal 网络的规则编排入口是 setupNonInternalNetworkRules

  1. setIcc 写入 DROPL311 调用,函数体 L363-L403)。它构造 -i bridgeIface -o bridgeIface -j 的规则:ICC=false 且为"插入"模式时追加 DROP 规则(对应 DOCKER-FORWARD 规则 5);ICC=true 时则不写任何同桥规则(对外网络场景下同桥流量由出站 ACCEPT 覆盖),并顺带清理旧版本遗留的规则。注意 DROP 规则是无条件写在 DOCKER-FORWARD 链上的,位置在 DOCKER-BRIDGE 跳转之后,因此不会误伤已裁决的入站 DNAT 流量。
  2. outRuleNoICC 写入 ACCEPTL322-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 网络,路径不同:setupInternalNetworkRulesL437-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 MASQUERADE192.0.2.0/24 的容器流量出桥时做源地址伪装(MASQUERADE 从路由表选源地址,实现于 L286-L290),与默认桥 docker0172.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 filteriptables -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。
登录后查看全文
热门项目推荐
相关项目推荐