Home | History | Annotate | Line # | Download | only in dns
      1 /*	$NetBSD: nta.c,v 1.13 2026/01/29 18:37:49 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  *
      6  * SPDX-License-Identifier: MPL-2.0
      7  *
      8  * This Source Code Form is subject to the terms of the Mozilla Public
      9  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  *
     12  * See the COPYRIGHT file distributed with this work for additional
     13  * information regarding copyright ownership.
     14  */
     15 
     16 /*! \file */
     17 
     18 #include <inttypes.h>
     19 #include <stdbool.h>
     20 
     21 #include <isc/async.h>
     22 #include <isc/buffer.h>
     23 #include <isc/log.h>
     24 #include <isc/loop.h>
     25 #include <isc/mem.h>
     26 #include <isc/result.h>
     27 #include <isc/rwlock.h>
     28 #include <isc/string.h>
     29 #include <isc/time.h>
     30 #include <isc/timer.h>
     31 #include <isc/util.h>
     32 
     33 #include <dns/db.h>
     34 #include <dns/fixedname.h>
     35 #include <dns/log.h>
     36 #include <dns/name.h>
     37 #include <dns/nta.h>
     38 #include <dns/qp.h>
     39 #include <dns/rdataset.h>
     40 #include <dns/resolver.h>
     41 #include <dns/time.h>
     42 
     43 struct dns_ntatable {
     44 	unsigned int magic;
     45 	isc_mem_t *mctx;
     46 	dns_view_t *view;
     47 	isc_rwlock_t rwlock;
     48 	isc_loopmgr_t *loopmgr;
     49 	isc_refcount_t references;
     50 	dns_qpmulti_t *table;
     51 	atomic_bool shuttingdown;
     52 };
     53 
     54 struct dns__nta {
     55 	unsigned int magic;
     56 	isc_mem_t *mctx;
     57 	isc_loop_t *loop;
     58 	isc_refcount_t references;
     59 	dns_ntatable_t *ntatable;
     60 	bool forced;
     61 	isc_timer_t *timer;
     62 	dns_fetch_t *fetch;
     63 	dns_rdataset_t rdataset;
     64 	dns_rdataset_t sigrdataset;
     65 	dns_name_t name;
     66 	isc_stdtime_t expiry;
     67 	bool shuttingdown;
     68 };
     69 
     70 #define NTA_MAGIC     ISC_MAGIC('N', 'T', 'A', 'n')
     71 #define VALID_NTA(nn) ISC_MAGIC_VALID(nn, NTA_MAGIC)
     72 
     73 static void
     74 dns__nta_shutdown(dns__nta_t *nta);
     75 
     76 static void
     77 qp_attach(void *uctx, void *pval, uint32_t ival);
     78 static void
     79 qp_detach(void *uctx, void *pval, uint32_t ival);
     80 static size_t
     81 qp_makekey(dns_qpkey_t key, void *uctx, void *pval, uint32_t ival);
     82 static void
     83 qp_triename(void *uctx, char *buf, size_t size);
     84 
     85 static dns_qpmethods_t qpmethods = {
     86 	qp_attach,
     87 	qp_detach,
     88 	qp_makekey,
     89 	qp_triename,
     90 };
     91 
     92 static void
     93 dns__nta_destroy(dns__nta_t *nta) {
     94 	REQUIRE(nta->timer == NULL);
     95 
     96 	nta->magic = 0;
     97 	if (dns_rdataset_isassociated(&nta->rdataset)) {
     98 		dns_rdataset_disassociate(&nta->rdataset);
     99 	}
    100 	if (dns_rdataset_isassociated(&nta->sigrdataset)) {
    101 		dns_rdataset_disassociate(&nta->sigrdataset);
    102 	}
    103 	if (nta->fetch != NULL) {
    104 		dns_resolver_cancelfetch(nta->fetch);
    105 		dns_resolver_destroyfetch(&nta->fetch);
    106 	}
    107 	isc_loop_detach(&nta->loop);
    108 	dns_name_free(&nta->name, nta->mctx);
    109 	isc_mem_putanddetach(&nta->mctx, nta, sizeof(*nta));
    110 }
    111 
    112 #if DNS_NTA_TRACE
    113 ISC_REFCOUNT_TRACE_IMPL(dns__nta, dns__nta_destroy);
    114 #else
    115 ISC_REFCOUNT_IMPL(dns__nta, dns__nta_destroy);
    116 #endif
    117 
    118 void
    119 dns_ntatable_create(dns_view_t *view, isc_loopmgr_t *loopmgr,
    120 		    dns_ntatable_t **ntatablep) {
    121 	dns_ntatable_t *ntatable = NULL;
    122 
    123 	REQUIRE(ntatablep != NULL && *ntatablep == NULL);
    124 
    125 	ntatable = isc_mem_get(view->mctx, sizeof(*ntatable));
    126 	*ntatable = (dns_ntatable_t){
    127 		.loopmgr = loopmgr,
    128 	};
    129 
    130 	isc_mem_attach(view->mctx, &ntatable->mctx);
    131 	dns_view_weakattach(view, &ntatable->view);
    132 
    133 	isc_rwlock_init(&ntatable->rwlock);
    134 	dns_qpmulti_create(view->mctx, &qpmethods, view, &ntatable->table);
    135 
    136 	isc_refcount_init(&ntatable->references, 1);
    137 
    138 	ntatable->magic = NTATABLE_MAGIC;
    139 	*ntatablep = ntatable;
    140 }
    141 
    142 static void
    143 dns__ntatable_destroy(dns_ntatable_t *ntatable) {
    144 	ntatable->magic = 0;
    145 	isc_rwlock_destroy(&ntatable->rwlock);
    146 	dns_qpmulti_destroy(&ntatable->table);
    147 	INSIST(ntatable->view == NULL);
    148 	isc_mem_putanddetach(&ntatable->mctx, ntatable, sizeof(*ntatable));
    149 }
    150 
    151 #if DNS_NTA_TRACE
    152 ISC_REFCOUNT_TRACE_IMPL(dns_ntatable, dns__ntatable_destroy);
    153 #else
    154 ISC_REFCOUNT_IMPL(dns_ntatable, dns__ntatable_destroy);
    155 #endif
    156 
    157 static void
    158 fetch_done(void *arg) {
    159 	dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg;
    160 	dns__nta_t *nta = resp->arg;
    161 	isc_result_t eresult = resp->result;
    162 	dns_ntatable_t *ntatable = nta->ntatable;
    163 	dns_view_t *view = ntatable->view;
    164 	isc_stdtime_t now = isc_stdtime_now();
    165 
    166 	if (dns_rdataset_isassociated(&nta->rdataset)) {
    167 		dns_rdataset_disassociate(&nta->rdataset);
    168 	}
    169 	if (dns_rdataset_isassociated(&nta->sigrdataset)) {
    170 		dns_rdataset_disassociate(&nta->sigrdataset);
    171 	}
    172 	if (nta->fetch == resp->fetch) {
    173 		nta->fetch = NULL;
    174 	}
    175 	dns_resolver_destroyfetch(&resp->fetch);
    176 
    177 	if (resp->node != NULL) {
    178 		dns_db_detachnode(resp->db, &resp->node);
    179 	}
    180 	if (resp->db != NULL) {
    181 		dns_db_detach(&resp->db);
    182 	}
    183 
    184 	dns_resolver_freefresp(&resp);
    185 
    186 	switch (eresult) {
    187 	case ISC_R_SUCCESS:
    188 	case DNS_R_NCACHENXDOMAIN:
    189 	case DNS_R_NXDOMAIN:
    190 	case DNS_R_NCACHENXRRSET:
    191 	case DNS_R_NXRRSET:
    192 		RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
    193 		if (nta->expiry > now) {
    194 			nta->expiry = now;
    195 		}
    196 		RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
    197 		break;
    198 	default:
    199 		break;
    200 	}
    201 
    202 	/*
    203 	 * If we're expiring before the next recheck, we might
    204 	 * as well stop the timer now.
    205 	 */
    206 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
    207 	if (nta->timer != NULL && nta->expiry - now < view->nta_recheck) {
    208 		isc_timer_stop(nta->timer);
    209 	}
    210 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
    211 
    212 	dns__nta_detach(&nta); /* for dns_resolver_createfetch() */
    213 }
    214 
    215 static void
    216 checkbogus(void *arg) {
    217 	dns__nta_t *nta = arg;
    218 	dns_ntatable_t *ntatable = nta->ntatable;
    219 	dns_resolver_t *resolver = NULL;
    220 	isc_result_t result;
    221 
    222 	if (nta->fetch != NULL) {
    223 		dns_resolver_cancelfetch(nta->fetch);
    224 		nta->fetch = NULL;
    225 	}
    226 	if (dns_rdataset_isassociated(&nta->rdataset)) {
    227 		dns_rdataset_disassociate(&nta->rdataset);
    228 	}
    229 	if (dns_rdataset_isassociated(&nta->sigrdataset)) {
    230 		dns_rdataset_disassociate(&nta->sigrdataset);
    231 	}
    232 
    233 	if (atomic_load(&ntatable->shuttingdown)) {
    234 		isc_timer_stop(nta->timer);
    235 		return;
    236 	}
    237 
    238 	result = dns_view_getresolver(ntatable->view, &resolver);
    239 	if (result != ISC_R_SUCCESS) {
    240 		return;
    241 	}
    242 
    243 	dns__nta_ref(nta); /* for dns_resolver_createfetch */
    244 	result = dns_resolver_createfetch(
    245 		resolver, &nta->name, dns_rdatatype_nsec, NULL, NULL, NULL,
    246 		NULL, 0, DNS_FETCHOPT_NONTA, 0, NULL, NULL, NULL, nta->loop,
    247 		fetch_done, nta, NULL, &nta->rdataset, &nta->sigrdataset,
    248 		&nta->fetch);
    249 	if (result != ISC_R_SUCCESS) {
    250 		dns__nta_detach(&nta); /* for dns_resolver_createfetch() */
    251 	}
    252 	dns_resolver_detach(&resolver);
    253 }
    254 
    255 static void
    256 settimer(dns_ntatable_t *ntatable, dns__nta_t *nta, uint32_t lifetime) {
    257 	dns_view_t *view = NULL;
    258 	isc_interval_t interval;
    259 
    260 	REQUIRE(VALID_NTATABLE(ntatable));
    261 	REQUIRE(VALID_NTA(nta));
    262 
    263 	view = ntatable->view;
    264 	if (view->nta_recheck == 0 || lifetime <= view->nta_recheck) {
    265 		return;
    266 	}
    267 
    268 	isc_timer_create(nta->loop, checkbogus, nta, &nta->timer);
    269 	isc_interval_set(&interval, view->nta_recheck, 0);
    270 	isc_timer_start(nta->timer, isc_timertype_ticker, &interval);
    271 }
    272 
    273 static void
    274 nta_create(dns_ntatable_t *ntatable, const dns_name_t *name,
    275 	   dns__nta_t **target) {
    276 	dns__nta_t *nta = NULL;
    277 
    278 	REQUIRE(VALID_NTATABLE(ntatable));
    279 	REQUIRE(target != NULL && *target == NULL);
    280 
    281 	nta = isc_mem_get(ntatable->mctx, sizeof(dns__nta_t));
    282 	*nta = (dns__nta_t){
    283 		.ntatable = ntatable,
    284 		.name = DNS_NAME_INITEMPTY,
    285 		.magic = NTA_MAGIC,
    286 	};
    287 	isc_mem_attach(ntatable->mctx, &nta->mctx);
    288 	isc_loop_attach(isc_loop(), &nta->loop);
    289 
    290 	dns_rdataset_init(&nta->rdataset);
    291 	dns_rdataset_init(&nta->sigrdataset);
    292 
    293 	isc_refcount_init(&nta->references, 1);
    294 
    295 	dns_name_dupwithoffsets(name, nta->mctx, &nta->name);
    296 
    297 	*target = nta;
    298 }
    299 
    300 isc_result_t
    301 dns_ntatable_add(dns_ntatable_t *ntatable, const dns_name_t *name, bool force,
    302 		 isc_stdtime_t now, uint32_t lifetime) {
    303 	isc_result_t result = ISC_R_SUCCESS;
    304 	dns__nta_t *nta = NULL;
    305 	dns_qp_t *qp = NULL;
    306 	void *pval = NULL;
    307 
    308 	REQUIRE(VALID_NTATABLE(ntatable));
    309 
    310 	if (atomic_load(&ntatable->shuttingdown)) {
    311 		return ISC_R_SUCCESS;
    312 	}
    313 
    314 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
    315 	dns_qpmulti_write(ntatable->table, &qp);
    316 	nta_create(ntatable, name, &nta);
    317 	nta->forced = force;
    318 
    319 	result = dns_qp_insert(qp, nta, 0);
    320 	switch (result) {
    321 	case ISC_R_EXISTS:
    322 		result = dns_qp_getname(qp, &nta->name, &pval, NULL);
    323 		if (result == ISC_R_SUCCESS) {
    324 			/*
    325 			 * an NTA already existed: throw away the
    326 			 * new one and update the old one.
    327 			 */
    328 			dns__nta_detach(&nta); /* for nta_create */
    329 			nta = pval;
    330 			break;
    331 		}
    332 		/* update the NTA's timer as if it were new */
    333 		FALLTHROUGH;
    334 	case ISC_R_SUCCESS:
    335 		nta->expiry = now + lifetime;
    336 		if (!force) {
    337 			settimer(ntatable, nta, lifetime);
    338 		}
    339 		break;
    340 	default:
    341 		break;
    342 	}
    343 
    344 	dns_qp_compact(qp, DNS_QPGC_MAYBE);
    345 	dns_qpmulti_commit(ntatable->table, &qp);
    346 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
    347 
    348 	return result;
    349 }
    350 
    351 isc_result_t
    352 dns_ntatable_delete(dns_ntatable_t *ntatable, const dns_name_t *name) {
    353 	isc_result_t result;
    354 	dns_qp_t *qp = NULL;
    355 	void *pval = NULL;
    356 
    357 	REQUIRE(VALID_NTATABLE(ntatable));
    358 	REQUIRE(name != NULL);
    359 
    360 	dns_qpmulti_write(ntatable->table, &qp);
    361 	result = dns_qp_deletename(qp, name, &pval, NULL);
    362 	if (result == ISC_R_SUCCESS) {
    363 		dns__nta_t *n = pval;
    364 		dns__nta_shutdown(n);
    365 		dns__nta_detach(&n);
    366 	}
    367 	dns_qp_compact(qp, DNS_QPGC_MAYBE);
    368 	dns_qpmulti_commit(ntatable->table, &qp);
    369 
    370 	return result;
    371 }
    372 
    373 static void
    374 delete_expired(void *arg) {
    375 	dns__nta_t *nta = arg;
    376 	dns_ntatable_t *ntatable = nta->ntatable;
    377 	isc_result_t result;
    378 	dns_qp_t *qp = NULL;
    379 	void *pval = NULL;
    380 
    381 	REQUIRE(VALID_NTATABLE(ntatable));
    382 
    383 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
    384 	dns_qpmulti_write(ntatable->table, &qp);
    385 	result = dns_qp_getname(qp, &nta->name, &pval, NULL);
    386 	if (result == ISC_R_SUCCESS &&
    387 	    ((dns__nta_t *)pval)->expiry == nta->expiry && !nta->shuttingdown)
    388 	{
    389 		char nb[DNS_NAME_FORMATSIZE];
    390 		dns_name_format(&nta->name, nb, sizeof(nb));
    391 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
    392 			      DNS_LOGMODULE_NTA, ISC_LOG_INFO,
    393 			      "deleting expired NTA at %s", nb);
    394 		dns_qp_deletename(qp, &nta->name, NULL, NULL);
    395 		dns__nta_shutdown(nta);
    396 		dns__nta_unref(nta);
    397 	}
    398 	dns_qp_compact(qp, DNS_QPGC_MAYBE);
    399 	dns_qpmulti_commit(ntatable->table, &qp);
    400 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
    401 	dns__nta_detach(&nta);
    402 	dns_ntatable_detach(&ntatable);
    403 }
    404 
    405 bool
    406 dns_ntatable_covered(dns_ntatable_t *ntatable, isc_stdtime_t now,
    407 		     const dns_name_t *name, const dns_name_t *anchor) {
    408 	isc_result_t result;
    409 	dns__nta_t *nta = NULL;
    410 	bool answer = false;
    411 	dns_qpread_t qpr;
    412 	void *pval = NULL;
    413 
    414 	REQUIRE(VALID_NTATABLE(ntatable));
    415 	REQUIRE(dns_name_isabsolute(name));
    416 
    417 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
    418 	dns_qpmulti_query(ntatable->table, &qpr);
    419 	result = dns_qp_lookup(&qpr, name, NULL, NULL, NULL, &pval, NULL);
    420 	nta = pval;
    421 
    422 	switch (result) {
    423 	case ISC_R_SUCCESS:
    424 		/* Exact match */
    425 		break;
    426 	case DNS_R_PARTIALMATCH:
    427 		/*
    428 		 * Found a NTA that's an ancestor of 'name'; we
    429 		 * now have to make sure 'anchor' isn't below it.
    430 		 */
    431 		if (!dns_name_issubdomain(&nta->name, anchor)) {
    432 			goto done;
    433 		}
    434 		/* Ancestor match */
    435 		break;
    436 	default:
    437 		/* Found nothing */
    438 		goto done;
    439 	}
    440 
    441 	if (nta->expiry <= now) {
    442 		/* NTA is expired */
    443 		dns__nta_ref(nta);
    444 		dns_ntatable_ref(nta->ntatable);
    445 		isc_async_current(delete_expired, nta);
    446 		goto done;
    447 	}
    448 
    449 	answer = true;
    450 done:
    451 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
    452 	dns_qpread_destroy(ntatable->table, &qpr);
    453 	return answer;
    454 }
    455 
    456 static isc_result_t
    457 putstr(isc_buffer_t **b, const char *str) {
    458 	isc_result_t result;
    459 
    460 	result = isc_buffer_reserve(*b, strlen(str));
    461 	if (result != ISC_R_SUCCESS) {
    462 		return result;
    463 	}
    464 
    465 	isc_buffer_putstr(*b, str);
    466 	return ISC_R_SUCCESS;
    467 }
    468 
    469 isc_result_t
    470 dns_ntatable_totext(dns_ntatable_t *ntatable, const char *view,
    471 		    isc_buffer_t **buf) {
    472 	isc_result_t result = ISC_R_SUCCESS;
    473 	isc_stdtime_t now = isc_stdtime_now();
    474 	dns_qpread_t qpr;
    475 	dns_qpiter_t iter;
    476 	bool first = true;
    477 	void *pval = NULL;
    478 
    479 	REQUIRE(VALID_NTATABLE(ntatable));
    480 
    481 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
    482 	dns_qpmulti_query(ntatable->table, &qpr);
    483 	dns_qpiter_init(&qpr, &iter);
    484 
    485 	while (dns_qpiter_next(&iter, NULL, &pval, NULL) == ISC_R_SUCCESS) {
    486 		dns__nta_t *n = pval;
    487 		char nbuf[DNS_NAME_FORMATSIZE];
    488 		char tbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
    489 		char obuf[DNS_NAME_FORMATSIZE + ISC_FORMATHTTPTIMESTAMP_SIZE +
    490 			  sizeof("expired:  \n")];
    491 		isc_time_t t;
    492 
    493 		dns_name_format(&n->name, nbuf, sizeof(nbuf));
    494 
    495 		if (n->expiry != 0xffffffffU) {
    496 			/* Normal NTA entries */
    497 			isc_time_set(&t, n->expiry, 0);
    498 			isc_time_formattimestamp(&t, tbuf, sizeof(tbuf));
    499 
    500 			snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s %s",
    501 				 first ? "" : "\n", nbuf,
    502 				 view != NULL ? "/" : "",
    503 				 view != NULL ? view : "",
    504 				 n->expiry <= now ? "expired" : "expiry", tbuf);
    505 		} else {
    506 			/* "validate-except" entries */
    507 			snprintf(obuf, sizeof(obuf), "%s%s%s%s: %s",
    508 				 first ? "" : "\n", nbuf,
    509 				 view != NULL ? "/" : "",
    510 				 view != NULL ? view : "", "permanent");
    511 		}
    512 
    513 		first = false;
    514 		result = putstr(buf, obuf);
    515 		if (result != ISC_R_SUCCESS) {
    516 			goto cleanup;
    517 		}
    518 	}
    519 
    520 cleanup:
    521 	dns_qpread_destroy(ntatable->table, &qpr);
    522 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
    523 	return result;
    524 }
    525 
    526 isc_result_t
    527 dns_ntatable_save(dns_ntatable_t *ntatable, FILE *fp) {
    528 	isc_result_t result = ISC_R_SUCCESS;
    529 	isc_stdtime_t now = isc_stdtime_now();
    530 	dns_qpread_t qpr;
    531 	dns_qpiter_t iter;
    532 	bool written = false;
    533 	void *pval = NULL;
    534 
    535 	REQUIRE(VALID_NTATABLE(ntatable));
    536 
    537 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
    538 	dns_qpmulti_query(ntatable->table, &qpr);
    539 	dns_qpiter_init(&qpr, &iter);
    540 
    541 	while (dns_qpiter_next(&iter, NULL, &pval, NULL) == ISC_R_SUCCESS) {
    542 		dns__nta_t *n = pval;
    543 		isc_buffer_t b;
    544 		char nbuf[DNS_NAME_FORMATSIZE + 1], tbuf[80];
    545 
    546 		/*
    547 		 * Skip this node if the expiry is already in the
    548 		 * past, or if this is a "validate-except" entry.
    549 		 */
    550 		if (n->expiry <= now || n->expiry == 0xffffffffU) {
    551 			continue;
    552 		}
    553 
    554 		isc_buffer_init(&b, nbuf, sizeof(nbuf));
    555 		result = dns_name_totext(&n->name, 0, &b);
    556 		if (result != ISC_R_SUCCESS) {
    557 			continue;
    558 		}
    559 
    560 		/* Zero terminate */
    561 		isc_buffer_putuint8(&b, 0);
    562 
    563 		isc_buffer_init(&b, tbuf, sizeof(tbuf));
    564 		dns_time32_totext(n->expiry, &b);
    565 
    566 		/* Zero terminate */
    567 		isc_buffer_putuint8(&b, 0);
    568 
    569 		fprintf(fp, "%s %s %s\n", nbuf,
    570 			n->forced ? "forced" : "regular", tbuf);
    571 		written = true;
    572 	}
    573 
    574 	dns_qpread_destroy(ntatable->table, &qpr);
    575 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_read);
    576 
    577 	if (result == ISC_R_SUCCESS && !written) {
    578 		result = ISC_R_NOTFOUND;
    579 	}
    580 
    581 	return result;
    582 }
    583 
    584 static void
    585 dns__nta_shutdown_cb(void *arg) {
    586 	dns__nta_t *nta = arg;
    587 
    588 	REQUIRE(VALID_NTA(nta));
    589 
    590 	if (isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) {
    591 		char nb[DNS_NAME_FORMATSIZE];
    592 		dns_name_format(&nta->name, nb, sizeof(nb));
    593 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC,
    594 			      DNS_LOGMODULE_NTA, ISC_LOG_DEBUG(3),
    595 			      "shutting down NTA %p at %s", nta, nb);
    596 	}
    597 	if (nta->timer) {
    598 		isc_timer_stop(nta->timer);
    599 		isc_timer_destroy(&nta->timer);
    600 	}
    601 
    602 	dns__nta_detach(&nta);
    603 }
    604 
    605 static void
    606 dns__nta_shutdown(dns__nta_t *nta) {
    607 	REQUIRE(VALID_NTA(nta));
    608 
    609 	dns__nta_ref(nta);
    610 	isc_async_run(nta->loop, dns__nta_shutdown_cb, nta);
    611 	nta->shuttingdown = true;
    612 }
    613 
    614 void
    615 dns_ntatable_shutdown(dns_ntatable_t *ntatable) {
    616 	dns_qpread_t qpr;
    617 	dns_qpiter_t iter;
    618 	void *pval = NULL;
    619 
    620 	REQUIRE(VALID_NTATABLE(ntatable));
    621 
    622 	RWLOCK(&ntatable->rwlock, isc_rwlocktype_write);
    623 	dns_qpmulti_query(ntatable->table, &qpr);
    624 	ntatable->shuttingdown = true;
    625 
    626 	dns_qpiter_init(&qpr, &iter);
    627 	while (dns_qpiter_next(&iter, NULL, &pval, NULL) == ISC_R_SUCCESS) {
    628 		dns__nta_t *n = pval;
    629 		dns__nta_shutdown(n);
    630 		dns__nta_detach(&n);
    631 	}
    632 
    633 	dns_qpread_destroy(ntatable->table, &qpr);
    634 	dns_view_weakdetach(&ntatable->view);
    635 	RWUNLOCK(&ntatable->rwlock, isc_rwlocktype_write);
    636 }
    637 
    638 static void
    639 qp_attach(void *uctx ISC_ATTR_UNUSED, void *pval,
    640 	  uint32_t ival ISC_ATTR_UNUSED) {
    641 	dns__nta_t *nta = pval;
    642 	dns__nta_ref(nta);
    643 }
    644 
    645 static void
    646 qp_detach(void *uctx ISC_ATTR_UNUSED, void *pval,
    647 	  uint32_t ival ISC_ATTR_UNUSED) {
    648 	dns__nta_t *nta = pval;
    649 	dns__nta_detach(&nta);
    650 }
    651 
    652 static size_t
    653 qp_makekey(dns_qpkey_t key, void *uctx ISC_ATTR_UNUSED, void *pval,
    654 	   uint32_t ival ISC_ATTR_UNUSED) {
    655 	dns__nta_t *nta = pval;
    656 	return dns_qpkey_fromname(key, &nta->name);
    657 }
    658 
    659 static void
    660 qp_triename(void *uctx, char *buf, size_t size) {
    661 	dns_view_t *view = uctx;
    662 	snprintf(buf, size, "view %s forwarder table", view->name);
    663 }
    664