]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Cap glue records cached from a referral 11972/head
authorOndřej Surý <ondrej@isc.org>
Wed, 6 May 2026 12:31:19 +0000 (14:31 +0200)
committerOndřej Surý <ondrej@isc.org>
Tue, 12 May 2026 14:18:40 +0000 (16:18 +0200)
The resolver marked every NS RR's glue from a referral for caching with
no aggregate bound, so a parent server returning many NS RRs and many
glue addresses per NS could inflate cache memory long beyond what
resolution can ever use.

Truncate each glue rdataset to DELEG_MAX_GLUES_PER_NS (20) A and 20 AAAA
records before marking it for caching.  The NS RRset itself is still
cached in full, bounded by max-records-per-type.

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..9eaa9fe
--- /dev/null
@@ -0,0 +1,63 @@
+$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      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..cba0fa3
--- /dev/null
@@ -0,0 +1,33 @@
+# 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.
+
+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 -cache")
+        watcher.wait_for_line("dumpdb complete")
+    db = isctest.text.TextFile(f"{ns3.identifier}/named_dump.db")
+
+    allowed_suffixes = range(20, 40)
+    skipped_suffixes = range(40, 44)
+
+    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
index 3c0fea208c43eca4e3196a9f9353a297261721f4..6827eb2a216ecd22a79a9b22f46a1c8c6252b25c 100644 (file)
  */
 #define NS_PROCESSING_LIMIT 20
 
+/*
+ * Cap on the number of glue addresses cached per NS owner from a referral.
+ * The resolver only ever tries a handful of addresses per NS, so accepting
+ * more than this from a single referral is wasted memory.  Each NS owner
+ * may contribute at most DELEG_MAX_GLUES_PER_NS A and DELEG_MAX_GLUES_PER_NS
+ * AAAA glue records.
+ */
+#define DELEG_MAX_GLUES_PER_NS 20
+
 /* Hash table for zone counters */
 #ifndef RES_DOMAIN_HASH_BITS
 #define RES_DOMAIN_HASH_BITS 12
@@ -6762,12 +6771,52 @@ unlock:
        return result;
 }
 
+/*
+ * Truncate 'rdataset' to at most 'max' rdata, by unlinking the trailing
+ * rdata from the underlying rdatalist.  The rdataset must be backed by a
+ * dns_rdatalist, which is the case for rdatasets parsed from a message.
+ */
+static void
+truncate_rdataset(dns_rdataset_t *rdataset, unsigned int max) {
+       dns_rdatalist_t *rdatalist = NULL;
+       dns_rdata_t *keep = NULL;
+       dns_rdata_t *next = NULL;
+       unsigned int i;
+
+       REQUIRE(max > 0);
+
+       if (dns_rdataset_count(rdataset) <= max) {
+               return;
+       }
+
+       dns_rdatalist_fromrdataset(rdataset, &rdatalist);
+
+       keep = ISC_LIST_HEAD(rdatalist->rdata);
+       for (i = 1; i < max && keep != NULL; i++) {
+               keep = ISC_LIST_NEXT(keep, link);
+       }
+       INSIST(keep != NULL);
+
+       next = ISC_LIST_NEXT(keep, link);
+       while (next != NULL) {
+               dns_rdata_t *unlinked = next;
+               next = ISC_LIST_NEXT(next, link);
+               ISC_LIST_UNLINK(rdatalist->rdata, unlinked, link);
+       }
+}
+
 static void
 mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external,
             bool gluing) {
        name->attributes.cache = true;
        if (gluing) {
                rdataset->trust = dns_trust_glue;
+               if (rdataset->type == dns_rdatatype_a ||
+                   rdataset->type == dns_rdatatype_aaaa)
+               {
+                       truncate_rdataset(rdataset, DELEG_MAX_GLUES_PER_NS);
+               }
+
                /*
                 * Glue with 0 TTL causes problems.  We force the TTL to
                 * 1 second to prevent this.