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