]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Cap glue records cached from a referral 11970/head
authorOndřej Surý <ondrej@isc.org>
Wed, 6 May 2026 10:35:22 +0000 (12:35 +0200)
committerOndřej Surý <ondrej@isc.org>
Tue, 12 May 2026 14:17:24 +0000 (16:17 +0200)
The resolver populated the delegation database with every NS RR and
every glue address from a referral, with no aggregate bound.  Resolution
only ever uses the first max-delegation-servers NS owners and a handful
of addresses per NS, so anything beyond that is dead memory.

Stop the NS loop in cache_delegns() at view->max_delegation_servers and
cap each glue rdataset at DELEG_MAX_GLUES_PER_NS (20) addresses, so each
NS owner contributes at most 20 A and 20 AAAA glues.

bin/tests/system/cap_glues/ns1/named.conf.j2 [new file with mode: 0644]
bin/tests/system/cap_glues/ns1/root.db [new file with mode: 0644]
bin/tests/system/cap_glues/ns2/named.conf.j2 [new file with mode: 0644]
bin/tests/system/cap_glues/ns2/tld.db [new file with mode: 0644]
bin/tests/system/cap_glues/ns3/named.conf.j2 [new file with mode: 0644]
bin/tests/system/cap_glues/ns3/root.hint [new file with mode: 0644]
bin/tests/system/cap_glues/tests_cap_glues.py [new file with mode: 0644]
lib/dns/resolver.c

diff --git a/bin/tests/system/cap_glues/ns1/named.conf.j2 b/bin/tests/system/cap_glues/ns1/named.conf.j2
new file mode 100644 (file)
index 0000000..1b3caaf
--- /dev/null
@@ -0,0 +1,15 @@
+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; };
+       recursion no;
+       dnssec-validation no;
+};
+
+zone "." {
+       type primary;
+       file "root.db";
+};
diff --git a/bin/tests/system/cap_glues/ns1/root.db b/bin/tests/system/cap_glues/ns1/root.db
new file mode 100644 (file)
index 0000000..0a34b86
--- /dev/null
@@ -0,0 +1,13 @@
+$TTL 300
+.                      IN SOA dnshoster.root-servers.nil. a.root.servers.nil. (
+                               2010    ; serial
+                               600     ; refresh
+                               600     ; retry
+                               1200    ; expire
+                               600     ; minimum
+                               )
+.                      NS      a.root-servers.nil.
+a.root-servers.nil.    A       10.53.0.1
+
+tld.                   NS      ns.tld.
+ns.tld.                        A       10.53.0.2
diff --git a/bin/tests/system/cap_glues/ns2/named.conf.j2 b/bin/tests/system/cap_glues/ns2/named.conf.j2
new file mode 100644 (file)
index 0000000..ab242e2
--- /dev/null
@@ -0,0 +1,15 @@
+options {
+       query-source address 10.53.0.2;
+       notify-source 10.53.0.2;
+       transfer-source 10.53.0.2;
+       port @PORT@;
+       pid-file "named.pid";
+       listen-on { 10.53.0.2; };
+       recursion no;
+       dnssec-validation no;
+};
+
+zone "tld" {
+       type primary;
+       file "tld.db";
+};
diff --git a/bin/tests/system/cap_glues/ns2/tld.db b/bin/tests/system/cap_glues/ns2/tld.db
new file mode 100644 (file)
index 0000000..61624fc
--- /dev/null
@@ -0,0 +1,79 @@
+$TTL 300
+@                      IN SOA  hoster.tld. ns.tld. (
+                               2010    ; serial
+                               600     ; refresh
+                               600     ; retry
+                               1200    ; expire
+                               600     ; minimum
+                               )
+
+               NS      ns
+ns             A       10.52.0.2
+
+example                NS      ns1.other-tld.
+example                NS      ns2.other-tld.
+example                NS      ns3.other-tld.
+example                NS      ns4.other-tld.
+example                NS      ns5.other-tld.
+example                NS      ns6.other-tld.
+example                NS      ns7.other-tld.
+example                NS      ns8.other-tld.
+example                NS      ns9.other-tld.
+example                NS      ns10.other-tld.
+example                NS      ns11.other-tld.
+example                NS      ns12.other-tld.
+example                NS      ns13.other-tld.
+example                NS      ns14.other-tld.
+example                NS      ns15.other-tld.
+
+example                NS      ns.example
+
+ns.example     A       10.53.0.20
+ns.example     A       10.53.0.21
+ns.example     A       10.53.0.22
+ns.example     A       10.53.0.23
+ns.example     A       10.53.0.24
+ns.example     A       10.53.0.25
+ns.example     A       10.53.0.26
+ns.example     A       10.53.0.27
+ns.example     A       10.53.0.28
+ns.example     A       10.53.0.29
+ns.example     A       10.53.0.30
+ns.example     A       10.53.0.31
+ns.example     A       10.53.0.32
+ns.example     A       10.53.0.33
+ns.example     A       10.53.0.34
+ns.example     A       10.53.0.35
+ns.example     A       10.53.0.36
+ns.example     A       10.53.0.37
+ns.example     A       10.53.0.38
+ns.example     A       10.53.0.39
+ns.example     A       10.53.0.40
+ns.example     A       10.53.0.41
+ns.example     A       10.53.0.42
+ns.example     A       10.53.0.43
+
+ns.example     AAAA    2001:db8::20
+ns.example     AAAA    2001:db8::21
+ns.example     AAAA    2001:db8::22
+ns.example     AAAA    2001:db8::23
+ns.example     AAAA    2001:db8::24
+ns.example     AAAA    2001:db8::25
+ns.example     AAAA    2001:db8::26
+ns.example     AAAA    2001:db8::27
+ns.example     AAAA    2001:db8::28
+ns.example     AAAA    2001:db8::29
+ns.example     AAAA    2001:db8::30
+ns.example     AAAA    2001:db8::31
+ns.example     AAAA    2001:db8::32
+ns.example     AAAA    2001:db8::33
+ns.example     AAAA    2001:db8::34
+ns.example     AAAA    2001:db8::35
+ns.example     AAAA    2001:db8::36
+ns.example     AAAA    2001:db8::37
+ns.example     AAAA    2001:db8::38
+ns.example     AAAA    2001:db8::39
+ns.example     AAAA    2001:db8::40
+ns.example     AAAA    2001:db8::41
+ns.example     AAAA    2001:db8::42
+ns.example     AAAA    2001:db8::43
diff --git a/bin/tests/system/cap_glues/ns3/named.conf.j2 b/bin/tests/system/cap_glues/ns3/named.conf.j2
new file mode 100644 (file)
index 0000000..32a161c
--- /dev/null
@@ -0,0 +1,31 @@
+options {
+       query-source address 10.53.0.3;
+       notify-source 10.53.0.3;
+       transfer-source 10.53.0.3;
+       port @PORT@;
+       pid-file "named.pid";
+       listen-on { 10.53.0.3; };
+       recursion yes;
+       dnssec-validation no;
+};
+
+server 10.53.0.2 {
+       // Avoid truncation of the additional section without TC, because some
+       // mandatory glues would already be in the additional section, thus the
+       // resolver wouldn't try again using TCP by itself.
+       tcp-only yes;
+};
+
+zone "." {
+       type hint;
+       file "root.hint";
+};
+
+key rndc_key {
+       secret "1234abcd8765";
+       algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+       inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
diff --git a/bin/tests/system/cap_glues/ns3/root.hint b/bin/tests/system/cap_glues/ns3/root.hint
new file mode 100644 (file)
index 0000000..fd81838
--- /dev/null
@@ -0,0 +1,3 @@
+$TTL 999999
+.                       IN NS          a.root-servers.nil.
+a.root-servers.nil.     IN A           10.53.0.1
diff --git a/bin/tests/system/cap_glues/tests_cap_glues.py b/bin/tests/system/cap_glues/tests_cap_glues.py
new file mode 100644 (file)
index 0000000..5b9f1a2
--- /dev/null
@@ -0,0 +1,48 @@
+# 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 test_cap_glues(ns3):
+    msg = isctest.query.create("example.tld.", "A")
+    isctest.query.udp(msg, ns3.ip)
+
+    with ns3.watch_log_from_here() as watcher:
+        ns3.rndc("dumpdb -deleg")
+        watcher.wait_for_line("dumpdb complete")
+    db = isctest.text.TextFile(f"{ns3.identifier}/named_dump.db")
+
+    names_len = len(db.grep(Re("example.tld. ... DELEG server-name=")))
+    if names_len == 12:
+        # 12 NS names, 1 NS with glues (so no server-name), so 13 NS in total.
+        allowed_suffixes = range(20, 40)
+        skipped_suffixes = range(40, 44)
+        assert len(db.grep(Re("example.tld. ... DELEG server-ipv4="))) == 1
+        assert (
+            len(db.grep(Re("example.tld. ... DELEG server-ipv4=.* server-ipv6="))) == 1
+        )
+
+        for n in allowed_suffixes:
+            assert len(db.grep(f"10.53.0.{n}")) == 1
+            assert len(db.grep(f"2001:db8::{n}")) == 1
+
+        for n in skipped_suffixes:
+            assert len(db.grep(f"10.53.0.{n}")) == 0
+            assert len(db.grep(f"2001:db8::{n}")) == 0
+    else:
+        # 13 NS names and no glues. This occurs if the 13 NS without glues
+        # has been processed first.
+        assert names_len == 13
+        assert len(db.grep(Re("example.tld. ... DELEG server-ipv4="))) == 0
+        assert len(db.grep(Re("example.tld. ... DELEG server-ipv6="))) == 0
index 19c4c29f0d044cdb08386f5a7749ae8d7e812695..6d3b994d1d13cb729b7bdbbb6bf51843a118017d 100644 (file)
 #define DEFAULT_MAX_QUERIES 50
 #endif /* ifndef DEFAULT_MAX_QUERIES */
 
+/*
+ * Cap on the number of glue addresses cached per NS owner in a referral
+ * delegation set.  The resolver itself will only ever try a handful of
+ * addresses per NS, so accepting more from a referral is wasted memory.
+ */
+#define DELEG_MAX_GLUES_PER_NS 20
+
 /* Hash table for zone counters */
 #ifndef RES_DOMAIN_HASH_BITS
 #define RES_DOMAIN_HASH_BITS 12
@@ -6636,6 +6643,8 @@ name_external(const dns_name_t *name, dns_rdatatype_t type, respctx_t *rctx) {
 static void
 cache_delegglue(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
                dns_rdataset_t *rdataset) {
+       size_t naddrs = 0;
+
        if (rdataset->ttl < *ttl) {
                *ttl = rdataset->ttl;
        }
@@ -6649,12 +6658,19 @@ cache_delegglue(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
                dns_rdata_tostruct(&rdata, &a, NULL);
                addr.type.in = a.in_addr;
                dns_delegset_addaddr(delegset, deleg, &addr);
+               naddrs++;
+
+               if (naddrs >= DELEG_MAX_GLUES_PER_NS) {
+                       break;
+               }
        }
 }
 
 static void
 cache_delegglue6(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
                 dns_rdataset_t *rdataset) {
+       size_t naddrs = 0;
+
        if (rdataset->ttl < *ttl) {
                *ttl = rdataset->ttl;
        }
@@ -6668,6 +6684,11 @@ cache_delegglue6(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
                dns_rdata_tostruct(&rdata, &aaaa, NULL);
                addr.type.in6 = aaaa.in6_addr;
                dns_delegset_addaddr(delegset, deleg, &addr);
+               naddrs++;
+
+               if (naddrs >= DELEG_MAX_GLUES_PER_NS) {
+                       break;
+               }
        }
 }
 
@@ -6709,12 +6730,20 @@ cache_delegns(respctx_t *rctx) {
                dns_name_getlabelsequence(rctx->ns_name, 1, labels - 1, parent);
        }
 
+       size_t ns_count = 0;
+       size_t max_servers = fctx->res->view->max_delegation_servers;
+
        DNS_RDATASET_FOREACH(rctx->ns_rdataset) {
                dns_rdataset_t *gluerdataset = NULL;
                dns_rdata_t rdata = DNS_RDATA_INIT;
                dns_rdata_ns_t ns;
                dns_deleg_t *deleg = NULL;
 
+               if (ns_count >= max_servers) {
+                       break;
+               }
+               ns_count++;
+
                /*
                 * We can't "group" all NS-based delegations into a single
                 * `dns_deleg_t` because some of them might have glues, some