☰
CoreDNS 0.9.10 版本深度解析:反向区非八位边界语法、dnssec 委派 DS 签名与 hosts 插件热加载
2026/10/5 4:29:14 网站建设 项目流程
  • 后端
  • 网络
  • 云原生

【免费下载链接】coredns

CoreDNS is a DNS server that chains plugins

项目地址:https://gitcode.com/gh_mirrors/co/coredns
点击查看免费下载

导读

CoreDNS 0.9.10 是项目在 2017 年 11 月发布的一个以修复与增强为主的次要版本(minor release),其核心亮点集中在三处:反向区(reverse zone)语法扩展至支持非八位边界 CIDR、dnssec 插件在签名委派(delegation)响应时自动插入并签名 DS 记录、以及hosts 插件改为在独立 goroutine 中周期性检查 /etc/hosts 的更新。本文以官方发布说明 notes/coredns-0.9.10.md 为骨架,结合当前仓库源码与测试用例逐项还原这三处改动的底层实现原理,读者读完即可理解 CoreDNS 如何把192.168.1.0/17这类写法正确展开为多个反向区,以及 dnssec 与 hosts 插件内部的实际工作流。

版本概览:一个聚焦修复的次要版本

CoreDNS 的核心定位是"一个通过插件链实现 DNS 功能的 DNS 服务器"(a DNS server that chains plugins)。0.9.10 属于 minor release,即不引入破坏性架构变化,而是对既有能力做修正与增强。发布说明将其改动划分为两个层面:

  • Core(核心):反向区语法扩展、大量文档清理、为更多平台发布预编译二进制。
  • Plugins(插件):dnssec 插件的委派签名增强、hosts 插件的异步重载。

从当前仓库的 notes 目录可以确认,该版本发布说明位于 notes/coredns-0.9.10.md,日期为 2017-11-03。需要说明的是,下文引用的源码均为当前仓库中的实现,它们保留了 0.9.10 引入的能力并在此基础上持续演进,因此可作为理解该版本特性的"当前形态证据"。

Core 变更一:反向区语法支持非八位边界

传统反向区的边界约束

在 DNS 体系中,IPv4 反向解析域为in-addr.arpa,每个标签对应一个十进制八位组;IPv6 反向解析域为ip6.arpa,每个标签对应一个十六进制半字节(nibble)。因此,一个完整的反向区在"名称标签"上天然以 8 位(IPv4)或 4 位(IPv6)为边界对齐:

  • IPv4:/8、/16、/24、/32可以直接对应到10.in-addr.arpa.、0.168.192.in-addr.arpa.这类名称;
  • IPv6:/4、/8、……、/32、/64等 4 的倍数前缀可直接映射到 nibble 边界。

0.9.10 引入的扩展语法

发布说明指出,反向区语法被扩展为允许非八位边界写法,并给出了示例:

192.168.1.0/17 { ... }

并明确"将会正确工作"(Will behave correctly)。也就是说,在 0.9.10 之前,Corefile 中反向区只能写八位对齐的 CIDR;而 0.9.10 起,任意前缀长度的 CIDR 写法都能被解析,CoreDNS 会将其自动拆分为多个边界对齐的反向区并同时托管。

源码原理:从 Corefile 字符串到多个反向区

这一能力在当前仓库中由三处代码协作完成:

  1. 字符串拆分与 CIDR 识别:plugin/normalize.go#L138-L179 中的SplitHostPort函数负责把 Corefile 里的"host(可能带端口)"拆开。其关键逻辑是:

    • 从字符串末尾寻找冒号并尝试解析为端口(反向区写法要求/掩码出现在端口之前);
    • 校验域名长度不超过 255 且是合法域名;
    • 调用net.ParseCIDR(s)尝试按 CIDR 解析,如果成功且不是非法形式(如:...开头或以0开头的 IPv6 段),就进入反向区展开流程;否则按普通域名返回。
  2. 按标签边界拆分子网:plugin/pkg/cidr/cidr.go#L17-L35 中的Split函数把不落在边界上的网络拆成若干个边界对齐的子网:

    • IPv4 的边界固定为 8 位(每个八位组一个标签),IPv6 的边界固定为 4 位(每个 nibble 一个标签);
    • 若掩码长度已是边界的整数倍,则原样返回(如10.0.0.0/16返回10.0.0.0/16);
    • 否则将掩码向上取整到下一个边界(如/17取整为/24),并把原网络细分出2^(newPrefix - oldPrefix)个子网。
  3. 生成反向区名称:plugin/pkg/cidr/cidr.go#L54-L83 中的Reverse函数把每个拆分后的子网转换成权威反向区:

    • 先用dns.ReverseAddr得到完整的反向名称;
    • 再根据掩码与边界的关系,用dns.NextLabel从尾部去掉多余的标签,使返回的区恰好覆盖该子网范围。

normalize.go中注释也明确提到它与 core/dnsserver/address.go 存在同源关系("we should unify these two impls"),可见反向区展开是贯穿核心启动流程与插件区名归一化的公共能力。

拆分的实际效果(测试数据)

Split与Reverse的行为在 plugin/pkg/cidr/cidr_test.go 中有完整表格化验证,例如:

输入 CIDR拆分后的子网生成的权威反向区
10.0.0.0/1510.0.0.0/16、10.1.0.0/160.10.in-addr.arpa.、1.10.in-addr.arpa.
10.0.0.0/1610.0.0.0/160.10.in-addr.arpa.
192.168.1.1/23192.168.0.0/24、192.168.1.0/240.168.192.in-addr.arpa.、1.168.192.in-addr.arpa.
10.129.60.0/224 个/24子网60.129.10.in-addr.arpa.等 4 个反向区
2001:db8::/312001:db8::/32、2001:db9::/328.b.d.0.1.0.0.2.ip6.arpa.、9.b.d.0.1.0.0.2.ip6.arpa.

在 plugin/normalize_test.go#L74-L141 的TestHostNormalizeExact中,还能看到带端口的写法(如10.0.0.0/8:53→10.in-addr.arpa.)、IPv6 长前缀(2001:db8::/115等)以及非法输入(如::fFFF:B/F/115返回 nil)的完整覆盖。

回到发布说明的例子192.168.1.0/17:按上述算法,/17向上取整到/24,会被拆成192.168.0.0/24到192.168.127.0/24共 128 个/24子网,对应0.168.192.in-addr.arpa.到127.168.192.in-addr.arpa.这 128 个反向区,CoreDNS 会对这些区同时生效。这就是"非八位边界写法也会正确工作"的完整含义。

Core 变更二:文档清理与更多平台的二进制

发布说明中另外两项 Core 层面的改动是:

  • 大量文档清理(Lots of documentation cleanups):0.9.10 对插件文档做了系统性整理。从当前仓库看,这一传统被延续下来,例如 plugin/hosts/README.md、plugin/dnssec/README.md 等每个插件目录下都配有独立 README,man 手册目录man/中也有coredns-*.7系列页面与 README.md 顶层说明。
  • 更多平台发布二进制(More platforms have binaries for each release):即每个发布版本同时提供更多操作系统/架构的预编译产物,方便用户在目标平台直接部署。

这两项属于发布工程层面的改进,不涉及运行时行为变化。

插件变更一:dnssec 在委派响应中插入并签名 DS 记录

背景:委派(delegation)与 DS 记录

当权威服务器返回一个委派响应(即子域由其他 DNS 服务器负责)时,父区需要在授权段(NS 段)提供DS 记录,用于 DNSSEC 验证链的衔接——解析器只有拿到父区签名的 DS 才能验证子区 DNSKEY 的合法性。0.9.10 之前,dnssec 插件在签名委派响应时并不会补充 DS;0.9.10 起,它在签名委派响应时会插入 DS 记录并对其签名。

源码实现

当前实现集中在 plugin/dnssec/dnssec.go#L44-L71 的Sign方法中,方法注释明确写着:"For delegations it will insert DS records and sign those"(对于委派,将插入 DS 记录并签名)。其流程为:

  1. 用response.Typify判定响应类型为Delegation;
  2. 遍历响应授权段的 RRset,收集所有dns.TypeDS记录;
  3. 若存在 DS 记录,则调用d.sign(ds, ...)为这些 DS 生成 RRSIG;
  4. 若不存在 DS 记录(委派点未配置 DS 时),则走d.nsec(...)分支,为"该处无 DS"生成 NSEC 记录并签名——这正是 dnssec 插件"NSEC black lies"(黑谎)认证式拒绝存在的实现方式,见 plugin/dnssec/black_lies.go,其中delegationBitmap明确列出了 DS 所在位置应包含的类型位图。

DS 记录的来源是 DNSKEY:在 plugin/dnssec/dnskey.go#L26 中,每个被加载的 DNSKEY 都会携带D *dns.DS字段,通过dk.ToDS(dns.SHA256)生成(默认使用 SHA-256 摘要算法,见 plugin/dnssec/dnskey.go#L66-L72),签名时直接取用。

测试验证

  • plugin/dnssec/dnssec_test.go#L129-L146 的TestDelegationSigned/TestDelegationUnSigned验证了委派响应是否被正确签名;
  • plugin/dnssec/handler_test.go 通过完整请求/响应用例验证:带 DO 位的 DS 查询会得到DS + RRSIG,而未签名区域的 DS 查询会得到签名的 NSEC 拒绝证据(相关断言如correctNsecForDS,见 plugin/dnssec/black_lies_test.go#L208-L213)。

插件变更二:hosts 插件在独立 goroutine 中检查 /etc/hosts 更新

背景

hosts 插件用于从/etc/hosts文件提供区域解析("serves zones from a/etc/hostsfile",见 plugin/hosts/README.md)。0.9.10 之前,hosts 文件内容在启动时预加载,后续改动无法被感知;0.9.10 起,插件改为在独立的 goroutine 中周期性检查 hosts 文件是否更新,改动即可被自动吸收,无需重启 CoreDNS。

源码实现

核心逻辑在 plugin/hosts/setup.go#L20-L40 的periodicHostsUpdate函数:

  • 创建一个time.NewTicker(h.options.reload)定时器,并启动 goroutine 循环监听 ticker;
  • 每次 tick 触发h.readHosts(),重新读取 hosts 文件内容;
  • 通过parseChan实现优雅关闭:在插件 Shutdown 时close(parseChan),goroutine 随即退出(plugin/hosts/setup.go#L55-L58);
  • 启动阶段(OnStartup)会先执行一次h.readHosts()完成初始加载(plugin/hosts/setup.go#L50-L53)。

重载间隔的默认值定义在 plugin/hosts/hostsfile.go#L45-L49 的newOptions中:reload默认为5 秒(README 亦写明 "The plugin reloads the content of the hosts file every 5 seconds")。Corefile 中可通过reload DURATION调整,设为 0 秒则禁用周期性重载(plugin/hosts/setup.go#L23-L25 中reload == 0时直接返回、不启动 goroutine)。典型的配置片段:

.:53 { hosts { fallthrough reload 30s } }

并发安全方面,hosts 文件的重载读取与查询共享通过锁保护;plugin/hosts/hostsfile.go#L116 的注释说明 mtime 与 size 只由单个 goroutine 读写,而 plugin/hosts/hostsfile_test.go#L252 还记录了历史上通过"重载时交换 hmap 引用"来避免指针悬空问题的回归测试。此外插件暴露了指标coredns_hosts_reload_timestamp_seconds(最近一次重载的时间戳,见 plugin/hosts/metrics.go#L18-L23),便于运维监控重载是否按预期发生。

发布信息与贡献者

0.9.10 的发布标签为v0.9.10。根据发布说明,参与该版本工作的贡献者包括:Chris O'Haver、Miek Gieben、Pat Moroney、Paul Hoffman、Sandeep Rajan、Yong Tang。发布说明还邀请社区通过 issue 跟踪与代码贡献参与后续版本——对应仓库的notes/目录中保留了自 0.9.9 至今的完整发布记录,可纵向对比各版本的演进脉络。

小结

CoreDNS 0.9.10 虽然体量不大,却为后续版本奠定了三项关键能力:

  1. 反向区灵活化:192.168.1.0/17这类非八位边界 CIDR 写法被完整支持,核心机制(SplitHostPort→cidr.Split→cidr.Reverse)至今仍保留在 plugin/normalize.go 与 plugin/pkg/cidr/cidr.go 中,并拥有完善的表格化测试;
  2. DNSSEC 委派完整性:dnssec 插件对委派响应自动插入并签名 DS 记录,配合 NSEC black lies 提供对"无 DS"情况的认证式否定,使 DNSSEC 验证链在委派点不再断裂;
  3. hosts 热更新:hosts 插件以独立 goroutine + ticker 的方式周期性重载/etc/hosts,默认 5 秒、可配置可关闭,并配有重载时间戳指标。

对希望深入源码的读者,建议按 plugin/pkg/cidr/cidr_test.go → plugin/normalize_test.go → plugin/dnssec/handler_test.go → plugin/hosts/setup.go 的顺序阅读测试与实现,可快速建立这三项能力的完整认知。

  • 后端
  • 网络
  • 云原生

【免费下载链接】coredns

CoreDNS is a DNS server that chains plugins

项目地址:https://gitcode.com/gh_mirrors/co/coredns
点击查看免费下载

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

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

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

立即咨询