1 /* $NetBSD: zt.c,v 1.11 2025/01/26 16:25:26 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/atomic.h> 22 #include <isc/file.h> 23 #include <isc/magic.h> 24 #include <isc/mem.h> 25 #include <isc/result.h> 26 #include <isc/string.h> 27 #include <isc/tid.h> 28 #include <isc/util.h> 29 30 #include <dns/log.h> 31 #include <dns/name.h> 32 #include <dns/qp.h> 33 #include <dns/rdataclass.h> 34 #include <dns/view.h> 35 #include <dns/zone.h> 36 #include <dns/zt.h> 37 38 #define ZTMAGIC ISC_MAGIC('Z', 'T', 'b', 'l') 39 #define VALID_ZT(zt) ISC_MAGIC_VALID(zt, ZTMAGIC) 40 41 struct dns_zt { 42 unsigned int magic; 43 isc_mem_t *mctx; 44 dns_qpmulti_t *multi; 45 46 atomic_bool flush; 47 isc_refcount_t references; 48 isc_refcount_t loads_pending; 49 }; 50 51 struct zt_load_params { 52 dns_zt_t *zt; 53 dns_zt_callback_t *loaddone; 54 void *loaddone_arg; 55 bool newonly; 56 }; 57 58 struct zt_freeze_params { 59 dns_view_t *view; 60 bool freeze; 61 }; 62 63 static void 64 ztqpattach(void *uctx ISC_ATTR_UNUSED, void *pval, 65 uint32_t ival ISC_ATTR_UNUSED) { 66 dns_zone_t *zone = pval; 67 dns_zone_ref(zone); 68 } 69 70 static void 71 ztqpdetach(void *uctx ISC_ATTR_UNUSED, void *pval, 72 uint32_t ival ISC_ATTR_UNUSED) { 73 dns_zone_t *zone = pval; 74 dns_zone_detach(&zone); 75 } 76 77 static size_t 78 ztqpmakekey(dns_qpkey_t key, void *uctx ISC_ATTR_UNUSED, void *pval, 79 uint32_t ival ISC_ATTR_UNUSED) { 80 dns_zone_t *zone = pval; 81 dns_name_t *name = dns_zone_getorigin(zone); 82 return dns_qpkey_fromname(key, name); 83 } 84 85 static void 86 ztqptriename(void *uctx, char *buf, size_t size) { 87 dns_view_t *view = uctx; 88 snprintf(buf, size, "view %s zone table", view->name); 89 } 90 91 static dns_qpmethods_t ztqpmethods = { 92 ztqpattach, 93 ztqpdetach, 94 ztqpmakekey, 95 ztqptriename, 96 }; 97 98 void 99 dns_zt_create(isc_mem_t *mctx, dns_view_t *view, dns_zt_t **ztp) { 100 dns_qpmulti_t *multi = NULL; 101 dns_zt_t *zt = NULL; 102 103 REQUIRE(ztp != NULL && *ztp == NULL); 104 REQUIRE(view != NULL); 105 106 dns_qpmulti_create(mctx, &ztqpmethods, view, &multi); 107 108 zt = isc_mem_get(mctx, sizeof(*zt)); 109 *zt = (dns_zt_t){ 110 .magic = ZTMAGIC, 111 .multi = multi, 112 .references = 1, 113 }; 114 115 isc_mem_attach(mctx, &zt->mctx); 116 117 *ztp = zt; 118 } 119 120 /* 121 * XXXFANF it isn't clear whether this function will be useful. There 122 * is only one zone table per view, so it is probably enough to let 123 * the qp-trie auto-GC do its thing. However it might be problematic 124 * if a very large zone is replaced, and its database memory is 125 * retained for a long time. 126 */ 127 void 128 dns_zt_compact(dns_zt_t *zt) { 129 dns_qp_t *qp = NULL; 130 131 REQUIRE(VALID_ZT(zt)); 132 133 dns_qpmulti_write(zt->multi, &qp); 134 dns_qp_compact(qp, DNS_QPGC_ALL); 135 dns_qpmulti_commit(zt->multi, &qp); 136 } 137 138 isc_result_t 139 dns_zt_mount(dns_zt_t *zt, dns_zone_t *zone) { 140 isc_result_t result; 141 dns_qp_t *qp = NULL; 142 143 REQUIRE(VALID_ZT(zt)); 144 145 dns_qpmulti_write(zt->multi, &qp); 146 result = dns_qp_insert(qp, zone, 0); 147 dns_qp_compact(qp, DNS_QPGC_MAYBE); 148 dns_qpmulti_commit(zt->multi, &qp); 149 150 return result; 151 } 152 153 isc_result_t 154 dns_zt_unmount(dns_zt_t *zt, dns_zone_t *zone) { 155 isc_result_t result; 156 dns_qp_t *qp = NULL; 157 158 REQUIRE(VALID_ZT(zt)); 159 160 dns_qpmulti_write(zt->multi, &qp); 161 result = dns_qp_deletename(qp, dns_zone_getorigin(zone), NULL, NULL); 162 dns_qp_compact(qp, DNS_QPGC_MAYBE); 163 dns_qpmulti_commit(zt->multi, &qp); 164 165 return result; 166 } 167 168 isc_result_t 169 dns_zt_find(dns_zt_t *zt, const dns_name_t *name, dns_ztfind_t options, 170 dns_zone_t **zonep) { 171 isc_result_t result; 172 dns_qpread_t qpr; 173 void *pval = NULL; 174 dns_ztfind_t exactmask = DNS_ZTFIND_NOEXACT | DNS_ZTFIND_EXACT; 175 dns_ztfind_t exactopts = options & exactmask; 176 dns_qpchain_t chain; 177 178 REQUIRE(VALID_ZT(zt)); 179 REQUIRE(exactopts != exactmask); 180 181 dns_qpmulti_query(zt->multi, &qpr); 182 183 if (exactopts == DNS_ZTFIND_EXACT) { 184 result = dns_qp_getname(&qpr, name, &pval, NULL); 185 } else { 186 result = dns_qp_lookup(&qpr, name, NULL, NULL, &chain, &pval, 187 NULL); 188 if (exactopts == DNS_ZTFIND_NOEXACT && result == ISC_R_SUCCESS) 189 { 190 /* get pval from the previous chain link */ 191 int len = dns_qpchain_length(&chain); 192 if (len >= 2) { 193 dns_qpchain_node(&chain, len - 2, NULL, &pval, 194 NULL); 195 result = DNS_R_PARTIALMATCH; 196 } else { 197 result = ISC_R_NOTFOUND; 198 } 199 } 200 } 201 dns_qpread_destroy(zt->multi, &qpr); 202 203 if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { 204 dns_zone_t *zone = pval; 205 /* 206 * If DNS_ZTFIND_MIRROR is set and the zone which was 207 * determined to be the deepest match for the supplied name is 208 * a mirror zone which is expired or not yet loaded, treat it 209 * as non-existent. This will trigger a fallback to recursion 210 * instead of returning a SERVFAIL. 211 * 212 * Note that currently only the deepest match in the zone table 213 * is checked. Consider a server configured with two mirror 214 * zones: "bar" and its child, "foo.bar". If zone data is 215 * available for "bar" but not for "foo.bar", a query with 216 * QNAME equal to or below "foo.bar" will cause ISC_R_NOTFOUND 217 * to be returned, not DNS_R_PARTIALMATCH, despite zone data 218 * being available for "bar". This is considered to be an edge 219 * case, handling which more appropriately is possible, but 220 * arguably not worth the added complexity. 221 */ 222 if ((options & DNS_ZTFIND_MIRROR) != 0 && 223 dns_zone_gettype(zone) == dns_zone_mirror && 224 !dns_zone_isloaded(zone)) 225 { 226 result = ISC_R_NOTFOUND; 227 } else { 228 dns_zone_attach(zone, zonep); 229 } 230 } 231 232 return result; 233 } 234 235 void 236 dns_zt_attach(dns_zt_t *zt, dns_zt_t **ztp) { 237 REQUIRE(VALID_ZT(zt)); 238 REQUIRE(ztp != NULL && *ztp == NULL); 239 240 isc_refcount_increment(&zt->references); 241 242 *ztp = zt; 243 } 244 245 static isc_result_t 246 flush(dns_zone_t *zone, void *uap) { 247 UNUSED(uap); 248 return dns_zone_flush(zone); 249 } 250 251 static void 252 zt_destroy(dns_zt_t *zt) { 253 isc_refcount_destroy(&zt->references); 254 isc_refcount_destroy(&zt->loads_pending); 255 256 if (atomic_load_acquire(&zt->flush)) { 257 (void)dns_zt_apply(zt, false, NULL, flush, NULL); 258 } 259 260 dns_qpmulti_destroy(&zt->multi); 261 zt->magic = 0; 262 isc_mem_putanddetach(&zt->mctx, zt, sizeof(*zt)); 263 } 264 265 void 266 dns_zt_detach(dns_zt_t **ztp) { 267 dns_zt_t *zt; 268 269 REQUIRE(ztp != NULL && VALID_ZT(*ztp)); 270 271 zt = *ztp; 272 *ztp = NULL; 273 274 if (isc_refcount_decrement(&zt->references) == 1) { 275 zt_destroy(zt); 276 } 277 } 278 279 void 280 dns_zt_flush(dns_zt_t *zt) { 281 REQUIRE(VALID_ZT(zt)); 282 atomic_store_release(&zt->flush, true); 283 } 284 285 static isc_result_t 286 load(dns_zone_t *zone, void *uap) { 287 isc_result_t result; 288 result = dns_zone_load(zone, uap != NULL); 289 if (result == DNS_R_CONTINUE || result == DNS_R_UPTODATE || 290 result == DNS_R_DYNAMIC) 291 { 292 result = ISC_R_SUCCESS; 293 } 294 return result; 295 } 296 297 isc_result_t 298 dns_zt_load(dns_zt_t *zt, bool stop, bool newonly) { 299 REQUIRE(VALID_ZT(zt)); 300 return dns_zt_apply(zt, stop, NULL, load, newonly ? &newonly : NULL); 301 } 302 303 static void 304 loaded_all(struct zt_load_params *params) { 305 if (params->loaddone != NULL) { 306 params->loaddone(params->loaddone_arg); 307 } 308 isc_mem_put(params->zt->mctx, params, sizeof(*params)); 309 } 310 311 /* 312 * Decrement the loads_pending counter; when counter reaches 313 * zero, call the loaddone callback that was initially set by 314 * dns_zt_asyncload(). 315 */ 316 static isc_result_t 317 loaded_one(void *uap) { 318 struct zt_load_params *params = uap; 319 dns_zt_t *zt = params->zt; 320 321 REQUIRE(VALID_ZT(zt)); 322 323 if (isc_refcount_decrement(&zt->loads_pending) == 1) { 324 loaded_all(params); 325 } 326 327 if (isc_refcount_decrement(&zt->references) == 1) { 328 zt_destroy(zt); 329 } 330 331 return ISC_R_SUCCESS; 332 } 333 334 /* 335 * Initiates asynchronous loading of zone 'zone'. 'callback' is a 336 * pointer to a function which will be used to inform the caller when 337 * the zone loading is complete. 338 */ 339 static isc_result_t 340 asyncload(dns_zone_t *zone, void *uap) { 341 struct zt_load_params *params = uap; 342 struct dns_zt *zt = params->zt; 343 isc_result_t result; 344 345 REQUIRE(VALID_ZT(zt)); 346 REQUIRE(zone != NULL); 347 348 isc_refcount_increment(&zt->references); 349 isc_refcount_increment(&zt->loads_pending); 350 351 result = dns_zone_asyncload(zone, params->newonly, loaded_one, params); 352 if (result != ISC_R_SUCCESS) { 353 /* 354 * Caller is holding a reference to zt->loads_pending 355 * and zt->references so these can't decrement to zero. 356 */ 357 isc_refcount_decrement1(&zt->references); 358 isc_refcount_decrement1(&zt->loads_pending); 359 } 360 return ISC_R_SUCCESS; 361 } 362 363 isc_result_t 364 dns_zt_asyncload(dns_zt_t *zt, bool newonly, dns_zt_callback_t *loaddone, 365 void *arg) { 366 isc_result_t result; 367 uint_fast32_t loads_pending; 368 struct zt_load_params *params = NULL; 369 370 REQUIRE(VALID_ZT(zt)); 371 372 /* 373 * Obtain a reference to zt->loads_pending so that asyncload can 374 * safely decrement both zt->references and zt->loads_pending 375 * without going to zero. 376 */ 377 loads_pending = isc_refcount_increment0(&zt->loads_pending); 378 INSIST(loads_pending == 0); 379 380 params = isc_mem_get(zt->mctx, sizeof(*params)); 381 *params = (struct zt_load_params){ 382 .zt = zt, 383 .newonly = newonly, 384 .loaddone = loaddone, 385 .loaddone_arg = arg, 386 }; 387 388 result = dns_zt_apply(zt, false, NULL, asyncload, params); 389 390 /* 391 * Have all the loads completed? 392 */ 393 if (isc_refcount_decrement(&zt->loads_pending) == 1) { 394 loaded_all(params); 395 } 396 397 return result; 398 } 399 400 static isc_result_t 401 freezezones(dns_zone_t *zone, void *uap) { 402 struct zt_freeze_params *params = uap; 403 bool frozen; 404 isc_result_t result = ISC_R_SUCCESS; 405 char classstr[DNS_RDATACLASS_FORMATSIZE]; 406 char zonename[DNS_NAME_FORMATSIZE]; 407 dns_zone_t *raw = NULL; 408 dns_view_t *view; 409 const char *vname; 410 const char *sep; 411 int level; 412 413 dns_zone_getraw(zone, &raw); 414 if (raw != NULL) { 415 zone = raw; 416 } 417 if (params->view != dns_zone_getview(zone)) { 418 if (raw != NULL) { 419 dns_zone_detach(&raw); 420 } 421 return ISC_R_SUCCESS; 422 } 423 if (dns_zone_gettype(zone) != dns_zone_primary) { 424 if (raw != NULL) { 425 dns_zone_detach(&raw); 426 } 427 return ISC_R_SUCCESS; 428 } 429 if (!dns_zone_isdynamic(zone, true)) { 430 if (raw != NULL) { 431 dns_zone_detach(&raw); 432 } 433 return ISC_R_SUCCESS; 434 } 435 436 frozen = dns_zone_getupdatedisabled(zone); 437 if (params->freeze) { 438 if (frozen) { 439 result = DNS_R_FROZEN; 440 } 441 if (result == ISC_R_SUCCESS) { 442 result = dns_zone_flush(zone); 443 } 444 if (result == ISC_R_SUCCESS) { 445 dns_zone_setupdatedisabled(zone, params->freeze); 446 } 447 } else { 448 if (frozen) { 449 result = dns_zone_loadandthaw(zone); 450 if (result == DNS_R_CONTINUE || 451 result == DNS_R_UPTODATE) 452 { 453 result = ISC_R_SUCCESS; 454 } 455 } 456 } 457 view = dns_zone_getview(zone); 458 if (strcmp(view->name, "_bind") == 0 || 459 strcmp(view->name, "_default") == 0) 460 { 461 vname = ""; 462 sep = ""; 463 } else { 464 vname = view->name; 465 sep = " "; 466 } 467 dns_rdataclass_format(dns_zone_getclass(zone), classstr, 468 sizeof(classstr)); 469 dns_name_format(dns_zone_getorigin(zone), zonename, sizeof(zonename)); 470 level = (result != ISC_R_SUCCESS) ? ISC_LOG_ERROR : ISC_LOG_DEBUG(1); 471 isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_ZONE, 472 level, "%s zone '%s/%s'%s%s: %s", 473 params->freeze ? "freezing" : "thawing", zonename, 474 classstr, sep, vname, isc_result_totext(result)); 475 if (raw != NULL) { 476 dns_zone_detach(&raw); 477 } 478 return result; 479 } 480 481 isc_result_t 482 dns_zt_freezezones(dns_zt_t *zt, dns_view_t *view, bool freeze) { 483 isc_result_t result, tresult; 484 struct zt_freeze_params params = { view, freeze }; 485 486 REQUIRE(VALID_ZT(zt)); 487 488 result = dns_zt_apply(zt, false, &tresult, freezezones, ¶ms); 489 if (tresult == ISC_R_NOTFOUND) { 490 tresult = ISC_R_SUCCESS; 491 } 492 return (result == ISC_R_SUCCESS) ? tresult : result; 493 } 494 495 typedef void 496 setview_cb(dns_zone_t *zone); 497 498 static isc_result_t 499 setview(dns_zone_t *zone, void *arg) { 500 setview_cb *cb = arg; 501 cb(zone); 502 return ISC_R_SUCCESS; 503 } 504 505 void 506 dns_zt_setviewcommit(dns_zt_t *zt) { 507 dns_zt_apply(zt, false, NULL, setview, dns_zone_setviewcommit); 508 } 509 510 void 511 dns_zt_setviewrevert(dns_zt_t *zt) { 512 dns_zt_apply(zt, false, NULL, setview, dns_zone_setviewrevert); 513 } 514 515 isc_result_t 516 dns_zt_apply(dns_zt_t *zt, bool stop, isc_result_t *sub, 517 isc_result_t (*action)(dns_zone_t *, void *), void *uap) { 518 isc_result_t result = ISC_R_SUCCESS; 519 isc_result_t tresult = ISC_R_SUCCESS; 520 dns_qpiter_t qpi; 521 dns_qpread_t qpr; 522 void *zone = NULL; 523 524 REQUIRE(VALID_ZT(zt)); 525 REQUIRE(action != NULL); 526 527 dns_qpmulti_query(zt->multi, &qpr); 528 dns_qpiter_init(&qpr, &qpi); 529 530 while (dns_qpiter_next(&qpi, NULL, &zone, NULL) == ISC_R_SUCCESS) { 531 result = action(zone, uap); 532 if (tresult == ISC_R_SUCCESS) { 533 tresult = result; 534 } 535 if (result != ISC_R_SUCCESS && stop) { 536 break; 537 } 538 } 539 dns_qpread_destroy(zt->multi, &qpr); 540 541 SET_IF_NOT_NULL(sub, tresult); 542 543 return result; 544 } 545