TLDR:使用Bind9的Response Policy Zone实现Split-Horizon DNS,同一域名不同网络区域解析到不同记录值的实现方法优化。

之前的文章由一次cert-manager续签失败引发的内部DNS设计思考中提到当把bind9配置成权威DNS域的时候,当zone记录中不存在的记录,由于是权威zone,因此会立即aa响应NXDOMAIN,而不是继续递归或者转发查询。当时的做法是将部分需要向公网权威dns查询的子域增加NS记录指向权威DNS,而bind9还是作为内网的权威dns域维护内部DNS记录。

事实上这种做法是错误的。 虽然的确能够查到正确的解析结果,但实际发生的是,bind9会把这些被委托出去的子域例如blog.hmhomelab.top当作独立的子区,并且向记录值中的NS查询blog.hmhomelab.top的SOA和NS信息。

这时候bind9期待权威dns响应

blog.hmhomelab.top. SOA
blog.hmhomelab.top. NS

但是权威dns托管的是hmhomelab.top这个域,因此响应的SOA仍旧是hmhomelab.top

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;blog.hmhomelab.top.            IN      SOA

;; AUTHORITY SECTION:
hmhomelab.top.          1800    IN      SOA     blair.ns.cloudflare.com. dns.cloudflare.com. 2410573912 10000 2400 604800 1800

bind9认为blair.ns.cloudflare.com托管了blog.hmhomelab.top这个子域,而blair.ns.cloudflare.com却返回了hmhomelab.top的SOA信息,就会产生Name hmhomelab.top (SOA) not subdomain of zone blog.hmhomelab.top – invalid response的报错日志。

27-Jul-2026 11:26:52.907 DNS format error from 162.159.38.121#53 resolving blog.hmhomelab.top/SOA for 172.26.0.1#46801: Name hmhomelab.top (SOA) not subdomain of zone blog.hmhomelab.top -- invalid response
27-Jul-2026 11:26:52.907 FORMERR resolving 'blog.hmhomelab.top/SOA/IN': 162.159.38.121#53
27-Jul-2026 11:26:53.083 DNS format error from 108.162.194.121#53 resolving blog.hmhomelab.top/SOA for 172.26.0.1#46801: Name hmhomelab.top (SOA) not subdomain of zone blog.hmhomelab.top -- invalid response
27-Jul-2026 11:26:53.083 FORMERR resolving 'blog.hmhomelab.top/SOA/IN': 108.162.194.121#53

尽管看似不影响使用,这只是凑巧可用,并且会影响首次解析,第二次查询解析才会收到结果。

需求

就像很多Homelab用户一样,常见的需求是

  • 用DNS服务商的API做家宽动态IP的DDNS
  • 公网/内网发布的服务用同样的域名,分别解析到不同的IP
  • 内网独有的解析,但是不想泄漏到公网DNS中

通常这类场景用adguard、pihole这类软件forwarder+部分记录override就行了,但我想保留AXFR/IXFR区域传送和RFC2136标准动态更新记录的特性,因此还是选择bind9作为主DNS使用。

正确的做法

由于我们需要部分记录从上游dns获取,部分记录自行维护,并且发起dns请求的客户端ip相同,因此这不是DNS视图功能的应用场景。正确的做法是用Response Policy Zone规则,覆盖原始的解析结果,有点像adguard/pihole的行为。

这里有两种做法,如果执着要在本地维护一个权威zone的话,可以起一个secondary zone,从公网权威dns同步记录,加上RPZ区域重写部分主机记录结果。

或者更简单的做法,默认forwarder,RPZ区域重写部分主机记录。

# named.conf
options {
    forwarders {
        [上游smartdns];
    };
    response-policy {
        zone "rpz.local";
    };
};
zone "rpz.local" {
    type master;
    file "/var/lib/bind/rpz.local";
};
# rpz.local 

$TTL 300

@   IN SOA ns1.local. admin.local. (
        2026072405
        1H
        15M
        1W
        300
)

    IN NS ns1.local.

a-host.hmhomelab.top.rpz.local.             IN A       [recordip]
cname-host.hmhomelab.top.rpz.local.             IN CNAME   cname-record.hmhomelab.top.rpz.local.

要注意的是rpz.local文件中,主机名不能写a-host.hmhomelab.top,这样bind9会认为这条记录不属于这个zone,因此会忽略这条记录。CNAME记录值同样也要写成hostname.domain.com.rpz.local,不然解析结果会是原始的hostname.domain.com而不会进到RPZ域中。不同版本的bind9可能配置语法不同,这里用的是BIND 9.20.18。

后记

依赖着AI的能力,优化了bind9中一些不致命的报错,顺带升级版本到了最新stable。不禁感叹AI的强大,可以和它头脑风暴验证自己的思路,大幅度提升了收集资料的效率。