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 \
tsig.c \
tsig_p.h \
ttl.c \
+ unreachcache.c \
update.c \
validator.c \
view.c \
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;
--- /dev/null
+/*
+ * 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 <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/loop.h>
+#include <isc/mem.h>
+#include <isc/stdtime.h>
+
+#include <dns/types.h>
+
+/***
+ *** 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
+ */
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;
* 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);
--- /dev/null
+/*
+ * 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 <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/async.h>
+#include <isc/buffer.h>
+#include <isc/hash.h>
+#include <isc/log.h>
+#include <isc/loop.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/rwlock.h>
+#include <isc/sockaddr.h>
+#include <isc/spinlock.h>
+#include <isc/stdtime.h>
+#include <isc/string.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/urcu.h>
+#include <isc/util.h>
+
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdatatype.h>
+#include <dns/types.h>
+#include <dns/unreachcache.h>
+
+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();
+}
#include <dns/time.h>
#include <dns/transport.h>
#include <dns/tsig.h>
+#include <dns/unreachcache.h>
#include <dns/view.h>
#include <dns/zone.h>
#include <dns/zt.h>
*/
#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,
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);
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);
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);
#include <dns/trace.h>
#include <dns/transport.h>
#include <dns/tsig.h>
+#include <dns/unreachcache.h>
#include <dns/view.h>
#include <dns/xfrin.h>
#include <dns/zone.h>
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) {
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:
#include <dns/time.h>
#include <dns/tsig.h>
#include <dns/ttl.h>
+#include <dns/unreachcache.h>
#include <dns/update.h>
#include <dns/xfrin.h>
#include <dns/zone.h>
* load. */
} dns_zoneloadflag_t;
-#define UNREACH_CACHE_SIZE 10U
-#define UNREACH_HOLD_TIME 600 /* 10 minutes */
-
#define CHECK(op) \
do { \
result = (op); \
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;
isc_ratelimiter_t *startupnotifyrl;
isc_ratelimiter_t *startuprefreshrl;
isc_rwlock_t rwlock;
- isc_rwlock_t urlock;
/* Locked by rwlock. */
dns_zonelist_t zones;
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;
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;
ENTER;
- now = isc_time_now();
-
LOCK_ZONE(zone);
if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) {
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",
/* 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);
}
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",
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,
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,
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;
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;
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,
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);
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);
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;
time_test \
transport_test \
tsig_test \
+ unreachcache_test \
update_test \
zonefile_test \
zonemgr_test \
--- /dev/null
+/*
+ * 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 <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/lib.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/os.h>
+#include <isc/sockaddr.h>
+#include <isc/thread.h>
+#include <isc/urcu.h>
+#include <isc/util.h>
+#include <isc/uv.h>
+
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/lib.h>
+#include <dns/name.h>
+#include <dns/rdatatype.h>
+#include <dns/unreachcache.h>
+
+#include <tests/dns.h>
+
+#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
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