chg: usr: Cap glue records cached from a referral

named cached every glue record from a referral, retaining far more
than resolution will ever use.  The number of nameservers and
addresses kept per referral is now bounded in the delegation database.

Closes #5701

Merge branch '5701-limit-the-number-of-GLUE-records' into 'main'

See merge request isc-projects/bind9!11970
This commit is contained in:
Ondřej Surý 2026-05-12 16:17:59 +02:00
commit 8c4e7d533e
8 changed files with 233 additions and 0 deletions

View file

@ -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";
};

View file

@ -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

View file

@ -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";
};

View file

@ -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

View file

@ -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; };
};

View file

@ -0,0 +1,3 @@
$TTL 999999
. IN NS a.root-servers.nil.
a.root-servers.nil. IN A 10.53.0.1

View file

@ -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

View file

@ -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