]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Refactor dns_badcache to use cds_lfht lock-free hashtable
authorOndřej Surý <ondrej@isc.org>
Mon, 19 Jun 2023 13:43:02 +0000 (15:43 +0200)
committerOndřej Surý <ondrej@isc.org>
Mon, 31 Jul 2023 13:51:15 +0000 (15:51 +0200)
The dns_badcache unit had (yet another) own locked hashtable
implementation.  Replace the hashtable used by dns_badcache with
lock-free cds_lfht implementation from liburcu.

12 files changed:
configure.ac
lib/dns/badcache.c
lib/dns/include/dns/badcache.h
lib/dns/include/dns/resolver.h
lib/dns/resolver.c
lib/dns/validator.c
lib/dns/view.c
lib/ns/client.c
lib/ns/query.c
tests/dns/Makefile.am
tests/dns/badcache_test.c [new file with mode: 0644]
tests/ns/query_test.c

index 78a4c5986a0e4a0c4cafc555cd2f2dea11211bce..928b1489fa1da4abced99fbdd1a6e60ddd6745e0 100644 (file)
@@ -236,7 +236,7 @@ AC_MSG_RESULT([$RCU_FLAVOR])
 # not add it automatically - we need to add it explicitly in such case.
 #
 PKG_CHECK_MODULES([LIBURCU], [$RCU_FLAVOR >= 0.13.0 liburcu-cds >= 0.13.0], [],
-                 [PKG_CHECK_MODULES([LIBURCU], [$RCU_FLAVOR liburcu-cds],
+                 [PKG_CHECK_MODULES([LIBURCU], [$RCU_FLAVOR >= 0.10.0 liburcu-cds >= 0.10.0],
                                     [LIBURCU_LIBS="$LIBURCU_LIBS -lurcu-common"])])
 
 AC_DEFINE_UNQUOTED([RCU_FLAVOR], ["$RCU_FLAVOR"], [Chosen Userspace-RCU flavor])
index 4fdf4c566499073ea6111553b9f3deb66e491c82..c134873069b823e3e023b030052c20e78a8a98ab 100644 (file)
 #include <isc/log.h>
 #include <isc/mem.h>
 #include <isc/mutex.h>
+#include <isc/refcount.h>
 #include <isc/rwlock.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/badcache.h>
@@ -37,476 +42,353 @@ typedef struct dns_bcentry dns_bcentry_t;
 
 struct dns_badcache {
        unsigned int magic;
-       isc_rwlock_t lock;
        isc_mem_t *mctx;
-
-       isc_mutex_t *tlocks;
-       dns_bcentry_t **table;
-
-       atomic_uint_fast32_t count;
-       atomic_uint_fast32_t sweep;
-
-       unsigned int minsize;
-       unsigned int size;
+       struct cds_lfht *ht;
+       atomic_bool purge_in_progress;
 };
 
 #define BADCACHE_MAGIC   ISC_MAGIC('B', 'd', 'C', 'a')
 #define VALID_BADCACHE(m) ISC_MAGIC_VALID(m, BADCACHE_MAGIC)
 
+#define BADCACHE_INIT_SIZE (1 << 10) /* Must be power of 2 */
+#define BADCACHE_MIN_SIZE  (1 << 8)  /* Must be power of 2 */
+
 struct dns_bcentry {
-       dns_bcentry_t *next;
+       isc_mem_t *mctx;
        dns_rdatatype_t type;
-       isc_time_t expire;
-       uint32_t flags;
-       unsigned int hashval;
+       _Atomic(isc_stdtime_t) expire;
+       atomic_uint_fast32_t flags;
        dns_fixedname_t fname;
        dns_name_t *name;
+
+       struct cds_lfht_node ht_node;
+       struct rcu_head rcu_head;
 };
 
 static void
-badcache_resize(dns_badcache_t *bc, isc_time_t *now);
+bcentry_print(dns_bcentry_t *bad, isc_stdtime_t now, FILE *fp);
 
-isc_result_t
-dns_badcache_init(isc_mem_t *mctx, unsigned int size, dns_badcache_t **bcp) {
-       dns_badcache_t *bc = NULL;
-       unsigned int i;
+static void
+bcentry_destroy(struct rcu_head *rcu_head);
 
-       REQUIRE(bcp != NULL && *bcp == NULL);
+dns_badcache_t *
+dns_badcache_new(isc_mem_t *mctx) {
        REQUIRE(mctx != NULL);
 
-       bc = isc_mem_get(mctx, sizeof(*bc));
-
+       dns_badcache_t *bc = isc_mem_get(mctx, sizeof(*bc));
        *bc = (dns_badcache_t){
-               .size = size,
-               .minsize = size,
+               .magic = BADCACHE_MAGIC,
        };
 
-       isc_mem_attach(mctx, &bc->mctx);
-       isc_rwlock_init(&bc->lock);
-
-       bc->table = isc_mem_getx(bc->mctx, sizeof(bc->table[0]) * size,
-                                ISC_MEM_ZERO);
-       bc->tlocks = isc_mem_getx(bc->mctx, sizeof(bc->tlocks[0]) * size,
-                                 ISC_MEM_ZERO);
-       for (i = 0; i < size; i++) {
-               isc_mutex_init(&bc->tlocks[i]);
-       }
+       bc->ht = cds_lfht_new(BADCACHE_INIT_SIZE, BADCACHE_MIN_SIZE, 0,
+                             CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL);
+       INSIST(bc->ht != NULL);
 
-       bc->magic = BADCACHE_MAGIC;
+       isc_mem_attach(mctx, &bc->mctx);
 
-       *bcp = bc;
-       return (ISC_R_SUCCESS);
+       return (bc);
 }
 
 void
 dns_badcache_destroy(dns_badcache_t **bcp) {
-       dns_badcache_t *bc;
-       unsigned int i;
-
        REQUIRE(bcp != NULL && *bcp != NULL);
-       bc = *bcp;
-       *bcp = NULL;
-
-       dns_badcache_flush(bc);
+       REQUIRE(VALID_BADCACHE(*bcp));
 
+       dns_badcache_t *bc = *bcp;
+       *bcp = NULL;
        bc->magic = 0;
-       isc_rwlock_destroy(&bc->lock);
-       for (i = 0; i < bc->size; i++) {
-               isc_mutex_destroy(&bc->tlocks[i]);
+
+       dns_bcentry_t *bad = NULL;
+       struct cds_lfht_iter iter;
+       cds_lfht_for_each_entry(bc->ht, &iter, bad, ht_node) {
+               INSIST(!cds_lfht_del(bc->ht, &bad->ht_node));
+               bcentry_destroy(&bad->rcu_head);
        }
-       isc_mem_put(bc->mctx, bc->table, sizeof(bc->table[0]) * bc->size);
-       isc_mem_put(bc->mctx, bc->tlocks, sizeof(bc->tlocks[0]) * bc->size);
+       RUNTIME_CHECK(!cds_lfht_destroy(bc->ht, NULL));
+
        isc_mem_putanddetach(&bc->mctx, bc, sizeof(dns_badcache_t));
 }
 
+static int
+bcentry_match(struct cds_lfht_node *ht_node, const void *key) {
+       const dns_name_t *name = key;
+       dns_bcentry_t *bad = caa_container_of(ht_node, dns_bcentry_t, ht_node);
+
+       return (dns_name_equal(bad->name, name));
+}
+
+static dns_bcentry_t *
+bcentry_new(dns_badcache_t *bc, const dns_name_t *name,
+           const dns_rdatatype_t type, const uint32_t flags,
+           const isc_stdtime_t expire) {
+       dns_bcentry_t *bad = isc_mem_get(bc->mctx, sizeof(*bad));
+       *bad = (dns_bcentry_t){
+               .type = type,
+               .flags = flags,
+               .expire = expire,
+       };
+       isc_mem_attach(bc->mctx, &bad->mctx);
+
+       bad->name = dns_fixedname_initname(&bad->fname);
+       dns_name_copy(name, bad->name);
+
+       return (bad);
+}
+
 static void
-badcache_resize(dns_badcache_t *bc, isc_time_t *now) {
-       dns_bcentry_t **newtable, *bad, *next;
-       isc_mutex_t *newlocks;
-       unsigned int newsize, i;
-       bool grow;
+bcentry_destroy(struct rcu_head *rcu_head) {
+       dns_bcentry_t *bad = caa_container_of(rcu_head, dns_bcentry_t,
+                                             rcu_head);
 
-       RWLOCK(&bc->lock, isc_rwlocktype_write);
+       isc_mem_putanddetach(&bad->mctx, bad, sizeof(*bad));
+}
 
+static void
+bcentry_evict(struct cds_lfht *ht, dns_bcentry_t *bad) {
        /*
-        * XXXWPK we will have a thundering herd problem here,
-        * as all threads will wait on the RWLOCK when there's
-        * a need to resize badcache.
-        * However, it happens so rarely it should not be a
-        * performance issue. This is because we double the
-        * size every time we grow it, and we don't shrink
-        * unless the number of entries really shrunk. In a
-        * high load situation, the number of badcache entries
-        * will eventually stabilize.
+        * The hashtable isn't locked in a traditional sense, so multiple
+        * threads can lookup and evict the same record at the same time.
+        *
+        * This is amplified by the bcentry_purge_next() that walks a few more
+        * records in the hashtable and evicts them if they are expired.
+        *
+        * We need to destroy the bcentry only once - from the thread that has
+        * deleted the entry from the hashtable, all other calls to this
+        * function were redundant.
         */
-       if (atomic_load_relaxed(&bc->count) > bc->size * 8) {
-               grow = true;
-       } else if (atomic_load_relaxed(&bc->count) < bc->size * 2 &&
-                  bc->size > bc->minsize)
-       {
-               grow = false;
-       } else {
-               /* Someone resized it already, bail. */
-               RWUNLOCK(&bc->lock, isc_rwlocktype_write);
-               return;
+       if (!cds_lfht_del(ht, &bad->ht_node)) {
+               call_rcu(&bad->rcu_head, bcentry_destroy);
        }
+}
 
-       if (grow) {
-               newsize = bc->size * 2 + 1;
-       } else {
-               newsize = (bc->size - 1) / 2;
-#ifdef __clang_analyzer__
-               /*
-                * XXXWPK there's a bug in clang static analyzer -
-                * `value % newsize` is considered undefined even though
-                * we check if newsize is larger than 0. This helps.
-                */
-               newsize += 1;
-#endif
+static bool
+bcentry_alive(struct cds_lfht *ht, dns_bcentry_t *bad, isc_stdtime_t now) {
+       if (cds_lfht_is_node_deleted(&bad->ht_node)) {
+               return (false);
+       } else if (atomic_load_relaxed(&bad->expire) < now) {
+               bcentry_evict(ht, bad);
+               return (false);
        }
-       RUNTIME_CHECK(newsize > 0);
 
-       newtable = isc_mem_getx(bc->mctx, sizeof(dns_bcentry_t *) * newsize,
-                               ISC_MEM_ZERO);
+       return (true);
+}
 
-       newlocks = isc_mem_get(bc->mctx, sizeof(isc_mutex_t) * newsize);
+#define cds_lfht_for_each_entry_next(ht, iter, pos, member)     \
+       for (cds_lfht_next(ht, iter),                           \
+            pos = cds_lfht_entry(cds_lfht_iter_get_node(iter), \
+                                 __typeof__(*(pos)), member);  \
+            pos != NULL; /**/                                  \
+            cds_lfht_next(ht, iter),                           \
+            pos = cds_lfht_entry(cds_lfht_iter_get_node(iter), \
+                                 __typeof__(*(pos)), member))
 
-       /* Copy existing mutexes */
-       for (i = 0; i < newsize && i < bc->size; i++) {
-               newlocks[i] = bc->tlocks[i];
-       }
-       /* Initialize additional mutexes if we're growing */
-       for (i = bc->size; i < newsize; i++) {
-               isc_mutex_init(&newlocks[i]);
-       }
-       /* Destroy extra mutexes if we're shrinking */
-       for (i = newsize; i < bc->size; i++) {
-               isc_mutex_destroy(&bc->tlocks[i]);
-       }
-
-       for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
-               for (bad = bc->table[i]; bad != NULL; bad = next) {
-                       next = bad->next;
-                       if (isc_time_compare(&bad->expire, now) < 0) {
-                               isc_mem_put(bc->mctx, bad, sizeof(*bad));
-                               atomic_fetch_sub_relaxed(&bc->count, 1);
-                       } else {
-                               bad->next = newtable[bad->hashval % newsize];
-                               newtable[bad->hashval % newsize] = bad;
-                       }
+static void
+bcentry_purge_next(struct cds_lfht *ht, struct cds_lfht_iter *iter,
+                  isc_stdtime_t now) {
+       /* Lazy-purge the table */
+       size_t count = 10;
+       dns_bcentry_t *bad;
+       cds_lfht_for_each_entry_next(ht, iter, bad, ht_node) {
+               if (!bcentry_alive(ht, bad, now)) {
+                       break;
+               }
+               if (--count == 0) {
+                       break;
                }
-               bc->table[i] = NULL;
        }
-
-       isc_mem_put(bc->mctx, bc->tlocks, sizeof(isc_mutex_t) * bc->size);
-       bc->tlocks = newlocks;
-
-       isc_mem_put(bc->mctx, bc->table, sizeof(*bc->table) * bc->size);
-       bc->size = newsize;
-       bc->table = newtable;
-
-       RWUNLOCK(&bc->lock, isc_rwlocktype_write);
 }
 
 void
 dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name,
                 dns_rdatatype_t type, bool update, uint32_t flags,
-                isc_time_t *expire) {
-       unsigned int hashval, hash;
-       dns_bcentry_t *bad, *prev, *next;
-       isc_time_t now;
-       bool resize = false;
-
+                isc_stdtime_t expire) {
        REQUIRE(VALID_BADCACHE(bc));
        REQUIRE(name != NULL);
-       REQUIRE(expire != NULL);
-
-       RWLOCK(&bc->lock, isc_rwlocktype_read);
-
-       now = isc_time_now();
-
-       hashval = dns_name_hash(name);
-       hash = hashval % bc->size;
-       LOCK(&bc->tlocks[hash]);
-       prev = NULL;
-       for (bad = bc->table[hash]; bad != NULL; bad = next) {
-               next = bad->next;
-               if (bad->type == type && dns_name_equal(name, bad->name)) {
-                       if (update) {
-                               bad->expire = *expire;
-                               bad->flags = flags;
-                       }
-                       break;
-               }
-               if (isc_time_compare(&bad->expire, &now) < 0) {
-                       if (prev == NULL) {
-                               bc->table[hash] = bad->next;
-                       } else {
-                               prev->next = bad->next;
-                       }
-                       isc_mem_put(bc->mctx, bad, sizeof(*bad));
-                       atomic_fetch_sub_relaxed(&bc->count, 1);
-               } else {
-                       prev = bad;
-               }
+
+       isc_stdtime_t now = isc_stdtime_now();
+       if (expire < now) {
+               expire = now;
        }
 
-       if (bad == NULL) {
-               unsigned int count;
-               isc_buffer_t buffer;
-
-               bad = isc_mem_get(bc->mctx, sizeof(*bad));
-               *bad = (dns_bcentry_t){ .type = type,
-                                       .hashval = hashval,
-                                       .expire = *expire,
-                                       .flags = flags,
-                                       .next = bc->table[hash] };
-
-               isc_buffer_init(&buffer, bad + 1, name->length);
-               bad->name = dns_fixedname_initname(&bad->fname);
-               dns_name_copy(name, bad->name);
-               bc->table[hash] = bad;
-
-               count = atomic_fetch_add_relaxed(&bc->count, 1);
-               if ((count > bc->size * 8) ||
-                   (count < bc->size * 2 && bc->size > bc->minsize))
-               {
-                       resize = true;
+       rcu_read_lock();
+       struct cds_lfht *ht = rcu_dereference(bc->ht);
+       INSIST(ht != NULL);
+
+       dns_bcentry_t *bad = NULL;
+       uint32_t hashval = dns_name_hash(name);
+
+       struct cds_lfht_iter iter;
+       dns_bcentry_t *found = NULL;
+       cds_lfht_for_each_entry_duplicate(ht, hashval, bcentry_match, name,
+                                         &iter, bad, ht_node) {
+               if (bcentry_alive(ht, bad, now) && bad->type == type) {
+                       found = bad;
+                       /*
+                        * We could bail-out on first match, but:
+                        * 1. there could be duplicate .type entries
+                        * 2. we want to check expire for all entries
+                        */
                }
-       } else {
-               bad->expire = *expire;
        }
 
-       UNLOCK(&bc->tlocks[hash]);
-       RWUNLOCK(&bc->lock, isc_rwlocktype_read);
-       if (resize) {
-               badcache_resize(bc, &now);
+       if (found == NULL) {
+               /*
+                * In theory, this could result in multiple entries for the same
+                * type, but we don't care much, as they are going to be roughly
+                * the same, and the last will always trump and the former
+                * entries will expire (see above).
+                */
+               bad = bcentry_new(bc, name, type, flags, expire);
+               cds_lfht_add(ht, hashval, &bad->ht_node);
+       } else if (update) {
+               atomic_store_relaxed(&found->expire, expire);
+               atomic_store_relaxed(&found->flags, flags);
        }
+
+       rcu_read_unlock();
 }
 
-bool
+isc_result_t
 dns_badcache_find(dns_badcache_t *bc, const dns_name_t *name,
-                 dns_rdatatype_t type, uint32_t *flagp, isc_time_t *now) {
-       dns_bcentry_t *bad, *prev, *next;
-       bool answer = false;
-       unsigned int i;
-       unsigned int hash;
-
+                 dns_rdatatype_t type, uint32_t *flagp, isc_stdtime_t now) {
        REQUIRE(VALID_BADCACHE(bc));
        REQUIRE(name != NULL);
-       REQUIRE(now != NULL);
 
-       RWLOCK(&bc->lock, isc_rwlocktype_read);
+       isc_result_t result = ISC_R_NOTFOUND;
 
-       /*
-        * XXXMUKS: dns_name_equal() is expensive as it does a
-        * octet-by-octet comparison, and it can be made better in two
-        * ways here. First, lowercase the names (use
-        * dns_name_downcase() instead of dns_name_copy() in
-        * dns_badcache_add()) so that dns_name_caseequal() can be used
-        * which the compiler will emit as SIMD instructions. Second,
-        * don't put multiple copies of the same name in the chain (or
-        * multiple names will have to be matched for equality), but use
-        * name->link to store the type specific part.
-        */
+       rcu_read_lock();
+       struct cds_lfht *ht = rcu_dereference(bc->ht);
+       INSIST(ht != NULL);
 
-       if (atomic_load_relaxed(&bc->count) == 0) {
-               goto skip;
-       }
+       dns_bcentry_t *bad = NULL;
+       uint32_t hashval = dns_name_hash(name);
 
-       hash = dns_name_hash(name) % bc->size;
-       prev = NULL;
-       LOCK(&bc->tlocks[hash]);
-       for (bad = bc->table[hash]; bad != NULL; bad = next) {
-               next = bad->next;
-               /*
-                * Search the hash list. Clean out expired records as we go.
-                */
-               if (isc_time_compare(&bad->expire, now) < 0) {
-                       if (prev != NULL) {
-                               prev->next = bad->next;
-                       } else {
-                               bc->table[hash] = bad->next;
-                       }
-
-                       isc_mem_put(bc->mctx, bad, sizeof(*bad));
-                       atomic_fetch_sub(&bc->count, 1);
-                       continue;
-               }
-               if (bad->type == type && dns_name_equal(name, bad->name)) {
-                       if (flagp != NULL) {
-                               *flagp = bad->flags;
-                       }
-                       answer = true;
-                       break;
+       struct cds_lfht_iter iter;
+       dns_bcentry_t *found = NULL;
+       cds_lfht_for_each_entry_duplicate(ht, hashval, bcentry_match, name,
+                                         &iter, bad, ht_node) {
+               if (bad->type == type && bcentry_alive(ht, bad, now)) {
+                       found = bad;
                }
-               prev = bad;
        }
-       UNLOCK(&bc->tlocks[hash]);
-skip:
 
-       /*
-        * Slow sweep to clean out stale records.
-        */
-       i = atomic_fetch_add(&bc->sweep, 1) % bc->size;
-       if (isc_mutex_trylock(&bc->tlocks[i]) == ISC_R_SUCCESS) {
-               bad = bc->table[i];
-               if (bad != NULL && isc_time_compare(&bad->expire, now) < 0) {
-                       bc->table[i] = bad->next;
-                       isc_mem_put(bc->mctx, bad, sizeof(*bad));
-                       atomic_fetch_sub_relaxed(&bc->count, 1);
+       if (found) {
+               result = ISC_R_SUCCESS;
+               if (flagp != NULL) {
+                       *flagp = atomic_load_relaxed(&found->flags);
                }
-               UNLOCK(&bc->tlocks[i]);
+
+               bcentry_purge_next(ht, &iter, now);
        }
 
-       RWUNLOCK(&bc->lock, isc_rwlocktype_read);
-       return (answer);
+       rcu_read_unlock();
+
+       return (result);
 }
 
 void
 dns_badcache_flush(dns_badcache_t *bc) {
-       dns_bcentry_t *entry, *next;
-       unsigned int i;
-
-       RWLOCK(&bc->lock, isc_rwlocktype_write);
        REQUIRE(VALID_BADCACHE(bc));
 
-       for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
-               for (entry = bc->table[i]; entry != NULL; entry = next) {
-                       next = entry->next;
-                       isc_mem_put(bc->mctx, entry, sizeof(*entry));
-                       atomic_fetch_sub_relaxed(&bc->count, 1);
-               }
-               bc->table[i] = NULL;
+       struct cds_lfht *ht =
+               cds_lfht_new(BADCACHE_INIT_SIZE, BADCACHE_MIN_SIZE, 0,
+                            CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL);
+       INSIST(ht != NULL);
+
+       /* First swap the hashtables */
+       rcu_read_lock();
+       ht = rcu_xchg_pointer(&bc->ht, ht);
+       rcu_read_unlock();
+
+       /* Make sure nobody is using the old hash table */
+       synchronize_rcu();
+
+       /* Flush the old hash table */
+       dns_bcentry_t *bad = NULL;
+       struct cds_lfht_iter iter;
+       cds_lfht_for_each_entry(ht, &iter, bad, ht_node) {
+               INSIST(!cds_lfht_del(ht, &bad->ht_node));
+               bcentry_destroy(&bad->rcu_head);
        }
-       RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+       RUNTIME_CHECK(!cds_lfht_destroy(ht, NULL));
 }
 
 void
 dns_badcache_flushname(dns_badcache_t *bc, const dns_name_t *name) {
-       dns_bcentry_t *bad, *prev, *next;
-       isc_time_t now;
-       unsigned int hash;
-
        REQUIRE(VALID_BADCACHE(bc));
        REQUIRE(name != NULL);
 
-       RWLOCK(&bc->lock, isc_rwlocktype_read);
-
-       now = isc_time_now();
-       hash = dns_name_hash(name) % bc->size;
-       LOCK(&bc->tlocks[hash]);
-       prev = NULL;
-       for (bad = bc->table[hash]; bad != NULL; bad = next) {
-               int n;
-               next = bad->next;
-               n = isc_time_compare(&bad->expire, &now);
-               if (n < 0 || dns_name_equal(name, bad->name)) {
-                       if (prev == NULL) {
-                               bc->table[hash] = bad->next;
-                       } else {
-                               prev->next = bad->next;
-                       }
-
-                       isc_mem_put(bc->mctx, bad, sizeof(*bad));
-                       atomic_fetch_sub_relaxed(&bc->count, 1);
-               } else {
-                       prev = bad;
-               }
+       rcu_read_lock();
+       struct cds_lfht *ht = rcu_dereference(bc->ht);
+       INSIST(ht != NULL);
+
+       dns_bcentry_t *bad = NULL;
+       uint32_t hashval = dns_name_hash(name);
+
+       struct cds_lfht_iter iter;
+       cds_lfht_for_each_entry_duplicate(ht, hashval, bcentry_match, name,
+                                         &iter, bad, ht_node) {
+               bcentry_evict(ht, bad);
        }
-       UNLOCK(&bc->tlocks[hash]);
 
-       RWUNLOCK(&bc->lock, isc_rwlocktype_read);
+       rcu_read_unlock();
 }
 
 void
 dns_badcache_flushtree(dns_badcache_t *bc, const dns_name_t *name) {
-       dns_bcentry_t *bad, *prev, *next;
-       unsigned int i;
-       int n;
-       isc_time_t now;
+       dns_bcentry_t *bad;
+       isc_stdtime_t now = isc_stdtime_now();
 
        REQUIRE(VALID_BADCACHE(bc));
        REQUIRE(name != NULL);
 
-       /*
-        * We write lock the tree to avoid relocking every node
-        * individually.
-        */
-       RWLOCK(&bc->lock, isc_rwlocktype_write);
-
-       now = isc_time_now();
-
-       for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
-               prev = NULL;
-               for (bad = bc->table[i]; bad != NULL; bad = next) {
-                       next = bad->next;
-                       n = isc_time_compare(&bad->expire, &now);
-                       if (n < 0 || dns_name_issubdomain(bad->name, name)) {
-                               if (prev == NULL) {
-                                       bc->table[i] = bad->next;
-                               } else {
-                                       prev->next = bad->next;
-                               }
-
-                               isc_mem_put(bc->mctx, bad, sizeof(*bad));
-                               atomic_fetch_sub_relaxed(&bc->count, 1);
-                       } else {
-                               prev = bad;
-                       }
+       rcu_read_lock();
+       struct cds_lfht *ht = rcu_dereference(bc->ht);
+       INSIST(ht != NULL);
+
+       struct cds_lfht_iter iter;
+       cds_lfht_for_each_entry(ht, &iter, bad, ht_node) {
+               if (dns_name_issubdomain(bad->name, name)) {
+                       bcentry_evict(ht, bad);
+               } else if (!bcentry_alive(ht, bad, now)) {
+                       /* Flush all the expired entries */
                }
        }
 
-       RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+       rcu_read_unlock();
 }
 
-void
-dns_badcache_print(dns_badcache_t *bc, const char *cachename, FILE *fp) {
+static void
+bcentry_print(dns_bcentry_t *bad, isc_stdtime_t now, FILE *fp) {
        char namebuf[DNS_NAME_FORMATSIZE];
        char typebuf[DNS_RDATATYPE_FORMATSIZE];
-       dns_bcentry_t *bad, *next, *prev;
-       isc_time_t now;
-       unsigned int i;
-       uint64_t t;
+
+       dns_name_format(bad->name, namebuf, sizeof(namebuf));
+       dns_rdatatype_format(bad->type, typebuf, sizeof(typebuf));
+       fprintf(fp, "; %s/%s [ttl %" PRIu32 "]\n", namebuf, typebuf,
+               atomic_load_relaxed(&bad->expire) - now);
+}
+
+void
+dns_badcache_print(dns_badcache_t *bc, const char *cachename, FILE *fp) {
+       dns_bcentry_t *bad;
+       isc_stdtime_t now = isc_stdtime_now();
 
        REQUIRE(VALID_BADCACHE(bc));
-       REQUIRE(cachename != NULL);
        REQUIRE(fp != NULL);
 
-       /*
-        * We write lock the tree to avoid relocking every node
-        * individually.
-        */
-       RWLOCK(&bc->lock, isc_rwlocktype_write);
        fprintf(fp, ";\n; %s\n;\n", cachename);
 
-       now = isc_time_now();
-       for (i = 0; atomic_load_relaxed(&bc->count) > 0 && i < bc->size; i++) {
-               prev = NULL;
-               for (bad = bc->table[i]; bad != NULL; bad = next) {
-                       next = bad->next;
-                       if (isc_time_compare(&bad->expire, &now) < 0) {
-                               if (prev != NULL) {
-                                       prev->next = bad->next;
-                               } else {
-                                       bc->table[i] = bad->next;
-                               }
-
-                               isc_mem_put(bc->mctx, bad, sizeof(*bad));
-                               atomic_fetch_sub_relaxed(&bc->count, 1);
-                               continue;
-                       }
-                       prev = bad;
-                       dns_name_format(bad->name, namebuf, sizeof(namebuf));
-                       dns_rdatatype_format(bad->type, typebuf,
-                                            sizeof(typebuf));
-                       t = isc_time_microdiff(&bad->expire, &now);
-                       t /= 1000;
-                       fprintf(fp,
-                               "; %s/%s [ttl "
-                               "%" PRIu64 "]\n",
-                               namebuf, typebuf, t);
+       rcu_read_lock();
+       struct cds_lfht *ht = rcu_dereference(bc->ht);
+       INSIST(ht != NULL);
+
+       struct cds_lfht_iter iter;
+       cds_lfht_for_each_entry(ht, &iter, bad, ht_node) {
+               if (bcentry_alive(ht, bad, now)) {
+                       bcentry_print(bad, now, fp);
                }
        }
-       RWUNLOCK(&bc->lock, isc_rwlocktype_write);
+
+       rcu_read_unlock();
 }
index 1021ba34a879d83673d88d18cac78ad47d877120..03eb2ec3b9a7f7943c7481004028d4578aaab6ef 100644 (file)
@@ -45,6 +45,9 @@
 #include <inttypes.h>
 #include <stdbool.h>
 
+#include <isc/mem.h>
+#include <isc/stdtime.h>
+
 #include <dns/types.h>
 
 ISC_LANG_BEGINDECLS
@@ -53,8 +56,8 @@ ISC_LANG_BEGINDECLS
  ***   Functions
  ***/
 
-isc_result_t
-dns_badcache_init(isc_mem_t *mctx, unsigned int size, dns_badcache_t **bcp);
+dns_badcache_t *
+dns_badcache_new(isc_mem_t *mctx);
 /*%
  * Allocate and initialize a badcache and store it in '*bcp'.
  *
@@ -76,7 +79,7 @@ dns_badcache_destroy(dns_badcache_t **bcp);
 void
 dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name,
                 dns_rdatatype_t type, bool update, uint32_t flags,
-                isc_time_t *expire);
+                isc_stdtime_t expire);
 /*%
  * Adds a badcache entry to the badcache 'bc' for name 'name' and
  * type 'type'.  If an entry already exists, then it will be updated if
@@ -89,14 +92,14 @@ dns_badcache_add(dns_badcache_t *bc, const dns_name_t *name,
  * \li expire != NULL
  */
 
-bool
+isc_result_t
 dns_badcache_find(dns_badcache_t *bc, const dns_name_t *name,
-                 dns_rdatatype_t type, uint32_t *flagp, isc_time_t *now);
+                 dns_rdatatype_t type, uint32_t *flagp, isc_stdtime_t now);
 /*%
- * Returns true if a record is found in the badcache 'bc' matching
+ * Returns ISC_R_SUCCESS if a record is found in the badcache 'bc' matching
  * 'name' and 'type', with an expiration date later than 'now'.
  * If 'flagp' is not NULL, then '*flagp' is updated to the flags
- * that were stored in the badcache entry.  Returns false if
+ * that were stored in the badcache entry.  Returns ISC_R_NOTFOUND if
  * no matching record is found.
  *
  * Requires:
index 5faa165e0283b8e4976d27df46bf3ae66ed07bf1..aeee2fa620d31d6752087d7687a3163f70d8e9c1 100644 (file)
@@ -588,7 +588,7 @@ dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name,
  * \li name to be valid.
  */
 
-bool
+isc_result_t
 dns_resolver_getbadcache(dns_resolver_t *resolver, const dns_name_t *name,
                         dns_rdatatype_t type, isc_time_t *now);
 /*%<
index 201979df61bc79294b6751daf9a9c0917a336357..0af07b8273ef4e0798abb3148172bcd4bc1f279c 100644 (file)
@@ -236,7 +236,6 @@ STATIC_ASSERT(NS_PROCESSING_LIMIT > NS_RR_LIMIT,
  */
 #define MAX_EDNS0_TIMEOUTS 3
 
-#define DNS_RESOLVER_BADCACHESIZE 1021
 #define DNS_RESOLVER_BADCACHETTL(fctx) \
        (((fctx)->res->lame_ttl > 30) ? (fctx)->res->lame_ttl : 30)
 
@@ -9906,7 +9905,6 @@ dns_resolver_create(dns_view_t *view, isc_loopmgr_t *loopmgr,
                    isc_tlsctx_cache_t *tlsctx_cache,
                    dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
                    dns_resolver_t **resp) {
-       isc_result_t result = ISC_R_SUCCESS;
        dns_resolver_t *res = NULL;
 
        /*
@@ -9951,11 +9949,7 @@ dns_resolver_create(dns_view_t *view, isc_loopmgr_t *loopmgr,
 #endif
        isc_refcount_init(&res->references, 1);
 
-       result = dns_badcache_init(res->mctx, DNS_RESOLVER_BADCACHESIZE,
-                                  &res->badcache);
-       if (result != ISC_R_SUCCESS) {
-               goto cleanup;
-       }
+       res->badcache = dns_badcache_new(res->mctx);
 
        /* This needs to be case sensitive to not lowercase options and type */
        isc_hashmap_create(view->mctx, RES_DOMAIN_HASH_BITS,
@@ -9984,11 +9978,6 @@ dns_resolver_create(dns_view_t *view, isc_loopmgr_t *loopmgr,
        *resp = res;
 
        return (ISC_R_SUCCESS);
-
-cleanup:
-       dns_view_weakdetach(&res->view);
-       isc_mem_putanddetach(&res->mctx, res, sizeof(*res));
-       return (result);
 }
 
 static void
@@ -10730,14 +10719,15 @@ dns_resolver_addbadcache(dns_resolver_t *resolver, const dns_name_t *name,
 #endif /* ifdef ENABLE_AFL */
        {
                dns_badcache_add(resolver->badcache, name, type, false, 0,
-                                expire);
+                                isc_time_seconds(expire));
        }
 }
 
-bool
+isc_result_t
 dns_resolver_getbadcache(dns_resolver_t *resolver, const dns_name_t *name,
                         dns_rdatatype_t type, isc_time_t *now) {
-       return (dns_badcache_find(resolver->badcache, name, type, NULL, now));
+       return (dns_badcache_find(resolver->badcache, name, type, NULL,
+                                 isc_time_seconds(now)));
 }
 
 void
index 3447d0482b30f5af89e3fb4d6a2fc3b3ce7672ba..0887c07ad973af370f66e847491f539594bc3096 100644 (file)
@@ -857,7 +857,9 @@ view_find(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type) {
 
        disassociate_rdatasets(val);
 
-       if (dns_resolver_getbadcache(val->view->resolver, name, type, &now)) {
+       result = dns_resolver_getbadcache(val->view->resolver, name, type,
+                                         &now);
+       if (result == ISC_R_SUCCESS) {
                dns_name_format(name, namebuf, sizeof(namebuf));
                dns_rdatatype_format(type, typebuf, sizeof(typebuf));
                validator_log(val, ISC_LOG_INFO, "bad cache hit (%s/%s)",
index 0399ad73f8bb8b0b8fbfdf0de2b1574639feb6ac..1fe0c16dabd93a72abc9797e396de1bba2bd8b95 100644 (file)
@@ -71,8 +71,7 @@
                        goto cleanup;        \
        } while (0)
 
-#define DNS_VIEW_DELONLYHASH   111
-#define DNS_VIEW_FAILCACHESIZE 1021
+#define DNS_VIEW_DELONLYHASH 111
 
 /*%
  * Default EDNS0 buffer size
@@ -146,11 +145,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, const char *name,
 
        dns_tsigkeyring_create(view->mctx, &view->dynamickeys);
 
-       result = dns_badcache_init(view->mctx, DNS_VIEW_FAILCACHESIZE,
-                                  &view->failcache);
-       if (result != ISC_R_SUCCESS) {
-               goto cleanup_dynkeys;
-       }
+       view->failcache = dns_badcache_new(view->mctx);
 
        isc_mutex_init(&view->new_zone_lock);
 
@@ -188,7 +183,6 @@ cleanup_new_zone_lock:
        isc_mutex_destroy(&view->new_zone_lock);
        dns_badcache_destroy(&view->failcache);
 
-cleanup_dynkeys:
        if (view->dynamickeys != NULL) {
                dns_tsigkeyring_detach(&view->dynamickeys);
        }
index d407cb3568573caf59372aef94fe3f7ddf8dc7df..061d8760b7ee098d94275232a0ee62f74cbeccca 100644 (file)
@@ -943,9 +943,10 @@ ns_client_error(ns_client_t *client, isc_result_t result) {
                isc_interval_set(&i, client->view->fail_ttl, 0);
                result = isc_time_nowplusinterval(&expire, &i);
                if (result == ISC_R_SUCCESS) {
-                       dns_badcache_add(
-                               client->view->failcache, client->query.qname,
-                               client->query.qtype, true, flags, &expire);
+                       dns_badcache_add(client->view->failcache,
+                                        client->query.qname,
+                                        client->query.qtype, true, flags,
+                                        isc_time_seconds(&expire));
                }
        }
 
index 7349582004339c6f9f89cf62ff1f27f86bc3f184..9d4ba5c13cc084093aa3f71b254f2c5ff7589eaa 100644 (file)
@@ -7000,7 +7000,7 @@ cleanup:
  */
 isc_result_t
 ns__query_sfcache(query_ctx_t *qctx) {
-       bool failcache;
+       isc_result_t failcache;
        uint32_t flags;
 
        /*
@@ -7013,20 +7013,22 @@ ns__query_sfcache(query_ctx_t *qctx) {
        flags = 0;
 #ifdef ENABLE_AFL
        if (qctx->client->manager->sctx->fuzztype == isc_fuzz_resolver) {
-               failcache = false;
-       } else {
+               failcache = ISC_R_NOTFOUND;
+       } else
+#endif /* ifdef ENABLE_AFL */
+       {
                failcache = dns_badcache_find(
                        qctx->view->failcache, qctx->client->query.qname,
-                       qctx->qtype, &flags, &qctx->client->tnow);
+                       qctx->qtype, &flags,
+                       isc_time_seconds(&qctx->client->tnow));
        }
-#else  /* ifdef ENABLE_AFL */
-       failcache = dns_badcache_find(qctx->view->failcache,
-                                     qctx->client->query.qname, qctx->qtype,
-                                     &flags, &qctx->client->tnow);
-#endif /* ifdef ENABLE_AFL */
-       if (failcache &&
-           (((flags & NS_FAILCACHE_CD) != 0) ||
-            ((qctx->client->message->flags & DNS_MESSAGEFLAG_CD) == 0)))
+
+       if (failcache != ISC_R_SUCCESS) {
+               return (ISC_R_COMPLETE);
+       }
+
+       if (((flags & NS_FAILCACHE_CD) != 0) ||
+           ((qctx->client->message->flags & DNS_MESSAGEFLAG_CD) == 0))
        {
                if (isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1))) {
                        char namebuf[DNS_NAME_FORMATSIZE];
index ea57322d9756a13f7fa5263450576945d6496195..da775f4995f869b70c1e47f7eaceed1cafb29889 100644 (file)
@@ -19,6 +19,7 @@ LDADD +=                              \
 
 check_PROGRAMS =               \
        acl_test                \
+       badcache_test           \
        db_test                 \
        dbdiff_test             \
        dbiterator_test         \
diff --git a/tests/dns/badcache_test.c b/tests/dns/badcache_test.c
new file mode 100644 (file)
index 0000000..dfe8adf
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * 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/md.h>
+#include <isc/mem.h>
+#include <isc/os.h>
+#include <isc/thread.h>
+#include <isc/urcu.h>
+#include <isc/util.h>
+#include <isc/uv.h>
+
+#include <dns/badcache.h>
+#include <dns/compress.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdatatype.h>
+
+#include <tests/dns.h>
+
+static uint32_t
+crc32(const uint8_t *buf, size_t size) {
+       uint32_t crc;
+       crc = 0xFFFFFFFF;
+       while (size--) {
+               crc = crc ^ *buf++;
+               for (size_t j = 0; j < 8; j++) {
+                       uint32_t mask = -(crc & 1);
+                       crc = (crc >> 1) ^ (0xedb88320 & mask);
+               }
+       }
+       return (~crc);
+}
+
+#define BADCACHE_TEST_FLAG 1 << 3
+
+ISC_LOOP_TEST_IMPL(basic) {
+       dns_badcache_t *bc = NULL;
+       dns_fixedname_t fname = { 0 };
+       dns_name_t *name = dns_fixedname_initname(&fname);
+       isc_stdtime_t now = isc_stdtime_now();
+       isc_result_t result;
+       uint32_t flags = BADCACHE_TEST_FLAG;
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+
+       bc = dns_badcache_new(mctx);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now + 60);
+
+       flags = 0;
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(flags, BADCACHE_TEST_FLAG);
+
+       flags = 0;
+       result = dns_badcache_find(bc, name, dns_rdatatype_a, &flags, now);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+       assert_int_equal(flags, 0);
+
+       dns_badcache_destroy(&bc);
+
+       isc_loopmgr_shutdown(loopmgr);
+}
+
+ISC_LOOP_TEST_IMPL(expire) {
+       dns_badcache_t *bc = NULL;
+       dns_fixedname_t fname = { 0 };
+       dns_name_t *name = dns_fixedname_initname(&fname);
+       isc_stdtime_t now = isc_stdtime_now();
+       isc_result_t result;
+       uint32_t flags = BADCACHE_TEST_FLAG;
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+
+       bc = dns_badcache_new(mctx);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now + 60);
+       dns_badcache_add(bc, name, dns_rdatatype_a, false, flags, now + 60);
+
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(flags, BADCACHE_TEST_FLAG);
+
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags,
+                                  now + 61);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       result = dns_badcache_find(bc, name, dns_rdatatype_a, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(flags, BADCACHE_TEST_FLAG);
+
+       dns_badcache_add(bc, name, dns_rdatatype_a, true, flags, now + 120);
+
+       result = dns_badcache_find(bc, name, dns_rdatatype_a, &flags, now + 61);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(flags, BADCACHE_TEST_FLAG);
+
+       dns_badcache_destroy(&bc);
+
+       isc_loopmgr_shutdown(loopmgr);
+}
+
+ISC_LOOP_TEST_IMPL(print) {
+       dns_badcache_t *bc = NULL;
+       dns_fixedname_t fname = { 0 };
+       dns_name_t *name = dns_fixedname_initname(&fname);
+       isc_stdtime_t now = isc_stdtime_now();
+       isc_stdtime_t expire = now + 60;
+       uint32_t flags = BADCACHE_TEST_FLAG;
+       FILE *file = NULL;
+       uint8_t buf[4096];
+       size_t len;
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+
+       bc = dns_badcache_new(mctx);
+       dns_badcache_add(bc, name, dns_rdatatype_a, false, flags, expire);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, expire);
+
+       file = fopen("./badcache.out", "w");
+       dns_badcache_print(bc, "badcache", file);
+       fclose(file);
+
+       file = fopen("./badcache.out", "r");
+       len = fread(buf, sizeof(buf[0]), ARRAY_SIZE(buf), file);
+       assert_int_equal(len, 68);
+       fclose(file);
+
+       /* Calculated as crc32 ./badcache.out */
+       assert_int_equal(crc32(buf, len), 0x7c96678f);
+
+       dns_badcache_destroy(&bc);
+
+       isc_loopmgr_shutdown(loopmgr);
+}
+
+ISC_LOOP_TEST_IMPL(flush) {
+       dns_badcache_t *bc = NULL;
+       dns_fixedname_t fname = { 0 };
+       dns_name_t *name = dns_fixedname_initname(&fname);
+       isc_stdtime_t now = isc_stdtime_now();
+       isc_result_t result;
+       uint32_t flags = BADCACHE_TEST_FLAG;
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+
+       bc = dns_badcache_new(mctx);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now + 60);
+
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_badcache_flush(bc);
+
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_badcache_destroy(&bc);
+
+       isc_loopmgr_shutdown(loopmgr);
+}
+
+ISC_LOOP_TEST_IMPL(flushname) {
+       dns_badcache_t *bc = NULL;
+       dns_fixedname_t fname = { 0 };
+       dns_name_t *name = dns_fixedname_initname(&fname);
+       isc_stdtime_t now = isc_stdtime_now();
+       isc_result_t result;
+       uint32_t flags = BADCACHE_TEST_FLAG;
+
+       bc = dns_badcache_new(mctx);
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now + 60);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_name_fromstring2(name, "sub.example.com.", NULL, 0, NULL);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now + 60);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_name_fromstring2(name, "sub.sub.example.com.", NULL, 0, NULL);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now + 60);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_name_fromstring2(name, "sub.example.com.", NULL, 0, NULL);
+       dns_badcache_flushname(bc, name);
+
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+       result = dns_badcache_find(bc, name, dns_rdatatype_a, &flags, now);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring2(name, "sub.sub.example.com.", NULL, 0, NULL);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_badcache_destroy(&bc);
+
+       isc_loopmgr_shutdown(loopmgr);
+}
+
+ISC_LOOP_TEST_IMPL(flushtree) {
+       dns_badcache_t *bc = NULL;
+       dns_fixedname_t fname = { 0 };
+       dns_name_t *name = dns_fixedname_initname(&fname);
+       isc_stdtime_t now = isc_stdtime_now();
+       isc_result_t result;
+       uint32_t flags = BADCACHE_TEST_FLAG;
+
+       bc = dns_badcache_new(mctx);
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now + 60);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(flags, BADCACHE_TEST_FLAG);
+
+       dns_name_fromstring2(name, "sub.example.com.", NULL, 0, NULL);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now + 60);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(flags, BADCACHE_TEST_FLAG);
+
+       dns_name_fromstring2(name, "sub.sub.example.com.", NULL, 0, NULL);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now + 60);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(flags, BADCACHE_TEST_FLAG);
+
+       dns_name_fromstring2(name, "sub.example.com.", NULL, 0, NULL);
+       dns_badcache_flushtree(bc, name);
+
+       dns_name_fromstring2(name, "sub.sub.example.com.", NULL, 0, NULL);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring2(name, "sub.example.com.", NULL, 0, NULL);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags, now);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       assert_int_equal(flags, BADCACHE_TEST_FLAG);
+
+       dns_badcache_destroy(&bc);
+
+       isc_loopmgr_shutdown(loopmgr);
+}
+
+ISC_LOOP_TEST_IMPL(purge) {
+       dns_badcache_t *bc = NULL;
+       dns_fixedname_t fname = { 0 };
+       dns_name_t *name = dns_fixedname_initname(&fname);
+       isc_stdtime_t now = isc_stdtime_now();
+       isc_result_t result;
+       uint32_t flags = BADCACHE_TEST_FLAG;
+
+       bc = dns_badcache_new(mctx);
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags,
+                                  now - 60);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_name_fromstring2(name, "sub.example.com.", NULL, 0, NULL);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags,
+                                  now - 60);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_name_fromstring2(name, "sub.sub.example.com.", NULL, 0, NULL);
+       dns_badcache_add(bc, name, dns_rdatatype_aaaa, false, flags, now);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags,
+                                  now - 60);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags,
+                                  now + 30);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring2(name, "sub.sub.example.com.", NULL, 0, NULL);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags,
+                                  now + 30);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring2(name, "sub.example.com.", NULL, 0, NULL);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags,
+                                  now + 30);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring2(name, "example.com.", NULL, 0, NULL);
+       result = dns_badcache_find(bc, name, dns_rdatatype_aaaa, &flags,
+                                  now + 30);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_badcache_destroy(&bc);
+
+       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(print, setup_managers, teardown_managers)
+ISC_TEST_ENTRY_CUSTOM(flush, setup_managers, teardown_managers)
+ISC_TEST_ENTRY_CUSTOM(flushname, setup_managers, teardown_managers)
+ISC_TEST_ENTRY_CUSTOM(flushtree, setup_managers, teardown_managers)
+ISC_TEST_ENTRY_CUSTOM(purge, setup_managers, teardown_managers)
+ISC_TEST_LIST_END
+
+ISC_TEST_MAIN
index adfcc031007bc5468f7cdb453993dcbe99ade4e3..6ef0fdd12b3007395cf063695e228e6b813d9256 100644 (file)
@@ -122,7 +122,8 @@ run_sfcache_test(const ns__query_sfcache_test_params_t *test) {
 
                dns_badcache_add(qctx->client->view->failcache, dns_rootname,
                                 dns_rdatatype_ns, true,
-                                test->cache_entry_flags, &expire);
+                                test->cache_entry_flags,
+                                isc_time_seconds(&expire));
        }
 
        /*