--- /dev/null
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+#include <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);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#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);
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;
'compress.c',
'db.c',
'dbiterator.c',
+ 'deleg.c',
'diff.c',
'dispatch.c',
'dlz.c',
--- /dev/null
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <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
'dbdiff',
'dbiterator',
'dbversion',
+ 'deleg',
'dispatch',
'dns64',
'dst',