]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Cache glue only for enabled address families 11889/head
authorColin Vidal <colin@isc.org>
Wed, 22 Apr 2026 14:54:24 +0000 (16:54 +0200)
committerColin Vidal <colin@isc.org>
Thu, 18 Jun 2026 06:48:20 +0000 (08:48 +0200)
When caching delegation NS data, only use A/AAAA glue records if the
resolver has the corresponding IPv4/IPv6 dispatcher configured. If IPv4
or IPv6 is disabled, ignore glue for that family and fall back to
caching the nameserver name if there is no glue from the other supported
family.

The new `cache_delegns` system test is covering delegation NS caching
with dual-stack resolver, IPv4-only, IPv6-only configurations. It also
set up an authoritative sever with zones with A-only, AAAA-only, and
dual-stack glue, which are all queried, and checks the delegation
database dump to confirm that the cached delegation data correspond to
the resolver configuration.

bin/tests/system/cache_delegns/ns1/named.conf.j2 [new file with mode: 0644]
bin/tests/system/cache_delegns/ns1/root.db.j2 [new file with mode: 0644]
bin/tests/system/cache_delegns/ns2/named.args.j2 [new file with mode: 0644]
bin/tests/system/cache_delegns/ns2/named.conf.j2 [new file with mode: 0644]
bin/tests/system/cache_delegns/ns2/root.hint [new file with mode: 0644]
bin/tests/system/cache_delegns/tests_cache_delegns.py [new file with mode: 0644]
lib/dns/resolver.c

diff --git a/bin/tests/system/cache_delegns/ns1/named.conf.j2 b/bin/tests/system/cache_delegns/ns1/named.conf.j2
new file mode 100644 (file)
index 0000000..81f7b54
--- /dev/null
@@ -0,0 +1,17 @@
+options {
+       query-source address 10.53.0.1;
+       notify-source 10.53.0.1;
+       transfer-source 10.53.0.1;
+       port @PORT@;
+       pid-file "named.pid";
+       listen-on { 10.53.0.1; };
+       listen-on-v6 { fd92:7065:b8e:ffff::1; };
+       recursion no;
+       notify yes;
+       dnssec-validation no;
+};
+
+zone "." {
+       type primary;
+       file "root.db";
+};
diff --git a/bin/tests/system/cache_delegns/ns1/root.db.j2 b/bin/tests/system/cache_delegns/ns1/root.db.j2
new file mode 100644 (file)
index 0000000..a6739ab
--- /dev/null
@@ -0,0 +1,21 @@
+$TTL 300
+.                      IN SOA  gson.nominum.com. a.root.servers.nil. (
+                               2000042100      ; serial
+                               600             ; refresh
+                               600             ; retry
+                               1200            ; expire
+                               600             ; minimum
+                               )
+.                      NS      a.root-servers.nil.
+a.root-servers.nil.    A       10.53.0.1
+a.root-servers.nil.    AAAA    fd92:7065:b8e:ffff::1
+
+test-a.                        NS      ns.test-a.
+ns.test-a.             A       10.10.10.10
+
+test-aaaa.             NS      ns.test-aaaa.
+ns.test-aaaa.          AAAA    acdc::acdc
+
+test-both.             NS      ns.test-both.
+ns.test-both.          A       11.11.11.11
+ns.test-both.          AAAA    ffac::dcff
diff --git a/bin/tests/system/cache_delegns/ns2/named.args.j2 b/bin/tests/system/cache_delegns/ns2/named.args.j2
new file mode 100644 (file)
index 0000000..94aed53
--- /dev/null
@@ -0,0 +1,4 @@
+{% set flagv4 = flagv4 | default("") %}
+{% set flagv6 = flagv6 | default("") %}
+
+-m record -c named.conf -d 99 -D cache_delegns-ns2 -g -T maxcachesize=2097152 @flagv4@ @flagv6@
diff --git a/bin/tests/system/cache_delegns/ns2/named.conf.j2 b/bin/tests/system/cache_delegns/ns2/named.conf.j2
new file mode 100644 (file)
index 0000000..6ab4db6
--- /dev/null
@@ -0,0 +1,53 @@
+{% set disablev4 = disablev4 | default(False) %}
+{% set disablev6 = disablev6 | default(False) %}
+{% set dns64 = dns64 | default(False) %}
+
+options {
+{% if disablev4 %}
+       query-source address none;
+{% else %}
+       query-source address 10.53.0.2;
+{% endif %}
+
+{% if disablev6 %}
+       query-source-v6 none;
+{% else %}
+       query-source-v6 address fd92:7065:b8e:ffff::2;
+{% endif %}
+
+       notify-source 10.53.0.2;
+       transfer-source 10.53.0.2;
+       listen-on { 10.53.0.2; };
+
+       notify-source-v6 fd92:7065:b8e:ffff::2;
+       transfer-source-v6 fd92:7065:b8e:ffff::2;
+       listen-on-v6 { fd92:7065:b8e:ffff::2; };
+
+       port @PORT@;
+       pid-file "named.pid";
+       recursion yes;
+       dnssec-validation no;
+
+{% if dns64 %}
+       resolver-use-dns64 yes;
+       dns64 fd92:7065:b8e:fffe::/96 {
+               clients { any; };
+               mapped { 10.53.0.4; any; };
+               suffix ::;
+       };
+{% endif %}
+};
+
+zone "." {
+       type hint;
+       file "root.hint";
+};
+
+key rndc_key {
+       secret "1234abcd8765";
+       algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+       inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
diff --git a/bin/tests/system/cache_delegns/ns2/root.hint b/bin/tests/system/cache_delegns/ns2/root.hint
new file mode 100644 (file)
index 0000000..9275f85
--- /dev/null
@@ -0,0 +1,4 @@
+$TTL 999999
+.                       IN NS   a.root-servers.nil.
+a.root-servers.nil.     IN A    10.53.0.1
+a.root-servers.nil.     IN AAAA fd92:7065:b8e:ffff::1
diff --git a/bin/tests/system/cache_delegns/tests_cache_delegns.py b/bin/tests/system/cache_delegns/tests_cache_delegns.py
new file mode 100644 (file)
index 0000000..64b1a9a
--- /dev/null
@@ -0,0 +1,102 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from re import compile as Re
+
+import isctest
+
+
+def query_and_dump(ns):
+    names = ["test-a", "test-aaaa", "test-both"]
+    for name in names:
+        msg = isctest.query.create(name, "A")
+        isctest.query.udp(msg, ns.ip)
+    with ns.watch_log_from_here() as watcher:
+        ns.rndc("dumpdb -deleg")
+        watcher.wait_for_line("dumpdb complete")
+    return isctest.text.TextFile(f"{ns.identifier}/named_dump.db")
+
+
+def found(dump, txt):
+    assert len(dump.grep(Re(txt))) == 1
+
+
+def nfound(dump, txt):
+    assert len(dump.grep(Re(txt))) == 0
+
+
+def reconfig(ns, templates, disablev4, disablev6, dns64=False):
+    templates.render(
+        f"{ns.identifier}/named.conf",
+        {"disablev4": disablev4, "disablev6": disablev6, "dns64": dns64},
+    )
+    with ns.watch_log_from_here() as watcher:
+        ns.rndc("flush")
+        watcher.wait_for_line("flushing caches in all views succeeded")
+        ns.rndc("reconfig")
+        watcher.wait_for_line("running")
+
+
+def test_cache_delegns(ns2, templates):
+    dump = query_and_dump(ns2)
+
+    # By default the resoler has IPv4 and IPv6 dispatchers, so all
+    # available glues are used and no server name is used.
+    found(dump, "test-a. .* DELEG server-ipv4=10.10.10.10")
+    found(dump, "test-aaaa. .* DELEG server-ipv6=acdc::acdc")
+    found(dump, "test-both. .* DELEG server-ipv4=11.11.11.11 server-ipv6=ffac::dcff")
+    nfound(dump, "test-a. .* DELEG server-name=.*")
+    nfound(dump, "test-aaaa. .* DELEG server-name=.*")
+    nfound(dump, "test-both. .* DELEG server-name=.*")
+
+    reconfig(ns2, templates, disablev4=False, disablev6=True)
+    dump = query_and_dump(ns2)
+
+    # The resolver only has IPv4 dispatcher now, so it won't uses the
+    # IPv6 glues (and uses the server name instead, if no IPv4 provided).
+    found(dump, "test-a. .* DELEG server-ipv4=10.10.10.10")
+    found(dump, "test-both. .* DELEG server-ipv4=11.11.11.11")
+    nfound(dump, "test-a. .* DELEG server-name=.*")
+    nfound(dump, "test-both. .* DELEG server-name=.*")
+
+    # Nor IPv4 (not provided) nor IPv6 (provided but not used) nor NS name
+    # (as no point storing it, we can't resolve it).
+    nfound(dump, "test-aaaa. .* DELEG .*")
+
+    reconfig(ns2, templates, disablev4=True, disablev6=False)
+    dump = query_and_dump(ns2)
+
+    # The resolver only has IPv6 dispatcher now, so it won't uses the
+    # IPv4 glues (and uses the server name instead, if no IPv6 provided).
+    nfound(dump, "test-a. .* DELEG server-ipv4=.*")
+    found(dump, "test-aaaa. .* DELEG server-ipv6=acdc::acdc")
+    found(dump, "test-both. .* DELEG server-ipv6=ffac::dcff")
+    nfound(dump, "test-aaaa. .* DELEG server-name=.*")
+    nfound(dump, "test-both. .* DELEG server-name=.*")
+
+    # Nor IPv4 (provided by not used) nor IPv6 (not provided) nor NS name
+    # (as no point storing it, we can't resolve it).
+    nfound(dump, "test-a. .* DELEG .*")
+
+    # This is now testing a specific case, where the resolver is IPv6 only
+    # but uses resolver-use-dns64 to resolve IPv4 glues only. In this
+    # specific case, the IPv4 glues must _still_ be added in the database.
+    # So in practice, the result is the same as if both IPv4 and IPv6 are
+    # enabled.
+    reconfig(ns2, templates, disablev4=True, disablev6=False, dns64=True)
+    dump = query_and_dump(ns2)
+
+    found(dump, "test-a. .* DELEG server-ipv4=10.10.10.10")
+    found(dump, "test-aaaa. .* DELEG server-ipv6=acdc::acdc")
+    found(dump, "test-both. .* DELEG server-ipv4=11.11.11.11 server-ipv6=ffac::dcff")
+    nfound(dump, "test-a. .* DELEG server-name=.*")
+    nfound(dump, "test-aaaa. .* DELEG server-name=.*")
+    nfound(dump, "test-both. .* DELEG server-name=.*")
index 20b75dd25ef23f84d82f96b5e884398637917347..699f833eb816cfcff8a306f19d5029c6bd34f6eb 100644 (file)
@@ -6730,6 +6730,13 @@ cache_delegglue(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
        dns_rdataset_t *rdataset = NULL;
        size_t naddrs = 0;
        isc_result_t result;
+       dns_resolver_t *res = rctx->fctx->res;
+       bool hasv4 = res->dispatches4 != NULL;
+       bool dns64 = !ISC_LIST_EMPTY(res->view->dns64) && res->view->usedns64;
+
+       if (!hasv4 && !dns64) {
+               return 0;
+       }
 
        result = dns_message_findname(rctx->query->rmessage,
                                      DNS_SECTION_ADDITIONAL, nsname,
@@ -6767,6 +6774,10 @@ cache_delegglue6(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
        size_t naddrs = 0;
        isc_result_t result;
 
+       if (rctx->fctx->res->dispatches6 == NULL) {
+               return 0;
+       }
+
        result = dns_message_findname(rctx->query->rmessage,
                                      DNS_SECTION_ADDITIONAL, nsname,
                                      dns_rdatatype_aaaa, 0, NULL, &rdataset);
@@ -6814,8 +6825,6 @@ cache_delegns(respctx_t *rctx) {
        dns_delegdb_t *delegdb = fctx->res->view->deleg;
        dns_delegset_t *delegset = NULL;
        dns_ttl_t ttl = rctx->ns_rdataset->ttl;
-       dns_fixedname_t fparent;
-       dns_name_t *parent = dns_fixedname_initname(&fparent);
        size_t labels;
        size_t ns_count = 0;
        size_t max_servers = fctx->res->view->max_delegation_servers;
@@ -6825,17 +6834,6 @@ cache_delegns(respctx_t *rctx) {
 
        dns_delegset_allocset(delegdb, &delegset);
 
-       /*
-        * The top of the delegated zone is `rctx->ns_name`. So truncating
-        * the first label gives the common parent domain allowed to get
-        * glues (this allows in-domain and sibling, but not different
-        * parents).
-        */
-       labels = dns_name_countlabels(rctx->ns_name);
-       if (labels > 1) {
-               dns_name_getlabelsequence(rctx->ns_name, 1, labels - 1, parent);
-       }
-
        DNS_RDATASET_FOREACH(rctx->ns_rdataset) {
                dns_rdata_t rdata = DNS_RDATA_INIT;
                dns_rdata_ns_t ns;
@@ -6883,12 +6881,28 @@ cache_delegns(respctx_t *rctx) {
                        continue;
                }
 
-               /* in-bailiwick/sibling GLUE */
-               if (labels > 1 && dns_name_issubdomain(&ns.name, parent)) {
-                       naddrs += cache_delegglue(delegset, deleg, &ttl, rctx,
-                                                 &ns.name);
-                       naddrs += cache_delegglue6(delegset, deleg, &ttl, rctx,
-                                                  &ns.name);
+               /*
+                * in-bailiwick/sibling GLUE
+                *
+                * The top of the delegated zone is `rctx->ns_name`. So
+                * truncating the first label gives the common parent domain
+                * allowed to get glues (this allows in-domain and sibling, but
+                * not different parents).
+                */
+               labels = dns_name_countlabels(rctx->ns_name);
+               if (labels > 1) {
+                       dns_fixedname_t fparent;
+                       dns_name_t *parent = dns_fixedname_initname(&fparent);
+
+                       dns_name_getlabelsequence(rctx->ns_name, 1, labels - 1,
+                                                 parent);
+
+                       if (dns_name_issubdomain(&ns.name, parent)) {
+                               naddrs += cache_delegglue(delegset, deleg, &ttl,
+                                                         rctx, &ns.name);
+                               naddrs += cache_delegglue6(
+                                       delegset, deleg, &ttl, rctx, &ns.name);
+                       }
                }
 
                if (naddrs == 0) {