From: Ondřej Surý Date: Wed, 6 May 2026 12:31:19 +0000 (+0200) Subject: Cap glue records cached from a referral X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ddf6274ba25e475af6a9b1276ff767205486714c;p=thirdparty%2Fbind9.git Cap glue records cached from a referral 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. --- 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 index 00000000000..1b3caaff9c7 --- /dev/null +++ b/bin/tests/system/cap_glues/ns1/named.conf.j2 @@ -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 index 00000000000..0a34b864363 --- /dev/null +++ b/bin/tests/system/cap_glues/ns1/root.db @@ -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 index 00000000000..ab242e2c092 --- /dev/null +++ b/bin/tests/system/cap_glues/ns2/named.conf.j2 @@ -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 index 00000000000..9eaa9fe7c9e --- /dev/null +++ b/bin/tests/system/cap_glues/ns2/tld.db @@ -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 index 00000000000..32a161cf2b2 --- /dev/null +++ b/bin/tests/system/cap_glues/ns3/named.conf.j2 @@ -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 index 00000000000..fd81838ef82 --- /dev/null +++ b/bin/tests/system/cap_glues/ns3/root.hint @@ -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 index 00000000000..cba0fa3ce06 --- /dev/null +++ b/bin/tests/system/cap_glues/tests_cap_glues.py @@ -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 diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 3c0fea208c4..6827eb2a216 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -236,6 +236,15 @@ */ #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.