Files
bind9/lib/dns/zt.c
T
Tony Finch b171cacf4f Use a qp-trie for the zone table
This change makes the zone table lock-free for reads. Previously, the
zone table used a red-black tree, which is not thread safe, so the hot
read path acquired both the per-view mutex and the per-zonetable
rwlock. (The double locking was to fix to cleanup races on shutdown.)

One visible difference is that zones are not necessarily shut down
promptly: it depends on when the qp-trie garbage collector cleans up
the zone table. The `catz` system test checks several times that zones
have been deleted; the test now checks for zones to be removed from
the server configuration, instead of being fully shut down. The catz
test does not churn through enough zones to trigger a gc, so the zones
are not fully detached until the server exits.

After this change, it is still possible to improve the way we handle
changes to the zone table, for instance, batching changes, or better
compaction heuristics.
2023-04-05 12:38:11 +01:00

540 lines
12 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.
*/
/*! \file */
#include <inttypes.h>
#include <stdbool.h>
#include <isc/atomic.h>
#include <isc/file.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/tid.h>
#include <isc/util.h>
#include <dns/log.h>
#include <dns/name.h>
#include <dns/qp.h>
#include <dns/rdataclass.h>
#include <dns/view.h>
#include <dns/zone.h>
#include <dns/zt.h>
#define ZTMAGIC ISC_MAGIC('Z', 'T', 'b', 'l')
#define VALID_ZT(zt) ISC_MAGIC_VALID(zt, ZTMAGIC)
struct dns_zt {
unsigned int magic;
isc_mem_t *mctx;
dns_qpmulti_t *multi;
atomic_bool flush;
isc_refcount_t references;
isc_refcount_t loads_pending;
};
struct zt_load_params {
dns_zt_t *zt;
dns_zt_callback_t *loaddone;
void *loaddone_arg;
bool newonly;
};
struct zt_freeze_params {
dns_view_t *view;
bool freeze;
};
static void
ztqpattach(void *uctx ISC_ATTR_UNUSED, void *pval,
uint32_t ival ISC_ATTR_UNUSED) {
dns_zone_t *zone = pval;
dns_zone_ref(zone);
}
static void
ztqpdetach(void *uctx ISC_ATTR_UNUSED, void *pval,
uint32_t ival ISC_ATTR_UNUSED) {
dns_zone_t *zone = pval;
dns_zone_detach(&zone);
}
static size_t
ztqpmakekey(dns_qpkey_t key, void *uctx ISC_ATTR_UNUSED, void *pval,
uint32_t ival ISC_ATTR_UNUSED) {
dns_zone_t *zone = pval;
dns_name_t *name = dns_zone_getorigin(zone);
return (dns_qpkey_fromname(key, name));
}
static void
ztqptriename(void *uctx, char *buf, size_t size) {
dns_view_t *view = uctx;
snprintf(buf, size, "view %s zone table", view->name);
}
static dns_qpmethods_t ztqpmethods = {
ztqpattach,
ztqpdetach,
ztqpmakekey,
ztqptriename,
};
void
dns_zt_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, dns_view_t *view,
dns_zt_t **ztp) {
dns_qpmulti_t *multi = NULL;
dns_zt_t *zt = NULL;
REQUIRE(ztp != NULL && *ztp == NULL);
REQUIRE(view != NULL);
dns_qpmulti_create(mctx, loopmgr, &ztqpmethods, view, &multi);
zt = isc_mem_get(mctx, sizeof(*zt));
*zt = (dns_zt_t){
.magic = ZTMAGIC,
.multi = multi,
.references = 1,
};
isc_mem_attach(mctx, &zt->mctx);
*ztp = zt;
}
/*
* XXXFANF it isn't clear whether this function will be useful. There
* is only one zone table per view, so it is probably enough to let
* the qp-trie auto-GC do its thing. However it might be problematic
* if a very large zone is replaced, and its database memory is
* retained for a long time.
*/
void
dns_zt_compact(dns_zt_t *zt) {
dns_qp_t *qp = NULL;
REQUIRE(VALID_ZT(zt));
dns_qpmulti_write(zt->multi, &qp);
dns_qp_compact(qp, DNS_QPGC_ALL);
dns_qpmulti_commit(zt->multi, &qp);
}
isc_result_t
dns_zt_mount(dns_zt_t *zt, dns_zone_t *zone) {
isc_result_t result;
dns_qp_t *qp = NULL;
REQUIRE(VALID_ZT(zt));
dns_qpmulti_write(zt->multi, &qp);
result = dns_qp_insert(qp, zone, 0);
dns_qp_compact(qp, DNS_QPGC_MAYBE);
dns_qpmulti_commit(zt->multi, &qp);
return (result);
}
isc_result_t
dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone) {
isc_result_t result;
dns_qp_t *qp = NULL;
REQUIRE(VALID_ZT(zt));
dns_qpmulti_write(zt->multi, &qp);
result = dns_qp_deletename(qp, dns_zone_getorigin(zone));
dns_qp_compact(qp, DNS_QPGC_MAYBE);
dns_qpmulti_commit(zt->multi, &qp);
return (result);
}
isc_result_t
dns_zt_find(dns_zt_t *zt, const dns_name_t *name, dns_ztfind_t options,
dns_zone_t **zonep) {
isc_result_t result;
dns_qpread_t qpr;
void *pval = NULL;
uint32_t ival;
dns_ztfind_t exactmask = DNS_ZTFIND_NOEXACT | DNS_ZTFIND_EXACT;
dns_ztfind_t exactopts = options & exactmask;
REQUIRE(VALID_ZT(zt));
REQUIRE(exactopts != exactmask);
if (isc_tid() == ISC_TID_UNKNOWN) {
dns_qpmulti_lockedread(zt->multi, &qpr);
} else {
dns_qpmulti_query(zt->multi, &qpr);
}
if (exactopts == DNS_ZTFIND_EXACT) {
result = dns_qp_getname(&qpr, name, &pval, &ival);
} else if (exactopts == DNS_ZTFIND_NOEXACT) {
result = dns_qp_findname_parent(&qpr, name, DNS_QPFIND_NOEXACT,
&pval, &ival);
} else {
result = dns_qp_findname_parent(&qpr, name, 0, &pval, &ival);
}
dns_qpread_destroy(zt->multi, &qpr);
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
dns_zone_t *zone = pval;
/*
* If DNS_ZTFIND_MIRROR is set and the zone which was
* determined to be the deepest match for the supplied name is
* a mirror zone which is expired or not yet loaded, treat it
* as non-existent. This will trigger a fallback to recursion
* instead of returning a SERVFAIL.
*
* Note that currently only the deepest match in the zone table
* is checked. Consider a server configured with two mirror
* zones: "bar" and its child, "foo.bar". If zone data is
* available for "bar" but not for "foo.bar", a query with
* QNAME equal to or below "foo.bar" will cause ISC_R_NOTFOUND
* to be returned, not DNS_R_PARTIALMATCH, despite zone data
* being available for "bar". This is considered to be an edge
* case, handling which more appropriately is possible, but
* arguably not worth the added complexity.
*/
if ((options & DNS_ZTFIND_MIRROR) != 0 &&
dns_zone_gettype(zone) == dns_zone_mirror &&
!dns_zone_isloaded(zone))
{
result = ISC_R_NOTFOUND;
} else {
dns_zone_attach(zone, zonep);
}
}
return (result);
}
void
dns_zt_attach(dns_zt_t *zt, dns_zt_t **ztp) {
REQUIRE(VALID_ZT(zt));
REQUIRE(ztp != NULL && *ztp == NULL);
isc_refcount_increment(&zt->references);
*ztp = zt;
}
static isc_result_t
flush(dns_zone_t *zone, void *uap) {
UNUSED(uap);
return (dns_zone_flush(zone));
}
static void
zt_destroy(dns_zt_t *zt) {
isc_refcount_destroy(&zt->references);
isc_refcount_destroy(&zt->loads_pending);
if (atomic_load_acquire(&zt->flush)) {
(void)dns_zt_apply(zt, false, NULL, flush, NULL);
}
dns_qpmulti_destroy(&zt->multi);
zt->magic = 0;
isc_mem_putanddetach(&zt->mctx, zt, sizeof(*zt));
}
void
dns_zt_detach(dns_zt_t **ztp) {
dns_zt_t *zt;
REQUIRE(ztp != NULL && VALID_ZT(*ztp));
zt = *ztp;
*ztp = NULL;
if (isc_refcount_decrement(&zt->references) == 1) {
zt_destroy(zt);
}
}
void
dns_zt_flush(dns_zt_t *zt) {
REQUIRE(VALID_ZT(zt));
atomic_store_release(&zt->flush, true);
}
static isc_result_t
load(dns_zone_t *zone, void *uap) {
isc_result_t result;
result = dns_zone_load(zone, uap != NULL);
if (result == DNS_R_CONTINUE || result == DNS_R_UPTODATE ||
result == DNS_R_DYNAMIC)
{
result = ISC_R_SUCCESS;
}
return (result);
}
isc_result_t
dns_zt_load(dns_zt_t *zt, bool stop, bool newonly) {
REQUIRE(VALID_ZT(zt));
return (dns_zt_apply(zt, stop, NULL, load, newonly ? &newonly : NULL));
}
static void
loaded_all(struct zt_load_params *params) {
if (params->loaddone != NULL) {
params->loaddone(params->loaddone_arg);
}
isc_mem_put(params->zt->mctx, params, sizeof(*params));
}
/*
* Decrement the loads_pending counter; when counter reaches
* zero, call the loaddone callback that was initially set by
* dns_zt_asyncload().
*/
static isc_result_t
loaded_one(void *uap) {
struct zt_load_params *params = uap;
dns_zt_t *zt = params->zt;
REQUIRE(VALID_ZT(zt));
if (isc_refcount_decrement(&zt->loads_pending) == 1) {
loaded_all(params);
}
if (isc_refcount_decrement(&zt->references) == 1) {
zt_destroy(zt);
}
return (ISC_R_SUCCESS);
}
/*
* Initiates asynchronous loading of zone 'zone'. 'callback' is a
* pointer to a function which will be used to inform the caller when
* the zone loading is complete.
*/
static isc_result_t
asyncload(dns_zone_t *zone, void *uap) {
struct zt_load_params *params = uap;
struct dns_zt *zt = params->zt;
isc_result_t result;
REQUIRE(VALID_ZT(zt));
REQUIRE(zone != NULL);
isc_refcount_increment(&zt->references);
isc_refcount_increment(&zt->loads_pending);
result = dns_zone_asyncload(zone, params->newonly, loaded_one, params);
if (result != ISC_R_SUCCESS) {
/*
* Caller is holding a reference to zt->loads_pending
* and zt->references so these can't decrement to zero.
*/
isc_refcount_decrement1(&zt->references);
isc_refcount_decrement1(&zt->loads_pending);
}
return (ISC_R_SUCCESS);
}
isc_result_t
dns_zt_asyncload(dns_zt_t *zt, bool newonly, dns_zt_callback_t *loaddone,
void *arg) {
isc_result_t result;
uint_fast32_t loads_pending;
struct zt_load_params *params = NULL;
REQUIRE(VALID_ZT(zt));
/*
* Obtain a reference to zt->loads_pending so that asyncload can
* safely decrement both zt->references and zt->loads_pending
* without going to zero.
*/
loads_pending = isc_refcount_increment0(&zt->loads_pending);
INSIST(loads_pending == 0);
params = isc_mem_get(zt->mctx, sizeof(*params));
*params = (struct zt_load_params){
.zt = zt,
.newonly = newonly,
.loaddone = loaddone,
.loaddone_arg = arg,
};
result = dns_zt_apply(zt, false, NULL, asyncload, params);
/*
* Have all the loads completed?
*/
if (isc_refcount_decrement(&zt->loads_pending) == 1) {
loaded_all(params);
}
return (result);
}
static isc_result_t
freezezones(dns_zone_t *zone, void *uap) {
struct zt_freeze_params *params = uap;
bool frozen;
isc_result_t result = ISC_R_SUCCESS;
char classstr[DNS_RDATACLASS_FORMATSIZE];
char zonename[DNS_NAME_FORMATSIZE];
dns_zone_t *raw = NULL;
dns_view_t *view;
const char *vname;
const char *sep;
int level;
dns_zone_getraw(zone, &raw);
if (raw != NULL) {
zone = raw;
}
if (params->view != dns_zone_getview(zone)) {
if (raw != NULL) {
dns_zone_detach(&raw);
}
return (ISC_R_SUCCESS);
}
if (dns_zone_gettype(zone) != dns_zone_primary) {
if (raw != NULL) {
dns_zone_detach(&raw);
}
return (ISC_R_SUCCESS);
}
if (!dns_zone_isdynamic(zone, true)) {
if (raw != NULL) {
dns_zone_detach(&raw);
}
return (ISC_R_SUCCESS);
}
frozen = dns_zone_getupdatedisabled(zone);
if (params->freeze) {
if (frozen) {
result = DNS_R_FROZEN;
}
if (result == ISC_R_SUCCESS) {
result = dns_zone_flush(zone);
}
if (result == ISC_R_SUCCESS) {
dns_zone_setupdatedisabled(zone, params->freeze);
}
} else {
if (frozen) {
result = dns_zone_loadandthaw(zone);
if (result == DNS_R_CONTINUE ||
result == DNS_R_UPTODATE)
{
result = ISC_R_SUCCESS;
}
}
}
view = dns_zone_getview(zone);
if (strcmp(view->name, "_bind") == 0 ||
strcmp(view->name, "_default") == 0)
{
vname = "";
sep = "";
} else {
vname = view->name;
sep = " ";
}
dns_rdataclass_format(dns_zone_getclass(zone), classstr,
sizeof(classstr));
dns_name_format(dns_zone_getorigin(zone), zonename, sizeof(zonename));
level = (result != ISC_R_SUCCESS) ? ISC_LOG_ERROR : ISC_LOG_DEBUG(1);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE,
level, "%s zone '%s/%s'%s%s: %s",
params->freeze ? "freezing" : "thawing", zonename,
classstr, sep, vname, isc_result_totext(result));
if (raw != NULL) {
dns_zone_detach(&raw);
}
return (result);
}
isc_result_t
dns_zt_freezezones(dns_zt_t *zt, dns_view_t *view, bool freeze) {
isc_result_t result, tresult;
struct zt_freeze_params params = { view, freeze };
REQUIRE(VALID_ZT(zt));
result = dns_zt_apply(zt, false, &tresult, freezezones, &params);
if (tresult == ISC_R_NOTFOUND) {
tresult = ISC_R_SUCCESS;
}
return ((result == ISC_R_SUCCESS) ? tresult : result);
}
typedef void
setview_cb(dns_zone_t *zone);
static isc_result_t
setview(dns_zone_t *zone, void *arg) {
setview_cb *cb = arg;
cb(zone);
return (ISC_R_SUCCESS);
}
void
dns_zt_setviewcommit(dns_zt_t *zt) {
dns_zt_apply(zt, false, NULL, setview, dns_zone_setviewcommit);
}
void
dns_zt_setviewrevert(dns_zt_t *zt) {
dns_zt_apply(zt, false, NULL, setview, dns_zone_setviewrevert);
}
isc_result_t
dns_zt_apply(dns_zt_t *zt, bool stop, isc_result_t *sub,
isc_result_t (*action)(dns_zone_t *, void *), void *uap) {
isc_result_t result = ISC_R_SUCCESS;
isc_result_t tresult = ISC_R_SUCCESS;
dns_qpiter_t qpi;
dns_qpread_t qpr;
void *zone = NULL;
uint32_t ival;
REQUIRE(VALID_ZT(zt));
REQUIRE(action != NULL);
dns_qpmulti_query(zt->multi, &qpr);
dns_qpiter_init(&qpr, &qpi);
while (dns_qpiter_next(&qpi, &zone, &ival) == ISC_R_SUCCESS) {
result = action(zone, uap);
if (tresult == ISC_R_SUCCESS) {
tresult = result;
}
if (result != ISC_R_SUCCESS && stop) {
break;
}
}
dns_qpread_destroy(zt->multi, &qpr);
if (sub != NULL) {
*sub = tresult;
}
return (result);
}