首页
/ Moby Bridge 网络 `--internal` 模式:容器 iptables 隔离规则全解

Moby Bridge 网络 `--internal` 模式:容器 iptables 隔离规则全解

2026-09-06 13:43:24作者:侯霆垣

本文以 Moby(Docker 引擎)仓库中 integration/network/bridge/iptablesdoc 下关于"用户自定义 --internal 网络"的文档为主体,完整解读该场景下 Docker 在 filter 表与 nat 表中生成的 iptables 规则,并结合 iptabler.goiptablesdoc_linux_test.go 的源码,说明这些规则从何而来、--internalenable_icc 两个选项如何在规则层面体现,以及如何复现和验证这份规则文档。

1. 文档定位:这是一份由测试自动生成的"现场取证"文档

--internal 网络的规则文档由两个文件组成:

生成机制在 index.md 中有明确说明:测试会真实启动一个 dockerd、按场景创建网络和容器、抓取 iptables,再套模板渲染成 Markdown,然后与仓库中的 golden 文件 diff——一旦引擎生成的规则发生变化,测试即失败。这意味着本文引用的每一条规则都来自一次真实 daemon 运行的快照,而非手写推演。源码注释(iptablesdoc_linux_test.go)还给出了更新流程:先检查 diff,修改对应 templates/ 中的描述,再用 TESTFLAGS='-update' 重新生成参考文档。

需要保留的原始警告(来自 index.md):

This is intended for development use — the structure of docker's iptables (and ip6tables) rules will change between releases, it is not a stable interface.

即这些规则属于实现细节,跨版本可能重排,不适合作为稳定接口依赖。另外该文档只展示 IPv4 规则,ip6tables 规则遵循相同模式。还有一个已知行为:bridge 驱动初始化时会删除其自定义链并在网络恢复时重建,但 filter-FORWARD 主链不清空,且网络重建顺序与原创建顺序无关,因此 daemon 重启后规则的排列顺序可能与本文不一致(firewalld reload 时 daemon 也会通过 dbus 事件重建规则)。

2. 场景设置:两个 --internal 网络,一个开 ICC、一个关 ICC

模板文档定义的场景是:两个容器分别位于两个不同的 --internal 用户自定义 bridge 网络上,其中一个允许容器间通信(ICC),另一个禁止。文档给出的等价命令为:

docker network create \
  -o com.docker.network.bridge.name=bridgeICC \
  --internal \
  --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1
docker run --network bridgeICC --name c1 busybox

docker network create \
  -o com.docker.network.bridge.name=bridgeNoICC \
  -o com.docker.network.bridge.enable_icc=true \
  --internal \
  --subnet 198.51.100.0/24 --gateway 198.51.100.1 bridge1
docker run --network bridgeNoICC --name c1 busybox

参数说明:

选项 作用
--internal 将网络标记为内部网络:不设置出向路由/NAT,容器无法从该网络访问外部,外部也无法经该网络出去
-o com.docker.network.bridge.name=<ifname> 指定宿主侧网桥接口名(bridgeICC/bridgeNoICC),便于在规则中识别
-o com.docker.network.bridge.enable_icc=true/false 控制同桥容器之间(ICC)是否允许互通
--subnet / --gateway 指定子网与网关地址。文档使用 192.0.2.0/24198.51.100.0/24 这两个 RFC 5737 文档用地址段,避免与真实网络冲突

从测试源码看(iptablesdoc_linux_test.go#L116-L135),该场景实际由两个 networkDesc 驱动:bridgeICCinternal: true)与 bridgeNoICCinternal: true, noICC: true),各自运行一个 busybox 容器,且都没有端口发布portMappings 为空)——这正是 --internal 网络的典型用法。noICC 会转化为 network.WithOption(bridge.EnableICC, "false")L318-L320)。需要指出:模板文字中给 bridgeNoICC 写的 enable_icc=true 与测试实际设置(false)不一致,从生成的规则结果(下文第 5 节的 DROP 规则)判断应以测试源码为准,模板文字疑为历史遗留。

对实际使用者,等价的简化写法是:docker network create --internal -d bridge mynet,以及用 -o com.docker.network.bridge.enable_icc=false 关闭 ICC。

3. filter 表规则全景

测试抓取方式为 iptables -vL --line-numbers -t filterL204-L213 定义了全部抓取命令),抓取前先 iptables -Z 清零计数器,再用正则把偶发计数归一化为 0 packets, 0 bytesrunIptables),保证 golden 文件稳定。以下规则取自 generated/usernet-internal.md

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 (1 references)
1        0     0 DROP       all  --  !docker0 docker0  anywhere             anywhere

Chain DOCKER-BRIDGE (1 references)
1        0     0 DOCKER     all  --  any    docker0  anywhere             anywhere

Chain DOCKER-CT (1 references)
1        0     0 ACCEPT     all  --  any    docker0  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 ACCEPT     all  --  bridgeICC bridgeICC  anywhere             anywhere
6        0     0 DROP       all  --  bridgeNoICC bridgeNoICC  anywhere             anywhere

Chain DOCKER-INTERNAL (1 references)
1        0     0 DROP       all  --  any    bridgeNoICC !198.51.100.0/24      anywhere
2        0     0 DROP       all  --  bridgeNoICC any     anywhere            !198.51.100.0/24
3        0     0 DROP       all  --  any    bridgeICC !192.0.2.0/24         anywhere
4        0     0 DROP       all  --  bridgeICC any     anywhere            !192.0.2.0/24

对应的一行式命令(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 ! -i docker0 -o docker0 -j DROP
-A DOCKER-BRIDGE -o docker0 -j DOCKER
-A DOCKER-CT -o docker0 -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 bridgeICC -o bridgeICC -j ACCEPT
-A DOCKER-FORWARD -i bridgeNoICC -o bridgeNoICC -j DROP
-A DOCKER-INTERNAL ! -s 198.51.100.0/24 -o bridgeNoICC -j DROP
-A DOCKER-INTERNAL ! -d 198.51.100.0/24 -i bridgeNoICC -j DROP
-A DOCKER-INTERNAL ! -s 192.0.2.0/24 -o bridgeICC -j DROP
-A DOCKER-INTERNAL ! -d 192.0.2.0/24 -i bridgeICC -j DROP

逐段解读:

主链骨架。FORWARD 链头两条 jump 分别进入 DOCKER-USER(用户自定义扩展点)和 DOCKER-FORWARD(Docker 全部转发决策)。filter 表的 INPUT/OUTPUT 链不被 Docker 使用(来自 index.md 的说明):宿主物理网络或本机发往容器的包,是作为"被路由进桥网络"的转发流量命中 FORWARD 的。

DOCKER-FORWARD 的四段式结构。规则 1–3 依次跳转 DOCKER-CT(放行到 docker0 的 RELATED/ESTABLISHED 会话)、DOCKER-INTERNAL--internal 网络专属的入出隔离)、DOCKER-BRIDGE(默认桥 docker0 相关处理);规则 4 放行一切离开 docker0 的流量。规则 5、6 才是本场景的主角:分别对 bridgeICCbridgeNoICC 上"同桥进出"(-i X -o X)的流量给出 ACCEPT/DROP 判决——这就是 ICC 开关的落点。

DOCKER-INTERNAL 的四条 DROP 规则。每个 --internal 网络各占两条:

  1. ! -s <子网> -o <桥>:源地址不在该网络子网内、却路由进入该网络(输出接口为桥)的包,丢弃。
  2. ! -d <子网> -i <桥>:目的地址不在该网络子网内、却从该网络路由出去(输入接口为桥)的包,丢弃。

文档原文对这两条规则的解释是:"Rule 1 drops any packet routed to the network that does not have a source address in the network's subnet. Rule 2 drops any packet routed out of the network that does not have a dest address in the network's subnet." 值得注意的是,这两条规则同时封死了宿主侧的访问路径:宿主机的 IP 不属于容器子网,因此宿主→internal 容器、internal 容器→宿主的流量都会被第 1、2 条 DROP 拦截——这与 --internal 网络"容器与宿主之间不可达"的行为一致。

4. 与"有外部访问的网络"对比

模板文档将本场景与 generated/usernet-portmap.md(用户自定义网络 + 端口发布的场景)逐点比较,差异恰好就是 --internal 的三条"减法":

  • DOCKER-FORWARD 中没有针对出向流量的 ACCEPT 规则:普通网络会有 -i bridge1 -j ACCEPT(容器可经该桥出去),internal 网络只有 -i bridge -o bridge 形式的同桥规则(规则 5/6);
  • filter 表 DOCKER 链中没有该网络的任何规则:普通网络的 DOCKER 链承担"跨网络隔离 + 到未发布端口的 DROP"职责,internal 网络不需要这些;
  • DOCKER-INTERNAL 中出现上述每网络两条的 DROP 规则,这是普通网络完全没有的链。

也就是说,--internal 的实现策略是:不在主转发路径上"开洞",而是在 DOCKER-INTERNAL 链里对子网做"入出双向白名单",凡是源/目的不属于该子网的进出流量一律 DROP。

5. ICC 与 no-ICC 的唯一区别

模板文档明确写道:

The only difference between bridgeICCbridgeNoICC is the rule in the DOCKER-FORWARD chain. To enable ICC, the rule for packets looping through the bridge is ACCEPT. For no-ICC it's DROP.

对照规则 5 和 6:-i bridgeICC -o bridgeICC -j ACCEPT vs -i bridgeNoICC -o bridgeNoICC -j DROP。两者匹配的都是从桥进入又从同一桥出去的流量,即同一网络上容器 A → 容器 B 的直接转发。ICC 开启时放行,关闭时丢弃。两条 DOCKER-INTERNAL 子网规则在两个网络上完全同构,可见 no-ICC 不改变子网隔离,只改变同桥互通。

从源码结构看这条规则的来源:ICC 开关在 bridge_linux.go 中通过解析 com.docker.network.bridge.enable_icc 选项存入 ncfg.EnableICC,再经 firewaller 的配置传递给 iptabler 层(其注释说明 ICC 为 false 时"容器之间应不能通信")。而 DOCKER-INTERNAL 链的创建与挂载在 iptabler.goNewChain(dockerInternalChain, iptables.Filter) 建链后,EnsureJumpRule 按 CT → INTERNAL → BRIDGE 的逆序插入跳转,最终呈现为 DOCKER-FORWARD 中规则 1→3 的顺序——与抓到的规则顺序一致。

6. nat 表:internal 网络的"空 DOCKER 链"

同一场景下的 nat 表(iptables -vL --line-numbers -t nat):

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    !docker0  172.17.0.0/16        anywhere

Chain DOCKER (2 references)
num   pkts bytes target     prot opt in     out     source               destination

对应命令:

-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 172.17.0.0/16 ! -o docker0 -j MASQUERADE

三个要点:

  1. nat 表 DOCKER 链为空:没有 DNAT 端口映射规则,与 filter 表 DOCKER 链为空呼应——internal 网络既不发布端口,也不参与跨网络端口转发;
  2. POSTROUTING 没有针对 192.0.2.0/16/198.51.100.0/24 的 MASQUERADE:仅有的 MASQUERADE 来自默认 bridge 网络 docker0(172.17.0.0/16)。对比普通用户网络场景(usernet-portmap.md),普通网络会为容器子网追加 MASQUERADE 出站源地址转换,internal 网络则完全没有——容器发出包的源地址就是其容器 IP(实际也到不了外部,因为 filter 层已 DROP);
  3. PREROUTING/OUTPUT 的 --dst-type LOCAL 跳入 DOCKER 是全局端口映射的入口骨架,与是否有 internal 网络无关。

7. 复现与验证

该文档的复现路径就是跑 TestBridgeIptablesDoc 这个集成测试。从 iptablesdoc_linux_test.go 可以看到其执行环境与前置条件:

  • 跳过条件L216-L218):firewalld 正在运行时跳过(规则会被 firewalld 干扰)、rootless 模式下跳过、防火墙后端为 nftables 时跳过——即本文规则对应 iptables legacy 模式;nftables 模式下存在对应文档 nftablesdoc/generated/usernet-internal.md,其用 table ip docker-bridges 中的 vmap/chain 组织等价逻辑,规则形态不同但语义相同;
  • 隔离方式:测试在 L3 网段(192.168.124.0/24)内为每个文档章节创建一个独立网络命名空间作为"宿主",在该命名空间内启动 dockerd(daemon.StartWithBusybox),并先把 eth0 置为 down 以减少干扰计数(L237-L248);
  • 抓取与比对:按第 3 节列出的命令抓取各表,写入 bundles/test-integration/TestBridgeIptablesDoc/golden.Assertgenerated/ 目录比对(L287-L301)。

在不改仓库的前提下,可以在一台启用 iptables legacy 的 Linux 机器上手工近似复现:依次执行第 2 节的 docker network create --internal ...docker run,再运行 iptables -S -t filteriptables -S -t nat,对照本文各节的规则逐一核对;若只想看 Docker 相关链,可直接 iptables -S DOCKER-FORWARDiptables -S DOCKER-INTERNAL。验证 ICC 行为时,可在两个 internal 网络上各起一个容器,用 nslookup/nc 测试同网容器互通(需 enable_icc 为 true 的网桥)与跨网互通(预期不通)。

8. 小结

--internal 网络在 iptables 层面的实现可以概括为三点:

  1. filter 表 DOCKER 链不加入该网络的任何规则,DOCKER-FORWARD 中也没有 -i <桥> 的出向 ACCEPT——从主路径上切断外部访问;
  2. DOCKER-INTERNAL 链为每个 internal 网络各加两条子网白名单 DROP:进网络必须来自子网、出网络必须去往子网,顺带屏蔽了宿主侧源/目的地址不匹配的流量;
  3. nat 表 DOCKER 链为空、POSTROUTING 无该子网的 MASQUERADE——无端口发布、无出站源地址转换。

com.docker.network.bridge.enable_icc 只影响 DOCKER-FORWARD 中 -i X -o X 那一条 ACCEPT/DROP 规则。由于这份文档由真实 daemon 运行 + golden diff 测试(TestBridgeIptablesDoc)持续守护,它是理解 Moby bridge 驱动防火墙行为最贴地气的材料;同时要记住它的定位是开发用途的规则快照,链名与规则结构在版本间可能变化。

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