From: Ondřej Surý Date: Wed, 6 May 2026 10:35:22 +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=ea00ea92fbb4bda681f5a3b4bb1670a123b60d11;p=thirdparty%2Fbind9.git Cap glue records cached from a referral 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. --- 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..61624fcc2c9 --- /dev/null +++ b/bin/tests/system/cap_glues/ns2/tld.db @@ -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 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..5b9f1a2222e --- /dev/null +++ b/bin/tests/system/cap_glues/tests_cap_glues.py @@ -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 diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 19c4c29f0d0..6d3b994d1d1 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -231,6 +231,13 @@ #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