Moby 中 routed 模式桥接网络的端口映射 nftables 规则全解:从 docker network create 到完整规则集
2026/9/7 2:14:51 网站建设 项目流程

Moby 中 routed 模式桥接网络的端口映射 nftables 规则全解:从 docker network create 到完整规则集

【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby

本篇技术文章基于 Moby(Docker Engine 的上游项目)仓库中integration/network/bridge/nftablesdoc目录下的关联文档,完整解析“routed(路由)模式桥接网络上带发布端口的容器”这一场景:如何在 Docker 中配置gateway_mode_ipv4=routed网络、该场景下宿主机 nftables 表ip docker-bridges的完整规则集长什么样,以及它与默认 NAT 模式在防火墙规则上的三处关键差异(无 DROP DIRECT ACCESS、放行 ICMP、无 masquerade),并结合桥接驱动源码说明这些差异是如何产生的、如何在集成测试中验证。

文档来源与生成机制:这份 nftables 文档是怎么来的

关联文档 usernet-portmap-routed.md 是 Moby 桥接网络防火墙文档体系中的一个“场景模板”。整个文档体系位于 nftablesdoc 目录,由仓库自身的集成测试TestBridgeNftablesDoc(定义于 nftablesdoc_linux_test.go)驱动生成:

  • 测试在独立的 L3 网段(L3Segment)中为每个场景启动一个 dockerd,按场景创建网络、运行容器,然后执行nft -s list table ip docker-bridges抓取真实规则;
  • 抓取的输出被拆分成以 map/chain 名为键的片段(见 runNftables),填入templates/目录下对应的 Markdown 模板;
  • 渲染结果写入generated/目录(本场景对应 generated/usernet-portmap-routed.md),并与仓库中的“golden”参考文件做 diff——规则有变化测试即失败。

index.md 中明确声明了两条重要前提,读者在使用这些规则时必须了解:

  1. 这不是稳定接口:Docker 的 nftables 规则结构在不同版本之间会变化,文档面向开发/排障用途;
  2. IPv4/IPv6 规则模式一致但表不同:IPv4 规则在ip docker-bridges表中,IPv6 规则在ip6 docker-bridges表中,文档只展示 IPv4 规则;且这些表每次 Docker 启动时都会重建。

此外,索引还说明了一个容易误解的点:Docker 不使用filter-INPUT钩子。来自宿主物理网络或宿主本身的报文,因为是被“路由”进桥接网络的,所以命中的是filter-FORWARD链;filter-OUTPUT同样不被使用。

场景复现:创建 routed 模式网络并发布端口

关联文档描述的场景是:在 user-defined 网络上运行一个容器并发布端口(published port),但该网络的 IPv4 网关模式被设为routed。文档给出的等价命令为:

docker network create \ -o com.docker.network.bridge.name=bridge1 \ -o com.docker.network.bridge.gateway_mode_ipv4=routed \ --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.namecom.docker.network.bridge.gateway_mode_ipv4是桥接驱动的受支持网络选项,定义在 labels.go(BridgeNameIPv4GatewayMode常量),另有gateway_mode_ipv6可对 IPv6 独立设置网关模式;
  • 网关模式的合法取值由 newGwMode 解析,共四种:nat(默认)、nat-unprotectedroutedisolated,分别对应 gwMode 常量;
  • 文档示例中--subnet 192.0.2.0/24 --gateway 192.0.2.1并非随手写的地址——测试代码里硬编码了同一组“文档专用”地址(docNetworks/docGateways,见 nftablesdoc_linux_test.go#L47-L49),并且 r outed 场景在测试索引中的定义(nftablesdoc_linux_test.go#L134-L146)就是:bridge1网络 +gwMode: "routed"+ 容器c1映射80/tcp → 8080。也就是说,上面两条命令与生成 golden 文档时执行的操作完全一致,可以直接复现。

从源码结构看,routed模式的语义是“禁用该网络的 NAT”:getNATDisabled 直接返回GwModeIPv4.routed()/GwModeIPv6.routed()的结果,即只要网关模式是routed,对应地址族的 NAT(包括 masquerade)就不生效。这也是下文“没有 masquerade 规则”这一差异的根源。

本场景的完整 nftables 规则集

下面是该场景下ip docker-bridges表的全部内容(来自 generated/usernet-portmap-routed.md;容器c1被分配了192.0.2.2,宿主机上还存在默认的docker0网桥):

table ip docker-bridges { map filter-forward-in-jumps { type ifname : verdict elements = { "docker0" : jump filter-forward-in__docker0, "bridge1" : jump filter-forward-in__bridge1 } } map filter-forward-out-jumps { type ifname : verdict elements = { "docker0" : jump filter-forward-out__docker0, "bridge1" : jump filter-forward-out__bridge1 } } map nat-postrouting-in-jumps { type ifname : verdict elements = { "docker0" : jump nat-postrouting-in__docker0, "bridge1" : jump nat-postrouting-in__bridge1 } } map nat-postrouting-out-jumps { type ifname : verdict elements = { "docker0" : jump nat-postrouting-out__docker0, "bridge1" : jump nat-postrouting-out__bridge1 } } chain filter-FORWARD { type filter hook forward priority filter; policy accept; oifname vmap @filter-forward-in-jumps iifname vmap @filter-forward-out-jumps } chain nat-OUTPUT { type nat hook output priority dstnat; policy accept; ip daddr != 127.0.0.0/8 fib daddr type local counter jump nat-prerouting-and-output } chain nat-POSTROUTING { type nat hook postrouting priority srcnat; policy accept; iifname vmap @nat-postrouting-out-jumps oifname vmap @nat-postrouting-in-jumps } chain nat-PREROUTING { type nat hook prerouting priority dstnat; policy accept; fib daddr type local counter jump nat-prerouting-and-output } chain nat-prerouting-and-output { } chain raw-PREROUTING { type filter hook prerouting priority raw; policy accept; } chain filter-forward-in__docker0 { ct state established,related counter accept iifname "docker0" counter accept comment "ICC" counter drop comment "UNPUBLISHED PORT DROP" } chain filter-forward-out__docker0 { ct state established,related counter accept counter accept comment "OUTGOING" } chain nat-postrouting-in__docker0 { } chain nat-postrouting-out__docker0 { oifname != "docker0" ip saddr 172.17.0.0/16 counter masquerade comment "MASQUERADE" } chain filter-forward-in__bridge1 { ct state established,related counter accept ip protocol icmp counter accept comment "ICMP" iifname "bridge1" counter accept comment "ICC" ip daddr 192.0.2.2 tcp dport 80 counter accept counter drop comment "UNPUBLISHED PORT DROP" } chain filter-forward-out__bridge1 { ct state established,related counter accept counter accept comment "OUTGOING" } chain nat-postrouting-in__bridge1 { } chain nat-postrouting-out__bridge1 { } }

规则的组织方式值得注意:filter-FORWARDnat-POSTROUTING两条挂钩链本身不做逐包判断,而是通过vmap(虚拟 map)按入接口/出接口名把报文跳转到每个网桥各自的 per-iface 链(filter-forward-in__bridge1等);四个*-jumpsmap 就是“接口名 → jump”的分发表。NAT 方向则由nat-PREROUTING/nat-OUTPUT通过fib daddr type local判断目标是否为本地地址,再统一进入nat-prerouting-and-output链。

与 NAT 模式的三处差异:routed 模式的核心特征

关联文档的主线是逐条对比 routed 模式与 NAT 模式网络 的规则,指出“大部分规则与 NAT 模式相同,但有三处不同”,外加一条关于用户态代理的说明。以下逐一展开,并给出源码层面的佐证。

差异一:raw-PREROUTING 中没有 "DROP DIRECT ACCESS" 规则——容器可被宿主机外部直接访问

文档原文:

In chain raw-PREROUTING, there's no "DROP DIRECT ACCESS" rule, so container can be accessed from outside the host.

对应规则(空链):

chain raw-PREROUTING { type filter hook prerouting priority raw; policy accept; }

在默认 NAT 模式下,raw-PREROUTING中会存在一条 "DROP DIRECT ACCESS" 规则,丢弃从外部网络直接发往容器 IP 的报文——端口访问只能经由宿主机 IP 上的 DNAT 映射进入。routed 模式下该规则不存在,外部报文可以直接以容器 IP(本例为192.0.2.2)为目的地址送达;配合filter-forward-in__bridge1中的接受规则:

ip daddr 192.0.2.2 tcp dport 80 counter accept

即“目的地址为容器 IP、目的端口为 80 的转发流量被放行”。因此docker run -p 8080:80在这个网络里的实际效果是:容器 IP 本身对外可路由,外部可以直接访问192.0.2.2:80,而无需经过宿主机地址的端口重写。

这一行为与源码中的处理一致:routed 模式下端口绑定不再走 NAT 端口映射路径,而是登记为 routed 绑定(port_mapping_linux.go#L351 等处将bnd.Mapper置为"routed",见 port_mapping_linux.go#L340-L411 附近的上下文注释说明该绑定禁用 NAT 并清除 HostIP);同时 nftabler/port.go#L174-L179 的注释也明确:对于gw_mode=routed网络,“PBs in routed mode don't map ports on the host”(routed 模式的端口绑定不在宿主机上映射端口),所以 loopback 映射类的过滤规则在该模式下是 no-op。

差异二:filter-forward-in 链中放行了 ICMP

文档原文:

In thefilter-forward-inchain, there's a rule to accept ICMP.

对应规则:

chain filter-forward-in__bridge1 { ct state established,related counter accept ip protocol icmp counter accept comment "ICMP" iifname "bridge1" counter accept comment "ICC" ip daddr 192.0.2.2 tcp dport 80 counter accept counter drop comment "UNPUBLISHED PORT DROP" }

注意规则顺序:先放行established,related连接,再放行 ICMP,然后是同网桥内的 ICC(容器间通信)流量与已发布端口的流量,最后落到UNPUBLISHED PORT DROP。由于 routed 模式下容器 IP 直接对外可达,ping 容器 IP 是常见排障手段,因此专门放行 ICMP。源码中该规则的生成位置见 iptabler/network.go#L315(注释 "Allow ICMP in routed mode.",nftables 后端与 iptables 后端共用同一套逻辑描述)。

filter-forward-out__bridge1则保持与 NAT 模式一致:放行既有的 established/related 连接以及所有出方向流量("OUTGOING"),保证容器对外发起的访问正常。

差异三:没有任何 masquerade 规则

文档原文:

There are no masquerade rules.

对应两条空链:

chain nat-prerouting-and-output { } chain nat-postrouting-out__bridge1 { }

对比同一张表里的docker0(默认桥,NAT 模式):nat-postrouting-out__docker0中存在masquerade comment "MASQUERADE"规则,而bridge1的对应链为空;负责 dstnat 的nat-prerouting-and-output链整体为空(没有把宿主机端口重写到容器端口的 DNAT 规则)。这正是routed模式的直接体现——转发路径上不重写源/目的地址:外部访问者看到的是容器真实 IP,容器对外通信也不需要 masquerade。对应源码即上文提到的 getNATDisabled:网关模式为routed时该地址族 NAT 被禁用,因此 nftabler 不再下发 DNAT 与 masquerade 规则。

补充说明:用户态代理(docker-proxy)不会为这些映射端口启动

文档结尾特别强调:

And, the userland proxy won't be started for mapped ports.

即在这个场景下,发布端口不依赖用户态代理docker-proxy转发流量,全部由内核态的 nftables 规则完成。结合上文源码证据可以推断其机制:routed 绑定由routed端口映射器处理,不走 iptables/nftables DNAT + 用户态代理这条 NAT 路径。这也意味着容器端口能否被外部访问完全取决于防火墙规则与容器 IP 的路由可达性,排障时应直接检查ip docker-bridges表而非代理进程。

如何在仓库中验证与再生成这份规则

上述规则并非手写的文档描述,而是由集成测试自动抓取并校验的,这为规则的准确性提供了可验证依据:

  1. 入口测试TestBridgeNftablesDoc(nftablesdoc_linux_test.go#L188-L228)会先检查环境:firewalld 运行中、rootless 或防火墙后端不是 nftables 时直接跳过;
  2. 每个场景(section)在独立 netns 中启动 daemon、创建网络并运行容器,其中 routed 场景由 索引中的 section 定义 驱动,网络选项通过 createBridgeNetworks 传入(bridge.IPv4GatewayModegateway_mode_ipv4标签);
  3. runNftables执行nft -s list table ip docker-bridges并将表内容拆成按 map/chain 命名的片段;
  4. generate 用templates/usernet-portmap-routed.md模板渲染出完整 Markdown,再与generated/usernet-portmap-routed.md做 golden 断言;
  5. 若 Docker 的防火墙行为发生变化导致 diff,按包注释给出的流程处理:先检查 diff 中的规则变化,再更新对应templates/下的描述,最后以TESTFLAGS='-update'重跑测试更新参考文档。

需要说明的是:模板文件本身是允许人工改动的(规则变化若源于模板文本可能不会触发测试失败,index.md 的 NOTE 中有此提示);真正的“规则快照”是generated/下的文件。此外,仓库中还有一组平行的 iptables 文档 iptablesdoc,记录了同一场景在 iptables 后端下的对应规则,可与本文的 nftables 规则互为参照。

小结与适用边界

  • 配置方式:通过com.docker.network.bridge.gateway_mode_ipv4=routed(以及可选的gateway_mode_ipv6)将桥接网络设为路由模式,端口发布后容器 IP 直接对外可达,无需经宿主机端口重写;
  • 规则特征ip docker-bridges表):raw-PREROUTING为空(无 DROP DIRECT ACCESS)、filter-forward-in__<br>放行 ICMP 及已发布端口、nat-prerouting-and-outputnat-postrouting-out__<br>为空(无 DNAT/无 masquerade);
  • 代理行为:routed 绑定的端口映射不启动用户态代理,由内核规则直接承载流量;
  • 适用前提与限制:这些规则结构面向开发/排障用途,会随版本变化,不是稳定接口;文档只展示 IPv4 表,IPv6 在ip6 docker-bridges中按相同模式生成;表在每次 daemon 启动时重建;生成文档的测试依赖 nftables 防火墙后端且在有 root 权限、无 firewalld 干扰的环境运行。

【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询