dbversion_test.c revision 1.4 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