2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-23 02:28:55 +00:00
bind/lib/dns/zt.c

603 lines
14 KiB
C
Raw Normal View History

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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 */
2000-06-22 22:00:42 +00:00
#include <inttypes.h>
#include <stdbool.h>
#include <isc/atomic.h>
#include <isc/file.h>
1999-10-13 18:19:56 +00:00
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/result.h>
#include <isc/string.h>
2011-09-03 19:53:15 +00:00
#include <isc/task.h>
1999-12-16 22:24:22 +00:00
#include <isc/util.h>
#include <dns/log.h>
#include <dns/name.h>
2000-04-12 21:32:13 +00:00
#include <dns/rbt.h>
#include <dns/rdataclass.h>
#include <dns/view.h>
#include <dns/zone.h>
2000-04-12 21:32:13 +00:00
#include <dns/zt.h>
struct zt_load_params {
dns_zt_zoneloaded_t dl;
2020-02-13 14:44:37 -08:00
bool newonly;
};
struct dns_zt {
/* Unlocked. */
2020-02-13 14:44:37 -08:00
unsigned int magic;
isc_mem_t *mctx;
dns_rdataclass_t rdclass;
isc_rwlock_t rwlock;
dns_zt_allloaded_t loaddone;
void *loaddone_arg;
struct zt_load_params *loadparams;
/* Atomic */
2020-02-13 14:44:37 -08:00
atomic_bool flush;
isc_refcount_t references;
isc_refcount_t loads_pending;
/* Locked by lock. */
dns_rbt_t *table;
};
struct zt_freeze_params {
dns_view_t *view;
bool freeze;
};
2020-02-13 14:44:37 -08:00
#define ZTMAGIC ISC_MAGIC('Z', 'T', 'b', 'l')
#define VALID_ZT(zt) ISC_MAGIC_VALID(zt, ZTMAGIC)
2020-02-14 08:14:03 +01:00
static void
auto_detach(void *, void *);
2020-02-14 08:14:03 +01:00
static isc_result_t
load(dns_zone_t *zone, void *uap);
2020-02-14 08:14:03 +01:00
static isc_result_t
asyncload(dns_zone_t *zone, void *callback);
2020-02-14 08:14:03 +01:00
static isc_result_t
freezezones(dns_zone_t *zone, void *uap);
2020-02-14 08:14:03 +01:00
static isc_result_t
doneloading(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task);
isc_result_t
2020-02-13 14:44:37 -08:00
dns_zt_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, dns_zt_t **ztp) {
dns_zt_t *zt;
isc_result_t result;
REQUIRE(ztp != NULL && *ztp == NULL);
2001-11-12 19:05:39 +00:00
zt = isc_mem_get(mctx, sizeof(*zt));
zt->table = NULL;
result = dns_rbt_create(mctx, auto_detach, zt, &zt->table);
if (result != ISC_R_SUCCESS) {
goto cleanup_zt;
}
isc_rwlock_init(&zt->rwlock, 0, 0);
zt->mctx = NULL;
isc_mem_attach(mctx, &zt->mctx);
isc_refcount_init(&zt->references, 1);
atomic_init(&zt->flush, false);
zt->rdclass = rdclass;
zt->magic = ZTMAGIC;
zt->loaddone = NULL;
zt->loaddone_arg = NULL;
zt->loadparams = NULL;
isc_refcount_init(&zt->loads_pending, 0);
*ztp = zt;
return (ISC_R_SUCCESS);
cleanup_zt:
2001-11-12 19:05:39 +00:00
isc_mem_put(mctx, zt, sizeof(*zt));
return (result);
}
isc_result_t
2020-02-13 14:44:37 -08:00
dns_zt_mount(dns_zt_t *zt, dns_zone_t *zone) {
isc_result_t result;
2020-02-13 14:44:37 -08:00
dns_zone_t *dummy = NULL;
dns_name_t *name;
REQUIRE(VALID_ZT(zt));
name = dns_zone_getorigin(zone);
RWLOCK(&zt->rwlock, isc_rwlocktype_write);
result = dns_rbt_addname(zt->table, name, zone);
if (result == ISC_R_SUCCESS) {
1999-10-13 18:19:56 +00:00
dns_zone_attach(zone, &dummy);
}
RWUNLOCK(&zt->rwlock, isc_rwlocktype_write);
return (result);
}
isc_result_t
2020-02-13 14:44:37 -08:00
dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone) {
isc_result_t result;
2020-02-13 14:44:37 -08:00
dns_name_t *name;
REQUIRE(VALID_ZT(zt));
name = dns_zone_getorigin(zone);
RWLOCK(&zt->rwlock, isc_rwlocktype_write);
result = dns_rbt_deletename(zt->table, name, false);
RWUNLOCK(&zt->rwlock, isc_rwlocktype_write);
return (result);
}
isc_result_t
dns_zt_find(dns_zt_t *zt, const dns_name_t *name, unsigned int options,
2020-02-13 14:44:37 -08:00
dns_name_t *foundname, dns_zone_t **zonep) {
isc_result_t result;
2020-02-13 14:44:37 -08:00
dns_zone_t *dummy = NULL;
2000-04-19 18:21:24 +00:00
unsigned int rbtoptions = 0;
REQUIRE(VALID_ZT(zt));
if ((options & DNS_ZTFIND_NOEXACT) != 0) {
2000-04-19 18:21:24 +00:00
rbtoptions |= DNS_RBTFIND_NOEXACT;
}
2000-04-19 18:21:24 +00:00
RWLOCK(&zt->rwlock, isc_rwlocktype_read);
2000-04-19 18:21:24 +00:00
result = dns_rbt_findname(zt->table, name, rbtoptions, foundname,
(void **)(void *)&dummy);
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
/*
* 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(dummy) == dns_zone_mirror &&
2020-02-13 14:44:37 -08:00
!dns_zone_isloaded(dummy))
{
result = ISC_R_NOTFOUND;
} else {
dns_zone_attach(dummy, zonep);
}
}
RWUNLOCK(&zt->rwlock, isc_rwlocktype_read);
1999-10-13 18:19:56 +00:00
return (result);
}
void
2020-02-13 14:44:37 -08:00
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
2020-02-13 14:44:37 -08:00
flush(dns_zone_t *zone, void *uap) {
UNUSED(uap);
2011-12-10 22:09:41 +00:00
return (dns_zone_flush(zone));
}
static void
2020-02-13 14:44:37 -08:00
zt_destroy(dns_zt_t *zt) {
if (atomic_load_acquire(&zt->flush)) {
(void)dns_zt_apply(zt, false, NULL, flush, NULL);
}
dns_rbt_destroy(&zt->table);
isc_rwlock_destroy(&zt->rwlock);
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);
}
isc_result_t
2020-02-13 14:44:37 -08:00
dns_zt_load(dns_zt_t *zt, bool stop, bool newonly) {
isc_result_t result;
struct zt_load_params params;
REQUIRE(VALID_ZT(zt));
params.newonly = newonly;
RWLOCK(&zt->rwlock, isc_rwlocktype_read);
result = dns_zt_apply(zt, stop, NULL, load, &params);
RWUNLOCK(&zt->rwlock, isc_rwlocktype_read);
return (result);
1999-10-25 13:34:17 +00:00
}
static isc_result_t
2020-02-13 14:44:37 -08:00
load(dns_zone_t *zone, void *paramsv) {
isc_result_t result;
struct zt_load_params *params = (struct zt_load_params *)paramsv;
result = dns_zone_load(zone, params->newonly);
if (result == DNS_R_CONTINUE || result == DNS_R_UPTODATE ||
2020-02-13 14:44:37 -08:00
result == DNS_R_DYNAMIC)
{
result = ISC_R_SUCCESS;
}
return (result);
}
static void
2020-02-13 14:44:37 -08:00
call_loaddone(dns_zt_t *zt) {
dns_zt_allloaded_t loaddone = zt->loaddone;
2020-02-13 14:44:37 -08:00
void *loaddone_arg = zt->loaddone_arg;
/*
* Set zt->loaddone, zt->loaddone_arg and zt->loadparams to NULL
* before calling loaddone.
*/
zt->loaddone = NULL;
zt->loaddone_arg = NULL;
isc_mem_put(zt->mctx, zt->loadparams, sizeof(struct zt_load_params));
zt->loadparams = NULL;
/*
* Call the callback last.
*/
if (loaddone != NULL) {
loaddone(loaddone_arg);
}
}
isc_result_t
dns_zt_asyncload(dns_zt_t *zt, bool newonly, dns_zt_allloaded_t alldone,
2020-02-13 14:44:37 -08:00
void *arg) {
isc_result_t result;
uint_fast32_t loads_pending;
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);
/*
* Only one dns_zt_asyncload call at a time should be active so
* these pointers should be NULL. They are set back to NULL
* before the zt->loaddone (alldone) is called in call_loaddone.
*/
INSIST(zt->loadparams == NULL);
INSIST(zt->loaddone == NULL);
INSIST(zt->loaddone_arg == NULL);
zt->loadparams = isc_mem_get(zt->mctx, sizeof(struct zt_load_params));
zt->loadparams->dl = doneloading;
zt->loadparams->newonly = newonly;
zt->loaddone = alldone;
zt->loaddone_arg = arg;
RWLOCK(&zt->rwlock, isc_rwlocktype_read);
result = dns_zt_apply(zt, false, NULL, asyncload, zt);
RWUNLOCK(&zt->rwlock, isc_rwlocktype_read);
/*
* Have all the loads completed?
*/
if (isc_refcount_decrement(&zt->loads_pending) == 1) {
call_loaddone(zt);
}
return (result);
1999-10-25 13:34:17 +00:00
}
/*
2011-09-02 23:46:33 +00:00
* 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
2020-02-13 14:44:37 -08:00
asyncload(dns_zone_t *zone, void *zt_) {
isc_result_t result;
struct dns_zt *zt = (dns_zt_t *)zt_;
REQUIRE(zone != NULL);
isc_refcount_increment(&zt->references);
isc_refcount_increment(&zt->loads_pending);
result = dns_zone_asyncload(zone, zt->loadparams->newonly,
*zt->loadparams->dl, zt);
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_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));
RWLOCK(&zt->rwlock, isc_rwlocktype_read);
result = dns_zt_apply(zt, false, &tresult, freezezones, &params);
RWUNLOCK(&zt->rwlock, isc_rwlocktype_read);
if (tresult == ISC_R_NOTFOUND) {
tresult = ISC_R_SUCCESS;
}
return ((result == ISC_R_SUCCESS) ? tresult : result);
}
static isc_result_t
2020-02-13 14:44:37 -08:00
freezezones(dns_zone_t *zone, void *uap) {
struct zt_freeze_params *params = uap;
2020-02-13 14:44:37 -08:00
bool frozen;
isc_result_t result = ISC_R_SUCCESS;
2020-02-13 14:44:37 -08:00
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, "_defaul"
2020-02-13 14:44:37 -08:00
"t") == 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);
}
void
2020-02-13 14:44:37 -08:00
dns_zt_setviewcommit(dns_zt_t *zt) {
dns_rbtnode_t *node;
dns_rbtnodechain_t chain;
2020-02-13 14:44:37 -08:00
isc_result_t result;
REQUIRE(VALID_ZT(zt));
Convert dispatch to netmgr The flow of operations in dispatch is changing and will now be similar for both UDP and TCP queries: 1) Call dns_dispatch_addresponse() to assign a query ID and register that we'll be listening for a response with that ID soon. the parameters for this function include callback functions to inform the caller when the socket is connected and when the message has been sent, as well as a task action that will be sent when the response arrives. (later this could become a netmgr callback, but at this stage to minimize disruption to the calling code, we continue to use isc_task for the response event.) on successful completion of this function, a dispatch entry object will be instantiated. 2) Call dns_dispatch_connect() on the dispatch entry. this runs isc_nm_udpconnect() or isc_nm_tcpdnsconnect(), as needed, and begins listening for responses. the caller is informed via a callback function when the connection is established. 3) Call dns_dispatch_send() on the dispatch entry. this runs isc_nm_send() to send a request. 4) Call dns_dispatch_removeresponse() to terminate listening and close the connection. Implementation comments below: - As we will be using netmgr buffers now. code to send the length in TCP queries has also been removed as that is handled by the netmgr. - TCP dispatches can be used by multiple simultaneous queries, so dns_dispatch_connect() now checks whether the dispatch is already connected before calling isc_nm_tcpdnsconnect() again. - Running dns_dispatch_getnext() from a non-network thread caused a crash due to assertions in the netmgr read functions that appear to be unnecessary now. the assertions have been removed. - fctx->nqueries was formerly incremented when the connection was successful, but is now incremented when the query is started and decremented if the connection fails. - It's no longer necessary for each dispatch to have a pool of tasks, so there's now a single task per dispatch. - Dispatch code to avoid UDP ports already in use has been removed. - dns_resolver and dns_request have been modified to use netmgr callback functions instead of task events. some additional changes were needed to handle shutdown processing correctly. - Timeout processing is not yet fully converted to use netmgr timeouts. - Fixed a lock order cycle reported by TSAN (view -> zone-> adb -> view) by by calling dns_zt functions without holding the view lock.
2021-01-14 13:02:57 -08:00
RWLOCK(&zt->rwlock, isc_rwlocktype_read);
dns_rbtnodechain_init(&chain);
result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL);
while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
result = dns_rbtnodechain_current(&chain, NULL, NULL, &node);
if (result == ISC_R_SUCCESS && node->data != NULL) {
dns_zone_setviewcommit(node->data);
}
result = dns_rbtnodechain_next(&chain, NULL, NULL);
}
dns_rbtnodechain_invalidate(&chain);
Convert dispatch to netmgr The flow of operations in dispatch is changing and will now be similar for both UDP and TCP queries: 1) Call dns_dispatch_addresponse() to assign a query ID and register that we'll be listening for a response with that ID soon. the parameters for this function include callback functions to inform the caller when the socket is connected and when the message has been sent, as well as a task action that will be sent when the response arrives. (later this could become a netmgr callback, but at this stage to minimize disruption to the calling code, we continue to use isc_task for the response event.) on successful completion of this function, a dispatch entry object will be instantiated. 2) Call dns_dispatch_connect() on the dispatch entry. this runs isc_nm_udpconnect() or isc_nm_tcpdnsconnect(), as needed, and begins listening for responses. the caller is informed via a callback function when the connection is established. 3) Call dns_dispatch_send() on the dispatch entry. this runs isc_nm_send() to send a request. 4) Call dns_dispatch_removeresponse() to terminate listening and close the connection. Implementation comments below: - As we will be using netmgr buffers now. code to send the length in TCP queries has also been removed as that is handled by the netmgr. - TCP dispatches can be used by multiple simultaneous queries, so dns_dispatch_connect() now checks whether the dispatch is already connected before calling isc_nm_tcpdnsconnect() again. - Running dns_dispatch_getnext() from a non-network thread caused a crash due to assertions in the netmgr read functions that appear to be unnecessary now. the assertions have been removed. - fctx->nqueries was formerly incremented when the connection was successful, but is now incremented when the query is started and decremented if the connection fails. - It's no longer necessary for each dispatch to have a pool of tasks, so there's now a single task per dispatch. - Dispatch code to avoid UDP ports already in use has been removed. - dns_resolver and dns_request have been modified to use netmgr callback functions instead of task events. some additional changes were needed to handle shutdown processing correctly. - Timeout processing is not yet fully converted to use netmgr timeouts. - Fixed a lock order cycle reported by TSAN (view -> zone-> adb -> view) by by calling dns_zt functions without holding the view lock.
2021-01-14 13:02:57 -08:00
RWUNLOCK(&zt->rwlock, isc_rwlocktype_read);
}
void
2020-02-13 14:44:37 -08:00
dns_zt_setviewrevert(dns_zt_t *zt) {
dns_rbtnode_t *node;
dns_rbtnodechain_t chain;
2020-02-13 14:44:37 -08:00
isc_result_t result;
REQUIRE(VALID_ZT(zt));
dns_rbtnodechain_init(&chain);
result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL);
while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
result = dns_rbtnodechain_current(&chain, NULL, NULL, &node);
if (result == ISC_R_SUCCESS && node->data != NULL) {
dns_zone_setviewrevert(node->data);
}
result = dns_rbtnodechain_next(&chain, NULL, NULL);
}
dns_rbtnodechain_invalidate(&chain);
}
isc_result_t
dns_zt_apply(dns_zt_t *zt, bool stop, isc_result_t *sub,
2020-02-13 14:44:37 -08:00
isc_result_t (*action)(dns_zone_t *, void *), void *uap) {
dns_rbtnode_t *node;
1999-10-13 18:19:56 +00:00
dns_rbtnodechain_t chain;
2020-02-13 14:44:37 -08:00
isc_result_t result, tresult = ISC_R_SUCCESS;
dns_zone_t *zone;
1999-10-13 18:19:56 +00:00
REQUIRE(VALID_ZT(zt));
1999-10-25 13:34:17 +00:00
REQUIRE(action != NULL);
1999-10-13 18:19:56 +00:00
dns_rbtnodechain_init(&chain);
1999-10-13 18:19:56 +00:00
result = dns_rbtnodechain_first(&chain, zt->table, NULL, NULL);
if (result == ISC_R_NOTFOUND) {
/*
* The tree is empty.
*/
tresult = result;
result = ISC_R_NOMORE;
}
while (result == DNS_R_NEWORIGIN || result == ISC_R_SUCCESS) {
result = dns_rbtnodechain_current(&chain, NULL, NULL, &node);
if (result == ISC_R_SUCCESS) {
1999-10-13 18:19:56 +00:00
zone = node->data;
if (zone != NULL) {
result = (action)(zone, uap);
}
if (result != ISC_R_SUCCESS && stop) {
tresult = result;
goto cleanup; /* don't break */
} else if (result != ISC_R_SUCCESS &&
tresult == ISC_R_SUCCESS) {
tresult = result;
}
1999-10-13 18:19:56 +00:00
}
result = dns_rbtnodechain_next(&chain, NULL, NULL);
}
if (result == ISC_R_NOMORE) {
result = ISC_R_SUCCESS;
}
cleanup:
1999-10-13 18:19:56 +00:00
dns_rbtnodechain_invalidate(&chain);
if (sub != NULL) {
*sub = tresult;
}
return (result);
1999-10-13 18:19:56 +00:00
}
/*
* 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
2020-02-13 14:44:37 -08:00
doneloading(dns_zt_t *zt, dns_zone_t *zone, isc_task_t *task) {
UNUSED(zone);
2011-09-03 19:53:15 +00:00
UNUSED(task);
REQUIRE(VALID_ZT(zt));
if (isc_refcount_decrement(&zt->loads_pending) == 1) {
call_loaddone(zt);
}
if (isc_refcount_decrement(&zt->references) == 1) {
zt_destroy(zt);
}
return (ISC_R_SUCCESS);
}
1999-10-13 18:19:56 +00:00
/***
*** Private
***/
static void
2020-02-13 14:44:37 -08:00
auto_detach(void *data, void *arg) {
dns_zone_t *zone = data;
UNUSED(arg);
dns_zone_detach(&zone);
}