diff --git a/bin/named/main.c b/bin/named/main.c index 92d3b2b9b2..984c01c85a 100644 --- a/bin/named/main.c +++ b/bin/named/main.c @@ -120,6 +120,9 @@ extern unsigned int dns_zone_mkey_hour; extern unsigned int dns_zone_mkey_day; extern unsigned int dns_zone_mkey_month; +extern unsigned int dns_adb_entrywindow; +extern unsigned int dns_adb_cachemin; + static bool want_stats = false; static char absolute_conffile[PATH_MAX]; static char saved_command_line[4096] = { 0 }; @@ -723,6 +726,10 @@ parse_T_opt(char *option) { transferstuck = true; } else if (!strncmp(option, "tat=", 4)) { named_g_tat_interval = atoi(option + 4); + } else if (!strncmp(option, "adbentrywindow=", 15)) { + dns_adb_entrywindow = atoi(option + 15); + } else if (!strncmp(option, "adbcachemin=", 12)) { + dns_adb_cachemin = atoi(option + 12); } else { fprintf(stderr, "unknown -T flag '%s'\n", option); } diff --git a/bin/tests/system/expiredglue/ns1/named.conf.j2 b/bin/tests/system/expiredglue/ns1/named.conf.j2 new file mode 100644 index 0000000000..5ad42a185a --- /dev/null +++ b/bin/tests/system/expiredglue/ns1/named.conf.j2 @@ -0,0 +1,39 @@ +/* + * 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. + */ + +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; +}; + +view "default" { + zone "." { + type primary; + file "root.db"; + }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/expiredglue/ns1/root.db b/bin/tests/system/expiredglue/ns1/root.db new file mode 100644 index 0000000000..41c97bf445 --- /dev/null +++ b/bin/tests/system/expiredglue/ns1/root.db @@ -0,0 +1,24 @@ +; 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. + +$TTL 300 +. IN SOA marka.isc.org. 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/expiredglue/ns2/named.conf.j2 b/bin/tests/system/expiredglue/ns2/named.conf.j2 new file mode 100644 index 0000000000..8851c3728d --- /dev/null +++ b/bin/tests/system/expiredglue/ns2/named.conf.j2 @@ -0,0 +1,37 @@ +/* + * 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. + */ + +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"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/expiredglue/ns2/tld.db b/bin/tests/system/expiredglue/ns2/tld.db new file mode 100644 index 0000000000..d0f06ce30b --- /dev/null +++ b/bin/tests/system/expiredglue/ns2/tld.db @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 +tld. IN SOA marka.isc.org. ns.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +tld. NS ns.tld. +ns.tld. A 10.53.0.2 + +example.tld. NS ns.dnshoster.tld. +missing.tld. NS ns.missing.tld. +dnshoster.tld. NS ns.dnshoster.tld. + +; Delegation's glue has a TTL of 300 on parent-side +ns.dnshoster.tld. A 10.53.0.3 diff --git a/bin/tests/system/expiredglue/ns3/dnshoster.tld.db b/bin/tests/system/expiredglue/ns3/dnshoster.tld.db new file mode 100644 index 0000000000..37c5862736 --- /dev/null +++ b/bin/tests/system/expiredglue/ns3/dnshoster.tld.db @@ -0,0 +1,24 @@ +; 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. + +$TTL 300 +dnshoster.tld. IN SOA marka.isc.org. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +; The TTL of the delegation's glue child-side is 2 seconds. +dnshoster.tld. NS ns.dnshoster.tld. +ns.dnshoster.tld. 2 A 10.53.0.3 +a.dnshoster.tld. 2 A 10.53.0.10 diff --git a/bin/tests/system/expiredglue/ns3/example.tld.db b/bin/tests/system/expiredglue/ns3/example.tld.db new file mode 100644 index 0000000000..4d49cae8be --- /dev/null +++ b/bin/tests/system/expiredglue/ns3/example.tld.db @@ -0,0 +1,22 @@ +; 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. + +$TTL 300 +example.tld. IN SOA marka.isc.org. ns.dnshoster.tld. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) + +example.tld. NS ns.dnshoster.tld. +a.example.tld. 2 A 10.53.0.10 diff --git a/bin/tests/system/expiredglue/ns3/named.conf.j2 b/bin/tests/system/expiredglue/ns3/named.conf.j2 new file mode 100644 index 0000000000..91d86edb15 --- /dev/null +++ b/bin/tests/system/expiredglue/ns3/named.conf.j2 @@ -0,0 +1,42 @@ +/* + * 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. + */ + +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 no; + dnssec-validation no; +}; + +zone "dnshoster.tld." { + type primary; + file "dnshoster.tld.db"; +}; + +zone "example.tld." { + type primary; + file "example.tld.db"; +}; + +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/expiredglue/ns4/named.args b/bin/tests/system/expiredglue/ns4/named.args new file mode 100644 index 0000000000..6d2c4141df --- /dev/null +++ b/bin/tests/system/expiredglue/ns4/named.args @@ -0,0 +1 @@ +-D expiredglue-ns4 -m record -c named.conf -d 99 -g -4 -T adbentrywindow=0 -T adbcachemin=1 -T maxcachesize=2097152 diff --git a/bin/tests/system/expiredglue/ns4/named.conf.j2 b/bin/tests/system/expiredglue/ns4/named.conf.j2 new file mode 100644 index 0000000000..7dfe5deacc --- /dev/null +++ b/bin/tests/system/expiredglue/ns4/named.conf.j2 @@ -0,0 +1,37 @@ +/* + * 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. + */ + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + recursion yes; + dnssec-validation no; +}; + +zone "." { + type hint; + file "root.hint"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/expiredglue/ns4/root.hint b/bin/tests/system/expiredglue/ns4/root.hint new file mode 100644 index 0000000000..d7d0e1faba --- /dev/null +++ b/bin/tests/system/expiredglue/ns4/root.hint @@ -0,0 +1,14 @@ +; 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. + +$TTL 999999 +. IN NS a.root-servers.nil. +a.root-servers.nil. IN A 10.53.0.1 diff --git a/bin/tests/system/expiredglue/tests_expiredglue.py b/bin/tests/system/expiredglue/tests_expiredglue.py new file mode 100644 index 0000000000..a7f3c3d137 --- /dev/null +++ b/bin/tests/system/expiredglue/tests_expiredglue.py @@ -0,0 +1,55 @@ +# 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 time + +import isctest + + +def test_expiredglue(ns4): + msg1 = isctest.query.create("a.example.tld.", "A") + res1 = isctest.query.udp(msg1, ns4.ip) + isctest.check.noerror(res1) + isctest.check.rr_count_eq(res1.answer, 1) + + msg2 = isctest.query.create("a.dnshoster.tld.", "A") + res2 = isctest.query.udp(msg2, ns4.ip) + isctest.check.rr_count_eq(res2.answer, 1) + + msg3 = isctest.query.create("ns.dnshoster.tld.", "A") + res3 = isctest.query.udp(msg3, ns4.ip) + isctest.check.rr_count_eq(res3.answer, 1) + + time.sleep(3) + + # Even if the glue is expired but the delegation is not, named + # is able to "recover" by looking up the hints again and does + # not bails out with a fetch loop detection. + res1_2 = isctest.query.udp(msg1, ns4.ip) + isctest.check.same_data(res1_2, res1) + + time.sleep(3) + res2_2 = isctest.query.udp(msg2, ns4.ip) + isctest.check.same_data(res2_2, res2) + + time.sleep(3) + res3_2 = isctest.query.udp(msg3, ns4.ip) + isctest.check.same_data(res3_2, res3) + + +def test_loopdetected(ns4): + msg = isctest.query.create("a.missing.tld.", "A") + with ns4.watch_log_from_here() as watcher: + res = isctest.query.udp(msg, ns4.ip) + + # However, this is a valid fetch loop, and named detects it. + watcher.wait_for_line("loop detected resolving 'ns.missing.tld/A'") + isctest.check.servfail(res) diff --git a/lib/dns/adb.c b/lib/dns/adb.c index 06256be588..2e61c75534 100644 --- a/lib/dns/adb.c +++ b/lib/dns/adb.c @@ -63,12 +63,8 @@ /*! * For type 3 negative cache entries, we will remember that the address is * broken for this long. XXXMLG This is also used for actual addresses, too. - * The intent is to keep us from constantly asking about A/AAAA records - * if the zone has extremely low TTLs. */ -#define ADB_CACHE_MINIMUM 10 /*%< seconds */ #define ADB_CACHE_MAXIMUM 86400 /*%< seconds (86400 = 24 hours) */ -#define ADB_ENTRY_WINDOW 60 /*%< seconds */ #define ADB_HASH_SIZE (1 << 12) @@ -284,6 +280,12 @@ ISC_REFCOUNT_TRACE_DECL(dns_adbentry); ISC_REFCOUNT_DECL(dns_adbentry); #endif +/* + * ADB settings that can be tweaked with named -T option + */ +unsigned int dns_adb_entrywindow = 60; +unsigned int dns_adb_cachemin = 10; + /* * Internal functions (and prototypes). */ @@ -457,10 +459,10 @@ enum { * Due to the ttlclamp(), the TTL is never 0 unless the trust is ultimate, * in which case we need to set the expiration to have immediate effect. */ -#define ADJUSTED_EXPIRE(expire, now, ttl) \ - ((ttl != 0) \ - ? ISC_MIN(expire, ISC_MAX(now + ADB_ENTRY_WINDOW, now + ttl)) \ - : INT_MAX) +#define ADJUSTED_EXPIRE(expire, now, ttl) \ + ((ttl != 0) ? ISC_MIN(expire, \ + ISC_MAX(now + dns_adb_entrywindow, now + ttl)) \ + : INT_MAX) /* * Error states. @@ -533,8 +535,12 @@ inc_adbstats(dns_adb_t *adb, isc_statscounter_t counter) { static dns_ttl_t ttlclamp(dns_ttl_t ttl) { - if (ttl < ADB_CACHE_MINIMUM) { - ttl = ADB_CACHE_MINIMUM; + if (ttl < dns_adb_cachemin) { + /* + * Avoid to constantly ask about A/AAAA records if the zone has + * extremely low TTLs. + */ + ttl = dns_adb_cachemin; } if (ttl > ADB_CACHE_MAXIMUM) { ttl = ADB_CACHE_MAXIMUM; @@ -567,7 +573,11 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset, case dns_trust_additional: case dns_trust_pending_answer: case dns_trust_pending_additional: - rdataset->ttl = ADB_CACHE_MINIMUM; + /* + * Avoid to constantly ask about A/AAAA records if the zone has + * extremely low TTLs. + */ + rdataset->ttl = dns_adb_cachemin; break; case dns_trust_ultimate: rdataset->ttl = 0; @@ -972,7 +982,7 @@ new_adbentry(dns_adb_t *adb, const isc_sockaddr_t *addr, isc_stdtime_t now) { .quota = adb->quota, .references = ISC_REFCOUNT_INITIALIZER(1), .adb = dns_adb_ref(adb), - .expires = now + ADB_ENTRY_WINDOW, + .expires = now + dns_adb_entrywindow, .loop = isc_loop_ref(isc_loop()), .magic = DNS_ADBENTRY_MAGIC, }; @@ -2018,6 +2028,10 @@ post_copy: find->cbarg = cbarg; } + if (wanted_fetches) { + find->options |= DNS_ADBFIND_STARTEDFETCH; + } + *findp = find; UNLOCK(&adbname->lock); diff --git a/lib/dns/include/dns/adb.h b/lib/dns/include/dns/adb.h index dfa52236dc..399e665ee8 100644 --- a/lib/dns/include/dns/adb.h +++ b/lib/dns/include/dns/adb.h @@ -194,6 +194,10 @@ struct dns_adbfind { */ #define DNS_ADBFIND_STATICSTUB 0x00001000 #define DNS_ADBFIND_NOVALIDATE 0x00002000 +/*% + * This specific find created a fetch + */ +#define DNS_ADBFIND_STARTEDFETCH 0x00010000 /*% * The answers to queries come back as a list of these. diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index f475af5ffd..f09b502ba6 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -1087,6 +1087,9 @@ set_stats(dns_resolver_t *res, isc_statscounter_t counter, uint64_t val) { } } +static bool +waiting_for_fetch(fetchctx_t *fctx, const dns_name_t *name, + dns_rdatatype_t type, const dns_name_t *domain); static void valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_t *rdataset, @@ -3340,9 +3343,10 @@ sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) { } /* - * Return true iff the ADB find has a pending fetch for 'type'. This is - * used to find out whether we're in a loop, where a fetch is waiting for a - * find which is waiting for that same fetch. + * Return true iff the ADB find has an already pending fetch for 'type'. This + * is used to find out whether we're in a loop, where a fetch is waiting for a + * find which is waiting for that same fetch. So if the current find actually + * started the fetch, we know it can't be a loop, so we returns false. * * Note: This could be done with either an equivalence check (e.g., * query_pending == DNS_ADBFIND_INET) or with a bit check, as below. If @@ -3361,7 +3365,11 @@ sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) { * evil. */ static bool -waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) { +already_waiting_for(dns_adbfind_t *find, dns_rdatatype_t type) { + if ((find->options & DNS_ADBFIND_STARTEDFETCH) != 0) { + return false; + } + switch (type) { case dns_rdatatype_a: return (find->query_pending & DNS_ADBFIND_INET) != 0; @@ -3471,22 +3479,25 @@ findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port, * We don't know any of the addresses for this name. * * The find may be waiting on a resolver fetch for a server - * address. We need to make sure it isn't waiting on *this* + * address. We need to make sure it isn't waiting before *this* * fetch, because if it is, we won't be answering it and it * won't be answering us. */ - if (waiting_for(find, fctx->type) && dns_name_equal(name, fctx->name)) { - fctx->adberr++; + if (already_waiting_for(find, fctx->type) && + dns_name_equal(name, fctx->name)) + { isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, "loop detected resolving '%s'", fctx->info); + fctx->adberr++; if ((find->options & DNS_ADBFIND_WANTEVENT) != 0) { dns_adb_cancelfind(find); } else { dns_adb_destroyfind(&find); fetchctx_detach(&fctx); } + return; } @@ -10126,12 +10137,27 @@ get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name, return ISC_R_SUCCESS; } +static bool +is_samedomain(const dns_name_t *domain1, const dns_name_t *domain2) { + if (domain1 == NULL && domain2 == NULL) { + return true; + } + + if (domain1 == NULL || domain2 == NULL) { + return false; + } + + return !dns_name_compare(domain1, domain2); +} + static bool waiting_for_fetch(fetchctx_t *fctx, const dns_name_t *name, - dns_rdatatype_t type) { + dns_rdatatype_t type, const dns_name_t *domain) { while (fctx != NULL) { if (type == fctx->type && !dns_name_compare(name, fctx->name)) { - return true; + if (is_samedomain(domain, fctx->domain)) { + return true; + } } fctx = fctx->parent; } @@ -10181,18 +10207,30 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name, log_fetch(name, type); - if (waiting_for_fetch(parent, name, type)) { + /* + * This fetch loop detection enable to guard against loop scenarios + * where the DNSSEC is involved. See + * `4d307ac67a0e3f9831c9a4e66ac481e2f9ceebb5`. This is a complementary + * detection with the ADB lookup loop detection (in `findname()`). + */ + if (waiting_for_fetch(parent, name, type, domain)) { if (isc_log_wouldlog(ISC_LOG_INFO)) { char namebuf[DNS_NAME_FORMATSIZE + 1]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; + char domainbuf[DNS_NAME_FORMATSIZE + 1] = { 0 }; dns_name_format(name, namebuf, sizeof(namebuf)); dns_rdatatype_format(type, typebuf, sizeof(typebuf)); + if (domain != NULL) { + dns_name_format(domain, domainbuf, + sizeof(domainbuf)); + } isc_log_write(DNS_LOGCATEGORY_RESOLVER, DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(2), - "fetch loop detected resolving '%s/%s'", - namebuf, typebuf); + "fetch loop detected resolving '%s/%s " + "(in '%s'?)", + namebuf, typebuf, domainbuf); } return DNS_R_LOOPDETECTED; }