1 /* $NetBSD: dbversion_test.c,v 1.4 2025/05/21 14:48:06 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 #include <inttypes.h> 17 #include <sched.h> /* IWYU pragma: keep */ 18 #include <setjmp.h> 19 #include <stdarg.h> 20 #include <stddef.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <unistd.h> 24 25 #define UNIT_TESTING 26 #include <cmocka.h> 27 28 #include <isc/file.h> 29 #include <isc/result.h> 30 #include <isc/serial.h> 31 #include <isc/stdtime.h> 32 #include <isc/string.h> 33 #include <isc/util.h> 34 35 #include <dns/db.h> 36 #include <dns/nsec3.h> 37 #include <dns/rdatalist.h> 38 #include <dns/rdataset.h> 39 #include <dns/rdatasetiter.h> 40 41 #include <tests/dns.h> 42 43 static char tempname[11] = "dtXXXXXXXX"; 44 static dns_db_t *db1 = NULL, *db2 = NULL; 45 static dns_dbversion_t *v1 = NULL, *v2 = NULL; 46 47 /* 48 * The code below enables us to trap assertion failures for testing 49 * purposes. local_callback() is set as the callback function for 50 * isc_assertion_failed(). It calls mock_assert() so that CMOCKA 51 * will be able to see it, then returns to the calling function via 52 * longjmp() so that the abort() call in isc_assertion_failed() will 53 * never be reached. Use check_assertion() to check for assertions 54 * instead of expect_assert_failure(). 55 */ 56 jmp_buf assertion; 57 58 #define check_assertion(function_call) \ 59 do { \ 60 const int r = setjmp(assertion); \ 61 if (r == 0) { \ 62 expect_assert_failure(function_call); \ 63 } \ 64 } while (false); 65 66 static void 67 local_callback(const char *file, int line, isc_assertiontype_t type, 68 const char *cond) { 69 UNUSED(type); 70 71 mock_assert(1, cond, file, line); 72 longjmp(assertion, 1); 73 } 74 75 static int 76 setup_test(void **state) { 77 isc_result_t res; 78 79 UNUSED(state); 80 81 isc_assertion_setcallback(local_callback); 82 83 res = dns_db_create(mctx, ZONEDB_DEFAULT, dns_rootname, dns_dbtype_zone, 84 dns_rdataclass_in, 0, NULL, &db1); 85 assert_int_equal(res, ISC_R_SUCCESS); 86 dns_db_newversion(db1, &v1); 87 assert_non_null(v1); 88 89 res = dns_db_create(mctx, ZONEDB_DEFAULT, dns_rootname, dns_dbtype_zone, 90 dns_rdataclass_in, 0, NULL, &db2); 91 assert_int_equal(res, ISC_R_SUCCESS); 92 dns_db_newversion(db2, &v2); 93 assert_non_null(v1); 94 95 return 0; 96 } 97 98 static int 99 teardown_test(void **state) { 100 UNUSED(state); 101 102 if (strcmp(tempname, "dtXXXXXXXX") != 0) { 103 unlink(tempname); 104 } 105 106 if (v1 != NULL) { 107 dns_db_closeversion(db1, &v1, false); 108 assert_null(v1); 109 } 110 if (db1 != NULL) { 111 dns_db_detach(&db1); 112 assert_null(db1); 113 } 114 115 if (v2 != NULL) { 116 dns_db_closeversion(db2, &v2, false); 117 assert_null(v2); 118 } 119 if (db2 != NULL) { 120 dns_db_detach(&db2); 121 assert_null(db2); 122 } 123 124 return 0; 125 } 126 127 /* 128 * Check dns_db_attachversion() passes with matching db and version, and 129 * asserts with mis-matching db and version. 130 */ 131 ISC_RUN_TEST_IMPL(attachversion) { 132 dns_dbversion_t *v = NULL; 133 134 UNUSED(state); 135 136 dns_db_attachversion(db1, v1, &v); 137 assert_ptr_equal(v, v1); 138 dns_db_closeversion(db1, &v, false); 139 assert_null(v); 140 141 check_assertion(dns_db_attachversion(db1, v2, &v)); 142 } 143 144 /* 145 * Check dns_db_closeversion() passes with matching db and version, and 146 * asserts with mis-matching db and version. 147 */ 148 ISC_RUN_TEST_IMPL(closeversion) { 149 UNUSED(state); 150 151 assert_non_null(v1); 152 dns_db_closeversion(db1, &v1, false); 153 assert_null(v1); 154 155 check_assertion(dns_db_closeversion(db1, &v2, false)); 156 } 157 158 /* 159 * Check dns_db_find() passes with matching db and version, and 160 * asserts with mis-matching db and version. 161 */ 162 ISC_RUN_TEST_IMPL(find) { 163 isc_result_t res; 164 dns_rdataset_t rdataset; 165 dns_fixedname_t fixed; 166 dns_name_t *name = NULL; 167 168 UNUSED(state); 169 170 name = dns_fixedname_initname(&fixed); 171 172 dns_rdataset_init(&rdataset); 173 res = dns_db_find(db1, dns_rootname, v1, dns_rdatatype_soa, 0, 0, NULL, 174 name, &rdataset, NULL); 175 /* 176 * Note: in the QPzone database, the root node always exists, 177 * even if it's empty, so we would get DNS_R_NXRRSET from this 178 * query. In other databases (including the old RBTDB) the root 179 * node can be nonexistent, and the query would then return 180 * DNS_R_NXDOMAIN. Allow for both possibilities. 181 */ 182 assert_true(res == DNS_R_NXRRSET || res == DNS_R_NXDOMAIN); 183 184 if (dns_rdataset_isassociated(&rdataset)) { 185 dns_rdataset_disassociate(&rdataset); 186 } 187 188 dns_rdataset_init(&rdataset); 189 check_assertion((void)dns_db_find(db1, dns_rootname, v2, 190 dns_rdatatype_soa, 0, 0, NULL, name, 191 &rdataset, NULL)); 192 } 193 194 /* 195 * Check dns_db_allrdatasets() passes with matching db and version, and 196 * asserts with mis-matching db and version. 197 */ 198 ISC_RUN_TEST_IMPL(allrdatasets) { 199 isc_result_t res; 200 dns_dbnode_t *node = NULL; 201 dns_rdatasetiter_t *iterator = NULL; 202 203 UNUSED(state); 204 205 res = dns_db_findnode(db1, dns_rootname, false, &node); 206 assert_int_equal(res, ISC_R_SUCCESS); 207 208 res = dns_db_allrdatasets(db1, node, v1, 0, 0, &iterator); 209 assert_int_equal(res, ISC_R_SUCCESS); 210 211 check_assertion(dns_db_allrdatasets(db1, node, v2, 0, 0, &iterator)); 212 213 dns_rdatasetiter_destroy(&iterator); 214 assert_null(iterator); 215 216 dns_db_detachnode(db1, &node); 217 assert_null(node); 218 } 219 220 /* 221 * Check dns_db_findrdataset() passes with matching db and version, and 222 * asserts with mis-matching db and version. 223 */ 224 ISC_RUN_TEST_IMPL(findrdataset) { 225 isc_result_t res; 226 dns_rdataset_t rdataset; 227 dns_dbnode_t *node = NULL; 228 229 UNUSED(state); 230 231 res = dns_db_findnode(db1, dns_rootname, false, &node); 232 assert_int_equal(res, ISC_R_SUCCESS); 233 234 dns_rdataset_init(&rdataset); 235 res = dns_db_findrdataset(db1, node, v1, dns_rdatatype_soa, 0, 0, 236 &rdataset, NULL); 237 assert_int_equal(res, ISC_R_NOTFOUND); 238 239 if (dns_rdataset_isassociated(&rdataset)) { 240 dns_rdataset_disassociate(&rdataset); 241 } 242 243 dns_rdataset_init(&rdataset); 244 check_assertion(dns_db_findrdataset(db1, node, v2, dns_rdatatype_soa, 0, 245 0, &rdataset, NULL)); 246 247 dns_db_detachnode(db1, &node); 248 assert_null(node); 249 } 250 251 /* 252 * Check dns_db_deleterdataset() passes with matching db and version, and 253 * asserts with mis-matching db and version. 254 */ 255 ISC_RUN_TEST_IMPL(deleterdataset) { 256 isc_result_t res; 257 dns_dbnode_t *node = NULL; 258 259 UNUSED(state); 260 261 res = dns_db_findnode(db1, dns_rootname, false, &node); 262 assert_int_equal(res, ISC_R_SUCCESS); 263 264 res = dns_db_deleterdataset(db1, node, v1, dns_rdatatype_soa, 0); 265 assert_int_equal(res, DNS_R_UNCHANGED); 266 267 check_assertion( 268 dns_db_deleterdataset(db1, node, v2, dns_rdatatype_soa, 0)); 269 dns_db_detachnode(db1, &node); 270 assert_null(node); 271 } 272 273 /* 274 * Check dns_db_subtractrdataset() passes with matching db and version, and 275 * asserts with mis-matching db and version. 276 */ 277 ISC_RUN_TEST_IMPL(subtract) { 278 isc_result_t res; 279 dns_rdataset_t rdataset; 280 dns_rdatalist_t rdatalist; 281 dns_dbnode_t *node = NULL; 282 283 UNUSED(state); 284 285 dns_rdataset_init(&rdataset); 286 dns_rdatalist_init(&rdatalist); 287 288 rdatalist.rdclass = dns_rdataclass_in; 289 290 dns_rdatalist_tordataset(&rdatalist, &rdataset); 291 292 res = dns_db_findnode(db1, dns_rootname, false, &node); 293 assert_int_equal(res, ISC_R_SUCCESS); 294 295 res = dns_db_subtractrdataset(db1, node, v1, &rdataset, 0, NULL); 296 assert_int_equal(res, DNS_R_UNCHANGED); 297 298 if (dns_rdataset_isassociated(&rdataset)) { 299 dns_rdataset_disassociate(&rdataset); 300 } 301 302 dns_rdataset_init(&rdataset); 303 dns_rdatalist_tordataset(&rdatalist, &rdataset); 304 305 check_assertion( 306 dns_db_subtractrdataset(db1, node, v2, &rdataset, 0, NULL)); 307 308 dns_db_detachnode(db1, &node); 309 assert_null(node); 310 } 311 312 /* 313 * Check dns_db_addrdataset() passes with matching db and version, and 314 * asserts with mis-matching db and version. 315 */ 316 ISC_RUN_TEST_IMPL(addrdataset) { 317 isc_result_t res; 318 dns_rdataset_t rdataset; 319 dns_dbnode_t *node = NULL; 320 dns_rdatalist_t rdatalist; 321 322 UNUSED(state); 323 324 dns_rdataset_init(&rdataset); 325 dns_rdatalist_init(&rdatalist); 326 327 rdatalist.rdclass = dns_rdataclass_in; 328 329 dns_rdatalist_tordataset(&rdatalist, &rdataset); 330 331 res = dns_db_findnode(db1, dns_rootname, false, &node); 332 assert_int_equal(res, ISC_R_SUCCESS); 333 334 res = dns_db_addrdataset(db1, node, v1, 0, &rdataset, 0, NULL); 335 assert_int_equal(res, ISC_R_SUCCESS); 336 337 check_assertion( 338 dns_db_addrdataset(db1, node, v2, 0, &rdataset, 0, NULL)); 339 340 dns_db_detachnode(db1, &node); 341 assert_null(node); 342 } 343 344 /* 345 * Check dns_db_getnsec3parameters() passes with matching db and version, 346 * and asserts with mis-matching db and version. 347 */ 348 ISC_RUN_TEST_IMPL(getnsec3parameters) { 349 isc_result_t res; 350 dns_hash_t hash; 351 uint8_t flags; 352 uint16_t iterations; 353 unsigned char salt[DNS_NSEC3_SALTSIZE]; 354 size_t salt_length = sizeof(salt); 355 356 UNUSED(state); 357 358 res = dns_db_getnsec3parameters(db1, v1, &hash, &flags, &iterations, 359 salt, &salt_length); 360 assert_int_equal(res, ISC_R_NOTFOUND); 361 362 check_assertion(dns_db_getnsec3parameters( 363 db1, v2, &hash, &flags, &iterations, salt, &salt_length)); 364 } 365 366 /* 367 * Check that the correct node contents are found after a rollback. 368 */ 369 ISC_RUN_TEST_IMPL(rollback) { 370 isc_result_t res; 371 dns_rdata_t rdata1 = DNS_RDATA_INIT, rdata2 = DNS_RDATA_INIT; 372 dns_rdataset_t input1 = DNS_RDATASET_INIT; 373 dns_rdataset_t input2 = DNS_RDATASET_INIT; 374 dns_rdataset_t rdataset1 = DNS_RDATASET_INIT; 375 dns_rdataset_t rdataset2 = DNS_RDATASET_INIT; 376 dns_rdatalist_t rdatalist1, rdatalist2; 377 dns_rdata_t out1 = DNS_RDATA_INIT, out2 = DNS_RDATA_INIT; 378 dns_dbnode_t *node = NULL; 379 char *txt1 = (char *)"\006text 1"; 380 char *txt2 = (char *)"\006text 2"; 381 size_t len1 = strlen(txt1), len2 = strlen(txt2); 382 char buf[1024]; 383 isc_buffer_t b; 384 385 UNUSED(state); 386 387 isc_buffer_init(&b, buf, sizeof(buf)); 388 389 /* Set up two rdatasets to insert */ 390 rdata1.rdclass = dns_rdataclass_in; 391 rdata1.type = dns_rdatatype_txt; 392 rdata2 = rdata1; 393 394 rdata1.length = len1; 395 rdata1.data = (unsigned char *)txt1; 396 rdata2.length = len2; 397 rdata2.data = (unsigned char *)txt2; 398 399 dns_rdatalist_init(&rdatalist1); 400 rdatalist1.rdclass = dns_rdataclass_in; 401 rdatalist1.type = dns_rdatatype_txt; 402 rdatalist1.ttl = 3600; 403 rdatalist2 = rdatalist1; 404 405 ISC_LIST_APPEND(rdatalist1.rdata, &rdata1, link); 406 ISC_LIST_APPEND(rdatalist2.rdata, &rdata2, link); 407 408 dns_rdatalist_tordataset(&rdatalist1, &input1); 409 dns_rdatalist_tordataset(&rdatalist2, &input2); 410 411 /* db1: Insert the first version ("text 1"), and commit */ 412 res = dns_db_findnode(db1, dns_rootname, true, &node); 413 assert_int_equal(res, ISC_R_SUCCESS); 414 res = dns_db_addrdataset(db1, node, v1, 0, &input1, 0, NULL); 415 assert_int_equal(res, ISC_R_SUCCESS); 416 dns_db_closeversion(db1, &v1, true); /* commit */ 417 assert_null(v1); 418 dns_db_detachnode(db1, &node); 419 assert_null(node); 420 421 /* db2: Insert the first version ("text 1"), and commit */ 422 res = dns_db_findnode(db2, dns_rootname, true, &node); 423 assert_int_equal(res, ISC_R_SUCCESS); 424 res = dns_db_addrdataset(db2, node, v2, 0, &input1, 0, NULL); 425 assert_int_equal(res, ISC_R_SUCCESS); 426 dns_db_closeversion(db2, &v2, true); /* commit */ 427 assert_null(v2); 428 dns_db_detachnode(db2, &node); 429 assert_null(node); 430 431 /* Reopen the versions */ 432 dns_db_newversion(db1, &v1); 433 assert_non_null(v1); 434 dns_db_newversion(db2, &v2); 435 assert_non_null(v2); 436 437 /* db1: Insert the second version ("text 2"), and roll back */ 438 res = dns_db_findnode(db1, dns_rootname, true, &node); 439 assert_int_equal(res, ISC_R_SUCCESS); 440 res = dns_db_addrdataset(db1, node, v1, 0, &input2, 0, NULL); 441 assert_int_equal(res, ISC_R_SUCCESS); 442 dns_db_closeversion(db1, &v1, false); /* rollback */ 443 assert_null(v1); 444 dns_db_detachnode(db1, &node); 445 assert_null(node); 446 447 /* db2: Insert the second version ("text 2"), and commit */ 448 res = dns_db_findnode(db2, dns_rootname, true, &node); 449 assert_int_equal(res, ISC_R_SUCCESS); 450 res = dns_db_addrdataset(db2, node, v2, 0, &input2, 0, NULL); 451 assert_int_equal(res, ISC_R_SUCCESS); 452 dns_db_closeversion(db2, &v2, true); /* commit */ 453 assert_null(v2); 454 dns_db_detachnode(db2, &node); 455 assert_null(node); 456 457 /* db1: Look it up and check that the first version is found */ 458 dns_db_currentversion(db1, &v1); 459 assert_non_null(v1); 460 res = dns_db_findnode(db1, dns_rootname, true, &node); 461 assert_int_equal(res, ISC_R_SUCCESS); 462 res = dns_db_findrdataset(db1, node, v1, dns_rdatatype_txt, 0, 0, 463 &rdataset1, NULL); 464 assert_int_equal(res, ISC_R_SUCCESS); 465 466 /* db1: Convert result to text */ 467 res = dns_rdataset_first(&rdataset1); 468 assert_int_equal(res, ISC_R_SUCCESS); 469 dns_rdataset_current(&rdataset1, &out1); 470 471 res = dns_rdata_totext(&out1, NULL, &b); 472 assert_int_equal(res, ISC_R_SUCCESS); 473 isc_buffer_putuint8(&b, 0); 474 475 /* db1: We should have "text 1" */ 476 assert_string_equal(buf, "\"text 1\""); 477 478 dns_rdataset_disassociate(&rdataset1); 479 480 dns_db_closeversion(db1, &v1, true); 481 assert_null(v1); 482 dns_db_detachnode(db1, &node); 483 assert_null(node); 484 485 /* db2: Look it up and check that the second version is found */ 486 dns_db_currentversion(db2, &v2); 487 assert_non_null(v2); 488 res = dns_db_findnode(db2, dns_rootname, true, &node); 489 assert_int_equal(res, ISC_R_SUCCESS); 490 res = dns_db_findrdataset(db2, node, v2, dns_rdatatype_txt, 0, 0, 491 &rdataset2, NULL); 492 assert_int_equal(res, ISC_R_SUCCESS); 493 494 /* db2: Convert result to text */ 495 res = dns_rdataset_first(&rdataset2); 496 assert_int_equal(res, ISC_R_SUCCESS); 497 dns_rdataset_current(&rdataset2, &out2); 498 isc_buffer_init(&b, buf, sizeof(buf)); 499 res = dns_rdata_totext(&out2, NULL, &b); 500 assert_int_equal(res, ISC_R_SUCCESS); 501 isc_buffer_putuint8(&b, 0); 502 503 /* db2: We should have "text 2" */ 504 assert_string_equal(buf, "\"text 2\""); 505 506 dns_rdataset_disassociate(&rdataset2); 507 508 dns_db_closeversion(db2, &v2, true); 509 assert_null(v2); 510 dns_db_detachnode(db2, &node); 511 assert_null(node); 512 } 513 514 ISC_TEST_LIST_START 515 ISC_TEST_ENTRY_CUSTOM(find, setup_test, teardown_test) 516 ISC_TEST_ENTRY_CUSTOM(allrdatasets, setup_test, teardown_test) 517 ISC_TEST_ENTRY_CUSTOM(findrdataset, setup_test, teardown_test) 518 ISC_TEST_ENTRY_CUSTOM(deleterdataset, setup_test, teardown_test) 519 ISC_TEST_ENTRY_CUSTOM(subtract, setup_test, teardown_test) 520 ISC_TEST_ENTRY_CUSTOM(addrdataset, setup_test, teardown_test) 521 ISC_TEST_ENTRY_CUSTOM(getnsec3parameters, setup_test, teardown_test) 522 ISC_TEST_ENTRY_CUSTOM(attachversion, setup_test, teardown_test) 523 ISC_TEST_ENTRY_CUSTOM(closeversion, setup_test, teardown_test) 524 ISC_TEST_ENTRY_CUSTOM(rollback, setup_test, teardown_test) 525 ISC_TEST_LIST_END 526 527 ISC_TEST_MAIN 528