]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Redesign the unreachable primaries cache
authorAram Sargsyan <aram@isc.org>
Tue, 18 Feb 2025 08:34:41 +0000 (08:34 +0000)
committerAram Sargsyan <aram@isc.org>
Wed, 4 Jun 2025 09:16:35 +0000 (09:16 +0000)
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.

12 files changed:
lib/dns/Makefile.am
lib/dns/include/dns/types.h
lib/dns/include/dns/unreachcache.h [new file with mode: 0644]
lib/dns/include/dns/view.h
lib/dns/include/dns/zone.h
lib/dns/unreachcache.c [new file with mode: 0644]
lib/dns/view.c
lib/dns/xfrin.c
lib/dns/zone.c
tests/dns/Makefile.am
tests/dns/unreachcache_test.c [new file with mode: 0644]
tests/dns/zonemgr_test.c

index d76a2028d276f84ac0d64ff5682db9a1761c36ed..f77045fe3586032f283e684882f5468948a8a814 100644 (file)
@@ -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                          \
index 491b1a83157444b99c0ec37f754381151771fa84..03b5b7cb4d65241042ded26d8c399e5536c4fecf 100644 (file)
@@ -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 (file)
index 0000000..f772f95
--- /dev/null
@@ -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 <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
+ */
index f33192d39ecaffe5d8495b96ba5717515432dd74..efbbbdeffb0194646657a2b37b1c6fed934615b0 100644 (file)
@@ -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;
index bd015e7de9b0180bd89c447af80c05a0b9cc2982..cb1b4b0773931fe794d5aaedd3ad3de929aa2fb9 100644 (file)
@@ -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 (file)
index 0000000..efbfd86
--- /dev/null
@@ -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 <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();
+}
index e7d16b9f4a307c483e456b3ce8a5c020ed482114..12b02669e52b19e94ec4edd3f847c84d786070ce 100644 (file)
@@ -60,6 +60,7 @@
 #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,
@@ -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);
index 15e4be753b45e71ca5178a70e76791d618a89ffe..609bad2118681efebf877f3d9120f1236dfd773e 100644 (file)
@@ -42,6 +42,7 @@
 #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>
@@ -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:
index 8edc1beb5f75b71df724803e715a227129b7d048..6e54a086441612630fd8d4754f2b760a384affa0 100644 (file)
@@ -87,6 +87,7 @@
 #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>
@@ -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;
index 8d4cc4c1511d8ac33194ead313d2685466c0b221..b10d482221288ecdfe58c43e035007f7f241349c 100644 (file)
@@ -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 (file)
index 0000000..735e9de
--- /dev/null
@@ -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 <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
index fff8322482e075b558ed720d2751f4ae504e3188..4e35d8ddb4d4407513bf2c40d21260edb3b370c3 100644 (file)
@@ -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