Moby Bridge 网络 `--internal` 模式:容器 iptables 隔离规则全解
本文以 Moby(Docker 引擎)仓库中 integration/network/bridge/iptablesdoc 下关于"用户自定义 --internal 网络"的文档为主体,完整解读该场景下 Docker 在 filter 表与 nat 表中生成的 iptables 规则,并结合 iptabler.go 与 iptablesdoc_linux_test.go 的源码,说明这些规则从何而来、--internal 与 enable_icc 两个选项如何在规则层面体现,以及如何复现和验证这份规则文档。
1. 文档定位:这是一份由测试自动生成的"现场取证"文档
--internal 网络的规则文档由两个文件组成:
- templates/usernet-internal.md:手写模板,负责场景说明与规则解读,其中用
{{index . "LFilter4"}}等占位符预留规则输出; - generated/usernet-internal.md:由测试
TestBridgeIptablesDoc渲染出的最终文档,包含真实抓取的 iptables 输出。
生成机制在 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/24、198.51.100.0/24 这两个 RFC 5737 文档用地址段,避免与真实网络冲突 |
从测试源码看(iptablesdoc_linux_test.go#L116-L135),该场景实际由两个 networkDesc 驱动:bridgeICC(internal: true)与 bridgeNoICC(internal: 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 filter(L204-L213 定义了全部抓取命令),抓取前先 iptables -Z 清零计数器,再用正则把偶发计数归一化为 0 packets, 0 bytes(runIptables),保证 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 才是本场景的主角:分别对 bridgeICC 和 bridgeNoICC 上"同桥进出"(-i X -o X)的流量给出 ACCEPT/DROP 判决——这就是 ICC 开关的落点。
DOCKER-INTERNAL 的四条 DROP 规则。每个 --internal 网络各占两条:
! -s <子网> -o <桥>:源地址不在该网络子网内、却路由进入该网络(输出接口为桥)的包,丢弃。! -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
bridgeICC和bridgeNoICCis 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.go:NewChain(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
三个要点:
- nat 表 DOCKER 链为空:没有 DNAT 端口映射规则,与 filter 表 DOCKER 链为空呼应——internal 网络既不发布端口,也不参与跨网络端口转发;
- 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); - 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.Assert与generated/目录比对(L287-L301)。
在不改仓库的前提下,可以在一台启用 iptables legacy 的 Linux 机器上手工近似复现:依次执行第 2 节的 docker network create --internal ... 与 docker run,再运行 iptables -S -t filter、iptables -S -t nat,对照本文各节的规则逐一核对;若只想看 Docker 相关链,可直接 iptables -S DOCKER-FORWARD、iptables -S DOCKER-INTERNAL。验证 ICC 行为时,可在两个 internal 网络上各起一个容器,用 nslookup/nc 测试同网容器互通(需 enable_icc 为 true 的网桥)与跨网互通(预期不通)。
8. 小结
--internal 网络在 iptables 层面的实现可以概括为三点:
- filter 表 DOCKER 链不加入该网络的任何规则,DOCKER-FORWARD 中也没有
-i <桥>的出向 ACCEPT——从主路径上切断外部访问; - DOCKER-INTERNAL 链为每个 internal 网络各加两条子网白名单 DROP:进网络必须来自子网、出网络必须去往子网,顺带屏蔽了宿主侧源/目的地址不匹配的流量;
- nat 表 DOCKER 链为空、POSTROUTING 无该子网的 MASQUERADE——无端口发布、无出站源地址转换。
而 com.docker.network.bridge.enable_icc 只影响 DOCKER-FORWARD 中 -i X -o X 那一条 ACCEPT/DROP 规则。由于这份文档由真实 daemon 运行 + golden diff 测试(TestBridgeIptablesDoc)持续守护,它是理解 Moby bridge 驱动防火墙行为最贴地气的材料;同时要记住它的定位是开发用途的规则快照,链名与规则结构在版本间可能变化。
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