Moby 桥接网络 iptables 规则全解:基于 TestBridgeIptablesDoc 生成的规则文档源码导读
Moby(Docker Engine)的 bridge 网络驱动在初始化、创建网络、发布端口时会在内核中写入大量 iptables/ip6tables 规则,这些规则决定了容器端口映射、网络隔离与内外通信的可达性。本文基于仓库中 bridge iptables 规则文档 逐场景展开:先讲清这套文档的生成与维护机制(它由集成测试 TestBridgeIptablesDoc 真实运行守护进程、创建网络与容器后捕获 iptables 快照生成),再逐条解读新守护进程、用户自定义网络端口映射、--internal 网络、routed 网关模式、Swarm ingress 等场景下 filter/nat/raw 三张表的具体规则,并结合源码定位每条规则的写入函数,帮助运维与内核网络方向的读者把“看到的 iptables 输出”与“moby 哪段代码产生的”对应起来。
文档定位:开发用途,规则结构不是稳定接口
规则文档 开头明确了两点前提,使用文档前必须了解:
- 仅用于开发用途:Docker 的 iptables(及 ip6tables)规则结构会在版本之间变化,不是稳定接口。任何依赖具体链名或规则顺序的外部工具、防火墙策略都应视为不受支持的用法。
- 文档是测试生成的:本文档由集成测试
TestBridgeIptablesDoc生成——它启动一个真实的 dockerd,按需创建网络与容器,捕获iptables -vL/iptables -S输出,再用text/template逐节渲染成 Markdown。生成结果与仓库中 generated/ 下的“黄金参考文件”做 diff,一旦出现差异测试即失败(但模板本身的改动可能不被察觉)。 - 仅展示 IPv4 规则:ip6tables 规则与 iptables 规则遵循同样的模式,文档只展示 IPv4。
测试实现的完整逻辑可以在 iptablesdoc_linux_test.go 中看到,关键机制包括:
- 场景清单即索引:包内变量
index(第 74 行起)定义了全部 9 个文档小节,每节声明网络(子网、网关、noICC/internal/gwMode选项)与容器端口映射,例如usernet-portmap.md一节使用192.0.2.0/24/192.0.2.1,容器映射80/tcp → 8080。 - 隔离环境:通过
networking.NewL3Segment为每个小节创建独立网络命名空间的“主机”,守护进程在各自 netns 中启动(--userland-proxy=false等参数按小节追加),避免串扰;runIptables会先iptables -Z清零计数、再用正则把“X packets, Y bytes”统一替换为0 packets, 0 bytes,保证 golden 对比稳定。 - golden 对比与更新:生成内容先写入
bundles/test-integration/TestBridgeIptablesDoc/,再与generated/<name>.md做golden.Assert;规则变更时先检查 diff,再更新对应templates/<name>.md的说明文字,最后以TESTFLAGS='-update'重新生成参考文档。 - 跳过条件:firewalld 运行中、rootless 模式、防火墙后端为 nftables 时测试跳过——这正是文档只对“纯 iptables 后端”有效的适用前提。
另外,文档说明:bridge 驱动在初始化时(configure,见当前仓库 daemon/libnetwork/drivers/bridge/bridge_linux.go)会删除自建的自定义链,随后在恢复各网络时重新创建规则;但 filter-FORWARD 主链不会被清空。由于网络恢复顺序并非原始创建顺序,守护进程重启后规则的排列可能不同。当 firewalld 运行时,其 reload 事件(经 dbus 送达)会使 iptables 规则被清空,守护进程注册了 reload 事件处理器以重建规则。
还有一个排查网络问题时常用的事实:Docker 不使用 filter-INPUT 与 filter-OUTPUT 链。来自主机物理网络或主机自身的包被路由进桥接网络后,命中的是 filter-FORWARD 链。
场景一:全新守护进程的初始规则集(new-daemon)
完整规则见 generated/new-daemon.md。守护进程启动时创建自定义链,并为默认 bridge 网络(docker0)建立规则。
filter 表
Chain FORWARD (policy ACCEPT)
1 DOCKER-USER all -- any any
2 DOCKER-FORWARD all -- any any
Chain DOCKER (1 references)
1 DROP all -- !docker0 docker0
Chain DOCKER-BRIDGE
1 DOCKER all -- any docker0
Chain DOCKER-CT
1 ACCEPT all -- any docker0 ctstate RELATED,ESTABLISHED
Chain DOCKER-FORWARD
1 DOCKER-CT
2 DOCKER-INTERNAL
3 DOCKER-BRIDGE
4 ACCEPT all -- docker0 any
等价命令:
-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
各链职责(按文档逐条解释):
- FORWARD 主链的两条无条件跳转:
DOCKER-USER由 libnetwork 的setupUserChain建立,Docker 不会往该链里加规则,它专门留给用户自定义规则,且基本保持在 FORWARD 最顶部(每新建一个网络时会删除重建该跳转,同时不影响其他网络流量);DOCKER-FORWARD由setupIPChains建立。守护进程初始化完成后不再触碰这两条规则,用户可自由向 FORWARD 追加规则(执行在 Docker 规则之后)。 - DOCKER-FORWARD 是 Docker filter 规则的第一级:初始规则插入链首、此后不再变动,逐网络规则则追加到链尾。第 4 条
ACCEPT -i docker0是创建该网络时由setupIPTablesInternal加入的,放行任何通过 DOCKER 链与隔离链之后离开该网络的包。 - DOCKER-CT:对发往各 bridge 网络的
RELATED,ESTABLISHED流量提前 ACCEPT,每个 bridge 网络一条 conntrack 规则。 - DOCKER-BRIDGE:每个 bridge 网络一条“跳入 DOCKER 链”的规则。
- DOCKER 链:实现每个容器的按端口/协议过滤。此处只有一条来自
setDefaultForwardRule的 DROP 规则——丢弃未起源于该网络却被路由到该网络的包。这意味着安全不依赖 FORWARD 主链的默认策略:即使 FORWARD 策略是 ACCEPT,未发布端口/协议的包仍会被丢弃。 - DOCKER-INTERNAL:服务于
--internal网络,新守护进程场景下为空。
文档还指出:上述 FORWARD 策略虽然是 ACCEPT,但 setupIPv4Forwarding 会在 sysctl net.ipv4.ip_forward 原本不是 1 而由守护进程在创建 IPv4 bridge 网络时代为设置时,把策略改为 DROP;IPv6 对 /proc/sys/net/ipv6/conf/{default,all}/forwarding 同理。
nat 表
Chain PREROUTING
1 DOCKER all -- any any ADDRTYPE match dst-type LOCAL
Chain OUTPUT
1 DOCKER all -- any any !loopback/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING
1 MASQUERADE all -- any !docker0 172.17.0.0/16
PREROUTING/OUTPUT中ADDRTYPE match dst-type LOCAL的跳转让所有目的地是本机的流量进入 nat DOCKER 链(端口 DNAT 发生地)。注意 OUTPUT 侧排除了 loopback。POSTROUTING的 MASQUERADE 规则让172.17.0.0/16子网流量离开docker0时做源地址转换,这是容器访问外网的基础。
场景二:用户自定义网络 + 发布端口(usernet-portmap)
这是最典型的场景,等价命令见 generated/usernet-portmap.md:
docker network create \
-o com.docker.network.bridge.name=bridge1 \
--subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1
docker run --network bridge1 -p 8080:80 --name c1 busybox
相对新守护进程的增量规则:
filter 表——每个新网络在 DOCKER-CT、DOCKER-BRIDGE、DOCKER-FORWARD 中各追加一条自己的规则;DOCKER 链头部插入端口 ACCEPT,尾部追加该网络的 DROP:
-A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp --dport 80 -j ACCEPT
-A DOCKER ! -i bridge1 -o bridge1 -j DROP
-A DOCKER-BRIDGE -o bridge1 -j DOCKER
-A DOCKER-CT -o bridge1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-FORWARD -i bridge1 -j ACCEPT
文档强调的规则插入次序要点:
- DOCKER-FORWARD 第 5 条(放行离开新网络的流量)追加到链尾;
- DOCKER 链的按端口 ACCEPT 在容器创建时加入(前面所有规则都在驱动/网络初始化期创建),并且按端口规则总是插入链首,保证排在始终追加在链尾的网络 DROP 规则之前。由于
docker0先于bridge1创建,bridge1的 ACCEPT 出现在docker0的 DROP 之上、bridge1的 DROP 出现在其下。
nat 表——端口映射的 DNAT 与子网 MASQUERADE:
-A DOCKER ! -i bridge1 -p tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80
-A POSTROUTING -s 192.0.2.0/24 ! -o bridge1 -j MASQUERADE
raw 表——filterDirectAccess 在 raw-PREROUTING 加入一条 DROP,阻止远端绕过发布流程直连容器地址上的映射端口:
-A PREROUTING -d 192.0.2.2/32 ! -i bridge1 -j DROP
这些规则在源码中的对应写入点(从源码结构看,位于 daemon/libnetwork/drivers/bridge/internal/iptabler/ 包):
- 端口 filter 规则:setPerPortForwarding;
- 网络级 DROP:setDefaultForwardRule;
- 初始化建链:setupIPChains。
场景三:端口发布在 loopback 地址(usernet-portmap-lo)
generated/usernet-portmap-lo.md 对应 docker run -p 127.0.0.1:8080:80。结论简明:filter 表与 nat 表与默认 nat 模式完全相同(文档直接折叠引用),差别体现在访问路径上——发布地址绑定到 127.0.0.1 后,只有本机可经 loopback 到达映射端口。
场景四:禁用 userland proxy(usernet-portmap-noproxy)
以 dockerd --userland-proxy=false 启动后重复同一映射,见 generated/usernet-portmap-noproxy.md。filter 表与启用 proxy 时完全一致,差异全部在 nat 表:
| 差异点 | 说明 |
|---|---|
| OUTPUT 跳 DOCKER 不再排除 loopback | ProgramChain 使 -A OUTPUT -m addrtype --dst-type LOCAL -j DOCKER 对回环地址也生效,本机到映射端口的流量直接走内核 DNAT |
| 新增自访问 MASQUERADE | -A POSTROUTING -s 192.0.2.2/32 -d 192.0.2.2/32 -p tcp --dport 80 -j MASQUERADE,覆盖容器访问自己在宿主机上的发布端口 |
| LOCAL 源地址 MASQUERADE 进 POSTROUTING | setupIPTablesInternal 加入 -o bridge1 -m addrtype --src-type LOCAL -j MASQUERADE 等规则 |
| DNAT 规则无目的 bridge 条件 | setPerPortNAT 生成的 -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80 不再带 ! -i bridge1 |
这组差异解释了为何无 userland proxy 时端口映射完全依赖内核 conntrack/NAT 路径,也解释了用户自行加防火墙规则时两种模式行为不同的常见原因。
场景五:禁用容器间通信(usernet-portmap-noicc)
-o com.docker.network.bridge.enable_icc=false 创建网络后,见 generated/usernet-portmap-noicc.md。与 ICC=true 相比,DOCKER-FORWARD 尾部用两条规则替换了原来单条离开网络的 ACCEPT:
-A DOCKER-FORWARD -i bridge1 -o bridge1 -j DROP # 由 setIcc 加入:网络内自环流量丢弃
-A DOCKER-FORWARD -i bridge1 ! -o bridge1 -j ACCEPT # 由 setupIPTablesInternal 加入:出向放行
从源码结构看,这条 ICC 控制规则由 setIcc 写入——它是 bridge 驱动里 enable_icc 选项落到 iptables 的唯一入口,其余(nat、raw)规则与默认场景一致。
场景六:--internal 网络(usernet-internal)
两个 --internal 网络(一个允许 ICC、一个禁用)的规则见 generated/usernet-internal.md,等价命令:
docker network create -o com.docker.network.bridge.name=bridgeICC \
--internal --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1
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-FORWARD 中没有该网络出向流量的 ACCEPT 规则;
- DOCKER 链中没有该网络的规则;
- DOCKER-INTERNAL 链按子网双向封锁,每个
--internal网络两条规则:
-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 # 源在网内、目的不在网内 → 丢
规则 1 丢弃“被路由进该网络但源地址不在子网内”的包,规则 2 丢弃“从该网络路由出去但目的地址不在子网内”的包——由此实现与外部网络的双向隔离。而 bridgeICC 与 bridgeNoICC 之间唯一的区别就在 DOCKER-FORWARD 一条规则:ICC 开启时桥内回环包 ACCEPT(-i bridgeICC -o bridgeICC -j ACCEPT),禁用时 DROP(-i bridgeNoICC -o bridgeNoICC -j DROP)。nat 表中同样没有这些网络的 MASQUERADE/DNAT 规则,印证 internal 网络不参与端口发布。
场景七:routed 与 nat-unprotected 网关模式
generated/usernet-portmap-routed.md 对应创建网络时指定 -o com.docker.network.bridge.gateway_mode_ipv4=routed(测试代码中通过 bridge.IPv4GatewayMode 选项注入,见 iptablesdoc_linux_test.go 第 316 行附近)。与 nat 模式相比,filter 表 DOCKER 链中多出一条 ICMP 放行:
-A DOCKER -o bridge1 -p icmp -j ACCEPT
routed 模式下容器直接路由而非 NAT,网关需要放行 ICMP 以维持路径健康检查与可达性探测;其余按端口 ACCEPT/DROP 结构不变。nat-unprotected 模式(gateway_mode_ipv4=nat-unprotected,见 generated/usernet-portmap-natunprot.md)则对应“不做 SNAT 保护”的网关形态,测试用例以同样的子网/端口映射模板生成,便于横向对比三种网关模式的规则差集。
场景八:Swarm 服务发布端口(swarm-portmap)
docker service create -p 8080:80 busybox top 的规则见 generated/swarm-portmap.md。ingress 负载均衡落地在 docker_gwbridge 网关端点上,规则形态与用户网络高度同构:
-A DOCKER -d 172.18.0.2/32 ! -i docker_gwbridge -o docker_gwbridge \
-p tcp --dport 8080 -j ACCEPT
-A DOCKER-FORWARD -i docker_gwbridge -o docker_gwbridge -j DROP
-A DOCKER-FORWARD -i docker_gwbridge ! -o docker_gwbridge -j ACCEPT
测试源码中的注释给出了一处值得注意的实现细节(iptablesdoc_linux_test.go 第 392-400 行):ingress 端口是作为普通端口映射发布在负载均衡 sandbox 的 docker_gwbridge 网关端点上的,且发生在任务容器启动后的几毫秒内,因此测试通过轮询 iptables -t nat -S DOCKER 中出现 DNAT 规则来判定 ingress 就绪。
参考路径汇总
| 类型 | 路径 |
|---|---|
| 文档入口 | integration/network/bridge/iptablesdoc/index.md |
| 生成测试 | integration/network/bridge/iptablesdoc/iptablesdoc_linux_test.go |
| 测试环境 | integration/network/bridge/iptablesdoc/main_linux_test.go |
| 黄金参考 | generated/(9 个场景 md) |
| 渲染模板 | templates/(9 个同名 md 模板) |
| 建链实现 | daemon/libnetwork/drivers/bridge/internal/iptabler/iptabler.go(setupIPChains) |
| 网络级规则 | daemon/libnetwork/drivers/bridge/internal/iptabler/network.go(setDefaultForwardRule、setIcc) |
| 端口级规则 | daemon/libnetwork/drivers/bridge/internal/iptabler/port.go(setPerPortForwarding) |
适用前提提醒:本文所有规则快照仅在 iptables(非 nftables)后端、非 rootless、无 firewalld 干扰的环境中成立;规则排列顺序在守护进程重启后可能变化;由于规则结构本身不是稳定接口,生产环境的防火墙集成应以“DOCKER-USER 链 + 用户自定义规则”这一官方预留扩展点为准,而不是解析具体规则内容。
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 StartedRust0623
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