☰
CoreDNS transfer 插件实战指南:为权威插件提供出站区域传输(AXFR/IXFR)与 NOTIFY 通知
2026/10/5 13:06:37 网站建设 项目流程
  • 后端
  • 网络
  • 云原生

【免费下载链接】coredns

CoreDNS is a DNS server that chains plugins

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

导读

transfer是 CoreDNS 中负责出站区域传输(outgoing zone transfer)的核心插件:它为file、auto、secondary、kubernetes等实现了transfer.Transferer接口的权威插件统一应答完整的 AXFR 全量传输请求,并以「IXFR 增量传输 + AXFR 回退」的方式处理增量同步,同时负责在区域变更时向各 secondary 服务器主动发送 NOTIFY 通知。读完本文,你将掌握transfer的配置语法(ZONE、to、source)、与acl插件组合限制传输来源、以及如何借助源码与测试用例理解其请求分发、64KB TCP 消息分片和 NOTIFY 重试的底层机制。

插件定位:它不是区域数据的提供者,而是传输通道的调度者

阅读 plugin/transfer/README.md 可以知道,transfer自身并不持有任何 DNS 记录。它的职责是:为其他权威插件应答区域传输请求。任何插件只要实现了transfer.Transferer接口,就能借助transfer插件对外提供 AXFR / IXFR 服务,并享受统一的 NOTIFY 通知能力。

从 plugin.cfg 可以看到transfer:transfer被正式注册为 CoreDNS 的内置插件。目前官方文档明确列出以下插件基于它实现区域传输:

  • file:以本地 zone 文件为数据源的权威插件;
  • auto:自动加载 zone 文件的权威插件;
  • secondary:从上游主服务器拉取区域的插件;
  • kubernetes:以 Kubernetes Service / Pod 为数据源生成区域的插件。

如果你是一位插件作者,想为自己的插件接入区域传输能力,官方指引是直接阅读 plugin/transfer/transfer.go 了解接口契约——下面我们会从源码层面逐条拆解这份契约。

核心接口:transfer.Transferer契约

transfer插件之所以能通用地为多个权威插件服务,关键在 transfer.go 中定义的接口:

// Transferer may be implemented by plugins to enable zone transfers type Transferer interface { Transfer(zone string, serial uint32) (<-chan []dns.RR, error) }

接口契约有非常明确的语义约定(注释原文即规范):

  • serial == 0表示 AXFR 请求:插件必须把整区的记录全部写入返回的 channel。第一个写入的必须是 SOA 记录,随后是所有其他记录(包括全部 NS 记录及其 glue 记录),最后还要再写一次 SOA 作为传输结束标志。transfer插件只负责把这些记录原样转发给请求方,几乎不做校验。
  • serial != 0表示 IXFR 请求:如果请求方携带的 serial大于等于(更新于)当前区域的 serial,说明对方已是最新,只需向 channel 写入单个 SOA 记录后关闭 channel;如果请求方的 serial小于(更旧于)当前 serial,则执行AXFR 回退——按 AXFR 的方式把整个区域完整传输一遍。
  • 非权威判定:如果插件对该 zone 不权威,必须立即返回transfer.ErrNotAuthoritative错误(定义见 transfer.go)。这一约定至关重要,它保证transfer可以在多个实现了Transferer的插件之间做正确的选择,而不会错误地让插件 X 去传输本应由插件 Y 提供的区域数据。

以file插件为例,plugin/file/xfr.go 先通过lookupZone确认自己是该 zone 的权威,否则返回ErrNotAuthoritative;确认后调用z.Transfer(serial)。其内部实现(plugin/file/xfr.go)正是上述契约的典型写照:IXFR 且 serial 相等时只发 SOA,否则发送 apex(含 SOA)、遍历整棵树的所有记录、最后再补一个 SOA 并关闭 channel。

kubernetes插件的实现位于 plugin/kubernetes/xfr.go,其 AXFR 输出包含 SOA、按需去重的 NS 记录、NS 地址(glue)、以及按 namespace 暴露策略过滤后的 Service 记录;对于多集群场景(isMultiClusterZone)还会走transferMultiClusterServices分支。

配置语法与参数详解

transfer的 Corefile 语法如下(原文语法,完整保留):

transfer [ZONE...] { to ADDRESS... source ADDRESS }

ZONE:要应答传输请求的区域

  • 可指定一个或多个 zone;留空时自动继承所在 server block 的 zone 列表(源码中由plugin.OriginsFromArgsOrServerBlock(c.RemainingArgs(), c.ServerBlockKeys)实现,见 setup.go)。
  • 要对某个 zone 应答传输,同一个 server block 中必须存在另一个同样服务该 zone、且实现了transfer.Transferer的插件——否则没有数据来源,传输自然无从谈起。
  • 允许在一条配置中多次书写transfer块,为不同 zone 组配置不同的to白名单。例如 setup_test.go 中同时配置了transfer example.net example.org {...}与transfer example.com example.edu {...},分别对应两组to地址。

toADDRESS...:允许向其传输的地址

  • 指定允许接收传输的地址列表,to可以被多次指定(多次出现会累积生效)。
  • 使用*表示允许向所有地址传输。
  • 合法的地址格式包括:1.2.3.4、12:34::56(IPv6)、1.2.3.4:5300(IPv4 加端口)、[12:34::56]:5300(IPv6 加端口,方括号包裹)。
  • 省略端口时默认补全为 53:解析逻辑见 setup.go,它调用parse.HostPort(host, transport.Port)做归一化,例如1.2.3.4会被规范化为1.2.3.4:53、[1::2]:34保持端口 34 不变(对应测试见 setup_test.go)。
  • to是必填项:若transfer块内没有出现任何to,配置解析会直接报错'to' is required(见 setup.go)。

sourceADDRESS:发送 NOTIFY 时的本地源地址

  • 指定向to地址发送**区域变更通知(NOTIFY)**时使用的本地 IP 地址。
  • 注意它的作用范围很窄:只影响 NOTIFY 数据包的源地址,并不会改变哪些客户端被允许请求 AXFR/IXFR——允许列表仍由to决定。
  • 解析限制:source必须是合法 IP(net.ParseIP校验),且每个transfer块只能出现一次,重复指定会报source already specified(见 setup.go)。
  • 从 setup_test.go 可以看到几类典型的解析错误场景:缺失to、未知子指令、source给了域名而非 IP、重复source,都会被配置解析器拒绝。

配置解析的完整行为(源码级)

parseTransfer 是完整的解析函数,其行为要点:

  1. 每个transfer块生成一个内部xfr结构(Zones、to、source三个字段,见 transfer.go);
  2. 支持多个块 → 多个xfr,最终汇总到Transfer.xfrs列表;
  3. to支持*与带端口地址,source只收一个合法 IP;
  4. 除了to/source之外的任何子指令都会报unknown property错误。

请求处理主流程:从 TCP 请求到 AXFR/IXFR 应答

transfer的请求处理入口是ServeDNS(transfer.go),完整流程如下:

  1. 类型过滤:只有QType为 AXFR 或 IXFR 的请求才进入传输处理,其他类型直接交给链上下一插件(plugin.NextOrFailure)。
  2. 协议强制:区域传输必须走TCP(DNS 协议规定 AXFR/IXFR 不使用 UDP),非 TCP 请求直接返回REFUSED(transfer.go)。测试用例统一使用test.ResponseWriter{TCP: true}构造 TCP 场景。
  3. 区域匹配:通过longestMatch(transfer.go)在所有xfr块中找到最长匹配的 zone;查询sub.example.org.时,若同时配置了example.org.与sub.example.org.,则更具体的sub.example.org.胜出(对应测试TestLongestMatchMostSpecificZone,见 transfer_test.go)。匹配是大小写不敏感的(TestAXFRZoneMatchCaseInsensitive)。若没有匹配的 zone,请求交回链上后续插件处理。
  4. 来源白名单校验:调用x.allowed(state)(transfer.go),遍历to列表,*直接放行,否则将请求方 IP 与配置地址逐条比对;不匹配则构造REFUSED响应返回(测试TestTransferNotAllowed验证了该行为,transfer_test.go)。
  5. 提取 IXFR serial:IXFR 请求的 AUTHORITY 段必须恰好包含一条 SOA,从中取出 serial;段为空或不是 SOA 则返回SERVFAIL(transfer.go)。
  6. 选择 Transferer:依次调用Transferers中每个插件的Transfer(zone, serial);遇到ErrNotAuthoritative就跳过尝试下一个,直到找到第一个能提供数据的插件(transfer.go)。Transferers列表在启动时自动收集——见 setup.go,它会遍历当前 server block 的所有 handler,把实现了Transferer的插件全部收集进来。select_test.go中的TestZoneSelection验证了「非权威插件被跳过、正确插件被选中」的选择逻辑。
  7. TSIG 支持:如果请求携带 TSIG,transfer会把配置中的 TSIG 密钥表(tsigSecret,来自dnsserver配置,在 OnStartup 时注入)交给dns.Transfer.Out使用,实现带签名的传输(transfer.go)。
  8. 流式写出:从生产者 channel 不断取记录,累积成批次,通过dns.Transfer.Out写回客户端;全部记录传输完成后关闭 channel 并等待写出 goroutine 结束,随后记录日志Outgoing transfer of %d records of zone %q to %s for %d SOA serial。

大区域传输的 64KB 消息分片

DNS over TCP 单条消息上限为 64KB(dns.MaxMsgSize,65535 字节)。transfer.go 对生产者送来的记录做按字节数的批量分片:每条记录累加dns.Len(rr),一旦累计超过约 63000 字节(为 12 字节消息头与 question 段预留余量)就把当前批次作为一个dns.Envelope写出并重置。

测试用例TestTransferLargeRecordBatching(transfer_test.go)用 300 条约 240 字节的 TXT 记录(总计约 75KB)验证了:传输被正确拆分成多个TCP 消息,每个消息打包后都不超过dns.MaxMsgSize,且记录总数(302 = 300 TXT + 2 SOA)不丢失。

客户端中断时的生产者排空

若客户端在传输中途断连(写出失败),transfer.go 会启动一个 goroutine 继续把生产者 channel 中的剩余记录排空,避免生产者的 goroutine 因 channel 缓冲区写满而永久阻塞泄漏。TestTransferDrainsProducerOnClientError(transfer_test.go)专门验证了这一点,failed_write_test.go中的TestWriteMessageFailed则验证了写出失败会向上返回错误。

IXFR 的两个分支在实现中的体现

  • 区域已最新:当 IXFR serial 等于(或新于)当前 serial 时,Transferer 只向 channel 写入一个 SOA。ServeDNS检测到「只收到 1 条记录且它是 SOA」时,不再走常规写出路径,而是直接把这条 SOA 作为单个应答消息返回,并记录日志Outgoing noop, incremental transfer for up to date zone(transfer.go)。测试TestTransferIXFRCurrent验证应答恰好含 1 条 SOA 记录。
  • 区域已过期 → AXFR 回退:serial 更旧时走完整 AXFR 流程。测试TestTransferIXFRFallback(transfer_test.go)用serial-1发起 IXFR,验证最终返回的应答结构与 AXFR 完全一致(validateAXFRResponse要求首尾均为 SOA、共 4 条记录)。

NOTIFY 通知:区域变更时主动告知 secondary

当插件检测到区域数据变化并希望通知其 secondary 时,会回调transfer插件的Notify方法(notify.go):

  1. 构造 NOTIFY 消息m.SetNotify(zone);
  2. 用longestMatch找到匹配该 zone 的xfr块(无匹配则静默返回,不报错);
  3. 遍历该块to列表中的每个 IP 地址(*被跳过,因为通配符没有可通知的具体目标),逐个发送 NOTIFY;
  4. 每个地址的发送失败会被累积,最终通过errors.Join合并返回。

file插件在收到来自主服务器的 NOTIFY 后触发transferIn拉取更新(plugin/file/file.go),而secondary插件正是依赖这一整套「NOTIFY + 主动拉取」机制维持与主服务器的同步。

发送细节与重试逻辑

sendNotify(notify.go)的实现要点:

  • 使用UDP发送(dns.Client默认);
  • 每个地址最多重试 3 次;
  • 只要收到RcodeSuccess即视为成功;
  • 全部重试失败时,根据「网络错误」还是「rcode 非成功」构造不同错误信息。

source的底层实现

notifyClient(notify.go)在配置了source时,会给dns.Client设置Dialer = &net.Dialer{LocalAddr: &net.UDPAddr{IP: x.source}},从而让 NOTIFY 数据包携带指定的源 IP。notify_test.go中的TestNotifyClientSource验证了:未配置source时不设置 Dialer,配置了 IPv6 地址后 Dialer 的 LocalAddr 正是该 IP。TestNotifyMultipleFailures则验证多地址通知的失败聚合——当两个目标分别返回 SERVFAIL 和 REFUSED 时,返回的错误同时包含两个地址与各自的 rcode 描述。

实战配置示例

示例一:与acl组合,限制传输来源网段

transfer自身的to只能做「精确 IP 列表」匹配(源码注释中的 TODO 也承认暂不支持网段,见 transfer.go)。要按网段做精细管控,官方推荐叠加acl插件。下面的 Corefile 片段(摘自 plugin/transfer/README.md 原示例,完整保留)将 AXFR/IXFR 传输限制在10.1.0.0/16网段:

... acl { allow type AXFR net 10.1.0.0/16 allow type IXFR net 10.1.0.0/16 block type AXFR net * block type IXFR net * } transfer { to * } ...

acl插件的policy结构按 action(allow/block/filter/drop)与 qtype 匹配查询(plugin/acl/acl.go),type AXFR/type IXFR正是按 qtype 命中后执行 allow/block。这样transfer侧用to *放开所有来源,真正的网段级访问控制由acl兜底完成。

示例二:从指定本地地址发送 NOTIFY

当服务器有多张网卡、需要让 secondary 从特定地址收到 NOTIFY 时,使用source(摘自 plugin/transfer/README.md 原示例):

... transfer { to 2001:db8::1 source 2001:db8::53 } ...

to 2001:db8::1表示只允许 IPv6 地址2001:db8::1发起传输请求,同时 NOTIFY 将从2001:db8::53发出。注意source不影响哪些客户端被允许请求传输。

示例三:多区域、多目标的白名单配置

参照 setup_test.go 中的解析用例,可以为一个 server block 内的多个区域分别配置传输策略:

example.org { file example.org example.org.signed transfer example.org { to 192.0.2.1 192.0.2.2:5353 [2001:db8::10]:53 source 192.0.2.53 } transfer { to * } }
  • 第一个transfer块:example.org的传输只允许发给192.0.2.1、192.0.2.2:5353、2001:db8::10(端口 53),NOTIFY 源地址固定为192.0.2.53;
  • 第二个transfer块:不写 zone,继承 server block 的 zone 列表,to *表示其余场景放行——具体如何取舍取决于你的安全策略。

常用排错与验证手段

  • 查看传输日志:transfer使用独立的transferlogger(clog.NewWithPlugin("transfer"),transfer.go)。成功的传输会打印Outgoing transfer of N records ...,IXFR 无需更新时打印Outgoing noop, incremental transfer for up to date zone ...,NOTIFY 发送打印Sent notifies for zone ...(Debug 级别)。
  • 用 dig 验证 AXFR:在支持 TCP 的客户端上执行dig @127.0.0.1 example.org AXFR,观察是否返回以 SOA 开头和结尾的完整区域记录。
  • 用 dig 验证 IXFR:dig @127.0.0.1 example.org IXFR=当前serial应只返回单条 SOA;用更小的 serial(如IXFR=1)应触发 AXFR 回退,返回完整区域。
  • 用 dig 验证 NOTIFY 源地址:dig @127.0.0.1 example.org NOTIFY配合抓包或对端日志,确认通知确实来自source指定的 IP。
  • 返回码语义:来源不在to白名单或 acl 拦截时收到REFUSED;非 TCP 的 AXFR/IXFR 请求也返回REFUSED;IXFR 请求的 AUTHORITY 段不含 SOA 时返回SERVFAIL——这些行为均可由 transfer_test.go 与 failed_write_test.go 中的测试用例佐证。

小结

transfer插件通过一个简洁的Transferer接口把「区域数据传输」这件事与具体数据源解耦:file、auto、secondary、kubernetes各自负责生成区域记录,transfer统一负责应答 AXFR/IXFR、执行 IXFR→AXFR 回退、以及向 secondary 发送 NOTIFY。配置上只需掌握ZONE、to、source三个要素,再配合acl插件即可构建出网段级精细管控的权威 DNS 传输方案;源码层面的最长区域匹配、Transferer 选择、64KB 分片写出与 NOTIFY 重试逻辑,则为深入定制或自行实现Transferer的插件作者提供了清晰的可参考实现。

  • 后端
  • 网络
  • 云原生

【免费下载链接】coredns

CoreDNS is a DNS server that chains plugins

项目地址:https://gitcode.com/gh_mirrors/co/coredns
点击查看免费下载
上一篇:NVIDIA Profile Inspector终极指南:解锁显卡隐藏设置,提升游戏性能
下一篇:微信红包自动助手:无需Root的智能抢红包解决方案

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

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

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

立即咨询