bind9/lib/dns/deleg.c
Ondřej Surý 28483b3b73
Drop in-domain NS without glue from the delegation set
Pull the dns_message_findname() lookups into cache_delegglue() and
cache_delegglue6() so each helper now owns its glue lookup and returns
the number of addresses cached.  cache_delegns() splits referrals into
two cases: in-domain (the NS name is below the delegation point) and
sibling/in-bailiwick.

An in-domain NS without glue is unresolvable by definition - the
resolver would have to ask the very server it's trying to find.  Log
"missing mandatory glue" at notice level and skip the deleg entirely
rather than leaving an unusable entry in the set.  A new
dns_delegset_freedeleg() undoes a fresh dns_delegset_allocdeleg() so
the rest of the delegation set is preserved.
2026-05-15 07:26:38 +02:00

1060 lines
27 KiB
C

/*
* 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>
#include "probes-dns.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;
};
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]);
}
LIBDNS_DELEGDB_CREATE(delegdb);
*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);
LIBDNS_DELEGDB_REUSE(newview->deleg);
}
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) {
delegdb_node_t *parent = NULL;
dns_qpchain_node(chain, len - 2, (void **)&parent, NULL);
if (isactive(parent, now)) {
*node = parent;
return;
}
len--;
}
/*
* No active proper ancestor was found in the chain. Signal
* "no parent" so the caller does not mistake the original
* matched node for an ancestor.
*/
*node = NULL;
}
/*
* 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 above = (options & DNS_DBFIND_ABOVE) != 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);
}
/*
* Walk up the chain when:
* - we have an exact match but the caller asked for DNS_DBFIND_ABOVE
* (i.e. the caller wants the deepest *proper* ancestor), or
* - the matched node is no longer active and we need to fall
* back to the closest still-active ancestor (this applies
* equally to exact and partial matches).
*
* getparentnode() sets 'node' to NULL when no active ancestor
* exists in the chain, so we must NULL-check before dereferencing
* 'node' below.
*/
if ((result == ISC_R_SUCCESS && above) || !isactive(node, now)) {
getparentnode(&chain, &node, now);
}
if (node != NULL && isactive(node, now)) {
dns_name_copy(&node->zonecut, zonecut);
INSIST(node->delegset);
dns_delegset_attach(node->delegset, delegsetp);
ISC_SIEVE_MARK(node, visited);
return ISC_R_SUCCESS;
}
/*
* The expired node will be replaced when the resolver fetches
* a fresh delegation, so there is no need to schedule explicit
* cleanup here. Stale nodes that are never replaced will
* eventually be evicted by the SIEVE policy under memory
* pressure.
*/
return ISC_R_NOTFOUND;
}
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 = {};
char namebuf[DNS_NAME_FORMATSIZE];
if (LIBDNS_DELEGDB_LOOKUP_START_ENABLED() ||
LIBDNS_DELEGDB_LOOKUP_DONE_ENABLED())
{
dns_name_format(name, namebuf, sizeof(namebuf));
}
LIBDNS_DELEGDB_LOOKUP_START(delegdb, namebuf);
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();
LIBDNS_DELEGDB_LOOKUP_DONE(delegdb, namebuf, result);
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_freedeleg(dns_delegset_t *delegset, dns_deleg_t **delegp) {
REQUIRE(DNS_DELEGSET_VALID(delegset));
REQUIRE(delegp != NULL && *delegp != NULL);
REQUIRE(ISC_LIST_EMPTY((*delegp)->addresses));
REQUIRE(ISC_LIST_EMPTY((*delegp)->names));
dns_deleg_t *deleg = *delegp;
*delegp = NULL;
ISC_LIST_UNLINK(delegset->delegs, deleg, link);
isc_mem_put(delegset->mctx, deleg, sizeof(*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_qp_t *qp, dns_delegdb_t *delegdb, size_t requested) {
delegdb_node_t *node = NULL;
size_t reclaimed = 0;
if (!isc_mem_isovermem(delegdb->mctx)) {
return;
}
LIBDNS_DELEGDB_CLEANUP_START(delegdb, (int)requested);
while (reclaimed < requested) {
node = ISC_SIEVE_NEXT(delegdb->lru[isc_tid()], visited, link);
if (node == NULL) {
break;
}
reclaimed += node->size;
if (LIBDNS_DELEGDB_EVICT_ENABLED()) {
char namebuf[DNS_NAME_FORMATSIZE];
dns_name_format(&node->zonecut, namebuf,
sizeof(namebuf));
LIBDNS_DELEGDB_EVICT(delegdb, node, namebuf);
}
ISC_SIEVE_UNLINK(delegdb->lru[isc_tid()], node, link);
(void)dns_qp_deletename(qp, &node->zonecut,
DNS_DBNAMESPACE_NORMAL, NULL, NULL);
}
LIBDNS_DELEGDB_CLEANUP_DONE(delegdb, (int)reclaimed);
}
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 size_t
delegdb_node_prepare(dns_delegdb_t *delegdb, isc_stdtime_t now, dns_ttl_t ttl,
const dns_name_t *zonecut, dns_delegset_t *delegset,
delegdb_node_t **nodep) {
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);
return sizeof(**nodep) + (*nodep)->size;
}
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;
char zonecutbuf[DNS_NAME_FORMATSIZE];
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);
if (LIBDNS_DELEGDB_INSERT_START_ENABLED() ||
LIBDNS_DELEGDB_INSERT_DONE_ENABLED())
{
dns_name_format(zonecut, zonecutbuf, sizeof(zonecutbuf));
}
LIBDNS_DELEGDB_INSERT_START(delegdb, zonecutbuf);
rcu_read_lock();
nodes = rcu_dereference(delegdb->nodes);
if (nodes == NULL) {
CLEANUP(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.
*/
size_t requested = delegdb_node_prepare(delegdb, now, ttl, zonecut,
delegset, &node);
/*
* Add the node in the DB
*/
dns_qpmulti_write(nodes, &qp);
delegdb_cleanup(qp, delegdb, requested);
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();
LIBDNS_DELEGDB_INSERT_DONE(delegdb, zonecutbuf, result);
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(isc_mem_t *mctx, 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(mctx, sizeof(*delegset));
*delegset = (dns_delegset_t){
.magic = DNS_DELEGSET_MAGIC,
.mctx = isc_mem_ref(mctx),
.references = ISC_REFCOUNT_INITIALIZER(1),
.delegs = ISC_LIST_INITIALIZER,
.expires = rdataset->ttl + isc_stdtime_now(),
.staticstub = rdataset->attributes.staticstub
};
deleg = isc_mem_get(delegset->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;
char namebuf[DNS_NAME_FORMATSIZE];
if (LIBDNS_DELEGDB_DELETE_ENABLED()) {
dns_name_format(name, namebuf, sizeof(namebuf));
}
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();
LIBDNS_DELEGDB_DELETE(delegdb, namebuf, (int)tree, result);
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);
}
LIBDNS_DELEGDB_SHUTDOWN(delegdb);
}
}
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) {
size_t lowater;
size_t hiwater;
REQUIRE(VALID_DELEGDB(delegdb));
if (size != 0 && size < DELEGDB_MINSIZE) {
size = DELEGDB_MINSIZE;
}
hiwater = size - (size >> 3); /* Approximately 7/8ths. */
lowater = size - (size >> 2); /* Approximately 3/4ths. */
if (size == 0 || hiwater == 0 || 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, hiwater, lowater);
}
}