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本篇技术指南基于 Moby 仓库中 nftables 文档模板 usernet-portmap-natunprot.md 展开深入讲解桥接网络gateway_mode_ipv4nat-unprotected场景下的 nftables 规则布局如何复现该场景、完整的ip docker-bridges规则表长什么样、与标准 NAT 模式相比filter-forward-in链和raw-PREROUTING链发生了哪些变化以及这些变化在firewaller/nftabler源码中的实现依据。读完后你可以独立读懂 Docker 在该模式下生成的全部 nftables 规则并理解“非保护 NAT”的确切语义。场景与前提nat-unprotected是 Moby 桥接网络驱动支持的一种网关模式gateway mode。在 bridge_linux.go 中定义了全部合法取值const ( gwModeDefault gwMode gwModeNAT gwMode nat gwModeNATUnprot gwMode nat-unprotected gwModeRouted gwMode routed gwModeIsolated gwMode isolated )创建网络时通过com.docker.network.bridge.gateway_mode_ipv4选项指定newGwMode 负责解析并在遇到未知值时报unknown gateway mode错误。按仓库文档模板描述该场景等价于以禁用用户态代理userland proxy的方式运行 dockerd然后创建一个nat-unprotected网络并在其上运行带端口映射的容器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对应参数说明-o com.docker.network.bridge.namebridge1指定网桥设备名为bridge1便于在 nftables 中按接口名定位规则-o com.docker.network.bridge.gateway_mode_ipv4nat-unprotectedIPv4 网关模式设为“非保护 NAT”--subnet 192.0.2.0/24 --gateway 192.0.2.1TEST-NET-1 文档网段网关为.1-p 8080:80发布宿主机 8080 端口到容器 80 端口。需要注意的前提来自 index.md 与生成测试 nftablesdoc_linux_test.go这套 nftables 规则仅面向开发用途其结构在版本间会变化不是稳定接口生成测试要求宿主机防火墙后端为nftables、未运行 firewalld、且非 rootlessDocker 每次启动时会重建规则表tables are re-created each time Docker startsIPv6 规则与 IPv4 模式相同只是位于ip6 docker-bridges表因此文档只展示 IPv4ip docker-bridges。完整 nftables 规则表该文档模板通过{{index . Ruleset4}}等占位符注入真实捕获的规则。由 TestBridgeNftablesDoc 生成的 usernet-portmap-natunprot.md 中完整的table ip docker-bridges如下容器c1已获取192.0.2.2-p 8080:80生效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 } }几个值得注意的结构点四个ifname : verdict类型的 vmapvirtual map是这套规则的分发机制filter-FORWARD、nat-POSTROUTING等钩子链只做vmap查表跳转把流量分发到每个网桥的专属链__docker0、__bridge1实现按接口隔离nat-prerouting-and-output链中的dnat to 192.0.2.2:80 comment DNAT就是-p 8080:80的落地规则iifname ! bridge1条件排除了来自网桥本接口方向的包nat-postrouting-out__bridge1的masquerade规则把离开bridge1且源地址在192.0.2.0/24的包做地址伪装支撑容器出网Docker 不使用 filter-INPUT 钩子来自宿主机物理网络或宿主机自身的包会被路由进桥接网络命中 filter-FORWARD见 index.md 的说明。与标准 NAT 模式的关键差异文档模板的核心论点是与标准 nat 模式网络 相比大部分规则相同但有两处关键不同它们共同定义了nat-unprotected的语义——不做未发布端口的过滤也不阻止按容器 IP 直连。差异一filter-forward-in 链没有按端口过滤对比两张表标准nat模式默认桥docker0的入方向链末尾是丢弃规则chain filter-forward-in__docker0 { ct state established,related counter accept iifname docker0 counter accept comment ICC counter drop comment UNPUBLISHED PORT DROP }nat-unprotected网络的入方向链则替换为放行规则chain filter-forward-in__bridge1 { ct state established,related counter accept iifname bridge1 counter accept comment ICC counter accept comment UNPROTECTED }也就是说filter-forward-in__bridge1链没有针对发布端口的逐端口放行规则而是接受任意端口的入方向包。这一点在源码 nftabler/network.go 中一目了然——网络配置里的Unprotected标志决定最终落入哪个分支// Incoming traffic if conf.Unprotected { tm.Create(nftables.Rule{ Chain: fwdInChain, Group: fwdInFinalRuleGroup, Rule: []string{counter accept comment UNPROTECTED}, }) } else { tm.Create(nftables.Rule{ Chain: fwdInChain, Group: fwdInFinalRuleGroup, Rule: []string{counter drop comment UNPUBLISHED PORT DROP}, }) }Unprotected的字段定义在 firewaller.go// NetworkConfigFam contains network configuration for a single address family. type NetworkConfigFam struct { HostIP netip.Addr Prefix netip.Prefix Routed bool // Unprotected is true if no rules to filter unpublished ports or direct access from // any remote host are required. Unprotected bool }模板文档特别注明了这条accept的实际用途当filter-FORWARD链的默认策略是 drop 时如果没有这条放行规则转发流量会被整体丢弃UNPROTECTED规则承担了“兜底放行”的角色。差异二raw-PREROUTING 链中没有 DROP DIRECT ACCESS 规则上面的生成文档里raw-PREROUTING链几乎是空的chain raw-PREROUTING { type filter hook prerouting priority raw; policy accept; }而在标准nat模式下每当一个端点容器加入网络时Docker 会在这里追加一条按容器 IP 丢弃外部直连包的规则。这条规则的创建逻辑在 nftabler/endpoint.gofunc (n *network) filterDirectAccess(updater func(nftables.Obj), fam nftables.Family, conf firewaller.NetworkConfigFam, epIP netip.Addr) { if n.config.Internal || conf.Unprotected || conf.Routed || n.fw.config.AllowDirectRouting { return } ifNames : strings.Join(n.config.TrustedHostInterfaces, , ) updater(nftables.Rule{ Chain: rawPreroutingChain, Group: rawPreroutingPortsRuleGroup, Rule: []string{ string(fam), daddr, epIP.String(), iifname ! {, n.config.IfName, ,, ifNames, } counter drop comment DROP DIRECT ACCESS, }, }) }filterDirectAccess在conf.Unprotected为真时直接返回no-op因此nat-unprotected网络下永远不会写入DROP DIRECT ACCESS规则。其效果就是模板文档所写的In chain raw-PREROUTING, theres no DROP DIRECT ACCESS rule, so container can be accessed from outside the host——外部主机可以直接按容器的 IP如192.0.2.2访问该容器而不必经由-p发布的端口做 DNAT。保持不变的规则dnat 与 masquerade文档模板最后强调dnat和masquerade规则仍然存在而且如果启用了用户态代理userland proxy它依然会被启动。这在规则表里可以直接验证端口发布的 DNATnat-prerouting-and-output链中的iifname ! bridge1 tcp dport 8080 counter dnat to 192.0.2.2:80 comment DNAT出网伪装nat-postrouting-out__bridge1链中的oifname ! bridge1 ip saddr 192.0.2.0/24 counter masquerade comment MASQUERADE其生成逻辑在 nftabler/network.go未指定HostIP时选择 masquerade指定了则退化为固定地址的 SNAT。逐端口的规则端口发布规则、以及nat模式下的按端口转发放行由 nftabler/port.go 中的setPerPortRules处理其中同样接收n.config.Config4.Unprotected作为参数——在nat模式下它会为发布端口写入 filter 链中的放行条目而nat-unprotected模式下这一层过滤被网络级的UNPROTECTED放行所取代。iptables 后端的语义与之对应见 iptabler/endpoint.go 与 iptabler/port.go注释明确写道 “gw_modenat-unprotected means theres minimal security for NATed ports”对应的规则文档在 iptablesdoc/generated/usernet-portmap-natunprot.md。可以把nat-unprotected理解为“保留了 NAT 的连通性、移除了 NAT 的隔离性”地址转换照常工作但未发布端口不再被屏蔽、容器 IP 不再对外不可达。这份文档是如何生成和校验的上述规则表并非手写而是由集成测试 TestBridgeNftablesDoc 自动捕获生成的理解其流程有助于确认规则内容的可信度场景声明index切片中的 usernet-portmap-natunprot.md 条目 声明了gwMode: nat-unprotected、网络名bridge1、容器c1的端口映射80/tcp - 8080网段取自docNetworks/docGateways192.0.2.0/24隔离执行每个 section 在自己的网络命名空间里启动一个真实的 dockerdrunTestNet创建网络、运行容器规则捕获runNftables 执行nft -s list table ip docker-bridges把输出按 map/chain 切块存入模板数据Ruleset4、chain filter-forward-in__bridge1等键并将priority -100规范化为dstnat字样模板渲染generate 用text/template渲染templates/下与 section 同名的模板文件即本篇基于的 templates/usernet-portmap-natunprot.md黄金文件比对渲染结果写入 bundles 目录并与 generated/ 中的参考文档做 golden 对比规则不一致则测试失败需要更新时先检查 diff、修改对应模板描述再用TESTFLAGS-update重新生成见该测试文件的包注释。在单元测试层面nftabler_test.go 遍历nat、nat-unprotected、routed三种网关模式将Unprotected: gwmode nat-unprotected传入 firewaller 配置并与 testdata 中的 golden 文件 逐条比对其中每个gwmnat-unprotected的 golden 文件都包含counter accept comment UNPROTECTED行——与上文规则表相互印证。小结nat-unprotected通过com.docker.network.bridge.gateway_mode_ipv4选项指定在源码中解析为 gwModeNATUnprot并映射为 firewaller 的Unprotected标志该模式下 nftables 规则与标准 NAT 模式几乎一致唯二区别是入方向链以counter accept comment UNPROTECTED取代逐端口放行与UNPUBLISHED PORT DROP以及raw-PREROUTING中不写入DROP DIRECT ACCESS从而允许外部直接按容器 IP 访问DNAT端口发布与 masquerade出网伪装规则不受影响用户态代理的启用行为也不变由于未发布端口不再被过滤、容器地址对外可达这是一种“最小安全”的 NAT 模式应在明确接受这一风险面后再用于生产网络本文所述规则可通过 generated/usernet-portmap-natunprot.md 复核或参照 nftablesdoc_linux_test.go 描述的TESTFLAGS-update流程重新生成。【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考