From 14915b024142c91ff7c70969b3097ddb6ebbca22 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Tue, 18 Feb 2025 08:34:41 +0000 Subject: [PATCH 1/2] Redesign the unreachable primaries cache The cache for unreachable primaries was added to BIND 9 in 2006 via 1372e172d0e0b08996376b782a9041d1e3542489. It features a 10-slot LRU array with 600 seconds (10 minutes) fixed delay. During this time, any primary with a hiccup would be blocked for the whole block duration (unless overwritten by a different entry). As this design is not very flexible (i.e. the fixed delay and the fixed amount of the slots), redesign it based on the badcache.c module, which was implemented earlier for a similar mechanism. The differences between the new code and the badcache module were large enough to create a new module instead of trying to make the badcache module universal, which could complicate the implementation. The new design implements an exponential backoff for entries which are added again soon after expiring, i.e. the next expiration happens in double the amount of time of the previous expiration, but in no more time than the defined maximum value. The initial and the maximum expiration values are hard-coded, but, if required, it should be trivial to implement configurable knobs. --- lib/dns/Makefile.am | 2 + lib/dns/include/dns/types.h | 1 + lib/dns/include/dns/unreachcache.h | 132 +++++++++ lib/dns/include/dns/view.h | 1 + lib/dns/include/dns/zone.h | 38 --- lib/dns/unreachcache.c | 436 +++++++++++++++++++++++++++++ lib/dns/view.c | 16 ++ lib/dns/xfrin.c | 16 +- lib/dns/zone.c | 157 +---------- tests/dns/Makefile.am | 1 + tests/dns/unreachcache_test.c | 189 +++++++++++++ tests/dns/zonemgr_test.c | 65 ----- 12 files changed, 801 insertions(+), 253 deletions(-) create mode 100644 lib/dns/include/dns/unreachcache.h create mode 100644 lib/dns/unreachcache.c create mode 100644 tests/dns/unreachcache_test.c diff --git a/lib/dns/Makefile.am b/lib/dns/Makefile.am index d76a2028d2..f77045fe35 100644 --- a/lib/dns/Makefile.am +++ b/lib/dns/Makefile.am @@ -131,6 +131,7 @@ libdns_la_HEADERS = \ include/dns/tsig.h \ include/dns/ttl.h \ include/dns/types.h \ + include/dns/unreachcache.h \ include/dns/update.h \ include/dns/validator.h \ include/dns/view.h \ @@ -247,6 +248,7 @@ libdns_la_SOURCES = \ tsig.c \ tsig_p.h \ ttl.c \ + unreachcache.c \ update.c \ validator.c \ view.c \ diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 491b1a8315..03b5b7cb4d 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -167,6 +167,7 @@ typedef struct dns_tsigkeyring dns_tsigkeyring_t; typedef struct dns_tsigkey dns_tsigkey_t; typedef uint32_t dns_ttl_t; typedef uint32_t dns_typepair_t; +typedef struct dns_unreachcache dns_unreachcache_t; typedef struct dns_update_state dns_update_state_t; typedef struct dns_validator dns_validator_t; typedef struct dns_view dns_view_t; diff --git a/lib/dns/include/dns/unreachcache.h b/lib/dns/include/dns/unreachcache.h new file mode 100644 index 0000000000..f772f95db9 --- /dev/null +++ b/lib/dns/include/dns/unreachcache.h @@ -0,0 +1,132 @@ +/* + * 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. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/unreachcache.h + * \brief + * Defines dns_unreachcache_t, the "unreachable cache" object. + * + * Notes: + *\li An unreachable cache object is a hash table of + * isc_sockaddr_t/isc_sockaddr_t tuples, indicating whether a given tuple + * is known to be "unreachable" in some sense (e.g. an unresponsive primary + * server). This is currently used by the secondary servers for the + * "unreachable cache". + * + * Reliability: + * + * Resources: + * + * Security: + * + * Standards: + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include + +#include + +/*** + *** Functions + ***/ + +dns_unreachcache_t * +dns_unreachcache_new(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, + const uint16_t expire_min_s, const uint16_t expire_max_s, + const uint16_t backoff_eligible_s); +/*% + * Allocate and initialize an unreachable cache. A newly entered entry expires + * in 'expire_min_s' seconds, a duplicate entry refreshes the expire timer. + * However, after expiring, if the same entry is added again in less that the + * 'backoff_eligible_s' time, then the next expire happens in a double amount of + * time of the previous expiration, but no more than in 'expire_max_s' seconds. + * + * Requires: + * \li mctx != NULL + * \li expire_min_s > 0 + * \li expire_min_s <= expire_max_s + */ + +void +dns_unreachcache_destroy(dns_unreachcache_t **ucp); +/*% + * Flush and then free unreachcache in 'ucp'. '*ucp' is set to NULL on return. + * + * Requires: + * \li '*ucp' to be a valid unreachcache + */ + +void +dns_unreachcache_add(dns_unreachcache_t *uc, const isc_sockaddr_t *remote, + const isc_sockaddr_t *local); +/*% + * Adds an unreachcache entry to the unreachcache 'uc' for addresses 'remote' + * and 'local'. If an entry already exists, then it is refreshed. See also + * the documentation of the dns_unreachcache_new() function. + * + * Requires: + * \li uc to be a valid unreachcache. + * \li remote != NULL + * \li local != NULL + */ + +isc_result_t +dns_unreachcache_find(dns_unreachcache_t *uc, const isc_sockaddr_t *remote, + const isc_sockaddr_t *local); +/*% + * Returns ISC_R_SUCCESS if a record is found in the unreachcache 'uc' matching + * 'remote' and 'local', with an expiration date later than 'now'. Returns + * ISC_R_NOTFOUND otherwise. + * + * Requires: + * \li uc to be a valid unreachcache. + * \li remote != NULL + * \li local != NULL + */ + +void +dns_unreachcache_remove(dns_unreachcache_t *uc, const isc_sockaddr_t *remote, + const isc_sockaddr_t *local); +/*% + * Removes a record that is found in the unreachcache 'uc' matching 'remote' and + * 'local', if it exists. + * + * Requires: + * \li uc to be a valid unreachcache. + * \li remote != NULL + * \li local != NULL + * \li now != NULL + */ + +void +dns_unreachcache_flush(dns_unreachcache_t *uc); +/*% + * Flush the entire unreachable cache. + * + * Requires: + * \li uc to be a valid unreachcache + */ diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index f33192d39e..efbbbdeffb 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -178,6 +178,7 @@ struct dns_view { dns_dlzdblist_t dlz_unsearched; uint32_t fail_ttl; dns_badcache_t *failcache; + dns_unreachcache_t *unreachcache; unsigned int udpsize; uint32_t sig0key_checks_limit; uint32_t sig0message_checks_limit; diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index bd015e7de9..cb1b4b0773 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -1953,44 +1953,6 @@ dns_zone_getxfr(dns_zone_t *zone, dns_xfrin_t **xfrp, bool *is_firstrefresh, * ISC_R_FAILURE error while trying to get the transfer information */ -void -dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, - isc_sockaddr_t *local, isc_time_t *now); -/*%< - * Add the pair of addresses to the unreachable cache. - * - * Requires: - *\li 'zmgr' to be a valid zone manager. - *\li 'remote' to be a valid sockaddr. - *\li 'local' to be a valid sockaddr. - */ - -bool -dns_zonemgr_unreachable(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, - isc_sockaddr_t *local, isc_time_t *now); -/*%< - * Returns true if the given local/remote address pair - * is found in the zone maanger's unreachable cache. - * - * Requires: - *\li 'zmgr' to be a valid zone manager. - *\li 'remote' to be a valid sockaddr. - *\li 'local' to be a valid sockaddr. - *\li 'now' != NULL - */ - -void -dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, - isc_sockaddr_t *local); -/*%< - * Remove the pair of addresses from the unreachable cache. - * - * Requires: - *\li 'zmgr' to be a valid zone manager. - *\li 'remote' to be a valid sockaddr. - *\li 'local' to be a valid sockaddr. - */ - void dns_zonemgr_set_tlsctx_cache(dns_zonemgr_t *zmgr, isc_tlsctx_cache_t *tlsctx_cache); diff --git a/lib/dns/unreachcache.c b/lib/dns/unreachcache.c new file mode 100644 index 0000000000..efbfd86053 --- /dev/null +++ b/lib/dns/unreachcache.c @@ -0,0 +1,436 @@ +/* + * 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. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +typedef struct dns_ucentry dns_ucentry_t; + +typedef struct dns_uckey { + const isc_sockaddr_t *remote; + const isc_sockaddr_t *local; +} dns__uckey_t; + +struct dns_unreachcache { + unsigned int magic; + isc_mem_t *mctx; + uint16_t expire_min_s; + uint16_t expire_max_s; + uint16_t backoff_eligible_s; + struct cds_lfht *ht; + struct cds_list_head *lru; + uint32_t nloops; +}; + +#define UNREACHCACHE_MAGIC ISC_MAGIC('U', 'R', 'C', 'a') +#define VALID_UNREACHCACHE(m) ISC_MAGIC_VALID(m, UNREACHCACHE_MAGIC) + +#define UNREACHCACHE_INIT_SIZE (1 << 4) /* Must be power of 2 */ +#define UNREACHCACHE_MIN_SIZE (1 << 5) /* Must be power of 2 */ + +struct dns_ucentry { + isc_loop_t *loop; + isc_stdtime_t expire; + unsigned int exp_backoff_n; + uint16_t wait_time; + bool confirmed; + + struct cds_lfht_node ht_node; + struct rcu_head rcu_head; + struct cds_list_head lru_head; + + isc_sockaddr_t remote; + isc_sockaddr_t local; +}; + +static void +ucentry_destroy(struct rcu_head *rcu_head); + +static bool +ucentry_alive(struct cds_lfht *ht, dns_ucentry_t *unreach, isc_stdtime_t now, + bool alive_or_waiting); + +dns_unreachcache_t * +dns_unreachcache_new(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, + const uint16_t expire_min_s, const uint16_t expire_max_s, + const uint16_t backoff_eligible_s) { + REQUIRE(loopmgr != NULL); + REQUIRE(expire_min_s > 0); + REQUIRE(expire_min_s <= expire_max_s); + + uint32_t nloops = isc_loopmgr_nloops(loopmgr); + dns_unreachcache_t *uc = isc_mem_get(mctx, sizeof(*uc)); + *uc = (dns_unreachcache_t){ + .magic = UNREACHCACHE_MAGIC, + .expire_min_s = expire_min_s, + .expire_max_s = expire_max_s, + .backoff_eligible_s = backoff_eligible_s, + .nloops = nloops, + }; + + uc->ht = cds_lfht_new(UNREACHCACHE_INIT_SIZE, UNREACHCACHE_MIN_SIZE, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + INSIST(uc->ht != NULL); + + uc->lru = isc_mem_cget(mctx, uc->nloops, sizeof(uc->lru[0])); + for (size_t i = 0; i < uc->nloops; i++) { + CDS_INIT_LIST_HEAD(&uc->lru[i]); + } + + isc_mem_attach(mctx, &uc->mctx); + + return uc; +} + +void +dns_unreachcache_destroy(dns_unreachcache_t **ucp) { + REQUIRE(ucp != NULL && *ucp != NULL); + REQUIRE(VALID_UNREACHCACHE(*ucp)); + dns_unreachcache_t *uc = *ucp; + *ucp = NULL; + uc->magic = 0; + + dns_ucentry_t *unreach = NULL; + struct cds_lfht_iter iter; + cds_lfht_for_each_entry(uc->ht, &iter, unreach, ht_node) { + INSIST(!cds_lfht_del(uc->ht, &unreach->ht_node)); + ucentry_destroy(&unreach->rcu_head); + } + RUNTIME_CHECK(!cds_lfht_destroy(uc->ht, NULL)); + + isc_mem_cput(uc->mctx, uc->lru, uc->nloops, sizeof(uc->lru[0])); + + isc_mem_putanddetach(&uc->mctx, uc, sizeof(dns_unreachcache_t)); +} + +static int +ucentry_match(struct cds_lfht_node *ht_node, const void *key0) { + const dns__uckey_t *key = key0; + dns_ucentry_t *unreach = caa_container_of(ht_node, dns_ucentry_t, + ht_node); + + return isc_sockaddr_equal(&unreach->remote, key->remote) && + isc_sockaddr_equal(&unreach->local, key->local); +} + +static uint32_t +ucentry_hash(const dns__uckey_t *key) { + return isc_sockaddr_hash(key->remote, false) ^ + isc_sockaddr_hash(key->local, false); +} + +static dns_ucentry_t * +ucentry_lookup(struct cds_lfht *ht, uint32_t hashval, dns__uckey_t *key) { + struct cds_lfht_iter iter; + + cds_lfht_lookup(ht, hashval, ucentry_match, key, &iter); + + return cds_lfht_entry(cds_lfht_iter_get_node(&iter), dns_ucentry_t, + ht_node); +} + +static dns_ucentry_t * +ucentry_new(isc_loop_t *loop, const isc_sockaddr_t *remote, + const isc_sockaddr_t *local, const isc_stdtime_t expire, + const isc_stdtime_t wait_time) { + isc_mem_t *mctx = isc_loop_getmctx(loop); + dns_ucentry_t *unreach = isc_mem_get(mctx, sizeof(*unreach)); + *unreach = (dns_ucentry_t){ + .remote = *remote, + .local = *local, + .expire = expire, + .wait_time = wait_time, + .loop = isc_loop_ref(loop), + .lru_head = CDS_LIST_HEAD_INIT(unreach->lru_head), + }; + + return unreach; +} + +static void +ucentry_destroy(struct rcu_head *rcu_head) { + dns_ucentry_t *unreach = caa_container_of(rcu_head, dns_ucentry_t, + rcu_head); + isc_loop_t *loop = unreach->loop; + isc_mem_t *mctx = isc_loop_getmctx(loop); + + isc_mem_put(mctx, unreach, sizeof(*unreach)); + isc_loop_unref(loop); +} + +static void +ucentry_evict_async(void *arg) { + dns_ucentry_t *unreach = arg; + + RUNTIME_CHECK(unreach->loop == isc_loop()); + + cds_list_del(&unreach->lru_head); + call_rcu(&unreach->rcu_head, ucentry_destroy); +} + +static void +ucentry_evict(struct cds_lfht *ht, dns_ucentry_t *unreach) { + if (!cds_lfht_del(ht, &unreach->ht_node)) { + if (unreach->loop == isc_loop()) { + ucentry_evict_async(unreach); + return; + } + isc_async_run(unreach->loop, ucentry_evict_async, unreach); + } +} + +static bool +ucentry_alive(struct cds_lfht *ht, dns_ucentry_t *unreach, isc_stdtime_t now, + bool alive_or_waiting) { + if (cds_lfht_is_node_deleted(&unreach->ht_node)) { + return false; + } else if (unreach->expire < now) { + bool is_waiting = unreach->expire + unreach->wait_time >= now; + + if (is_waiting) { + /* + * Wait some minimum time before evicting an expired + * entry so we can support exponential backoff for + * nodes which enter again shortly after expiring. + * + * The return value depends on whether the caller is + * interested to know if the node is in either active or + * waiting state (i.e. not eviceted), or is interested + * only if it's still alive (i.e. not expired). + */ + return alive_or_waiting; + } + + /* The entry is already expired, evict it before returning. */ + ucentry_evict(ht, unreach); + return false; + } + + return true; +} + +static void +ucentry_purge(struct cds_lfht *ht, struct cds_list_head *lru, + isc_stdtime_t now) { + size_t count = 10; + dns_ucentry_t *unreach; + cds_list_for_each_entry_rcu(unreach, lru, lru_head) { + if (ucentry_alive(ht, unreach, now, true)) { + break; + } + if (--count == 0) { + break; + } + } +} + +static void +ucentry_backoff(const dns_unreachcache_t *uc, const isc_stdtime_t now, + dns_ucentry_t *new, const dns_ucentry_t *old) { + /* + * Perform exponential backoff if this is an expired entry wating to be + * evicted. Otherwise it's a duplicate entry and no backoff is required + * as we will just update the cache with a new entry that has the same + * expiration time as the old one, but calculated freshly, based on the + * current time. + */ + if (old->expire < now) { + new->exp_backoff_n = old->exp_backoff_n + 1; + } else { + new->exp_backoff_n = old->exp_backoff_n; + } + for (size_t i = 0; i < new->exp_backoff_n; i++) { + new->expire += uc->expire_min_s; + if (new->expire > now + uc->expire_max_s) { + new->expire = now + uc->expire_max_s; + break; + } + } +} + +void +dns_unreachcache_add(dns_unreachcache_t *uc, const isc_sockaddr_t *remote, + const isc_sockaddr_t *local) { + REQUIRE(VALID_UNREACHCACHE(uc)); + REQUIRE(remote != NULL); + REQUIRE(local != NULL); + + isc_loop_t *loop = isc_loop(); + uint32_t tid = isc_tid(); + struct cds_list_head *lru = &uc->lru[tid]; + isc_stdtime_t now = isc_stdtime_now(); + isc_stdtime_t expire = now + uc->expire_min_s; + bool exp_backoff_activated = false; + + rcu_read_lock(); + struct cds_lfht *ht = rcu_dereference(uc->ht); + INSIST(ht != NULL); + + dns__uckey_t key = { + .remote = remote, + .local = local, + }; + uint32_t hashval = ucentry_hash(&key); + + dns_ucentry_t *unreach = ucentry_new(loop, remote, local, expire, + uc->backoff_eligible_s); + struct cds_lfht_node *ht_node; + do { + ht_node = cds_lfht_add_unique(ht, hashval, ucentry_match, &key, + &unreach->ht_node); + if (ht_node != &unreach->ht_node) { + /* The entry already exists, get it. */ + dns_ucentry_t *found = caa_container_of( + ht_node, dns_ucentry_t, ht_node); + + /* + * Consider unreachability as confirmed only if + * an entry is submitted at least twice, i.e. there + * was an older entry (which is exactly this case). + */ + unreach->confirmed = true; + + /* + * Recalculate the expire time of the new entry based + * on the old entry's exponential backoff value. + */ + if (!exp_backoff_activated) { + exp_backoff_activated = true; + ucentry_backoff(uc, now, unreach, found); + } + + /* + * Evict the old entry, so we can try to insert the new + * one again. + */ + ucentry_evict(ht, found); + } + } while (ht_node != &unreach->ht_node); + + /* No locking, instead we are using per-thread lists */ + cds_list_add_tail_rcu(&unreach->lru_head, lru); + + ucentry_purge(ht, lru, now); + + rcu_read_unlock(); +} + +isc_result_t +dns_unreachcache_find(dns_unreachcache_t *uc, const isc_sockaddr_t *remote, + const isc_sockaddr_t *local) { + REQUIRE(VALID_UNREACHCACHE(uc)); + REQUIRE(remote != NULL); + REQUIRE(local != NULL); + + isc_result_t result = ISC_R_NOTFOUND; + isc_stdtime_t now = isc_stdtime_now(); + + rcu_read_lock(); + struct cds_lfht *ht = rcu_dereference(uc->ht); + INSIST(ht != NULL); + + dns__uckey_t key = { + .remote = remote, + .local = local, + }; + uint32_t hashval = ucentry_hash(&key); + + dns_ucentry_t *found = ucentry_lookup(ht, hashval, &key); + if (found != NULL && found->confirmed && + ucentry_alive(ht, found, now, false)) + { + result = ISC_R_SUCCESS; + } + + uint32_t tid = isc_tid(); + struct cds_list_head *lru = &uc->lru[tid]; + ucentry_purge(ht, lru, now); + + rcu_read_unlock(); + + return result; +} + +void +dns_unreachcache_remove(dns_unreachcache_t *uc, const isc_sockaddr_t *remote, + const isc_sockaddr_t *local) { + REQUIRE(VALID_UNREACHCACHE(uc)); + REQUIRE(remote != NULL); + REQUIRE(local != NULL); + + isc_stdtime_t now = isc_stdtime_now(); + + rcu_read_lock(); + struct cds_lfht *ht = rcu_dereference(uc->ht); + INSIST(ht != NULL); + + dns__uckey_t key = { + .remote = remote, + .local = local, + }; + uint32_t hashval = ucentry_hash(&key); + + dns_ucentry_t *found = ucentry_lookup(ht, hashval, &key); + if (found != NULL) { + ucentry_evict(ht, found); + } + + uint32_t tid = isc_tid(); + struct cds_list_head *lru = &uc->lru[tid]; + ucentry_purge(ht, lru, now); + + rcu_read_unlock(); +} + +void +dns_unreachcache_flush(dns_unreachcache_t *uc) { + REQUIRE(VALID_UNREACHCACHE(uc)); + + rcu_read_lock(); + struct cds_lfht *ht = rcu_dereference(uc->ht); + INSIST(ht != NULL); + + /* Flush the hash table */ + dns_ucentry_t *unreach; + struct cds_lfht_iter iter; + cds_lfht_for_each_entry(ht, &iter, unreach, ht_node) { + ucentry_evict(ht, unreach); + } + + rcu_read_unlock(); +} diff --git a/lib/dns/view.c b/lib/dns/view.c index e7d16b9f4a..12b02669e5 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -84,6 +85,11 @@ */ #define DEFAULT_EDNS_BUFSIZE 1232 +/* Exponental backoff from 10 seconds to 640 seconds */ +#define UNREACH_HOLD_TIME_INITIAL_SEC ((uint16_t)10) +#define UNREACH_HOLD_TIME_MAX_SEC (UNREACH_HOLD_TIME_INITIAL_SEC << 6) +#define UNREACH_BACKOFF_ELIGIBLE_SEC ((uint16_t)120) + void dns_view_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, dns_dispatchmgr_t *dispatchmgr, dns_rdataclass_t rdclass, @@ -149,6 +155,10 @@ dns_view_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, view->failcache = dns_badcache_new(view->mctx, loopmgr); + view->unreachcache = dns_unreachcache_new( + view->mctx, loopmgr, UNREACH_HOLD_TIME_INITIAL_SEC, + UNREACH_HOLD_TIME_MAX_SEC, UNREACH_BACKOFF_ELIGIBLE_SEC); + isc_mutex_init(&view->new_zone_lock); dns_order_create(view->mctx, &view->order); @@ -355,6 +365,9 @@ destroy(dns_view_t *view) { if (view->failcache != NULL) { dns_badcache_destroy(&view->failcache); } + if (view->unreachcache != NULL) { + dns_unreachcache_destroy(&view->unreachcache); + } isc_mutex_destroy(&view->new_zone_lock); isc_mutex_destroy(&view->lock); isc_refcount_destroy(&view->references); @@ -1391,6 +1404,9 @@ dns_view_flushcache(dns_view_t *view, bool fixuponly) { if (view->failcache != NULL) { dns_badcache_flush(view->failcache); } + if (view->unreachcache != NULL) { + dns_unreachcache_flush(view->unreachcache); + } rcu_read_lock(); adb = rcu_dereference(view->adb); diff --git a/lib/dns/xfrin.c b/lib/dns/xfrin.c index 15e4be753b..609bad2118 100644 --- a/lib/dns/xfrin.c +++ b/lib/dns/xfrin.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -1448,8 +1449,9 @@ xfrin_connect_done(isc_result_t result, isc_region_t *region ISC_ATTR_UNUSED, zmgr = dns_zone_getmgr(xfr->zone); if (zmgr != NULL) { - dns_zonemgr_unreachabledel(zmgr, &xfr->primaryaddr, - &xfr->sourceaddr); + dns_view_t *view = dns_zone_getview(xfr->zone); + dns_unreachcache_remove(view->unreachcache, &xfr->primaryaddr, + &xfr->sourceaddr); } if (xfr->tsigkey != NULL && xfr->tsigkey->key != NULL) { @@ -1480,16 +1482,16 @@ failure: case ISC_R_CONNREFUSED: case ISC_R_TIMEDOUT: /* - * Add the server to unreachable primaries table if + * Add the server to unreachable primaries cache if * the server has a permanent networking error or * the connection attempt as timed out. */ zmgr = dns_zone_getmgr(xfr->zone); if (zmgr != NULL) { - isc_time_t now = isc_time_now(); - - dns_zonemgr_unreachableadd(zmgr, &xfr->primaryaddr, - &xfr->sourceaddr, &now); + dns_view_t *view = dns_zone_getview(xfr->zone); + dns_unreachcache_add(view->unreachcache, + &xfr->primaryaddr, + &xfr->sourceaddr); } break; default: diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 8edc1beb5f..6e54a08644 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -87,6 +87,7 @@ #include #include #include +#include #include #include #include @@ -600,9 +601,6 @@ typedef enum { * load. */ } dns_zoneloadflag_t; -#define UNREACH_CACHE_SIZE 10U -#define UNREACH_HOLD_TIME 600 /* 10 minutes */ - #define CHECK(op) \ do { \ result = (op); \ @@ -610,14 +608,6 @@ typedef enum { goto failure; \ } while (0) -struct dns_unreachable { - isc_sockaddr_t remote; - isc_sockaddr_t local; - atomic_uint_fast32_t expire; - atomic_uint_fast32_t last; - uint32_t count; -}; - struct dns_zonemgr { unsigned int magic; isc_mem_t *mctx; @@ -632,7 +622,6 @@ struct dns_zonemgr { isc_ratelimiter_t *startupnotifyrl; isc_ratelimiter_t *startuprefreshrl; isc_rwlock_t rwlock; - isc_rwlock_t urlock; /* Locked by rwlock. */ dns_zonelist_t zones; @@ -648,10 +637,6 @@ struct dns_zonemgr { unsigned int serialqueryrate; unsigned int startupserialqueryrate; - /* Locked by urlock. */ - /* LRU cache */ - struct dns_unreachable unreachable[UNREACH_CACHE_SIZE]; - dns_keymgmt_t *keymgmt; isc_tlsctx_cache_t *tlsctx_cache; @@ -13309,7 +13294,6 @@ stub_glue_response(void *arg) { uint32_t addr_count, cnamecnt; isc_result_t result; isc_sockaddr_t curraddr; - isc_time_t now; dns_rdataset_t *addr_rdataset = NULL; dns_dbnode_t *node = NULL; @@ -13319,8 +13303,6 @@ stub_glue_response(void *arg) { ENTER; - now = isc_time_now(); - LOCK_ZONE(zone); if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { @@ -13333,8 +13315,8 @@ stub_glue_response(void *arg) { isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); if (dns_request_getresult(request) != ISC_R_SUCCESS) { - dns_zonemgr_unreachableadd(zone->zmgr, &curraddr, - &zone->sourceaddr, &now); + dns_unreachcache_add(zone->view->unreachcache, &curraddr, + &zone->sourceaddr); dns_zone_log(zone, ISC_LOG_INFO, "could not refresh stub from primary %s" " (source %s): %s", @@ -13487,7 +13469,7 @@ cleanup: /* If last request, release all related resources */ if (atomic_fetch_sub_release(&stub->pending_requests, 1) == 1) { isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); - stub_finish_zone_update(stub, now); + stub_finish_zone_update(stub, isc_time_now()); UNLOCK_ZONE(zone); stub->magic = 0; dns_zone_idetach(&stub->zone); @@ -13762,8 +13744,8 @@ stub_callback(void *arg) { } FALLTHROUGH; default: - dns_zonemgr_unreachableadd(zone->zmgr, &curraddr, - &zone->sourceaddr, &now); + dns_unreachcache_add(zone->view->unreachcache, &curraddr, + &zone->sourceaddr); dns_zone_log(zone, ISC_LOG_INFO, "could not refresh stub from primary " "%s (source %s): %s", @@ -14117,9 +14099,9 @@ refresh_callback(void *arg) { zone->type == dns_zone_redirect) && DNS_ZONE_OPTION(zone, DNS_ZONEOPT_TRYTCPREFRESH)) { - if (!dns_zonemgr_unreachable( - zone->zmgr, &curraddr, - &zone->sourceaddr, &now)) + if (dns_unreachcache_find( + zone->view->unreachcache, &curraddr, + &zone->sourceaddr) != ISC_R_SUCCESS) { DNS_ZONE_SETFLAG( zone, @@ -14361,8 +14343,8 @@ refresh_callback(void *arg) { DNS_ZONE_FLAG(zone, DNS_ZONEFLG_FORCEXFER) || isc_serial_gt(serial, oldserial)) { - if (dns_zonemgr_unreachable(zone->zmgr, &curraddr, - &zone->sourceaddr, &now)) + if (dns_unreachcache_find(zone->view->unreachcache, &curraddr, + &zone->sourceaddr) == ISC_R_SUCCESS) { dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, @@ -15727,7 +15709,7 @@ dns_zone_notifyreceive(dns_zone_t *zone, isc_sockaddr_t *from, UNLOCK_ZONE(zone); if (to != NULL) { - dns_zonemgr_unreachabledel(zone->zmgr, from, to); + dns_unreachcache_remove(zone->view->unreachcache, from, to); } dns_zone_refresh(zone); return ISC_R_SUCCESS; @@ -18521,7 +18503,6 @@ got_transfer_quota(void *arg) { isc_netaddr_t primaryip; isc_sockaddr_t primaryaddr; isc_sockaddr_t sourceaddr; - isc_time_t now; dns_transport_type_t soa_transport_type = DNS_TRANSPORT_NONE; const char *soa_before = ""; bool loaded; @@ -18533,12 +18514,10 @@ got_transfer_quota(void *arg) { return; } - now = isc_time_now(); - primaryaddr = dns_remote_curraddr(&zone->primaries); isc_sockaddr_format(&primaryaddr, primary, sizeof(primary)); - if (dns_zonemgr_unreachable(zone->zmgr, &primaryaddr, &zone->sourceaddr, - &now)) + if (dns_unreachcache_find(zone->view->unreachcache, &primaryaddr, + &zone->sourceaddr) == ISC_R_SUCCESS) { isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN, ISC_LOG_INFO, @@ -19194,15 +19173,8 @@ dns_zonemgr_create(isc_mem_t *mctx, isc_nm_t *netmgr, dns_zonemgr_t **zmgrp) { ISC_LIST_INIT(zmgr->zones); ISC_LIST_INIT(zmgr->waiting_for_xfrin); ISC_LIST_INIT(zmgr->xfrin_in_progress); - memset(zmgr->unreachable, 0, sizeof(zmgr->unreachable)); - for (size_t i = 0; i < UNREACH_CACHE_SIZE; i++) { - atomic_init(&zmgr->unreachable[i].expire, 0); - } isc_rwlock_init(&zmgr->rwlock); - /* Unreachable lock. */ - isc_rwlock_init(&zmgr->urlock); - isc_ratelimiter_create(loop, &zmgr->checkdsrl); isc_ratelimiter_create(loop, &zmgr->notifyrl); isc_ratelimiter_create(loop, &zmgr->refreshrl); @@ -19410,7 +19382,6 @@ zonemgr_free(dns_zonemgr_t *zmgr) { isc_mem_cput(zmgr->mctx, zmgr->mctxpool, zmgr->workers, sizeof(zmgr->mctxpool[0])); - isc_rwlock_destroy(&zmgr->urlock); isc_rwlock_destroy(&zmgr->rwlock); isc_rwlock_destroy(&zmgr->tlsctx_cache_rwlock); @@ -19701,106 +19672,6 @@ dns_zonemgr_getserialqueryrate(dns_zonemgr_t *zmgr) { return zmgr->serialqueryrate; } -bool -dns_zonemgr_unreachable(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, - isc_sockaddr_t *local, isc_time_t *now) { - unsigned int i; - uint32_t seconds = isc_time_seconds(now); - uint32_t count = 0; - - REQUIRE(DNS_ZONEMGR_VALID(zmgr)); - - RWLOCK(&zmgr->urlock, isc_rwlocktype_read); - for (i = 0; i < UNREACH_CACHE_SIZE; i++) { - if (atomic_load(&zmgr->unreachable[i].expire) >= seconds && - isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) && - isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) - { - atomic_store_relaxed(&zmgr->unreachable[i].last, - seconds); - count = zmgr->unreachable[i].count; - break; - } - } - RWUNLOCK(&zmgr->urlock, isc_rwlocktype_read); - return i < UNREACH_CACHE_SIZE && count > 1U; -} - -void -dns_zonemgr_unreachabledel(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, - isc_sockaddr_t *local) { - unsigned int i; - - REQUIRE(DNS_ZONEMGR_VALID(zmgr)); - - RWLOCK(&zmgr->urlock, isc_rwlocktype_read); - for (i = 0; i < UNREACH_CACHE_SIZE; i++) { - if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) && - isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) - { - atomic_store_relaxed(&zmgr->unreachable[i].expire, 0); - break; - } - } - RWUNLOCK(&zmgr->urlock, isc_rwlocktype_read); -} - -void -dns_zonemgr_unreachableadd(dns_zonemgr_t *zmgr, isc_sockaddr_t *remote, - isc_sockaddr_t *local, isc_time_t *now) { - uint32_t seconds = isc_time_seconds(now); - uint32_t expire = 0, last = seconds; - unsigned int slot = UNREACH_CACHE_SIZE, oldest = 0; - bool update_entry = true; - REQUIRE(DNS_ZONEMGR_VALID(zmgr)); - - RWLOCK(&zmgr->urlock, isc_rwlocktype_write); - for (unsigned int i = 0; i < UNREACH_CACHE_SIZE; i++) { - /* Existing entry? */ - if (isc_sockaddr_equal(&zmgr->unreachable[i].remote, remote) && - isc_sockaddr_equal(&zmgr->unreachable[i].local, local)) - { - update_entry = false; - slot = i; - expire = atomic_load_relaxed( - &zmgr->unreachable[i].expire); - break; - } - /* Pick first empty slot? */ - if (atomic_load_relaxed(&zmgr->unreachable[i].expire) < seconds) - { - slot = i; - break; - } - /* The worst case, least recently used slot? */ - if (atomic_load_relaxed(&zmgr->unreachable[i].last) < last) { - last = atomic_load_relaxed(&zmgr->unreachable[i].last); - oldest = i; - } - } - - /* We haven't found any existing or free slots, use the oldest */ - if (slot == UNREACH_CACHE_SIZE) { - slot = oldest; - } - - if (expire < seconds) { - /* Expired or new entry, reset count to 1 */ - zmgr->unreachable[slot].count = 1; - } else { - zmgr->unreachable[slot].count++; - } - atomic_store_relaxed(&zmgr->unreachable[slot].expire, - seconds + UNREACH_HOLD_TIME); - atomic_store_relaxed(&zmgr->unreachable[slot].last, seconds); - if (update_entry) { - zmgr->unreachable[slot].remote = *remote; - zmgr->unreachable[slot].local = *local; - } - - RWUNLOCK(&zmgr->urlock, isc_rwlocktype_write); -} - void dns_zone_stopxfr(dns_zone_t *zone) { dns_xfrin_t *xfr = NULL; diff --git a/tests/dns/Makefile.am b/tests/dns/Makefile.am index 8d4cc4c151..b10d482221 100644 --- a/tests/dns/Makefile.am +++ b/tests/dns/Makefile.am @@ -50,6 +50,7 @@ check_PROGRAMS = \ time_test \ transport_test \ tsig_test \ + unreachcache_test \ update_test \ zonefile_test \ zonemgr_test \ diff --git a/tests/dns/unreachcache_test.c b/tests/dns/unreachcache_test.c new file mode 100644 index 0000000000..735e9ded9e --- /dev/null +++ b/tests/dns/unreachcache_test.c @@ -0,0 +1,189 @@ +/* + * 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. + */ + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define EXPIRE_MIN_S 5 +#define EXPIRE_MAX_S 10 +#define BACKOFF_ELGIBLE_S 5 + +ISC_LOOP_TEST_IMPL(basic) { + dns_unreachcache_t *uc = NULL; + struct in_addr localhost4 = { 0 }; + isc_sockaddr_t src_addrv4 = { 0 }, dst_addrv4 = { 0 }, + src_addrv6 = { 0 }, dst_addrv6 = { 0 }; + const uint16_t src_port = 1234; + const uint16_t dst_port = 5678; + isc_result_t result; + + isc_sockaddr_fromin(&src_addrv4, &localhost4, src_port); + isc_sockaddr_fromin(&dst_addrv4, &localhost4, dst_port); + isc_sockaddr_fromin6(&src_addrv6, &in6addr_loopback, src_port); + isc_sockaddr_fromin6(&dst_addrv6, &in6addr_loopback, dst_port); + + uc = dns_unreachcache_new(mctx, loopmgr, EXPIRE_MIN_S, EXPIRE_MAX_S, + BACKOFF_ELGIBLE_S); + dns_unreachcache_add(uc, &dst_addrv4, &src_addrv4); + dns_unreachcache_add(uc, &dst_addrv6, &src_addrv6); + + /* Added but unconfirmed (at least another add required to confirm). */ + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_NOTFOUND); + result = dns_unreachcache_find(uc, &dst_addrv6, &src_addrv6); + assert_int_equal(result, ISC_R_NOTFOUND); + + /* Confirmed. */ + dns_unreachcache_add(uc, &dst_addrv4, &src_addrv4); + dns_unreachcache_add(uc, &dst_addrv6, &src_addrv6); + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_SUCCESS); + result = dns_unreachcache_find(uc, &dst_addrv6, &src_addrv6); + assert_int_equal(result, ISC_R_SUCCESS); + + /* Removal. */ + dns_unreachcache_remove(uc, &dst_addrv6, &src_addrv6); + result = dns_unreachcache_find(uc, &dst_addrv6, &src_addrv6); + assert_int_equal(result, ISC_R_NOTFOUND); + + /* Swapped addresses, should be not found. */ + result = dns_unreachcache_find(uc, &src_addrv4, &dst_addrv4); + assert_int_equal(result, ISC_R_NOTFOUND); + result = dns_unreachcache_find(uc, &src_addrv6, &dst_addrv6); + assert_int_equal(result, ISC_R_NOTFOUND); + + dns_unreachcache_destroy(&uc); + + isc_loopmgr_shutdown(loopmgr); +} + +ISC_LOOP_TEST_IMPL(expire) { + dns_unreachcache_t *uc = NULL; + struct in_addr localhost4 = { 0 }; + isc_sockaddr_t src_addrv4 = { 0 }, dst_addrv4 = { 0 }; + const uint16_t src_port = 1234; + const uint16_t dst_port = 5678; + isc_result_t result; + + isc_sockaddr_fromin(&src_addrv4, &localhost4, src_port); + isc_sockaddr_fromin(&dst_addrv4, &localhost4, dst_port); + + uc = dns_unreachcache_new(mctx, loopmgr, EXPIRE_MIN_S, EXPIRE_MAX_S, + BACKOFF_ELGIBLE_S); + /* Two adds to "confirm" the addition. */ + dns_unreachcache_add(uc, &dst_addrv4, &src_addrv4); + dns_unreachcache_add(uc, &dst_addrv4, &src_addrv4); + + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_SUCCESS); + + sleep(1); + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_SUCCESS); + + sleep(EXPIRE_MIN_S); + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_NOTFOUND); + + /* + * Because of the exponentatl backoff, the new quick addition after the + * previous expiration should expire in 2 x EXPIRE_MIN_S seconds. + */ + dns_unreachcache_add(uc, &dst_addrv4, &src_addrv4); + + sleep(1); + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_SUCCESS); + + sleep(EXPIRE_MIN_S); + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_SUCCESS); + + sleep(EXPIRE_MIN_S); + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_NOTFOUND); + + dns_unreachcache_destroy(&uc); + + isc_loopmgr_shutdown(loopmgr); +} + +ISC_LOOP_TEST_IMPL(flush) { + dns_unreachcache_t *uc = NULL; + struct in_addr localhost4 = { 0 }; + isc_sockaddr_t src_addrv4 = { 0 }, dst_addrv4 = { 0 }; + const uint16_t src_port = 1234; + const uint16_t dst_port = 5678; + isc_result_t result; + + isc_sockaddr_fromin(&src_addrv4, &localhost4, src_port); + isc_sockaddr_fromin(&dst_addrv4, &localhost4, dst_port); + + uc = dns_unreachcache_new(mctx, loopmgr, EXPIRE_MIN_S, EXPIRE_MAX_S, + BACKOFF_ELGIBLE_S); + /* Two adds to "confirm" the addition. */ + dns_unreachcache_add(uc, &dst_addrv4, &src_addrv4); + dns_unreachcache_add(uc, &dst_addrv4, &src_addrv4); + + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_unreachcache_flush(uc); + + result = dns_unreachcache_find(uc, &dst_addrv4, &src_addrv4); + assert_int_equal(result, ISC_R_NOTFOUND); + + dns_unreachcache_destroy(&uc); + + isc_loopmgr_shutdown(loopmgr); +} + +ISC_TEST_LIST_START +ISC_TEST_ENTRY_CUSTOM(basic, setup_managers, teardown_managers) +ISC_TEST_ENTRY_CUSTOM(expire, setup_managers, teardown_managers) +ISC_TEST_ENTRY_CUSTOM(flush, setup_managers, teardown_managers) +ISC_TEST_LIST_END + +ISC_TEST_MAIN diff --git a/tests/dns/zonemgr_test.c b/tests/dns/zonemgr_test.c index fff8322482..4e35d8ddb4 100644 --- a/tests/dns/zonemgr_test.c +++ b/tests/dns/zonemgr_test.c @@ -125,75 +125,10 @@ ISC_LOOP_TEST_IMPL(zonemgr_createzone) { isc_loopmgr_shutdown(loopmgr); } -/* manage and release a zone */ -ISC_LOOP_TEST_IMPL(zonemgr_unreachable) { - dns_zonemgr_t *myzonemgr = NULL; - dns_zone_t *zone = NULL; - isc_sockaddr_t addr1, addr2; - struct in_addr in; - isc_result_t result; - isc_time_t now; - - UNUSED(arg); - - now = isc_time_now(); - - dns_zonemgr_create(mctx, netmgr, &myzonemgr); - - result = dns_test_makezone("foo", &zone, NULL, false); - assert_int_equal(result, ISC_R_SUCCESS); - - result = dns_zonemgr_managezone(myzonemgr, zone); - assert_int_equal(result, ISC_R_SUCCESS); - - in.s_addr = inet_addr("10.53.0.1"); - isc_sockaddr_fromin(&addr1, &in, 2112); - in.s_addr = inet_addr("10.53.0.2"); - isc_sockaddr_fromin(&addr2, &in, 5150); - assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now)); - /* - * We require multiple unreachableadd calls to mark a server as - * unreachable. - */ - dns_zonemgr_unreachableadd(myzonemgr, &addr1, &addr2, &now); - assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now)); - dns_zonemgr_unreachableadd(myzonemgr, &addr1, &addr2, &now); - assert_true(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now)); - - in.s_addr = inet_addr("10.53.0.3"); - isc_sockaddr_fromin(&addr2, &in, 5150); - assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now)); - /* - * We require multiple unreachableadd calls to mark a server as - * unreachable. - */ - dns_zonemgr_unreachableadd(myzonemgr, &addr1, &addr2, &now); - dns_zonemgr_unreachableadd(myzonemgr, &addr1, &addr2, &now); - assert_true(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now)); - - dns_zonemgr_unreachabledel(myzonemgr, &addr1, &addr2); - assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now)); - - in.s_addr = inet_addr("10.53.0.2"); - isc_sockaddr_fromin(&addr2, &in, 5150); - assert_true(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now)); - dns_zonemgr_unreachabledel(myzonemgr, &addr1, &addr2); - assert_false(dns_zonemgr_unreachable(myzonemgr, &addr1, &addr2, &now)); - - dns_zonemgr_releasezone(myzonemgr, zone); - dns_zone_detach(&zone); - dns_zonemgr_shutdown(myzonemgr); - dns_zonemgr_detach(&myzonemgr); - assert_null(myzonemgr); - - isc_loopmgr_shutdown(loopmgr); -} - ISC_TEST_LIST_START ISC_TEST_ENTRY_CUSTOM(zonemgr_create, setup_test, teardown_test) ISC_TEST_ENTRY_CUSTOM(zonemgr_managezone, setup_test, teardown_test) ISC_TEST_ENTRY_CUSTOM(zonemgr_createzone, setup_test, teardown_test) -ISC_TEST_ENTRY_CUSTOM(zonemgr_unreachable, setup_test, teardown_test) ISC_TEST_LIST_END ISC_TEST_MAIN From 0f2fba46ad4f5bcce6ad72098f51bf464eac913c Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Tue, 15 Apr 2025 14:57:52 +0000 Subject: [PATCH 2/2] Document the new unreachable cache behavior Update the documentaion to include information about how the cache's exponential backoff works, and how to clear the cache. --- doc/arm/config-auth.inc.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/arm/config-auth.inc.rst b/doc/arm/config-auth.inc.rst index b054fb0904..77d1c7ae99 100644 --- a/doc/arm/config-auth.inc.rst +++ b/doc/arm/config-auth.inc.rst @@ -87,6 +87,13 @@ The numbers in parentheses in the following text refer to the numbered items in from the primary (as described in section 2 a. above). If the zone file has changed, propagation is practically immediate. +.. Note:: When a primary server is unreachable, :iscman:`named` initially caches + that information for 10 seconds. After that initial period, :iscman:`named` + may try that server again; if the server remains unresponsive, + :iscman:`named` caches its unreachable status for up to 640 seconds using an + exponential backoff. During that time :iscman:`named` does not try to contact + the affected server. The cache can be cleared using :option:`rndc flush`. + The authoritative samples all use NOTIFY but identify the statements used, so that they can be removed if not required.