]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Introduce a delegation database
authorColin Vidal <colin@isc.org>
Tue, 20 Jan 2026 13:58:53 +0000 (14:58 +0100)
committerColin Vidal <colin@isc.org>
Mon, 30 Mar 2026 18:41:13 +0000 (20:41 +0200)
Add `dns_delegdb_t`, a qpmulti-based database enabling to lookup a
delegation set (`dns_delegset_t`) from a zonecut name (`dns_name_t`). A
delegation set object essentially contains an expiration time and a list
of delegation (`dns_deleg_t`). Finally, a delegation can be either:

- A list of IP addresses (`isc_netaddrlist_t`), for NS-based delegation
  providing glues or DELEG-based delegation using `server-ipv4=` or
  `server-ipv6=`;
- Or a list of nameserver names, for NS-based delegation without glues,
  or DELEG-based delegation using `server-name=`;
- Or a list of nameserver names, for DELEG-based delegation using
  `include-delegparam=`.

The delegation database API provides lookup by closest zonecut,
delegation and delegation set builders as well as insertion of those
newly built delegation set, dumping to a `FILE *`, conversion from an NS
rdataset to a delegation set, deletion of a specific zonecut or all the
sub-tree of a given zonecut.

A memory context is internally used inside the delegation database and
can be constraint to a maximum size. Once it gets close to its maximum
size and a new delegation set is inserted into the database, a
reclamation flow is run internally removing the least recently used
entries.

The delegation set and delegation objects are, once they been inserted
into the database, read-only object. Thus, the caller can use them
without concurrency or locking concerns, and must detached them once its
done with it.

lib/dns/deleg.c [new file with mode: 0644]
lib/dns/include/dns/deleg.h [new file with mode: 0644]
lib/dns/include/dns/types.h
lib/dns/meson.build
tests/dns/deleg_test.c [new file with mode: 0644]
tests/dns/meson.build

diff --git a/lib/dns/deleg.c b/lib/dns/deleg.c
new file mode 100644 (file)
index 0000000..dc7f5a4
--- /dev/null
@@ -0,0 +1,988 @@
+/*
+ * 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 <isc/async.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/sieve.h>
+#include <isc/stdtime.h>
+#include <isc/urcu.h>
+#include <isc/uv.h>
+
+#include <dns/deleg.h>
+#include <dns/name.h>
+#include <dns/qp.h>
+#include <dns/view.h>
+
+#define DELEGDB_NODE_MAGIC      ISC_MAGIC('D', 'e', 'G', 'N')
+#define VALID_DELEGDB_NODE(node) ISC_MAGIC_VALID(node, DELEGDB_NODE_MAGIC)
+
+#define DELEGDB_MAGIC    ISC_MAGIC('D', 'e', 'G', 'D')
+#define VALID_DELEGDB(db) ISC_MAGIC_VALID(db, DELEGDB_MAGIC)
+
+#define DELEGDB_MINSIZE (1024 * 1024) /* 1MiB */
+
+typedef struct delegdb_node delegdb_node_t;
+
+struct dns_delegdb {
+       unsigned int magic;
+
+       /*
+        * The DB uses its own memory context in order to easily enforce
+        * overmem policies based on allocations made from this memory context.
+        */
+       isc_mem_t *mctx;
+       isc_refcount_t references;
+
+       size_t nloops;
+       ISC_SIEVE(delegdb_node_t) * lru;
+
+       dns_qpmulti_t *nodes;
+
+       /*
+        * Keep track of now many owners are actually using the delegdb. For
+        * instance:
+        *
+        * - During a server reload, the new view will (by default)
+        *   start owning the existing delegdb from the previous instance of the
+        *   same view using `dns_delegdb_reuse()`. This will increase `owners`
+        *   by one.
+        *
+        * - Later on, either the old instance of the view (or the new one,
+        *   in case of reload failure) will call `dns_delegdb_shutdown()` on
+        *   the delegdb. This will decrement `owners` by one.
+        *
+        * If `owners` is bigger than 1 when `dns_delegdb_shutdown()` is called,
+        * it means the delegdb must not be shutdown because there are other
+        * owners using it, so `dns_delegdb_shutdown()` bails off in this case.
+        * (After decrementing `owners`.)
+        */
+       isc_refcount_t owners;
+
+       size_t lowater;
+       size_t hiwater;
+};
+
+static void
+delegdb_destroy(dns_delegdb_t *delegdb) {
+       REQUIRE(VALID_DELEGDB(delegdb));
+       REQUIRE(delegdb->nodes == NULL);
+
+       delegdb->magic = 0;
+       isc_mem_cput(delegdb->mctx, delegdb->lru, delegdb->nloops,
+                    sizeof(delegdb->lru[0]));
+
+       isc_mem_putanddetach(&delegdb->mctx, delegdb, sizeof(*delegdb));
+}
+
+ISC_REFCOUNT_IMPL(dns_delegdb, delegdb_destroy);
+
+struct delegdb_node {
+       unsigned int magic;
+       dns_delegdb_t *delegdb;
+       isc_refcount_t references;
+
+       /* LRU */
+       isc_loop_t *loop;
+       ISC_LINK(delegdb_node_t) link;
+       bool visited;
+
+       /*
+        * Used to build a list of nodes to be deleted (when running the
+        * delete tree flow).
+        */
+       ISC_LINK(delegdb_node_t) deadlink;
+
+       /*
+        * Immutable node data
+        */
+       size_t size;
+       dns_name_t zonecut;
+       dns_delegset_t *delegset;
+};
+
+/*
+ * All node cleanup is done on the node's owning loop so that the node
+ * remains fully valid (name, delegset, SIEVE link) until it is actually
+ * destroyed.  This is important because after a node is removed from the
+ * QP trie, it may still be linked in the owning loop's SIEVE list; if
+ * another thread's eviction could encounter a half-destroyed node, we
+ * would get a use-after-free.  By deferring everything to the owning
+ * loop, the node is intact until the SIEVE unlink happens.
+ */
+static void
+delegdb_node_destroy_async(void *arg) {
+       delegdb_node_t *node = arg;
+       isc_mem_t *mctx = NULL;
+
+       REQUIRE(VALID_DELEGDB_NODE(node));
+       REQUIRE(DNS_DELEGSET_VALID(node->delegset));
+
+       node->magic = 0;
+
+       isc_mem_attach(node->delegdb->mctx, &mctx);
+
+       if (ISC_SIEVE_LINKED(node, link)) {
+               ISC_SIEVE_UNLINK(node->delegdb->lru[isc_tid()], node, link);
+       }
+
+       dns_name_free(&node->zonecut, mctx);
+       dns_delegset_detach(&node->delegset);
+
+       dns_delegdb_detach(&node->delegdb);
+       isc_loop_unref(node->loop);
+       isc_mem_putanddetach(&mctx, node, sizeof(*node));
+}
+
+static void
+delegdb_node_destroy(delegdb_node_t *node) {
+       REQUIRE(VALID_DELEGDB_NODE(node));
+
+       if (node->loop == isc_loop()) {
+               delegdb_node_destroy_async(node);
+       } else {
+               isc_async_run(node->loop, delegdb_node_destroy_async, node);
+       }
+}
+
+#ifdef DNS_DELEGDB_NODETRACE
+#define delegdb_node_ref(ptr) \
+       delegdb_node__ref(ptr, __func__, __FILE__, __LINE__)
+#define delegdb_node_unref(ptr) \
+       delegdb_node__unref(ptr, __func__, __FILE__, __LINE__)
+ISC_REFCOUNT_STATIC_TRACE_DECL(delegdb_node);
+ISC_REFCOUNT_STATIC_TRACE_IMPL(delegdb_node, delegdb_node_destroy);
+#else
+ISC_REFCOUNT_STATIC_DECL(delegdb_node);
+ISC_REFCOUNT_STATIC_IMPL(delegdb_node, delegdb_node_destroy);
+#endif
+
+static void
+dbnode_attach(ISC_ATTR_UNUSED void *uctx, void *pval,
+             ISC_ATTR_UNUSED uint32_t ival) {
+       delegdb_node_t *node = pval;
+
+       REQUIRE(VALID_DELEGDB_NODE(node));
+       delegdb_node_ref(node);
+}
+
+static void
+dbnode_detach(ISC_ATTR_UNUSED void *uctx, void *pval,
+             ISC_ATTR_UNUSED uint32_t ival) {
+       delegdb_node_t *node = pval;
+
+       REQUIRE(VALID_DELEGDB_NODE(node));
+       delegdb_node_unref(node);
+}
+
+static size_t
+makekey(dns_qpkey_t key, void *uctx ISC_ATTR_UNUSED, void *pval,
+       uint32_t ival ISC_ATTR_UNUSED) {
+       delegdb_node_t *data = pval;
+       return dns_qpkey_fromname(key, &data->zonecut, DNS_DBNAMESPACE_NORMAL);
+}
+
+static void
+triename(ISC_ATTR_UNUSED void *uctx, char *buf, size_t size) {
+       (void)strncpy(buf, "delegdb", size);
+}
+
+static dns_qpmethods_t qpmethods = { .attach = dbnode_attach,
+                                    .detach = dbnode_detach,
+                                    .makekey = makekey,
+                                    .triename = triename };
+
+void
+dns_delegdb_create(dns_delegdb_t **delegdbp) {
+       isc_mem_t *mctx = NULL;
+       dns_delegdb_t *delegdb = NULL;
+
+       REQUIRE(isc_loop_get(isc_tid()) == isc_loop_main());
+       REQUIRE(delegdbp != NULL && *delegdbp == NULL);
+
+       isc_mem_create("dns_delegdb", &mctx);
+       isc_mem_setdestroycheck(mctx, true);
+
+       delegdb = isc_mem_get(mctx, sizeof(*delegdb));
+       *delegdb = (dns_delegdb_t){ .magic = DELEGDB_MAGIC,
+                                   .mctx = mctx,
+                                   .references = ISC_REFCOUNT_INITIALIZER(1),
+                                   .nloops = isc_loopmgr_nloops(),
+                                   .owners = ISC_REFCOUNT_INITIALIZER(1) };
+
+       dns_qpmulti_create(mctx, &qpmethods, &delegdb->nodes, &delegdb->nodes);
+
+       delegdb->lru = isc_mem_cget(mctx, delegdb->nloops,
+                                   sizeof(delegdb->lru[0]));
+       for (size_t i = 0; i < delegdb->nloops; i++) {
+               ISC_SIEVE_INIT(delegdb->lru[i]);
+       }
+
+       *delegdbp = delegdb;
+}
+
+void
+dns_delegdb_reuse(dns_view_t *oldview, dns_view_t *newview) {
+       REQUIRE(isc_loop_get(isc_tid()) == isc_loop_main());
+       REQUIRE(DNS_VIEW_VALID(oldview));
+       REQUIRE(DNS_VIEW_VALID(newview));
+
+       dns_delegdb_attach(oldview->deleg, &newview->deleg);
+       isc_refcount_increment(&oldview->deleg->owners);
+}
+
+typedef struct nodes_rcu_head {
+       isc_mem_t *mctx;
+       dns_qpmulti_t *nodes;
+       struct rcu_head rcu_head;
+} nodes_rcu_head_t;
+
+static void
+deleg_destroy_qpmulti(struct rcu_head *rcu_head) {
+       nodes_rcu_head_t *nrh = caa_container_of(rcu_head, nodes_rcu_head_t,
+                                                rcu_head);
+
+       dns_qpmulti_destroy(&nrh->nodes);
+
+       isc_mem_putanddetach(&nrh->mctx, nrh, sizeof(*nrh));
+}
+
+inline static bool
+isactive(delegdb_node_t *node, dns_ttl_t now) {
+       return node->delegset->expires > now;
+}
+
+static void
+getparentnode(dns_qpchain_t *chain, delegdb_node_t **node, dns_ttl_t now) {
+       size_t len = dns_qpchain_length(chain);
+
+       while (len >= 2) {
+               *node = NULL;
+               dns_qpchain_node(chain, len - 2, (void **)node, NULL);
+
+               if (isactive(*node, now)) {
+                       break;
+               }
+               len--;
+       }
+}
+
+/*
+ * NOTE: Caller needs to hold a RCU read critical section.
+ */
+static isc_result_t
+dns__deleg_lookup(dns_delegdb_t *delegdb, dns_qpread_t *qpr,
+                 const dns_name_t *name, isc_stdtime_t optnow,
+                 unsigned int options, dns_name_t *zonecut,
+                 dns_name_t *deepestzonecut, dns_delegset_t **delegsetp) {
+       isc_result_t result = ISC_R_SUCCESS;
+       delegdb_node_t *node = NULL;
+       isc_stdtime_t now = optnow > 0 ? optnow : isc_stdtime_now();
+
+       dns_qpchain_t chain = {};
+       bool noexact = (options & DNS_DBFIND_NOEXACT) != 0;
+
+       REQUIRE(VALID_DELEGDB(delegdb));
+       REQUIRE(DNS_NAME_VALID(name));
+       REQUIRE(dns_name_hasbuffer(zonecut));
+       REQUIRE(deepestzonecut == NULL || dns_name_hasbuffer(deepestzonecut));
+
+       result = dns_qp_lookup(qpr, name, DNS_DBNAMESPACE_NORMAL, NULL, &chain,
+                              (void **)&node, NULL);
+
+       if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) {
+               return ISC_R_NOTFOUND;
+       }
+       INSIST(VALID_DELEGDB_NODE(node));
+
+       if (deepestzonecut != NULL) {
+               dns_name_copy(&node->zonecut, deepestzonecut);
+       }
+
+       if (result == ISC_R_SUCCESS && (noexact || !isactive(node, now))) {
+               getparentnode(&chain, &node, now);
+       } else if (result == DNS_R_PARTIALMATCH && !isactive(node, now)) {
+               getparentnode(&chain, &node, now);
+       }
+
+       result = isactive(node, now) ? ISC_R_SUCCESS : ISC_R_NOTFOUND;
+       if (result == ISC_R_SUCCESS) {
+               dns_name_copy(&node->zonecut, zonecut);
+               INSIST(node->delegset);
+               dns_delegset_attach(node->delegset, delegsetp);
+               ISC_SIEVE_MARK(node, visited);
+       } else {
+               /*
+                * FIXME: if we lookup something that has expired, we need
+                * either the "deadnodes" (see qpcache) mechanism here - or call
+                * something like isc_async_run(delete_me, node).
+                */
+       }
+
+       return result;
+}
+
+isc_result_t
+dns_delegdb_lookup(dns_delegdb_t *delegdb, const dns_name_t *name,
+                  isc_stdtime_t now, unsigned int options, dns_name_t *zonecut,
+                  dns_name_t *deepestzonecut, dns_delegset_t **delegsetp) {
+       isc_result_t result = ISC_R_SHUTTINGDOWN;
+       dns_qpmulti_t *nodes = NULL;
+       dns_qpread_t qpr = {};
+
+       rcu_read_lock();
+       nodes = rcu_dereference(delegdb->nodes);
+       if (nodes != NULL) {
+               dns_qpmulti_query(nodes, &qpr);
+
+               result = dns__deleg_lookup(delegdb, &qpr, name, now, options,
+                                          zonecut, deepestzonecut, delegsetp);
+               dns_qpread_destroy(nodes, &qpr);
+       }
+       rcu_read_unlock();
+
+       return result;
+}
+
+void
+dns_delegset_allocset(dns_delegdb_t *delegdb, dns_delegset_t **delegsetp) {
+       REQUIRE(VALID_DELEGDB(delegdb));
+       REQUIRE(delegsetp != NULL && *delegsetp == NULL);
+
+       dns_delegset_t *delegset = isc_mem_get(delegdb->mctx,
+                                              sizeof(*delegset));
+       *delegset = (dns_delegset_t){
+               .magic = DNS_DELEGSET_MAGIC,
+               .references = ISC_REFCOUNT_INITIALIZER(1),
+               .delegs = ISC_LIST_INITIALIZER,
+       };
+       isc_mem_attach(delegdb->mctx, &delegset->mctx);
+
+       *delegsetp = delegset;
+}
+
+void
+dns_delegset_allocdeleg(dns_delegset_t *delegset, dns_deleg_type_t type,
+                       dns_deleg_t **delegp) {
+       dns_deleg_t *deleg = NULL;
+
+       REQUIRE(DNS_DELEGSET_VALID(delegset));
+       REQUIRE(delegp != NULL && *delegp == NULL);
+       REQUIRE(type != DNS_DELEGTYPE_UNDEFINED);
+
+       deleg = isc_mem_get(delegset->mctx, sizeof(*deleg));
+       *deleg = (dns_deleg_t){ .addresses = ISC_LIST_INITIALIZER,
+                               .names = ISC_LIST_INITIALIZER,
+                               .type = type,
+                               .link = ISC_LINK_INITIALIZER };
+
+       ISC_LIST_APPEND(delegset->delegs, deleg, link);
+       *delegp = deleg;
+}
+
+void
+dns_delegset_addaddr(dns_delegset_t *delegset, dns_deleg_t *deleg,
+                    const isc_netaddr_t *addr) {
+       isc_netaddrlink_t *addrlink = NULL;
+
+       REQUIRE(DNS_DELEGSET_VALID(delegset));
+       REQUIRE(deleg != NULL);
+       REQUIRE(addr != NULL);
+       REQUIRE(deleg->type == DNS_DELEGTYPE_DELEG_ADDRESSES ||
+               deleg->type == DNS_DELEGTYPE_NS_GLUES);
+
+       addrlink = isc_mem_get(delegset->mctx, sizeof(*addrlink));
+       *addrlink = (isc_netaddrlink_t){ .addr = *addr,
+                                        .link = ISC_LINK_INITIALIZER };
+
+       ISC_LIST_APPEND(deleg->addresses, addrlink, link);
+}
+
+static void
+addname(dns_delegset_t *delegset, dns_namelist_t *list,
+       const dns_name_t *name) {
+       dns_name_t *clone = NULL;
+
+       REQUIRE(DNS_DELEGSET_VALID(delegset));
+       REQUIRE(DNS_NAME_VALID(name));
+
+       clone = isc_mem_get(delegset->mctx, sizeof(*clone));
+       dns_name_init(clone);
+       dns_name_dup(name, delegset->mctx, clone);
+       ISC_LIST_APPEND(*list, clone, link);
+}
+
+void
+dns_delegset_adddelegparam(dns_delegset_t *delegset, dns_deleg_t *deleg,
+                          const dns_name_t *name) {
+       REQUIRE(deleg != NULL);
+       REQUIRE(deleg->type == DNS_DELEGTYPE_DELEG_PARAMS);
+       addname(delegset, &deleg->names, name);
+}
+
+void
+dns_delegset_addns(dns_delegset_t *delegset, dns_deleg_t *deleg,
+                  const dns_name_t *name) {
+       REQUIRE(deleg != NULL);
+
+       REQUIRE(deleg->type == DNS_DELEGTYPE_DELEG_NAMES ||
+               deleg->type == DNS_DELEGTYPE_NS_NAMES);
+       addname(delegset, &deleg->names, name);
+}
+
+static void
+delegdb_cleanup(dns_delegdb_t *delegdb, dns_qpmulti_t *nodes) {
+       dns_qp_t *qp = NULL;
+       delegdb_node_t *node = NULL;
+       size_t reclaimed = 0;
+       size_t requested = 0;
+
+       if (!isc_mem_isovermem(delegdb->mctx)) {
+               return;
+       }
+       requested = delegdb->hiwater - delegdb->lowater;
+
+       dns_qpmulti_write(nodes, &qp);
+
+       while (reclaimed < requested) {
+               node = ISC_SIEVE_NEXT(delegdb->lru[isc_tid()], visited, link);
+
+               if (node == NULL) {
+                       break;
+               }
+               reclaimed += node->size;
+
+               ISC_SIEVE_UNLINK(delegdb->lru[isc_tid()], node, link);
+               (void)dns_qp_deletename(qp, &node->zonecut,
+                                       DNS_DBNAMESPACE_NORMAL, NULL, NULL);
+       }
+
+       dns_qp_compact(qp, DNS_QPGC_ALL);
+       dns_qpmulti_commit(nodes, &qp);
+}
+
+static size_t
+delegset_size(dns_delegset_t *delegset) {
+       size_t sz = 0;
+
+       sz += sizeof(*delegset);
+       ISC_LIST_FOREACH(delegset->delegs, deleg, link) {
+               sz += sizeof(*deleg);
+               ISC_LIST_FOREACH(deleg->addresses, address, link) {
+                       sz += sizeof(*address);
+               }
+               ISC_LIST_FOREACH(deleg->names, name, link) {
+                       sz += sizeof(*name) + dns_name_size(name);
+               }
+       }
+
+       return sz;
+}
+
+static size_t
+delegdb_node_size(const dns_name_t *zonecut, dns_delegset_t *delegset) {
+       size_t sz = 0;
+
+       sz += sizeof(delegdb_node_t);
+       sz += dns_name_size(zonecut);
+       sz += delegset_size(delegset);
+
+       return sz;
+}
+
+static void
+delegdb_node_prepare(dns_delegdb_t *delegdb, dns_qpmulti_t *nodes,
+                    isc_stdtime_t now, dns_ttl_t ttl,
+                    const dns_name_t *zonecut, dns_delegset_t *delegset,
+                    delegdb_node_t **nodep) {
+       delegdb_cleanup(delegdb, nodes);
+
+       if (ttl == 0) {
+               ttl = 1;
+       }
+       delegset->expires = ttl + now;
+
+       *nodep = isc_mem_get(delegdb->mctx, sizeof(**nodep));
+       **nodep =
+               (delegdb_node_t){ .magic = DELEGDB_NODE_MAGIC,
+                                 .references = ISC_REFCOUNT_INITIALIZER(1),
+                                 .zonecut = DNS_NAME_INITEMPTY,
+                                 .link = ISC_LINK_INITIALIZER,
+                                 .deadlink = ISC_LINK_INITIALIZER,
+                                 .size = delegdb_node_size(zonecut, delegset),
+                                 .loop = isc_loop_ref(isc_loop()) };
+
+       dns_delegdb_attach(delegdb, &(*nodep)->delegdb);
+       dns_delegset_attach(delegset, &(*nodep)->delegset);
+       dns_name_dup(zonecut, delegdb->mctx, &(*nodep)->zonecut);
+}
+
+isc_result_t
+dns_delegset_insert(dns_delegdb_t *delegdb, const dns_name_t *zonecut,
+                   dns_ttl_t ttl, dns_delegset_t *delegset) {
+       isc_result_t result;
+       delegdb_node_t *node = NULL;
+       dns_qp_t *qp = NULL;
+       dns_qpread_t qpr = {};
+       isc_stdtime_t now = isc_stdtime_now();
+       dns_qpmulti_t *nodes = NULL;
+
+       REQUIRE(VALID_DELEGDB(delegdb));
+       REQUIRE(DNS_NAME_VALID(zonecut));
+       REQUIRE(DNS_DELEGSET_VALID(delegset));
+
+       /*
+        * Only delegset allocated by the delegdb memory context can be added in
+        * the delegdb. This exclude transient delegset built from rdataset (see
+        * dns_delegset_fromrdataset()).
+        */
+       REQUIRE(delegset->mctx == delegdb->mctx);
+
+       rcu_read_lock();
+       nodes = rcu_dereference(delegdb->nodes);
+       if (nodes == NULL) {
+               rcu_read_unlock();
+               return ISC_R_SHUTTINGDOWN;
+       }
+
+       /*
+        * First, check (without write txn) if the node already exists and is
+        * still valid.
+        */
+       dns_qpmulti_query(nodes, &qpr);
+       result = dns_qp_lookup(&qpr, zonecut, DNS_DBNAMESPACE_NORMAL, NULL,
+                              NULL, (void **)&node, NULL);
+       if (result == ISC_R_SUCCESS) {
+               INSIST(VALID_DELEGDB_NODE(node));
+               if (node->delegset->expires > now) {
+                       dns_qpread_destroy(nodes, &qpr);
+                       CLEANUP(ISC_R_EXISTS);
+               }
+       }
+       dns_qpread_destroy(nodes, &qpr);
+
+       /*
+        * We're about to add a new delegation, check for state of overmem, and
+        * clean up expired/least recently used delegation, then allocate and
+        * initialize a new node.
+        */
+       delegdb_node_prepare(delegdb, nodes, now, ttl, zonecut, delegset,
+                            &node);
+
+       /*
+        * Add the node in the DB
+        */
+       dns_qpmulti_write(nodes, &qp);
+       if (result == ISC_R_SUCCESS) {
+               /*
+                * A node at the same zonecut exists, and it is expired. Ignore
+                * the return value, in case the overriden node would be removed
+                * in meantime by someone else.
+                */
+               (void)dns_qp_deletename(qp, zonecut, DNS_DBNAMESPACE_NORMAL,
+                                       NULL, NULL);
+       }
+
+       result = dns_qp_insert(qp, node, 0);
+       if (result != ISC_R_SUCCESS) {
+               /*
+                * Someone else added the node before (and there was no node to
+                * delete).
+                */
+
+               delegdb_node_unref(node);
+
+               /*
+                * Since not using an update (but write) transaction,
+                * _rollback() won't work here.
+                */
+               dns_qpmulti_commit(nodes, &qp);
+               CLEANUP(ISC_R_EXISTS);
+       }
+
+       /*
+        * The new delegation is added, and can be referenced by SIEVE
+        */
+       ISC_SIEVE_INSERT(delegdb->lru[isc_tid()], node, link);
+
+       delegdb_node_unref(node);
+       dns_qp_compact(qp, DNS_QPGC_MAYBE);
+       dns_qpmulti_commit(nodes, &qp);
+
+cleanup:
+       rcu_read_unlock();
+
+       return result;
+}
+
+static void
+delegset_destroy(dns_delegset_t *delegset) {
+       REQUIRE(DNS_DELEGSET_VALID(delegset));
+
+       delegset->magic = 0;
+       ISC_LIST_FOREACH(delegset->delegs, deleg, link) {
+               deleg->type = DNS_DELEGTYPE_UNDEFINED;
+
+               ISC_LIST_UNLINK(delegset->delegs, deleg, link);
+
+               ISC_LIST_FOREACH(deleg->addresses, address, link) {
+                       ISC_LIST_UNLINK(deleg->addresses, address, link);
+                       isc_mem_put(delegset->mctx, address, sizeof(*address));
+               }
+
+               ISC_LIST_FOREACH(deleg->names, nameserver, link) {
+                       ISC_LIST_UNLINK(deleg->names, nameserver, link);
+                       dns_name_free(nameserver, delegset->mctx);
+                       isc_mem_put(delegset->mctx, nameserver,
+                                   sizeof(*nameserver));
+               }
+
+               isc_mem_put(delegset->mctx, deleg, sizeof(*deleg));
+       }
+
+       isc_mem_putanddetach(&delegset->mctx, delegset, sizeof(*delegset));
+}
+ISC_REFCOUNT_IMPL(dns_delegset, delegset_destroy);
+
+static void
+tostring_namelist(dns_namelist_t *namelist, const char *id, FILE *fp) {
+       if (!ISC_LIST_EMPTY(*namelist)) {
+               fprintf(fp, " %s=", id);
+               ISC_LIST_FOREACH(*namelist, name, link) {
+                       isc_buffer_t nameb;
+                       char bdata[DNS_NAME_MAXWIRE] = { 0 };
+
+                       isc_buffer_init(&nameb, bdata, sizeof(bdata));
+                       dns_name_totext(name, 0, &nameb);
+                       fprintf(fp, "%s", bdata);
+
+                       if (name != ISC_LIST_TAIL(*namelist)) {
+                               fprintf(fp, ",");
+                       }
+               }
+       }
+}
+
+static void
+deleg_tostring_addresses(dns_deleg_t *deleg, FILE *fp) {
+       bool hasv4 = false;
+       bool hasv6 = false;
+
+       ISC_LIST_FOREACH(deleg->addresses, address, link) {
+               if (address->addr.family == AF_INET) {
+                       hasv4 = true;
+               } else {
+                       hasv6 = true;
+               }
+       }
+
+       if (hasv4) {
+               bool first = true;
+
+               fprintf(fp, " server-ipv4=");
+               ISC_LIST_FOREACH(deleg->addresses, address, link) {
+                       char addrstr[] = "255.255.255.255";
+
+                       if (address->addr.family == AF_INET6) {
+                               continue;
+                       }
+
+                       if (!first) {
+                               fprintf(fp, ",");
+                       }
+                       first = false;
+
+                       inet_ntop(AF_INET, &address->addr.type, addrstr,
+                                 sizeof(addrstr));
+                       fprintf(fp, "%s", addrstr);
+               }
+       }
+
+       if (hasv6) {
+               bool first = true;
+
+               fprintf(fp, " server-ipv6=");
+               ISC_LIST_FOREACH(deleg->addresses, address, link) {
+                       char addrstr[INET6_ADDRSTRLEN];
+
+                       if (address->addr.family == AF_INET) {
+                               continue;
+                       }
+
+                       if (!first) {
+                               fprintf(fp, ",");
+                       }
+                       first = false;
+
+                       inet_ntop(AF_INET6, &address->addr.type, addrstr,
+                                 sizeof(addrstr));
+                       fprintf(fp, "%s", addrstr);
+               }
+       }
+}
+
+static void
+delegset_tostring(const dns_name_t *zonecut, dns_delegset_t *delegset,
+                 isc_stdtime_t now, bool expired, FILE *fp) {
+       ISC_LIST_FOREACH(delegset->delegs, deleg, link) {
+               isc_buffer_t zonecutb;
+               char bdata[DNS_NAME_MAXWIRE];
+               dns_ttl_t ttl = 0;
+
+               if (delegset->expires > now) {
+                       ttl = delegset->expires - now;
+               } else {
+                       INSIST(expired);
+               }
+
+               isc_buffer_init(&zonecutb, bdata, sizeof(bdata));
+               dns_name_totext(zonecut, 0, &zonecutb);
+               fprintf(fp, "%s %u DELEG", bdata, ttl);
+
+               if (deleg->type == DNS_DELEGTYPE_DELEG_ADDRESSES ||
+                   deleg->type == DNS_DELEGTYPE_NS_GLUES)
+               {
+                       deleg_tostring_addresses(deleg, fp);
+               } else if (deleg->type == DNS_DELEGTYPE_DELEG_NAMES ||
+                          deleg->type == DNS_DELEGTYPE_NS_NAMES)
+               {
+                       tostring_namelist(&deleg->names, "server-name", fp);
+               } else if (deleg->type == DNS_DELEGTYPE_DELEG_PARAMS) {
+                       tostring_namelist(&deleg->names, "include-delegparam",
+                                         fp);
+               } else {
+                       UNREACHABLE();
+               }
+
+               fprintf(fp, "\n");
+       }
+}
+
+void
+dns_delegdb_dump(dns_delegdb_t *delegdb, bool expired, FILE *fp) {
+       REQUIRE(VALID_DELEGDB(delegdb));
+       REQUIRE(fp != NULL);
+
+       dns_qpiter_t it;
+       dns_qpread_t qpr = {};
+       delegdb_node_t *node = NULL;
+       isc_stdtime_t now = isc_stdtime_now();
+       dns_qpmulti_t *nodes = NULL;
+
+       rcu_read_lock();
+       nodes = rcu_dereference(delegdb->nodes);
+       if (nodes == NULL) {
+               rcu_read_unlock();
+               return;
+       }
+
+       dns_qpmulti_query(nodes, &qpr);
+
+       dns_qpiter_init(&qpr, &it);
+       while (dns_qpiter_next(&it, (void **)&node, NULL) == ISC_R_SUCCESS) {
+               if (!expired && !isactive(node, now)) {
+                       continue;
+               }
+
+               delegset_tostring(&node->zonecut, node->delegset, now, expired,
+                                 fp);
+       }
+
+       dns_qpread_destroy(nodes, &qpr);
+
+       rcu_read_unlock();
+}
+
+void
+dns_delegset_fromnsrdataset(dns_rdataset_t *rdataset,
+                           dns_delegset_t **delegsetp) {
+       dns_delegset_t *delegset = NULL;
+       dns_deleg_t *deleg = NULL;
+
+       if (rdataset == NULL || !dns_rdataset_isassociated(rdataset) ||
+           delegsetp == NULL || *delegsetp != NULL)
+       {
+               return;
+       }
+
+       REQUIRE(rdataset->type == dns_rdatatype_ns);
+
+       delegset = isc_mem_get(isc_g_mctx, sizeof(*delegset));
+       *delegset = (dns_delegset_t){
+               .magic = DNS_DELEGSET_MAGIC,
+               .references = ISC_REFCOUNT_INITIALIZER(1),
+               .delegs = ISC_LIST_INITIALIZER,
+               .expires = rdataset->ttl + isc_stdtime_now(),
+               .staticstub = rdataset->attributes.staticstub
+       };
+       isc_mem_attach(isc_g_mctx, &delegset->mctx);
+
+       deleg = isc_mem_get(isc_g_mctx, sizeof(*deleg));
+       *deleg = (dns_deleg_t){ .addresses = ISC_LIST_INITIALIZER,
+                               .names = ISC_LIST_INITIALIZER,
+                               .type = DNS_DELEGTYPE_NS_NAMES,
+                               .link = ISC_LINK_INITIALIZER };
+       ISC_LIST_APPEND(delegset->delegs, deleg, link);
+
+       DNS_RDATASET_FOREACH(rdataset) {
+               dns_rdata_t rdata = DNS_RDATA_INIT;
+               dns_rdata_ns_t ns;
+
+               dns_rdataset_current(rdataset, &rdata);
+               dns_rdata_tostruct(&rdata, &ns, NULL);
+               dns_delegset_addns(delegset, deleg, &ns.name);
+       }
+
+       *delegsetp = delegset;
+}
+
+static isc_result_t
+deleg_deletetree(dns_qp_t *qp, const dns_name_t *name) {
+       isc_result_t result;
+       delegdb_node_t *node = NULL;
+       dns_qpiter_t it;
+       ISC_LIST(delegdb_node_t) deadnodes = ISC_LIST_INITIALIZER;
+
+       result = dns_qp_lookup(qp, name, DNS_DBNAMESPACE_NORMAL, &it, NULL,
+                              (void **)&node, NULL);
+       if (result != ISC_R_SUCCESS) {
+               goto out;
+       }
+
+       INSIST(VALID_DELEGDB_NODE(node));
+       do {
+               /*
+                * Because QP doesn't allow deleting a node while using the
+                * iterator, the approach is different than `deleg_deletenode()`
+                * here. Instead of removing the node immediately, we add it
+                * into a list that we'll go through after, then delete each
+                * node.
+                */
+               ISC_LIST_APPEND(deadnodes, node, deadlink);
+
+               result = dns_qpiter_next(&it, (void **)&node, NULL);
+               if (result == ISC_R_NOMORE) {
+                       result = ISC_R_SUCCESS;
+                       break;
+               }
+
+               INSIST(VALID_DELEGDB_NODE(node));
+               if (!dns_name_issubdomain(&node->zonecut, name)) {
+                       break;
+               }
+       } while (result == ISC_R_SUCCESS);
+
+out:
+       if (ISC_LIST_EMPTY(deadnodes)) {
+               result = ISC_R_NOTFOUND;
+       } else {
+               /*
+                * Let's actually delete the deadnodes!
+                */
+               ISC_LIST_FOREACH(deadnodes, deadnode, deadlink) {
+                       result = dns_qp_deletename(qp, &deadnode->zonecut,
+                                                  DNS_DBNAMESPACE_NORMAL, NULL,
+                                                  NULL);
+                       INSIST(result == ISC_R_SUCCESS);
+               }
+       }
+
+       return result;
+}
+
+static isc_result_t
+deleg_deletenode(dns_qp_t *qp, const dns_name_t *name) {
+       return dns_qp_deletename(qp, name, DNS_DBNAMESPACE_NORMAL, NULL, NULL);
+}
+
+isc_result_t
+dns_delegdb_delete(dns_delegdb_t *delegdb, const dns_name_t *name, bool tree) {
+       REQUIRE(VALID_DELEGDB(delegdb));
+       REQUIRE(DNS_NAME_VALID(name));
+
+       dns_qpmulti_t *nodes = NULL;
+       dns_qp_t *qp = NULL;
+       isc_result_t result = ISC_R_SHUTTINGDOWN;
+
+       rcu_read_lock();
+       nodes = rcu_dereference(delegdb->nodes);
+       if (nodes != NULL) {
+               dns_qpmulti_write(nodes, &qp);
+               if (tree) {
+                       result = deleg_deletetree(qp, name);
+               } else {
+                       result = deleg_deletenode(qp, name);
+               }
+               if (result == ISC_R_SUCCESS) {
+                       dns_qp_compact(qp, DNS_QPGC_MAYBE);
+               }
+               dns_qpmulti_commit(nodes, &qp);
+       }
+       rcu_read_unlock();
+
+       return result;
+}
+
+static void
+delegdb_shutdown_async(void *arg) {
+       dns_delegdb_t *delegdb = arg;
+
+       REQUIRE(isc_loop_get(isc_tid()) == isc_loop_main());
+       REQUIRE(delegdb != NULL && VALID_DELEGDB(delegdb));
+       if (isc_refcount_decrement(&delegdb->owners) == 1) {
+               dns_qpmulti_t *nodes = rcu_xchg_pointer(&delegdb->nodes, NULL);
+
+               if (nodes != NULL) {
+                       nodes_rcu_head_t *nrh = isc_mem_get(delegdb->mctx,
+                                                           sizeof(*nrh));
+                       *nrh = (nodes_rcu_head_t){
+                               .mctx = isc_mem_ref(delegdb->mctx),
+                               .nodes = nodes,
+                       };
+                       call_rcu(&nrh->rcu_head, deleg_destroy_qpmulti);
+               }
+       }
+}
+
+void
+dns_delegdb_shutdown(dns_delegdb_t *delegdb) {
+       if (isc_loop_get(isc_tid()) == isc_loop_main()) {
+               delegdb_shutdown_async(delegdb);
+       } else {
+               isc_async_run(isc_loop_main(), delegdb_shutdown_async, delegdb);
+       }
+}
+
+void
+dns_delegdb_setsize(dns_delegdb_t *delegdb, size_t size) {
+       REQUIRE(VALID_DELEGDB(delegdb));
+
+       if (size != 0 && size < DELEGDB_MINSIZE) {
+               size = DELEGDB_MINSIZE;
+       }
+
+       delegdb->hiwater = size - (size >> 3); /* Approximately 7/8ths. */
+       delegdb->lowater = size - (size >> 2); /* Approximately 3/4ths. */
+
+       if (size == 0 || delegdb->hiwater == 0 || delegdb->lowater == 0) {
+               isc_mem_clearwater(delegdb->mctx);
+
+               /*
+                * TODO: Is it worth a warning if size > 0? Sounds like
+                * implicit overmem bypass, so the user should be warned...
+                */
+       } else {
+               isc_mem_setwater(delegdb->mctx, delegdb->hiwater,
+                                delegdb->lowater);
+       }
+}
diff --git a/lib/dns/include/dns/deleg.h b/lib/dns/include/dns/deleg.h
new file mode 100644 (file)
index 0000000..4a7bccb
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * 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
+
+#include <isc/refcount.h>
+#include <isc/stdtime.h>
+
+#include <dns/types.h>
+
+/*
+ * A `dns_deleg_t` object represents either:
+ *
+ * - a DELEG-based delegation with `server-ipv4=` and/or `server-ipv6=`
+ *   (DNS_DELEGTYPE_DELEG_ADDRESS)
+ *
+ * - a DELEG-based delegation with `server-name=` (DNS_DELEGTYPE_DELEG_NAMES)
+ *
+ * - a DELEG-based delegation with `include-delegparam=`
+ *   (DNS_DELEGTYPE_DELEG_PARAMS)
+ *
+ * - an NS-based delegation with glues (DNS_DELEGTYPE_NS_GLUES)
+ *
+ * - an NS-based delegation with no glues (DNS_DELEGTYPE_NS_NAMES)
+ *
+ * This object must be allocated using `dns_deleg_allocdeleg()`.
+ */
+
+typedef enum {
+       DNS_DELEGTYPE_UNDEFINED,
+       DNS_DELEGTYPE_DELEG_ADDRESSES,
+       DNS_DELEGTYPE_DELEG_NAMES,
+       DNS_DELEGTYPE_DELEG_PARAMS,
+       DNS_DELEGTYPE_NS_GLUES,
+       DNS_DELEGTYPE_NS_NAMES
+} dns_deleg_type_t;
+
+struct dns_deleg {
+       isc_netaddrlist_t addresses;
+       dns_namelist_t    names;
+       dns_deleg_type_t  type;
+       ISC_LINK(dns_deleg_t) link;
+};
+
+/*
+ * A delegation set. Once it's added to the delegation DB, it gets a
+ * read-only object thus doesn't require any locking nor copying when the
+ * caller gets it.
+ *
+ * The TTL is common to all the DELEG RR for the same zonecut
+ * https://datatracker.ietf.org/doc/html/rfc2181#section-5.2
+ *
+ * When the delegation is NS-based, the TTL is the lowest TTL of the referral
+ * (either of the NS, A or AAAA glues).
+ *
+ * If a zone contains NS and DELEG delegations, this delegation must only
+ * store the DELEG ones. (This is resolver responsibility to ensure that.)
+ */
+struct dns_delegset {
+       unsigned int   magic;
+       isc_mem_t     *mctx;
+       isc_refcount_t references;
+
+       dns_deleglist_t delegs;
+       isc_stdtime_t   expires;
+
+       /*
+        * Used only when a delegation is built from a local zone.
+        */
+       bool staticstub;
+};
+ISC_REFCOUNT_DECL(dns_delegset);
+
+#define DNS_DELEGSET_MAGIC ISC_MAGIC('D', 'e', 'G', 's')
+#define DNS_DELEGSET_VALID(delegset) \
+       ISC_MAGIC_VALID(delegset, DNS_DELEGSET_MAGIC)
+
+typedef struct dns_delegdb dns_delegdb_t;
+
+/*
+ * Allocate and initialize the delegation database. `db` is attached to the
+ * caller.
+ */
+void
+dns_delegdb_create(dns_delegdb_t **delegdbp);
+
+/*
+ * Attach a delegation DB from an existing view to another view. Used when
+ * reloading the server and the delegation DB is reused.
+ */
+void
+dns_delegdb_reuse(dns_view_t *oldview, dns_view_t *newview);
+
+/*
+ * Shutdown the delegation database. Must be called from any view shutting down
+ * which either created a delegdb or reused a delegdb.
+ */
+void
+dns_delegdb_shutdown(dns_delegdb_t *delegdb);
+
+/*
+ * Lookup for delegations of a given name in the DB. If found, the zonecut is
+ * written and the delegation set is attached to the caller, so it must be
+ * detached once the caller is done with it. Even though `delegset` is not
+ * const (for convenience with ISC_LIST_FOREACH macros, _attach, _detach
+ * functions, etc.) the `delegset` _is_ a read-only object, and must not be
+ * modified.
+ *
+ * If only the zonecut is needed from the caller, `delegset` can be NULL, it
+ * won't be attached.
+ *
+ * The zonecut must be a initialized and attached to a buffer.
+ *
+ * If `now` is 0, the actual expiration time is `isc_stdtime_now()`.
+ */
+isc_result_t
+dns_delegdb_lookup(dns_delegdb_t *db, const dns_name_t *name, isc_stdtime_t now,
+                  unsigned int options, dns_name_t *zonecut,
+                  dns_name_t *deepestzonecut, dns_delegset_t **delegset);
+
+/*
+ * Allocate and attach to the caller a new empty delegation set, but do not
+ * attach it in the DB yet, so the following API can be used to set its
+ * various properties.
+ *
+ * Because all those API calls (dns_deleg_alloc* and dns_deleg_add*) use
+ * the internal delegdb memory context, it _might_ in some circumstances
+ * allocate above its hiwater mark without reclaiming memory. The flow
+ * reclaiming memory is then run when adding the delegset into the database
+ * (dns_deleg_writeanddetach()).
+ *
+ * This could be changed to run through those API calls also if needed.
+ */
+void
+dns_delegset_allocset(dns_delegdb_t *db, dns_delegset_t **delegsetp);
+
+/*
+ * Allocate a new deleg struct and insert it into the delegation set. Can't
+ * be used on delegation set already attached in the DB.
+ */
+void
+dns_delegset_allocdeleg(dns_delegset_t *delegset, dns_deleg_type_t type,
+                       dns_deleg_t **delegp);
+
+/*
+ * Add a new IP into a delegation. Can't be used on a delegation from a
+ * delegation set already attached in the DB.
+ */
+void
+dns_delegset_addaddr(dns_delegset_t *delegset, dns_deleg_t *deleg,
+                    const isc_netaddr_t *addr);
+
+/*
+ * Add a new DELEGPARAM name into a delegation. Can't be used on a delegation
+ * from a delegation set already attached in the DB.
+ */
+void
+dns_delegset_adddelegparam(dns_delegset_t *delegset, dns_deleg_t *deleg,
+                          const dns_name_t *name);
+
+/*
+ * Add a new nameserver name into a delegation. Can't be used on a delegation
+ * from a delegation set already attached in the DB.
+ */
+void
+dns_delegset_addns(dns_delegset_t *delegset, dns_deleg_t *deleg,
+                  const dns_name_t *name);
+
+/*
+ * Add a delegation set into the DB for the given zonecut and a time to live. If
+ * a delegation already exists and is not expired, ISC_R_EXISTS is returned and
+ * the DB is not altered.
+ *
+ * This function also cleanup least recently used delegation is the database in
+ * an overmemory conditions (See dns_deleg_setsize()).
+ *
+ * TODO: once DELEG is supported, attempting to add a delegation from NS
+ * where a delegation from DELEG already exists would be rejected too.
+ */
+isc_result_t
+dns_delegset_insert(dns_delegdb_t *db, const dns_name_t *zonecut, dns_ttl_t ttl,
+                   dns_delegset_t *delegset);
+
+/*
+ * Dump the database in a textual format for a given name. If `expired` is
+ * false, only the non expired entries are shown. All entries are shown
+ * otherwise.
+ */
+void
+dns_delegdb_dump(dns_delegdb_t *db, bool expired, FILE *fp);
+
+/*
+ * Convert an NS rdataset into a delegset containing a single delegation
+ * (with possibly multiple nameserver). The allocated delegset is using the
+ * main memory context, thus, is not expected to be added into the deleg DB
+ * (which accepts only delegset allocated using `dns_deleg_alloc*()` APIs.
+ */
+void
+dns_delegset_fromnsrdataset(dns_rdataset_t  *rdataset,
+                           dns_delegset_t **delegsetp);
+
+/*
+ * Delete a delegation matching a name. If `tree` is true, this will also
+ * delete all names below `name`.
+ */
+isc_result_t
+dns_delegdb_delete(dns_delegdb_t *db, const dns_name_t *name, bool tree);
+
+/*
+ * Defines the size of the delegation cache. Whenever the effective cache
+ * size comes close to this size, least recently used cache entries are
+ * discarded. Value `0` means there is no limitation.
+ */
+void
+dns_delegdb_setsize(dns_delegdb_t *db, size_t size);
+
+ISC_REFCOUNT_DECL(dns_delegdb);
index f8d228c6d07346fd7da13cbb25f9f7cc6d8d0e4f..ea65a16bf9527c2427790af5a4a59be12260efd0 100644 (file)
@@ -178,6 +178,9 @@ typedef ISC_LIST(dns_zone_t) dns_zonelist_t;
 typedef struct dns_zonemgr   dns_zonemgr_t;
 typedef struct dns_zt       dns_zt_t;
 typedef struct dns_ipkeylist dns_ipkeylist_t;
+typedef struct dns_deleg     dns_deleg_t;
+typedef ISC_LIST(dns_deleg_t) dns_deleglist_t;
+typedef struct dns_delegset dns_delegset_t;
 
 typedef struct dst_gssapi_signverifyctx dst_gssapi_signverifyctx_t;
 
index 92174973ff44fa78e7d96ae54d2b40a9073939bd..42b9268cae16e2c0f21ace8dc8ebe8ebf35a98e6 100644 (file)
@@ -89,6 +89,7 @@ dns_srcset.add(
         'compress.c',
         'db.c',
         'dbiterator.c',
+        'deleg.c',
         'diff.c',
         'dispatch.c',
         'dlz.c',
diff --git a/tests/dns/deleg_test.c b/tests/dns/deleg_test.c
new file mode 100644 (file)
index 0000000..904eaee
--- /dev/null
@@ -0,0 +1,723 @@
+/*
+ * 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 <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+/*
+ * Mock isc_stdtime_now() as it makes testing easier (to compare
+ * generated/expected deleg data).
+ */
+static uint32_t stdtime_now = 100;
+
+static uint32_t
+isc_stdtime_now(void) {
+       return stdtime_now;
+}
+
+#include <isc/lib.h>
+#include <isc/list.h>
+#include <isc/netaddr.h>
+#include <isc/stdtime.h>
+
+#include <dns/deleg.h>
+#include <dns/fixedname.h>
+#include <dns/lib.h>
+#include <dns/name.h>
+
+/*
+ * Because of the mock above.
+ */
+#include "../dns/deleg.c"
+
+#include <tests/isc.h>
+
+static void
+shutdownloop(ISC_ATTR_UNUSED void *arg) {
+       isc_loopmgr_shutdown();
+}
+
+static void
+shutdowntest(dns_delegdb_t **dbp) {
+       dns_delegdb_shutdown(*dbp);
+       dns_delegdb_detach(dbp);
+       shutdownloop(NULL);
+}
+
+static void
+rundelegtest(isc_job_cb testcb) {
+       isc_loopmgr_create(isc_g_mctx, 1);
+
+       isc_loop_setup(isc_loop_main(), testcb, NULL);
+       isc_loopmgr_run();
+
+       isc_loopmgr_destroy();
+}
+
+static void
+addnamedeleg(const char *addrstr, dns_delegset_t *delegset, dns_deleg_t *deleg,
+            void (*fn)(dns_delegset_t *, dns_deleg_t *, const dns_name_t *)) {
+       dns_fixedname_t fname;
+       dns_name_t *name = dns_fixedname_initname(&fname);
+
+       dns_name_fromstring(name, addrstr, NULL, 0, NULL);
+       fn(delegset, deleg, name);
+}
+
+static void
+addipdeleg(unsigned int af, const char *addrstr, dns_delegset_t *delegset,
+          dns_deleg_t *deleg) {
+       isc_netaddr_t addr = { .family = af };
+
+       assert_true(af == AF_INET || af == AF_INET6);
+       assert_int_equal(inet_pton(af, addrstr, &addr.type), 1);
+       dns_delegset_addaddr(delegset, deleg, &addr);
+}
+
+static void
+writedb(dns_delegdb_t *db, const char *zonecutstr, dns_ttl_t expire,
+       dns_delegset_t **delegsetp, bool expectsuccess) {
+       dns_fixedname_t fzonecut;
+       dns_name_t *zonecut = dns_fixedname_initname(&fzonecut);
+       isc_result_t result;
+
+       dns_name_fromstring(zonecut, zonecutstr, NULL, 0, NULL);
+       result = dns_delegset_insert(db, zonecut, expire, *delegsetp);
+
+       dns_delegset_detach(delegsetp);
+       assert_null(*delegsetp);
+
+       if (expectsuccess) {
+               assert_int_equal(result, ISC_R_SUCCESS);
+       } else {
+               assert_int_equal(result, ISC_R_EXISTS);
+       }
+}
+
+static isc_result_t
+lookupdb(dns_delegdb_t *db, const char *namestr, isc_stdtime_t now,
+        unsigned int options, const char *expectedzcstr,
+        dns_delegset_t **delegsetp) {
+       isc_result_t result;
+       dns_fixedname_t fname, fexpectedzc, fzonecut;
+       dns_name_t *name = dns_fixedname_initname(&fname),
+                  *expectedzc = dns_fixedname_initname(&fexpectedzc),
+                  *zonecut = dns_fixedname_initname(&fzonecut);
+
+       dns_name_fromstring(expectedzc, expectedzcstr, NULL, 0, NULL);
+       dns_name_fromstring(name, namestr, NULL, 0, NULL);
+       result = dns_delegdb_lookup(db, name, now, options, zonecut, NULL,
+                                   delegsetp);
+
+       if (result == ISC_R_SUCCESS) {
+               assert_non_null(*delegsetp);
+               assert_true(dns_name_equal(zonecut, expectedzc));
+       } else {
+               assert_null(*delegsetp);
+       }
+
+       return result;
+}
+
+static void
+dumpdb(dns_delegdb_t *db, bool expired, const char *expected) {
+       constexpr char *filename = "delegdb-dump-test.db";
+       char buffer[1024 * 4] = { 0 };
+       FILE *fp = fopen(filename, "w+");
+
+       REQUIRE(fp != NULL);
+       dns_delegdb_dump(db, expired, fp);
+
+       fp = freopen(filename, "r", fp);
+       REQUIRE(fp != NULL);
+       REQUIRE(fread(buffer, sizeof(buffer) - 1, 1, fp) == 0);
+
+       assert_string_equal(expected, buffer);
+
+       REQUIRE(fclose(fp) == 0);
+       REQUIRE(unlink(filename) == 0);
+}
+
+static void
+basictests(ISC_ATTR_UNUSED void *arg) {
+       isc_result_t result;
+       dns_delegdb_t *db = NULL;
+       dns_deleg_t *deleg = NULL;
+       dns_delegset_t *delegset = NULL;
+       isc_stdtime_t now = isc_stdtime_now();
+
+       dns_delegdb_create(&db);
+       assert_non_null(db);
+
+       /*
+        * A non expired delegation for foo. zonecut
+        */
+       dns_delegset_allocset(db, &delegset);
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
+       addnamedeleg("ns.foo.", delegset, deleg, dns_delegset_addns);
+       deleg = NULL;
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES, &deleg);
+       addipdeleg(AF_INET, "1.2.3.4", delegset, deleg);
+       addipdeleg(AF_INET6, "1111:2222:3333::4444", delegset, deleg);
+       deleg = NULL;
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_NAMES, &deleg);
+       assert_non_null(deleg);
+       addnamedeleg("ns.example.", delegset, deleg, dns_delegset_addns);
+       deleg = NULL;
+       writedb(db, "foo.", 30, &delegset, true);
+
+       result = lookupdb(db, "baz.bar.gee.", 0, 0, "", &delegset);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       result = lookupdb(db, "baz.bar.foo.", 0, 0, "foo.", &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       /*
+        * A non expired delegation for bar.foo. zonecut
+        */
+       dns_delegset_allocset(db, &delegset);
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_NAMES, &deleg);
+       addnamedeleg("ns.bar.foo.", delegset, deleg, dns_delegset_addns);
+       addnamedeleg("ns2.bar.foo.", delegset, deleg, dns_delegset_addns);
+       deleg = NULL;
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES, &deleg);
+       addipdeleg(AF_INET, "8.9.10.11", delegset, deleg);
+       addipdeleg(AF_INET, "9.9.10.12", delegset, deleg);
+       addipdeleg(AF_INET6, "ACDC::ACDC", delegset, deleg);
+       deleg = NULL;
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+       addipdeleg(AF_INET6, "ABBA::ABBA", delegset, deleg);
+       addipdeleg(AF_INET, "13.14.15.16", delegset, deleg);
+       deleg = NULL;
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_PARAMS, &deleg);
+       addnamedeleg("delegns.gee.", delegset, deleg,
+                    dns_delegset_adddelegparam);
+       addnamedeleg("delegns2.gee.", delegset, deleg,
+                    dns_delegset_adddelegparam);
+       deleg = NULL;
+
+       writedb(db, "bar.foo.", 25, &delegset, true);
+
+       result = lookupdb(db, "baz.bar.gee.", 0, 0, "", &delegset);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       result = lookupdb(db, "baz.bar.foo.", 0, 0, "bar.foo.", &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       /*
+        * A expired delegation for bar.stuff. zonecut
+        */
+       dns_delegset_allocset(db, &delegset);
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
+       addnamedeleg("ns.bar.stuff.", delegset, deleg, dns_delegset_addns);
+       deleg = NULL;
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES, &deleg);
+       addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       deleg = NULL;
+
+       writedb(db, "bar.stuff.", 10, &delegset, true);
+       deleg = NULL;
+
+       result = lookupdb(db, "baz.bar.stuff.", now + 10, 0, "", &delegset);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       /*
+        * But, if we ask for a date before its expiration, it is visible. And
+        * it is possible to dump it as well. But of course the dump when
+        * expired won't get anythig.
+        */
+       result = lookupdb(db, "baz.bar.stuff.", now + 9, 0, "bar.stuff.",
+                         &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       /*
+        * A non expired delegation for bar.stuff. zonecut replace the expired
+        * one. Move the time forward 10 to make the entry expired.
+        */
+       stdtime_now += 10;
+       dns_delegset_allocset(db, &delegset);
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
+       addnamedeleg("ns.bar.stuff.", delegset, deleg, dns_delegset_addns);
+       deleg = NULL;
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES, &deleg);
+       addipdeleg(AF_INET6, "1111::3333", delegset, deleg);
+       deleg = NULL;
+
+       writedb(db, "bar.stuff.", 2, &delegset, true);
+
+       /*
+        * Attempt to override bar.stuff. even though the existing delegation is
+        * not expired. This will be rejected.
+        */
+       dns_delegset_allocset(db, &delegset);
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
+       addnamedeleg("wontbeindb.bar.stuff.", delegset, deleg,
+                    dns_delegset_addns);
+       deleg = NULL;
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+       addipdeleg(AF_INET6, "acdc::acdc", delegset, deleg);
+       deleg = NULL;
+
+       writedb(db, "bar.stuff.", 2, &delegset, false);
+       deleg = NULL;
+
+       result = lookupdb(db, "stuff.", 0, 0, "", &delegset);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       result = lookupdb(db, "idonotknowthis.at.all.stuff.", 0, 0, "",
+                         &delegset);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       result = lookupdb(db, "baz.bar.stuff.", 0, 0, "bar.stuff.", &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       char expected_dbdump[] =
+               "foo. 20 DELEG server-name=ns.foo.\n"
+               "foo. 20 DELEG server-ipv4=1.2.3.4 "
+               "server-ipv6=1111:2222:3333::4444\n"
+               "foo. 20 DELEG server-name=ns.example.\n"
+               "bar.foo. 15 DELEG server-name=ns.bar.foo.,ns2.bar.foo.\n"
+               "bar.foo. 15 DELEG server-ipv4=8.9.10.11,9.9.10.12 "
+               "server-ipv6=acdc::acdc\n"
+               "bar.foo. 15 DELEG server-ipv4=13.14.15.16 "
+               "server-ipv6=abba::abba\n"
+               "bar.foo. 15 DELEG "
+               "include-delegparam=delegns.gee.,delegns2.gee.\n"
+               "bar.stuff. 2 DELEG server-name=ns.bar.stuff.\n"
+               "bar.stuff. 2 DELEG server-ipv6=1111::3333\n";
+       dumpdb(db, false, expected_dbdump);
+
+       /*
+        * Dump in the "future", everything is seen as expired
+        */
+       stdtime_now += 300;
+       dumpdb(db, false, "");
+
+       /*
+        * Bump if we ask to dump expired entries, they'll be there (with TTL 0)
+        */
+       char expected_expired_dbdump[] =
+               "foo. 0 DELEG server-name=ns.foo.\n"
+               "foo. 0 DELEG server-ipv4=1.2.3.4 "
+               "server-ipv6=1111:2222:3333::4444\n"
+               "foo. 0 DELEG server-name=ns.example.\n"
+               "bar.foo. 0 DELEG server-name=ns.bar.foo.,ns2.bar.foo.\n"
+               "bar.foo. 0 DELEG server-ipv4=8.9.10.11,9.9.10.12 "
+               "server-ipv6=acdc::acdc\n"
+               "bar.foo. 0 DELEG server-ipv4=13.14.15.16 "
+               "server-ipv6=abba::abba\n"
+               "bar.foo. 0 DELEG "
+               "include-delegparam=delegns.gee.,delegns2.gee.\n"
+               "bar.stuff. 0 DELEG server-name=ns.bar.stuff.\n"
+               "bar.stuff. 0 DELEG server-ipv6=1111::3333\n";
+       dumpdb(db, true, expected_expired_dbdump);
+
+       shutdowntest(&db);
+}
+
+static void
+ttl0tests(ISC_ATTR_UNUSED void *arg) {
+       isc_result_t result;
+       dns_delegdb_t *db = NULL;
+       dns_deleg_t *deleg = NULL;
+       dns_delegset_t *delegset = NULL;
+       isc_stdtime_t now = isc_stdtime_now();
+       isc_buffer_t b;
+       char bdata[2048];
+
+       isc_buffer_init(&b, bdata, sizeof(bdata));
+       dns_delegdb_create(&db);
+       assert_non_null(db);
+
+       dns_delegset_allocset(db, &delegset);
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
+       addnamedeleg("ns.bar.stuff.", delegset, deleg, dns_delegset_addns);
+       deleg = NULL;
+
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+       addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       deleg = NULL;
+
+       writedb(db, "bar.stuff.", 0, &delegset, true);
+       deleg = NULL;
+
+       result = lookupdb(db, "baz.bar.stuff.", now, 0, "bar.stuff.",
+                         &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       result = lookupdb(db, "baz.bar.stuff.", now + 1, 0, "", &delegset);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       shutdowntest(&db);
+}
+
+static void
+noexacttests(ISC_ATTR_UNUSED void *arg) {
+       isc_result_t result;
+       dns_delegdb_t *db = NULL;
+       dns_deleg_t *deleg = NULL;
+       dns_delegset_t *delegset = NULL;
+       isc_stdtime_t now = isc_stdtime_now();
+       isc_buffer_t b;
+       char bdata[2048];
+
+       isc_buffer_init(&b, bdata, sizeof(bdata));
+       dns_delegdb_create(&db);
+       assert_non_null(db);
+
+       struct {
+               const char *name;
+               const char *expected;
+               const char *noexactexpected;
+               dns_ttl_t ttl;
+       } zonecuts[] = {
+               { "stuff.", "stuff.", "stuff.", 30 },
+               { "foo.stuff.", "foo.stuff.", "stuff.", 30 },
+               { "expired.foo.stuff.", "foo.stuff.", "foo.stuff.", 1 },
+               { "bar.expired.foo.stuff.", "bar.expired.foo.stuff.",
+                 "foo.stuff.", 30 },
+               { "baz.bar.expired.foo.stuff.", "baz.bar.expired.foo.stuff.",
+                 "bar.expired.foo.stuff.", 30 }
+       };
+
+       for (size_t i = 0; i < ARRAY_SIZE(zonecuts); i++) {
+               dns_delegset_allocset(db, &delegset);
+               dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES,
+                                       &deleg);
+               addipdeleg(AF_INET6, "1111::1111", delegset, deleg);
+               writedb(db, zonecuts[i].name, zonecuts[i].ttl, &delegset, true);
+               deleg = NULL;
+       }
+
+       for (size_t i = 0; i < ARRAY_SIZE(zonecuts); i++) {
+               result = lookupdb(db, zonecuts[i].name, now + 1, 0,
+                                 zonecuts[i].expected, &delegset);
+               assert_int_equal(result, ISC_R_SUCCESS);
+               dns_delegset_detach(&delegset);
+
+               result = lookupdb(db, zonecuts[i].name, now + 1,
+                                 DNS_DBFIND_NOEXACT,
+                                 zonecuts[i].noexactexpected, &delegset);
+               assert_int_equal(result, ISC_R_SUCCESS);
+               dns_delegset_detach(&delegset);
+       }
+
+       result = lookupdb(db, "gee.expired.foo.stuff.", now + 1, 0,
+                         "foo.stuff.", &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       shutdowntest(&db);
+}
+
+static void
+deletetests(ISC_ATTR_UNUSED void *arg) {
+       isc_result_t result;
+       dns_delegdb_t *db = NULL;
+       dns_deleg_t *deleg = NULL;
+       dns_delegset_t *delegset = NULL;
+       dns_fixedname_t fname;
+       dns_name_t *name = dns_fixedname_initname(&fname);
+
+       dns_delegdb_create(&db);
+       assert_non_null(db);
+
+       dns_delegset_allocset(db, &delegset);
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+       addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       writedb(db, "stuff.", 10, &delegset, true);
+       deleg = NULL;
+
+       dns_delegset_allocset(db, &delegset);
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+       addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       writedb(db, "baz.stuff.", 10, &delegset, true);
+       deleg = NULL;
+
+       dns_delegset_allocset(db, &delegset);
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+       addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       writedb(db, "bar.baz.stuff.", 10, &delegset, true);
+       deleg = NULL;
+
+       dns_delegset_allocset(db, &delegset);
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+       addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       writedb(db, "foo.bar.baz.stuff.", 10, &delegset, true);
+       deleg = NULL;
+
+       dns_name_fromstring(name, "foo.", NULL, 0, NULL);
+       result = dns_delegdb_delete(db, name, false);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+       result = dns_delegdb_delete(db, name, true);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring(name, "gee.foo.bar.stuff.", NULL, 0, NULL);
+       result = dns_delegdb_delete(db, name, false);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring(name, "foo.bar.baz.stuff.", NULL, 0, NULL);
+       result = dns_delegdb_delete(db, name, false);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_name_fromstring(name, "foo.bar.baz.stuff.", NULL, 0, NULL);
+       result = dns_delegdb_delete(db, name, false);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring(name, "baz.stuff.", NULL, 0, NULL);
+       result = dns_delegdb_delete(db, name, false);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = lookupdb(db, "bar.baz.stuff.", 5, 0, "bar.baz.stuff.",
+                         &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       dns_name_fromstring(name, "stuff.", NULL, 0, NULL);
+       result = dns_delegdb_delete(db, name, true);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       dns_name_fromstring(name, "stuff.", NULL, 0, NULL);
+       result = dns_delegdb_delete(db, name, false);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       dns_name_fromstring(name, "bar.baz.stuff.", NULL, 0, NULL);
+       result = dns_delegdb_delete(db, name, false);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       result = lookupdb(db, "bar.baz.stuff.", 5, 0, "bar.baz.stuff.",
+                         &delegset);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       /*
+        * Let's add stuff. back and query bar.baz.stuff. again. Because the
+        * node is NULL, it should go up until it finds stuff.
+        */
+       dns_delegset_allocset(db, &delegset);
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+       addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       writedb(db, "stuff.", 10, &delegset, true);
+       deleg = NULL;
+
+       result = lookupdb(db, "bar.baz.stuff.", 5, 0, "stuff.", &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       shutdowntest(&db);
+}
+
+/*
+ * The cleanup test is split into phases because node destruction is now
+ * fully deferred to the node's owning loop via isc_async_run().  After
+ * rcu_barrier() completes, the QP reclamation has fired (calling
+ * delegdb_node_destroy which schedules the async callback), but the
+ * actual memory free hasn't happened yet — it's pending on the loop's
+ * event queue.  We must return to the loop between phases so it can
+ * process the pending node destroys before we check memory usage.
+ */
+typedef struct {
+       dns_delegdb_t *db;
+       isc_stdtime_t now;
+} cleanup_ctx_t;
+
+static void
+cleanuptests_phase3(void *arg) {
+       cleanup_ctx_t *ctx = arg;
+       dns_delegdb_t *db = ctx->db;
+       isc_stdtime_t now = ctx->now;
+       dns_delegset_t *delegset = NULL;
+       isc_result_t result;
+
+       assert_int_in_range(isc_mem_inuse(db->mctx), 4000000, 4100000);
+
+       /*
+        * baz. is there, but bar. is gone, as it has been
+        * removed (even if it wasn't expired.)
+        */
+       result = lookupdb(db, "baz.", now, 0, "baz.", &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       result = lookupdb(db, "bar.", now, 0, "bar.", &delegset);
+       assert_int_equal(result, ISC_R_NOTFOUND);
+
+       shutdowntest(&db);
+}
+
+static void
+cleanuptests_phase2(void *arg) {
+       cleanup_ctx_t *ctx = arg;
+       dns_delegdb_t *db = ctx->db;
+       isc_stdtime_t now = ctx->now;
+       dns_deleg_t *deleg = NULL;
+       dns_delegset_t *delegset = NULL;
+       isc_result_t result;
+
+       assert_int_in_range(isc_mem_inuse(db->mctx), 4000000, 4100000);
+
+       /*
+        * bar. is there
+        */
+       result = lookupdb(db, "bar.", now, 0, "bar.", &delegset);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       dns_delegset_detach(&delegset);
+
+       /*
+        * Add yet another non expired record. But LRU will have to get
+        * rid of it because we're hitting the hiwater mark again.
+        */
+       dns_delegset_allocset(db, &delegset);
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+
+       for (size_t i = 0; i < 99999; i++) {
+               addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       }
+       assert_int_in_range(isc_mem_inuse(db->mctx), 8000000, 8100000);
+       writedb(db, "baz.", 30, &delegset, true);
+       deleg = NULL;
+
+       rcu_barrier();
+       isc_async_run(isc_loop(), cleanuptests_phase3, ctx);
+}
+
+static void
+cleanuptests(ISC_ATTR_UNUSED void *arg) {
+       static cleanup_ctx_t ctx;
+       dns_delegdb_t *db = NULL;
+       dns_deleg_t *deleg = NULL;
+       dns_delegset_t *delegset = NULL;
+
+       dns_delegdb_create(&db);
+       assert_non_null(db);
+
+       ctx = (cleanup_ctx_t){ .db = db, .now = isc_stdtime_now() };
+
+       /*
+        * hiwater is 4375000 = 5000000 - (5000000 >> 3)
+        * lowater is 3750000 = 5000000 - (5000000 >> 2)
+        */
+       dns_delegdb_setsize(db, 5000000);
+
+       /*
+        * A valid record
+        */
+       dns_delegset_allocset(db, &delegset);
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+       addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       writedb(db, "baz.", 300, &delegset, true);
+       deleg = NULL;
+
+       /*
+        * An expired record
+        */
+       dns_delegset_allocset(db, &delegset);
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+
+       assert_int_in_range(isc_mem_inuse(db->mctx), 500, 2000);
+
+       for (size_t i = 0; i < 99999; i++) {
+               addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       }
+
+       assert_int_in_range(isc_mem_inuse(db->mctx), 4000000, 4100000);
+
+       writedb(db, "stuff.", 10, &delegset, true);
+       deleg = NULL;
+       stdtime_now += 10;
+
+       /*
+        * A non expired record
+        */
+       dns_delegset_allocset(db, &delegset);
+       dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
+                               &deleg);
+
+       for (size_t i = 0; i < 99999; i++) {
+               addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
+       }
+
+       /*
+        * The zonecut is not added yet but the delegset being huge (allocated
+        * with DB mem context) overmem conditions will be detected, and the
+        * expired node will be removed
+        */
+       assert_int_in_range(isc_mem_inuse(db->mctx), 8000000, 8100000);
+       writedb(db, "bar.", 30, &delegset, true);
+       deleg = NULL;
+
+       /*
+        * stuff. internal node (and delegset) is now removed. rcu_barrier()
+        * is needed to kick off QP reclamation flow (and run the detaching
+        * functions from the DB nodes).  The actual memory free is deferred
+        * to the loop via isc_async_run(), so we continue in phase2 to let
+        * the loop process the pending node destroys.
+        */
+       rcu_barrier();
+       isc_async_run(isc_loop(), cleanuptests_phase2, &ctx);
+}
+
+ISC_RUN_TEST_IMPL(dns_deleg_basictests) { rundelegtest(basictests); }
+ISC_RUN_TEST_IMPL(dns_deleg_ttl0tests) { rundelegtest(ttl0tests); }
+ISC_RUN_TEST_IMPL(dns_deleg_noexacttests) { rundelegtest(noexacttests); }
+ISC_RUN_TEST_IMPL(dns_deleg_deletetests) { rundelegtest(deletetests); }
+ISC_RUN_TEST_IMPL(dns_deleg_cleanuptests) { rundelegtest(cleanuptests); }
+
+ISC_TEST_LIST_START
+ISC_TEST_ENTRY(dns_deleg_basictests)
+ISC_TEST_ENTRY(dns_deleg_ttl0tests)
+ISC_TEST_ENTRY(dns_deleg_noexacttests)
+ISC_TEST_ENTRY(dns_deleg_deletetests)
+ISC_TEST_ENTRY(dns_deleg_cleanuptests)
+ISC_TEST_LIST_END
+
+ISC_TEST_MAIN
index 73c811f10ef06e52514895c9283076a600d473a0..72b675471934a239ba964e7df102cde22dd857e8 100644 (file)
@@ -17,6 +17,7 @@ dns_tests = [
     'dbdiff',
     'dbiterator',
     'dbversion',
+    'deleg',
     'dispatch',
     'dns64',
     'dst',