Moby 中 nat-unprotected 桥接网络的 nftables 规则解析:发布端口的“无保护“模式 Moby 中 nat-unprotected 桥接网络的 nftables 规则解析发布端口的无保护模式【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby本文以 MobyDocker 引擎仓库中由集成测试自动生成的参考文档 usernet-portmap-natunprot.md 为主体完整解读在发布端口的容器位于nat-unprotected模式桥接网络时Docker 实际下发的 nftables 规则集。读完本文你能准确理解gateway_mode_ipv4nat-unprotected与默认nat模式在防火墙规则上的具体差异按端口放行规则缺失、DROP DIRECT ACCESS 规则缺失掌握该场景下完整的docker-bridges表结构并能结合仓库中 bridge 驱动的 nftabler 源码定位每条规则的生成位置。场景定义一个 nat-unprotected 网络上运行带发布端口的容器该文档描述的场景等价于以下操作在禁用 userland proxy 的守护进程上执行docker network create \ -o com.docker.network.bridge.namebridge1 \ -o com.docker.network.bridge.gateway_mode_ipv4nat-unprotected \ --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.namebridge1固定桥接设备名便于在 nftables 表中对照com.docker.network.bridge.gateway_mode_ipv4nat-unprotectedIPv4 网关模式保留 NAT但不对未发布端口和外部直接访问施加过滤规则--subnet/--gateway192.0.2.0/24/192.0.2.1显式指定子网与网关保证规则文档可复现在源码中网关模式是 bridge 驱动的一个枚举常量。bridge_linux.go 中定义了五种模式const ( gwModeDefault gwMode gwModeNAT gwMode nat gwModeNATUnprot gwMode nat-unprotected gwModeRouted gwMode routed gwModeIsolated gwMode isolated )nat-unprotected最终被解析为firewaller.NetworkConfigFam.Unprotected布尔位见同文件约 L491。firewaller.go 对该字段的注释给出了最权威的定义// Unprotected is true if no rules to filter unpublished ports or direct access from // any remote host are required. Unprotected bool即不需要过滤未发布端口的规则也不需要阻止任意远程主机直接访问容器 IP的规则。这正是该模式与routed容器 IP 直接可达、不经 NAT的关键区别——nat-unprotected的 DNAT 和 masquerade 依然生效只是放宽了入站方向的默认保护。与默认 nat 模式规则集的公共部分文档明确指出绝大部分规则与 nat 模式的发布端口场景usernet-portmap.md相同。完整表如下摘自生成文档IPv4docker-bridges表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 { iifname ! bridge1 tcp dport 8080 counter dnat to 192.0.2.2:80 comment DNAT } 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 iifname bridge1 counter accept comment ICC counter accept comment UNPROTECTED } 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 { oifname ! bridge1 ip saddr 192.0.2.0/24 counter masquerade comment MASQUERADE } }这张表值得注意的结构特征与 nftablesdoc 总览 的说明一致按接口分派的 vmap 跳转filter-FORWARD不直接写死规则而是通过oifname vmap filter-forward-in-jumps目的接口与iifname vmap filter-forward-out-jumps源接口两张ifname : verdict映射表把每个桥接设备路由到各自的filter-forward-in__bridge/filter-forward-out__bridge链。这样每个网络一条链互不干扰删除网络时只需删对应链。NAT 方向对称入站 DNAT 挂在nat-PREROUTING外部到达与nat-OUTPUT本机发起共用的nat-prerouting-and-output链上出站 SNAT 由nat-POSTROUTING经两张 nat-postrouting 跳转分派。IPv4/IPv6 平行IPv6 规则遵循相同模式位于ip6 docker-bridges表文档仅展示 IPv4。差异一filter-forward-in__bridge1没有按端口规则改为整体放行对比默认nat模式filter-forward-in链通常会在 ICC 放行之后为每个发布端口写一条tcp dport port accept最后以drop comment UNPUBLISHED PORT DROP收尾上表中默认桥docker0的链就是这个形态。而bridge1的入向链是chain filter-forward-in__bridge1 { ct state established,related counter accept iifname bridge1 counter accept comment ICC counter accept comment UNPROTECTED }没有逐端口的dport 8080放行规则末尾的兜底不是drop而是counter accept comment UNPROTECTED——任何端口从bridge1转发进来的包都被接受。文档给出的原因是若系统层面filter-FORWARD链的默认策略为drop则只有发布端口的流量才能被转发进容器未发布端口全部不可达nat-unprotected模式恰恰要取消这种限制。这条规则由 nftabler 的 network.go 生成if conf.Unprotected { // ... Rule: []string{counter accept comment UNPROTECTED}, }同目录的 golden 测试数据如 nftabler/testdata 下大量gwmnat-unprotected命名的.golden文件中均可看到counter packets 0 bytes 0 accept comment UNPROTECTED这一行与集成文档相互印证。而对应的iptabler实现里port.go 在config.Unprotected为真时直接跳过逐端口的过滤规则生成——两个防火墙后端iptables / nftables在此行为上保持一致。差异二raw-PREROUTING中没有 DROP DIRECT ACCESS 规则在nat模式下raw-PREROUTING链里会有一条针对外部主机直接访问容器 IP的 DROP 规则防止绕过宿主机的 DNAT 直接命中容器地址。而本场景中该链是空的chain raw-PREROUTING { type filter hook prerouting priority raw; policy accept; }文档明确说明由于不存在 DROP DIRECT ACCESS 规则容器可以从宿主机外部直接访问例如192.0.2.2可以从外部网卡直接被路由到。在 nftabler 源码中endpoint.go 的条件正体现了这一点if n.config.Internal || conf.Unprotected || conf.Routed || n.fw.config.AllowDirectRouting { // 跳过直接访问过滤DROP DIRECT ACCESS相关规则的写入 }即internal、nat-unprotected、routed、或守护进程级AllowDirectRouting四种情况之一成立时都会放行对容器 IP 的直接访问——但只有nat-unprotected仍保留 NAT因此它实际是routed 的可达性 nat 的地址转换的组合。DNAT 与 MASQUERADE 规则原样保留userland proxy 行为不变文档最后强调了一点容易被忽略的事实dnat and masquerade rules are still in-place. And, if the userland proxy is enabled, it is still started.对照上表可以确认两条 NAT 规则都在chain nat-prerouting-and-output { iifname ! bridge1 tcp dport 8080 counter dnat to 192.0.2.2:80 comment DNAT } chain nat-postrouting-out__bridge1 { oifname ! bridge1 ip saddr 192.0.2.0/24 counter masquerade comment MASQUERADE }DNAT外部/本机到:8080的 TCP 流量被重写到容器192.0.2.2:80iifname ! bridge1排除来自桥内容器互访的流量避免 hairpin 循环。MASQUERADE源地址属于192.0.2.0/24且出接口不是bridge1的包做源地址伪装保证回程报文经宿主机回送。也就是说nat-unprotected放松的只是入站过滤未发布端口、直接 IP 访问出站 SNAT 与入站 DNAT 的地址转换链路完全不受影响。文档开头的场景说明在禁用 userland proxy 的守护进程上运行只是为了让行为完全由内核规则决定、便于抓规则若启用了 userland proxydockerd --userland-proxytrue为默认代理进程仍会照常启动并监听宿主端口。文档的生成机制由集成测试从真实守护进程捕获这份文档不是人工编写的而是测试 nftablesdoc_linux_test.go 的TestBridgeNftablesDoc生成的。其流程在 index.md 与测试文件头注释中都有说明为本文档对应的 sectionusernet-portmap-natunprot.md单独创建一个网络命名空间在其中启动真实 dockerd对应配置见 nftablesdoc_linux_test.go 的index表gwMode: nat-unprotected、端口映射80/tcp → 8080。按 section 描述创建网络与容器createBridgeNetworks会把gwMode经bridge.IPv4GatewayMode选项传给docker network create与上文命令行等价。用nft -s list table ip docker-bridges捕获规则拆分成以链/映射名为键的片段runNftables函数。套用 templates/usernet-portmap-natunprot.md 这份 markdown 模板其中的{{index . Ruleset4}}、{{index . chain filter-forward-in__bridge1}}等占位符被真实规则替换生成 generated/usernet-portmap-natunprot.md。生成结果与仓库中的金标文件 diff不一致则测试失败——这保证每次规则引擎改动都会被显式审查。模板中两处占位符恰好就是本文重点展开的两个差异链{{index . chain filter-forward-in__bridge1}} ... {{index . chain raw-PREROUTING}}因此这份文档可以视为当前版本 Docker 在该场景下的规则事实需要留意其开头的警示见 index.md这是面向开发者的文档Docker 的 nftables 规则结构在版本之间会变不构成稳定接口不要在生产环境依赖具体链名。小结nat-unprotected 保留 DNAT/MASQUERADE 的 NAT 语义 取消两类入站保护入向链兜底从dropUNPUBLISHED PORT DROP变为acceptUNPROTECTEDraw-PREROUTING不再 DROP 对容器 IP 的直接访问。规则生成入口可追溯模式枚举与解析在 bridge_linux.go语义定义在 firewaller.gonftables 落地在 nftabler/network.go 与 nftabler/endpoint.go单元测试的 golden 数据在 nftabler/testdata。若你只想把容器 IP 变成外部可直达、但不需要发布端口应比较 usernet-portmap-routed.mdrouted 模式本文场景适合既要发布端口映射、又不想为未发布端口维持严格过滤的桥接网络。【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考