尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Moby 桥接网络 nftables 规则解析:禁用容器间通信(ICC)并发布端口

Moby 桥接网络 nftables 规则解析:禁用容器间通信(ICC)并发布端口 Moby 桥接网络 nftables 规则解析禁用容器间通信ICC并发布端口【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby当你在 MobyDocker Engine中以enable_iccfalse创建用户自定义桥接网络同时发布容器端口时内核防火墙中会出现一组特定的 nftables 规则。本文以仓库中 usernet-portmap-noicc.md 场景文档为主体完整给出该场景下ip docker-bridges表的全部规则、逐条解释其作用并深入 nftabler 源码 说明「放行 vs 丢弃」这一关键判定的产生位置最后给出在真实主机上自行验证的方法与前提。一、文档定位一个由测试自动生成的规则快照这篇场景文档属于integration/network/bridge/nftablesdoc目录下的一套nftables 规则文档体系它记录 Docker Engine 在桥接网络下究竟往内核里写入了什么规则。与手写文档不同它是测试驱动生成的nftablesdoc_linux_test.go 中的TestBridgeNftablesDoc会在一个独立网络命名空间里启动 dockerd按 index 中定义的各场景创建网络和容器随后执行nft -s list table ip docker-bridges抓取真实规则见 runNftables抓取到的规则按「map / chain」拆分成块用templates/下的text/template模板本文档模板即 usernet-portmap-noicc.md渲染成最终 markdown输出到 generated/usernet-portmap-noicc.md新生成的文档会与generated/中的 golden 参考做 diff不一致则测试失败golden 断言需要时用TESTFLAGS-update刷新参考文件。这意味着文中规则与当前代码库的实际行为一一对应而非人工维护的文字描述。同时要注意 index.md 给出的三条前提本文规则的解释都建立在这些前提之上该文档仅供开发参考——Docker 的 nftables 规则结构在不同版本间会变化不是稳定接口IPv6 规则遵循与 IPv4 完全相同的模式只是位于不同的表ip docker-bridges与ip6 docker-bridges文档只展示 IPv4这些表在每次 Docker 启动时重建filter-INPUT钩子不被使用从宿主物理网络或宿主本机到达的包会被路由进桥接网络、命中filter-FORWARD链filter-OUTPUT同理未被使用。二、场景与等效命令本文档描述的精确场景是用户自定义网络上关闭容器间通信ICC且容器发布了端口。等效的手动操作命令为与 测试中的场景定义 一致测试中通过bridge.EnableICC选项传入falsedocker network create \ -o com.docker.network.bridge.namebridge1 \ -o com.docker.network.bridge.enable_iccfalse \ --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1 docker run --network bridge1 -p 8080:80 --name c1 busybox要点网络名bridge1子网192.0.2.0/24网关192.0.2.1容器c1拿到192.0.2.2测试里容器 IP 由 IPAM 分配golden 文档中固定为.2-p 8080:80发布端口enable_iccfalse是本场景与 默认场景 usernet-portmap.md 的唯一区别。三、完整规则集IPv4以下是该场景下ip docker-bridges表的全量内容与 generated 文档 完全一致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; ip daddr 192.0.2.2 iifname ! bridge1 counter drop comment DROP DIRECT ACCESS } 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 drop 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 { oifname ! bridge1 ip saddr 192.0.2.0/24 counter masquerade comment MASQUERADE } }3.1 顶层结构vmap 分发到「每桥接一个」的链四个ifname : verdict类型的 map 把「接口名」映射到对应的 jump 链。filter-FORWARD链本体只有两条 vmap 指令按出接口oifname跳入filter-forward-in__桥按入接口iifname跳入filter-forward-out__桥。这样每个网桥默认的docker0与用户创建的bridge1各有一套独立的进出过滤链互不干扰。nat-POSTROUTING同理按出/入接口分发到nat-postrouting-{in,out}__桥链。注意filter-FORWARD的policy acceptDocker 依赖显式 drop 规则来封锁流量而不是收紧默认策略链内所有未匹配的流量最终都会落到各链末尾的兜底规则如UNPUBLISHED PORT DROP。3.2 DNAT 与「禁止直连容器 IP」nat-prerouting-and-output中的iifname ! bridge1 tcp dport 8080 ... dnat to 192.0.2.2:80是端口发布的 dstnat 规则来自桥之外的流量宿主本机、外部网络访问8080时被改写到容器192.0.2.2:80。该链同时被nat-PREROUTING经fib daddr type local判定目标为本机地址后和nat-OUTPUT宿主机自身发起的流量复用因此curl 127.0.0.1:8080与从外网访问都走同一条 DNAT 规则raw-PREROUTING中的ip daddr 192.0.2.2 iifname ! bridge1 counter drop comment DROP DIRECT ACCESS是关键防护任何绕过 NAT 直接访问容器 IP192.0.2.2的包例如同 LAN 主机直连容器地址在进入 conntrack 之前就被丢弃。它保证了容器只能经由发布端口被触达nat-postrouting-out__bridge1中的 masquerade 规则保证容器发往外部网络oifname ! bridge1时源地址192.0.2.0/24被替换为宿主地址即常见的 SNAT/MASQUERADE 行为docker0侧对应172.17.0.0/16。3.3 每桥接的进出过滤链以bridge1为例filter-forward-out__bridge1容器出站方向先放行 conntrack 中established,related状态的包保证回包畅通其余全部 accept并打OUTGOING注释——出站不做限制出网过滤交给后续路由链filter-forward-in__bridge1入站方向是安全策略的核心下一节单独剖析。四、核心差异filter-forward-in__bridge1中的 ICC drop 规则文档的核心结论只有一句话除 ICC 判定外本场景的规则与 启用 ICC 的网络 完全相同。差异全部集中在这条链chain filter-forward-in__bridge1 { ct state established,related counter accept iifname bridge1 counter drop comment ICC ip daddr 192.0.2.2 tcp dport 80 counter accept counter drop comment UNPUBLISHED PORT DROP }逐条拆解规则按顺序匹配ct state established,related counter accept处于已建立/相关状态的连接直接放行。这是 conntrack 的固有语义——即使网络已关闭 ICC规则变更之前已经建立的连接的回程流量仍能通过但新连接的初始 SYN 不受此规则影响会继续向下匹配。iifname bridge1 counter drop comment ICC入接口是本桥的包即源与目的都在同一bridge1网络上的容器互访新连接在此被丢弃。这正是enable_iccfalse的内核级实现。作为对照filter-forward-in__docker0中对应位置是iifname docker0 counter accept comment ICC放行两条规则唯一的差别就是 verdict。ip daddr 192.0.2.2 tcp dport 80 counter accept放行已 DNAT 的发布端口流量。从外部来的8080经 DNAT 改写为192.0.2.2:80后被路由进bridge1命中这条 accept由于它排在 ICC drop 之后外部访问不受 ICC 策略影响而容器间访问192.0.2.2:80会被第 2 条先行丢弃。counter drop comment UNPUBLISHED PORT DROP兜底丢弃——凡未发布端口的入站流量包括试图直连容器上未发布端口的流量一律丢弃。counter关键字使得每条规则都带计数运维时可用nft -s list直接看到每条规则命中了多少包这对排查「为什么容器 A ping 不通容器 B」这类问题非常有用。五、源码纵深iccVerdict是如何决定的文档中「drop而不是 accept」的差别在源码里对应一个非常小的分支。在 nftabler/network.goiccVerdict : accept if !n.config.ICC { iccVerdict drop }随后该 verdict 被填入规则非 internal 网络分支// Inter-Container Communication tm.Create(nftables.Rule{ Chain: fwdInChain, Group: fwdInICCRuleGroup, Rule: []string{iifname , n.config.IfName, counter, iccVerdict, comment ICC}, })这解释了 golden 文档中为什么 ICC 行永远是iifname bridge1 counter accept|drop comment ICC的固定形态。UNPUBLISHED PORT DROP兜底则来自同一文件的最终规则组非--internal网络一律 drop。ICC配置沿这条链路向上游溯源用户输入docker network create -o com.docker.network.bridge.enable_iccfalse ...。选项标签常量定义在 bridge 驱动的 labels.go选项解析bridge_linux.go 中对EnableICC选项用strconv.ParseBool(value)解析为布尔值因此false/0/no等均可接受默认值默认的docker0桥接配置中EnableICC默认为truebridge_linux.go 附近的默认bridgeConfig即不显式指定时容器间通信是放行的配置传递firewaller.go 中ICC字段注释明确其含义——ICC is false if containers on the bridge should not be able to communicatenftables 后端与 iptables 后端共用该配置持久化网络配置变更含EnableICC会写入网络 storebridge_store.godaemon 重启后按原配置重建规则与「表在每次 Docker 启动时重建」的描述一致。顺带一提同一功能在 iptables 后端的对应实现位于 iptabler/network.go 的setIcc相关逻辑可见 Moby 目前同时维护 nftables 与 iptables 两套防火墙后端而本系列文档只覆盖 nftables 后端。六、在真实主机上自行验证你可以完全复现本节的验证流程。前提对应 测试的 skip 条件以 root 运行非 rootless宿主防火墙后端为 nftablesfirewalld运行时该测试会跳过因为 firewalld 会接管/重写规则无法文档化稳定输出;内核支持 nftables现代发行版默认支持。步骤# 1. 创建与文档等效的网络和容器 docker network create \ -o com.docker.network.bridge.namebridge1 \ -o com.docker.network.bridge.enable_iccfalse \ --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1 docker run -d --network bridge1 -p 8080:80 --name c1 busybox top # 2. 查看 Docker 写入的 nftables 表-s 显示计数器 sudo nft -s list table ip docker-bridges预期结果输出应与 generated/usernet-portmap-noicc.md 中的规则集一致其中容器 IP、宿主地址等可能因 IPAM 分配而异但链结构、comment标记DNAT、ICC、MASQUERADE、UNPUBLISHED PORT DROP、DROP DIRECT ACCESS应完全吻合。行为验证# 容器间通信应失败ICC 已禁用 docker run --rm --network bridge1 busybox wget -qO- --timeout3 192.0.2.2 || echo blocked as expected # 经发布端口的访问应成功 curl -s http://127.0.0.1:8080七、同系列的其他场景本文档只是整套 nftables 场景文档之一index.md 列出了全部快照可按需对比理解规则如何随配置变化New daemon刚启动、无任何用户网络的基线状态usernet-portmap与本场景相同但启用 ICC对照组usernet-portmap-lo端口发布到 loopback 地址usernet-portmap-noproxy--userland-proxyfalse关闭用户态代理usernet-internal--internal网络对比 ICC 开/关两种usernet-portmap-routedrouted 网关模式usernet-portmap-natunprotnat-unprotected 模式swarm-portmapSwarm 服务发布端口。小结enable_iccfalse在内核层面的全部效果就是让 per-bridge 入站过滤链中那条iifname 桥 counter drop comment ICC规则的 verdict 从accept变为drop源码中该判定由 nftabler 的iccVerdict分支 一行代码决定发布端口的 DNAT/MASQUERADE 链路nat-prerouting-and-output、raw-PREROUTING的直连拦截、每桥的 masquerade 链与 ICC 策略正交不受其影响这套文档由 TestBridgeNftablesDoc 从运行中的 daemon 实时抓取生成并做 golden 比对因此规则快照与当前代码行为严格一致但如 index.md 所警告Docker 的 nftables 结构不是稳定接口跨版本对比时需留意规则可能变化。【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表