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