1 /* $NetBSD: query_test.c,v 1.3 2025/01/26 16:25:51 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 <stdbool.h> 21 #include <stddef.h> 22 #include <stdlib.h> 23 #include <string.h> 24 25 #define UNIT_TESTING 26 #include <cmocka.h> 27 28 #include <isc/quota.h> 29 #include <isc/util.h> 30 31 #include <dns/badcache.h> 32 #include <dns/view.h> 33 #include <dns/zone.h> 34 35 #include <ns/client.h> 36 #include <ns/hooks.h> 37 #include <ns/query.h> 38 #include <ns/server.h> 39 #include <ns/stats.h> 40 41 #include <tests/ns.h> 42 43 /* can be used for client->sendcb to avoid disruption on sending a response */ 44 static void 45 send_noop(isc_buffer_t *buffer) { 46 UNUSED(buffer); 47 } 48 49 /***** 50 ***** ns__query_sfcache() tests 51 *****/ 52 53 /*% 54 * Structure containing parameters for ns__query_sfcache_test(). 55 */ 56 typedef struct { 57 const ns_test_id_t id; /* libns test identifier */ 58 unsigned int qflags; /* query flags */ 59 bool cache_entry_present; /* whether a SERVFAIL 60 * cache entry 61 * matching the query 62 * should be 63 * present */ 64 uint32_t cache_entry_flags; /* NS_FAILCACHE_* flags to 65 * set for 66 * the SERVFAIL cache entry 67 * */ 68 bool servfail_expected; /* whether a cached 69 * SERVFAIL is 70 * expected to be returned 71 * */ 72 } ns__query_sfcache_test_params_t; 73 74 /*% 75 * Perform a single ns__query_sfcache() check using given parameters. 76 */ 77 static void 78 run_sfcache_test(const ns__query_sfcache_test_params_t *test) { 79 ns_hooktable_t *query_hooks = NULL; 80 query_ctx_t *qctx = NULL; 81 isc_result_t result; 82 const ns_hook_t hook = { 83 .action = ns_test_hook_catch_call, 84 }; 85 86 REQUIRE(test != NULL); 87 REQUIRE(test->id.description != NULL); 88 REQUIRE(test->cache_entry_present || test->cache_entry_flags == 0); 89 90 /* 91 * Interrupt execution if ns_query_done() is called. 92 */ 93 94 ns_hooktable_create(mctx, &query_hooks); 95 ns_hook_add(query_hooks, mctx, NS_QUERY_DONE_BEGIN, &hook); 96 ns__hook_table = query_hooks; 97 98 /* 99 * Construct a query context for a ./NS query with given flags. 100 */ 101 { 102 const ns_test_qctx_create_params_t qctx_params = { 103 .qname = ".", 104 .qtype = dns_rdatatype_ns, 105 .qflags = test->qflags, 106 .with_cache = true, 107 }; 108 109 result = ns_test_qctx_create(&qctx_params, &qctx); 110 assert_int_equal(result, ISC_R_SUCCESS); 111 } 112 113 /* 114 * If this test wants a SERVFAIL cache entry matching the query to 115 * exist, create it. 116 */ 117 if (test->cache_entry_present) { 118 isc_interval_t hour; 119 isc_time_t expire; 120 121 isc_interval_set(&hour, 3600, 0); 122 result = isc_time_nowplusinterval(&expire, &hour); 123 assert_int_equal(result, ISC_R_SUCCESS); 124 125 dns_badcache_add(qctx->client->view->failcache, dns_rootname, 126 dns_rdatatype_ns, test->cache_entry_flags, 127 isc_time_seconds(&expire)); 128 } 129 130 /* 131 * Check whether ns__query_sfcache() behaves as expected. 132 */ 133 ns__query_sfcache(qctx); 134 135 if (test->servfail_expected) { 136 if (qctx->result != DNS_R_SERVFAIL) { 137 fail_msg("# test \"%s\" on line %d: " 138 "expected SERVFAIL, got %s", 139 test->id.description, test->id.lineno, 140 isc_result_totext(qctx->result)); 141 } 142 } else { 143 if (qctx->result != ISC_R_SUCCESS) { 144 fail_msg("# test \"%s\" on line %d: " 145 "expected success, got %s", 146 test->id.description, test->id.lineno, 147 isc_result_totext(qctx->result)); 148 } 149 } 150 151 /* 152 * Clean up. 153 */ 154 ns_test_qctx_destroy(&qctx); 155 ns_hooktable_free(mctx, (void **)&query_hooks); 156 } 157 158 /* test ns__query_sfcache() */ 159 ISC_LOOP_TEST_IMPL(ns__query_sfcache) { 160 const ns__query_sfcache_test_params_t tests[] = { 161 /* 162 * Sanity check for an empty SERVFAIL cache. 163 */ 164 { 165 NS_TEST_ID("query: RD=1, CD=0; cache: empty"), 166 .qflags = DNS_MESSAGEFLAG_RD, 167 .cache_entry_present = false, 168 .servfail_expected = false, 169 }, 170 /* 171 * Query: RD=1, CD=0. Cache entry: CD=0. Should SERVFAIL. 172 */ 173 { 174 NS_TEST_ID("query: RD=1, CD=0; cache: CD=0"), 175 .qflags = DNS_MESSAGEFLAG_RD, 176 .cache_entry_present = true, 177 .cache_entry_flags = 0, 178 .servfail_expected = true, 179 }, 180 /* 181 * Query: RD=1, CD=1. Cache entry: CD=0. Should not SERVFAIL: 182 * failed validation should not influence CD=1 queries. 183 */ 184 { 185 NS_TEST_ID("query: RD=1, CD=1; cache: CD=0"), 186 .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD, 187 .cache_entry_present = true, 188 .cache_entry_flags = 0, 189 .servfail_expected = false, 190 }, 191 /* 192 * Query: RD=1, CD=1. Cache entry: CD=1. Should SERVFAIL: 193 * SERVFAIL responses elicited by CD=1 queries can be 194 * "replayed" for other CD=1 queries during the lifetime of the 195 * SERVFAIL cache entry. 196 */ 197 { 198 NS_TEST_ID("query: RD=1, CD=1; cache: CD=1"), 199 .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD, 200 .cache_entry_present = true, 201 .cache_entry_flags = NS_FAILCACHE_CD, 202 .servfail_expected = true, 203 }, 204 /* 205 * Query: RD=1, CD=0. Cache entry: CD=1. Should SERVFAIL: if 206 * a CD=1 query elicited a SERVFAIL, a CD=0 query for the same 207 * QNAME and QTYPE will SERVFAIL as well. 208 */ 209 { 210 NS_TEST_ID("query: RD=1, CD=0; cache: CD=1"), 211 .qflags = DNS_MESSAGEFLAG_RD, 212 .cache_entry_present = true, 213 .cache_entry_flags = NS_FAILCACHE_CD, 214 .servfail_expected = true, 215 }, 216 /* 217 * Query: RD=0, CD=0. Cache entry: CD=0. Should not SERVFAIL 218 * despite a matching entry being present as the SERVFAIL cache 219 * should not be consulted for non-recursive queries. 220 */ 221 { 222 NS_TEST_ID("query: RD=0, CD=0; cache: CD=0"), 223 .qflags = 0, 224 .cache_entry_present = true, 225 .cache_entry_flags = 0, 226 .servfail_expected = false, 227 }, 228 }; 229 230 for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { 231 run_sfcache_test(&tests[i]); 232 } 233 234 isc_loop_teardown(mainloop, shutdown_interfacemgr, NULL); 235 isc_loopmgr_shutdown(loopmgr); 236 } 237 238 /***** 239 ***** ns__query_start() tests 240 *****/ 241 242 /*% 243 * Structure containing parameters for ns__query_start_test(). 244 */ 245 typedef struct { 246 const ns_test_id_t id; /* libns test identifier */ 247 const char *qname; /* QNAME */ 248 dns_rdatatype_t qtype; /* QTYPE */ 249 unsigned int qflags; /* query flags */ 250 bool disable_name_checks; /* if set to true, owner 251 * name checks will 252 * be disabled for the 253 * view created 254 */ 255 bool recursive_service; /* if set to true, the view 256 * created will have a cache 257 * database attached */ 258 const char *auth_zone_origin; /* origin name of the zone 259 * the created view will be 260 * authoritative for */ 261 const char *auth_zone_path; /* path to load the 262 * authoritative 263 * zone from */ 264 enum { /* expected result: */ 265 NS__QUERY_START_R_INVALID, 266 NS__QUERY_START_R_REFUSE, /* query should be REFUSED */ 267 NS__QUERY_START_R_CACHE, /* query should be answered from 268 * cache */ 269 NS__QUERY_START_R_AUTH, /* query should be answered using 270 * authoritative data */ 271 } expected_result; 272 } ns__query_start_test_params_t; 273 274 /*% 275 * Perform a single ns__query_start() check using given parameters. 276 */ 277 static void 278 run_start_test(const ns__query_start_test_params_t *test) { 279 ns_hooktable_t *query_hooks = NULL; 280 query_ctx_t *qctx = NULL; 281 isc_result_t result; 282 const ns_hook_t hook = { 283 .action = ns_test_hook_catch_call, 284 }; 285 286 REQUIRE(test != NULL); 287 REQUIRE(test->id.description != NULL); 288 REQUIRE((test->auth_zone_origin == NULL && 289 test->auth_zone_path == NULL) || 290 (test->auth_zone_origin != NULL && 291 test->auth_zone_path != NULL)); 292 293 /* 294 * Interrupt execution if query_lookup() or ns_query_done() is called. 295 */ 296 ns_hooktable_create(mctx, &query_hooks); 297 ns_hook_add(query_hooks, mctx, NS_QUERY_LOOKUP_BEGIN, &hook); 298 ns_hook_add(query_hooks, mctx, NS_QUERY_DONE_BEGIN, &hook); 299 ns__hook_table = query_hooks; 300 301 /* 302 * Construct a query context using the supplied parameters. 303 */ 304 { 305 const ns_test_qctx_create_params_t qctx_params = { 306 .qname = test->qname, 307 .qtype = test->qtype, 308 .qflags = test->qflags, 309 .with_cache = test->recursive_service, 310 }; 311 result = ns_test_qctx_create(&qctx_params, &qctx); 312 assert_int_equal(result, ISC_R_SUCCESS); 313 } 314 315 /* 316 * Enable view->checknames by default, disable if requested. 317 */ 318 qctx->client->view->checknames = !test->disable_name_checks; 319 320 /* 321 * Load zone from file and attach it to the client's view, if 322 * requested. 323 */ 324 if (test->auth_zone_path != NULL) { 325 result = ns_test_serve_zone(test->auth_zone_origin, 326 test->auth_zone_path, 327 qctx->client->view); 328 assert_int_equal(result, ISC_R_SUCCESS); 329 } 330 331 /* 332 * Check whether ns__query_start() behaves as expected. 333 */ 334 ns__query_start(qctx); 335 336 switch (test->expected_result) { 337 case NS__QUERY_START_R_REFUSE: 338 if (qctx->result != DNS_R_REFUSED) { 339 fail_msg("# test \"%s\" on line %d: " 340 "expected REFUSED, got %s", 341 test->id.description, test->id.lineno, 342 isc_result_totext(qctx->result)); 343 } 344 if (qctx->zone != NULL) { 345 fail_msg("# test \"%s\" on line %d: " 346 "no zone was expected to be attached to " 347 "query context, but some was", 348 test->id.description, test->id.lineno); 349 } 350 if (qctx->db != NULL) { 351 fail_msg("# test \"%s\" on line %d: " 352 "no database was expected to be attached to " 353 "query context, but some was", 354 test->id.description, test->id.lineno); 355 } 356 break; 357 case NS__QUERY_START_R_CACHE: 358 if (qctx->result != ISC_R_SUCCESS) { 359 fail_msg("# test \"%s\" on line %d: " 360 "expected success, got %s", 361 test->id.description, test->id.lineno, 362 isc_result_totext(qctx->result)); 363 } 364 if (qctx->zone != NULL) { 365 fail_msg("# test \"%s\" on line %d: " 366 "no zone was expected to be attached to " 367 "query context, but some was", 368 test->id.description, test->id.lineno); 369 } 370 if (qctx->db == NULL || qctx->db != qctx->client->view->cachedb) 371 { 372 fail_msg("# test \"%s\" on line %d: " 373 "cache database was expected to be " 374 "attached to query context, but it was not", 375 test->id.description, test->id.lineno); 376 } 377 break; 378 case NS__QUERY_START_R_AUTH: 379 if (qctx->result != ISC_R_SUCCESS) { 380 fail_msg("# test \"%s\" on line %d: " 381 "expected success, got %s", 382 test->id.description, test->id.lineno, 383 isc_result_totext(qctx->result)); 384 } 385 if (qctx->zone == NULL) { 386 fail_msg("# test \"%s\" on line %d: " 387 "a zone was expected to be attached to query " 388 "context, but it was not", 389 test->id.description, test->id.lineno); 390 } 391 if (qctx->db == qctx->client->view->cachedb) { 392 fail_msg("# test \"%s\" on line %d: " 393 "cache database was not expected to be " 394 "attached to query context, but it is", 395 test->id.description, test->id.lineno); 396 } 397 break; 398 case NS__QUERY_START_R_INVALID: 399 fail_msg("# test \"%s\" on line %d has no expected result set", 400 test->id.description, test->id.lineno); 401 break; 402 default: 403 UNREACHABLE(); 404 } 405 406 /* 407 * Clean up. 408 */ 409 if (test->auth_zone_path != NULL) { 410 ns_test_cleanup_zone(); 411 } 412 ns_test_qctx_destroy(&qctx); 413 ns_hooktable_free(mctx, (void **)&query_hooks); 414 } 415 416 /* test ns__query_start() */ 417 ISC_LOOP_TEST_IMPL(ns__query_start) { 418 size_t i; 419 420 const ns__query_start_test_params_t tests[] = { 421 /* 422 * Recursive foo/A query to a server without recursive service 423 * and no zones configured. Query should be REFUSED. 424 */ 425 { 426 NS_TEST_ID("foo/A, no cache, no auth"), 427 .qname = "foo", 428 .qtype = dns_rdatatype_a, 429 .qflags = DNS_MESSAGEFLAG_RD, 430 .recursive_service = false, 431 .expected_result = NS__QUERY_START_R_REFUSE, 432 }, 433 /* 434 * Recursive foo/A query to a server with recursive service and 435 * no zones configured. Query should be answered from cache. 436 */ 437 { 438 NS_TEST_ID("foo/A, cache, no auth"), 439 .qname = "foo", 440 .qtype = dns_rdatatype_a, 441 .recursive_service = true, 442 .expected_result = NS__QUERY_START_R_CACHE, 443 }, 444 /* 445 * Recursive foo/A query to a server with recursive service and 446 * zone "foo" configured. Query should be answered from 447 * authoritative data. 448 */ 449 { 450 NS_TEST_ID("foo/A, RD=1, cache, auth for foo"), 451 .qname = "foo", 452 .qtype = dns_rdatatype_a, 453 .qflags = DNS_MESSAGEFLAG_RD, 454 .recursive_service = true, 455 .auth_zone_origin = "foo", 456 .auth_zone_path = TESTS_DIR "/testdata/query/foo.db", 457 .expected_result = NS__QUERY_START_R_AUTH, 458 }, 459 /* 460 * Recursive bar/A query to a server without recursive service 461 * and zone "foo" configured. Query should be REFUSED. 462 */ 463 { 464 NS_TEST_ID("bar/A, RD=1, no cache, auth for foo"), 465 .qname = "bar", 466 .qtype = dns_rdatatype_a, 467 .qflags = DNS_MESSAGEFLAG_RD, 468 .recursive_service = false, 469 .auth_zone_origin = "foo", 470 .auth_zone_path = TESTS_DIR "/testdata/query/foo.db", 471 .expected_result = NS__QUERY_START_R_REFUSE, 472 }, 473 /* 474 * Recursive bar/A query to a server with recursive service and 475 * zone "foo" configured. Query should be answered from 476 * cache. 477 */ 478 { 479 NS_TEST_ID("bar/A, RD=1, cache, auth for foo"), 480 .qname = "bar", 481 .qtype = dns_rdatatype_a, 482 .qflags = DNS_MESSAGEFLAG_RD, 483 .recursive_service = true, 484 .auth_zone_origin = "foo", 485 .auth_zone_path = TESTS_DIR "/testdata/query/foo.db", 486 .expected_result = NS__QUERY_START_R_CACHE, 487 }, 488 /* 489 * Recursive bar.foo/DS query to a server with recursive 490 * service and zone "foo" configured. Query should be answered 491 * from authoritative data. 492 */ 493 { 494 NS_TEST_ID("bar.foo/DS, RD=1, cache, auth for foo"), 495 .qname = "bar.foo", 496 .qtype = dns_rdatatype_ds, 497 .qflags = DNS_MESSAGEFLAG_RD, 498 .recursive_service = true, 499 .auth_zone_origin = "foo", 500 .auth_zone_path = TESTS_DIR "/testdata/query/foo.db", 501 .expected_result = NS__QUERY_START_R_AUTH, 502 }, 503 /* 504 * Non-recursive bar.foo/DS query to a server with recursive 505 * service and zone "foo" configured. Query should be answered 506 * from authoritative data. 507 */ 508 { 509 NS_TEST_ID("bar.foo/DS, RD=0, cache, auth for foo"), 510 .qname = "bar.foo", 511 .qtype = dns_rdatatype_ds, 512 .qflags = 0, 513 .recursive_service = true, 514 .auth_zone_origin = "foo", 515 .auth_zone_path = TESTS_DIR "/testdata/query/foo.db", 516 .expected_result = NS__QUERY_START_R_AUTH, 517 }, 518 /* 519 * Recursive foo/DS query to a server with recursive service 520 * and zone "foo" configured. Query should be answered from 521 * cache. 522 */ 523 { 524 NS_TEST_ID("foo/DS, RD=1, cache, auth for foo"), 525 .qname = "foo", 526 .qtype = dns_rdatatype_ds, 527 .qflags = DNS_MESSAGEFLAG_RD, 528 .recursive_service = true, 529 .auth_zone_origin = "foo", 530 .auth_zone_path = TESTS_DIR "/testdata/query/foo.db", 531 .expected_result = NS__QUERY_START_R_CACHE, 532 }, 533 /* 534 * Non-recursive foo/DS query to a server with recursive 535 * service and zone "foo" configured. Query should be answered 536 * from authoritative data. 537 */ 538 { 539 NS_TEST_ID("foo/DS, RD=0, cache, auth for foo"), 540 .qname = "foo", 541 .qtype = dns_rdatatype_ds, 542 .qflags = 0, 543 .recursive_service = true, 544 .auth_zone_origin = "foo", 545 .auth_zone_path = TESTS_DIR "/testdata/query/foo.db", 546 .expected_result = NS__QUERY_START_R_AUTH, 547 }, 548 /* 549 * Recursive _foo/A query to a server with recursive service, 550 * no zones configured and owner name checks disabled. Query 551 * should be answered from cache. 552 */ 553 { 554 NS_TEST_ID("_foo/A, cache, no auth, name checks off"), 555 .qname = "_foo", 556 .qtype = dns_rdatatype_a, 557 .qflags = DNS_MESSAGEFLAG_RD, 558 .disable_name_checks = true, 559 .recursive_service = true, 560 .expected_result = NS__QUERY_START_R_CACHE, 561 }, 562 /* 563 * Recursive _foo/A query to a server with recursive service, 564 * no zones configured and owner name checks enabled. Query 565 * should be REFUSED. 566 */ 567 { 568 NS_TEST_ID("_foo/A, cache, no auth, name checks on"), 569 .qname = "_foo", 570 .qtype = dns_rdatatype_a, 571 .qflags = DNS_MESSAGEFLAG_RD, 572 .disable_name_checks = false, 573 .recursive_service = true, 574 .expected_result = NS__QUERY_START_R_REFUSE, 575 }, 576 }; 577 578 for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { 579 run_start_test(&tests[i]); 580 } 581 582 isc_loop_teardown(mainloop, shutdown_interfacemgr, NULL); 583 isc_loopmgr_shutdown(loopmgr); 584 } 585 586 /***** 587 ***** tests for ns_query_hookasync(). 588 *****/ 589 590 /*% 591 * Structure containing parameters for ns__query_hookasync_test(). 592 */ 593 typedef struct { 594 const ns_test_id_t id; /* libns test identifier */ 595 ns_hookpoint_t hookpoint; /* hook point specified for resume */ 596 ns_hookpoint_t hookpoint2; /* expected hook point used after resume */ 597 ns_hook_action_t action; /* action for the hook point */ 598 isc_result_t start_result; /* result of 'runasync' */ 599 bool quota_ok; /* true if recursion quota should be okay */ 600 bool do_cancel; /* true if query should be canceled 601 * in test */ 602 } ns__query_hookasync_test_params_t; 603 604 /* Data structure passed from tests to hooks */ 605 typedef struct hookasync_data { 606 bool async; /* true if in a hook-triggered 607 * asynchronous process */ 608 bool canceled; /* true if the query has been canceled */ 609 isc_result_t start_result; /* result of 'runasync' */ 610 ns_hook_resume_t *rev; /* resume state sent on completion */ 611 query_ctx_t qctx; /* shallow copy of qctx passed to hook */ 612 ns_hookpoint_t hookpoint; /* specifies where to resume */ 613 ns_hookpoint_t lasthookpoint; /* remember the last hook point called */ 614 } hookasync_data_t; 615 616 /* 617 * 'destroy' callback of hook recursion ctx. 618 * The dynamically allocated context will be freed here, thereby proving 619 * this is actually called; otherwise tests would fail due to memory leak. 620 */ 621 static void 622 destroy_hookactx(ns_hookasync_t **ctxp) { 623 ns_hookasync_t *ctx = *ctxp; 624 625 *ctxp = NULL; 626 isc_mem_putanddetach(&ctx->mctx, ctx, sizeof(*ctx)); 627 } 628 629 /* 'cancel' callback of hook recursion ctx. */ 630 static void 631 cancel_hookactx(ns_hookasync_t *ctx) { 632 /* Mark the hook data so the test can confirm this is called. */ 633 ((hookasync_data_t *)ctx->private)->canceled = true; 634 } 635 636 /* 'runasync' callback passed to ns_query_hookasync */ 637 static isc_result_t 638 test_hookasync(query_ctx_t *qctx, isc_mem_t *memctx, void *arg, 639 isc_loop_t *loop, isc_job_cb cb, void *evarg, 640 ns_hookasync_t **ctxp) { 641 hookasync_data_t *asdata = arg; 642 ns_hookasync_t *ctx = NULL; 643 ns_hook_resume_t *rev = NULL; 644 645 if (asdata->start_result != ISC_R_SUCCESS) { 646 return asdata->start_result; 647 } 648 649 ctx = isc_mem_get(memctx, sizeof(*ctx)); 650 rev = isc_mem_get(memctx, sizeof(*rev)); 651 *rev = (ns_hook_resume_t){ 652 .hookpoint = asdata->hookpoint, 653 .origresult = DNS_R_NXDOMAIN, 654 .saved_qctx = qctx, 655 .ctx = ctx, 656 .loop = loop, 657 .cb = cb, 658 .arg = evarg, 659 }; 660 661 asdata->rev = rev; 662 663 *ctx = (ns_hookasync_t){ 664 .destroy = destroy_hookactx, 665 .cancel = cancel_hookactx, 666 .private = asdata, 667 }; 668 isc_mem_attach(memctx, &ctx->mctx); 669 670 *ctxp = ctx; 671 return ISC_R_SUCCESS; 672 } 673 674 /* 675 * Main logic for hook actions. 676 * 'hookpoint' should identify the point that calls the hook. It will be 677 * remembered in the hook data, so that the test can confirm which hook point 678 * was last used. 679 */ 680 static ns_hookresult_t 681 hook_async_common(void *arg, void *data, isc_result_t *resultp, 682 ns_hookpoint_t hookpoint) { 683 query_ctx_t *qctx = arg; 684 hookasync_data_t *asdata = data; 685 isc_result_t result; 686 687 asdata->qctx = *qctx; /* remember passed ctx for inspection */ 688 asdata->lasthookpoint = hookpoint; /* ditto */ 689 690 if (!asdata->async) { 691 /* Initial call to the hook; start recursion */ 692 result = ns_query_hookasync(qctx, test_hookasync, asdata); 693 if (result == ISC_R_SUCCESS) { 694 asdata->async = true; 695 } 696 } else { 697 /* 698 * Resume from the completion of async event. The fetch handle 699 * should have been detached so that we can start another async 700 * event or DNS recursive resolution. 701 */ 702 INSIST(HANDLE_RECTYPE_HOOK(qctx->client) == NULL); 703 asdata->async = false; 704 switch (hookpoint) { 705 case NS_QUERY_GOT_ANSWER_BEGIN: 706 case NS_QUERY_NODATA_BEGIN: 707 case NS_QUERY_NXDOMAIN_BEGIN: 708 case NS_QUERY_NCACHE_BEGIN: 709 INSIST(*resultp == DNS_R_NXDOMAIN); 710 break; 711 default:; 712 } 713 } 714 715 *resultp = ISC_R_UNSET; 716 return NS_HOOK_RETURN; 717 } 718 719 static ns_hookresult_t 720 hook_async_query_setup(void *arg, void *data, isc_result_t *resultp) { 721 return hook_async_common(arg, data, resultp, NS_QUERY_SETUP); 722 } 723 724 static ns_hookresult_t 725 hook_async_query_start_begin(void *arg, void *data, isc_result_t *resultp) { 726 return hook_async_common(arg, data, resultp, NS_QUERY_START_BEGIN); 727 } 728 729 static ns_hookresult_t 730 hook_async_query_lookup_begin(void *arg, void *data, isc_result_t *resultp) { 731 return hook_async_common(arg, data, resultp, NS_QUERY_LOOKUP_BEGIN); 732 } 733 734 static ns_hookresult_t 735 hook_async_query_resume_begin(void *arg, void *data, isc_result_t *resultp) { 736 return hook_async_common(arg, data, resultp, NS_QUERY_RESUME_BEGIN); 737 } 738 739 static ns_hookresult_t 740 hook_async_query_got_answer_begin(void *arg, void *data, 741 isc_result_t *resultp) { 742 return hook_async_common(arg, data, resultp, NS_QUERY_GOT_ANSWER_BEGIN); 743 } 744 745 static ns_hookresult_t 746 hook_async_query_respond_any_begin(void *arg, void *data, 747 isc_result_t *resultp) { 748 return hook_async_common(arg, data, resultp, 749 NS_QUERY_RESPOND_ANY_BEGIN); 750 } 751 752 static ns_hookresult_t 753 hook_async_query_addanswer_begin(void *arg, void *data, isc_result_t *resultp) { 754 return hook_async_common(arg, data, resultp, NS_QUERY_ADDANSWER_BEGIN); 755 } 756 757 static ns_hookresult_t 758 hook_async_query_notfound_begin(void *arg, void *data, isc_result_t *resultp) { 759 return hook_async_common(arg, data, resultp, NS_QUERY_NOTFOUND_BEGIN); 760 } 761 762 static ns_hookresult_t 763 hook_async_query_prep_delegation_begin(void *arg, void *data, 764 isc_result_t *resultp) { 765 return hook_async_common(arg, data, resultp, 766 NS_QUERY_PREP_DELEGATION_BEGIN); 767 } 768 769 static ns_hookresult_t 770 hook_async_query_zone_delegation_begin(void *arg, void *data, 771 isc_result_t *resultp) { 772 return hook_async_common(arg, data, resultp, 773 NS_QUERY_ZONE_DELEGATION_BEGIN); 774 } 775 776 static ns_hookresult_t 777 hook_async_query_delegation_begin(void *arg, void *data, 778 isc_result_t *resultp) { 779 return hook_async_common(arg, data, resultp, NS_QUERY_DELEGATION_BEGIN); 780 } 781 782 static ns_hookresult_t 783 hook_async_query_delegation_recurse_begin(void *arg, void *data, 784 isc_result_t *resultp) { 785 return hook_async_common(arg, data, resultp, 786 NS_QUERY_DELEGATION_RECURSE_BEGIN); 787 } 788 789 static ns_hookresult_t 790 hook_async_query_nodata_begin(void *arg, void *data, isc_result_t *resultp) { 791 return hook_async_common(arg, data, resultp, NS_QUERY_NODATA_BEGIN); 792 } 793 794 static ns_hookresult_t 795 hook_async_query_nxdomain_begin(void *arg, void *data, isc_result_t *resultp) { 796 return hook_async_common(arg, data, resultp, NS_QUERY_NXDOMAIN_BEGIN); 797 } 798 799 static ns_hookresult_t 800 hook_async_query_ncache_begin(void *arg, void *data, isc_result_t *resultp) { 801 return hook_async_common(arg, data, resultp, NS_QUERY_NCACHE_BEGIN); 802 } 803 804 static ns_hookresult_t 805 hook_async_query_cname_begin(void *arg, void *data, isc_result_t *resultp) { 806 return hook_async_common(arg, data, resultp, NS_QUERY_CNAME_BEGIN); 807 } 808 809 static ns_hookresult_t 810 hook_async_query_dname_begin(void *arg, void *data, isc_result_t *resultp) { 811 return hook_async_common(arg, data, resultp, NS_QUERY_DNAME_BEGIN); 812 } 813 814 static ns_hookresult_t 815 hook_async_query_respond_begin(void *arg, void *data, isc_result_t *resultp) { 816 return hook_async_common(arg, data, resultp, NS_QUERY_RESPOND_BEGIN); 817 } 818 819 static ns_hookresult_t 820 hook_async_query_response_begin(void *arg, void *data, isc_result_t *resultp) { 821 return hook_async_common(arg, data, resultp, 822 NS_QUERY_PREP_RESPONSE_BEGIN); 823 } 824 825 static ns_hookresult_t 826 hook_async_query_done_begin(void *arg, void *data, isc_result_t *resultp) { 827 return hook_async_common(arg, data, resultp, NS_QUERY_DONE_BEGIN); 828 } 829 830 /* 831 * hook on destroying actx. Can't be used for async event, but we use this 832 * to remember the qctx at that point. 833 */ 834 static ns_hookresult_t 835 ns_test_qctx_destroy_hook(void *arg, void *data, isc_result_t *resultp) { 836 query_ctx_t *qctx = arg; 837 hookasync_data_t *asdata = data; 838 839 asdata->qctx = *qctx; /* remember passed ctx for inspection */ 840 *resultp = ISC_R_UNSET; 841 return NS_HOOK_CONTINUE; 842 } 843 844 static void 845 run_hookasync_test(const ns__query_hookasync_test_params_t *test) { 846 query_ctx_t *qctx = NULL; 847 isc_result_t result; 848 hookasync_data_t asdata = { 849 .async = false, 850 .canceled = false, 851 .start_result = test->start_result, 852 .hookpoint = test->hookpoint, 853 }; 854 const ns_hook_t testhook = { 855 .action = test->action, 856 .action_data = &asdata, 857 }; 858 const ns_hook_t destroyhook = { 859 .action = ns_test_qctx_destroy_hook, 860 .action_data = &asdata, 861 }; 862 isc_statscounter_t srvfail_cnt; 863 bool expect_servfail = false; 864 865 /* 866 * Prepare hooks. We always begin with ns__query_start for simplicity. 867 * Its action will specify various different resume points (unusual 868 * in practice, but that's fine for the testing purpose). 869 */ 870 ns__hook_table = NULL; 871 ns_hooktable_create(mctx, &ns__hook_table); 872 ns_hook_add(ns__hook_table, mctx, NS_QUERY_START_BEGIN, &testhook); 873 if (test->hookpoint2 != NS_QUERY_START_BEGIN) { 874 /* 875 * unless testing START_BEGIN itself, specify the hook for the 876 * expected resume point, too. 877 */ 878 ns_hook_add(ns__hook_table, mctx, test->hookpoint2, &testhook); 879 } 880 ns_hook_add(ns__hook_table, mctx, NS_QUERY_QCTX_DESTROYED, 881 &destroyhook); 882 883 { 884 const ns_test_qctx_create_params_t qctx_params = { 885 .qname = "test.example.com", 886 .qtype = dns_rdatatype_aaaa, 887 }; 888 result = ns_test_qctx_create(&qctx_params, &qctx); 889 INSIST(result == ISC_R_SUCCESS); 890 qctx->client->sendcb = send_noop; 891 } 892 893 /* 894 * Set recursion quota to the lowest possible value, then make it full 895 * if we want to exercise a quota failure case. 896 */ 897 isc_quota_max(&sctx->recursionquota, 1); 898 if (!test->quota_ok) { 899 result = isc_quota_acquire(&sctx->recursionquota); 900 INSIST(result == ISC_R_SUCCESS); 901 } 902 903 /* Remember SERVFAIL counter */ 904 srvfail_cnt = ns_stats_get_counter(qctx->client->manager->sctx->nsstats, 905 ns_statscounter_servfail); 906 907 /* 908 * If the query has been canceled, or async event didn't succeed, 909 * SERVFAIL will have to be sent. In this case we need to have 910 * 'reqhandle' attach to the client's handle as it's detached in 911 * query_error. 912 */ 913 if (test->start_result != ISC_R_SUCCESS || !test->quota_ok || 914 test->do_cancel) 915 { 916 expect_servfail = true; 917 isc_nmhandle_attach(qctx->client->handle, 918 &qctx->client->reqhandle); 919 } 920 921 /* 922 * Emulate query handling from query_start. 923 * Specified hook should be called. 924 */ 925 qctx->client->state = NS_CLIENTSTATE_WORKING; 926 result = ns__query_start(qctx); 927 INSIST(result == ISC_R_UNSET); 928 929 /* 930 * hook-triggered async event should be happening unless it hits 931 * recursion quota limit or 'runasync' callback fails. 932 */ 933 INSIST(asdata.async == 934 (test->quota_ok && test->start_result == ISC_R_SUCCESS)); 935 936 /* 937 * Emulate cancel if so specified. 938 * The cancel callback should be called. 939 */ 940 if (test->do_cancel) { 941 ns_query_cancel(qctx->client); 942 } 943 INSIST(asdata.canceled == test->do_cancel); 944 945 /* If async event has started, manually invoke the 'done' event. */ 946 if (asdata.async) { 947 qctx->client->now = 0; /* set to sentinel before resume */ 948 asdata.rev->cb(asdata.rev); 949 950 /* Confirm necessary cleanup has been performed. */ 951 INSIST(qctx->client->query.hookactx == NULL); 952 INSIST(qctx->client->state == NS_CLIENTSTATE_WORKING); 953 INSIST(ns_stats_get_counter( 954 qctx->client->manager->sctx->nsstats, 955 ns_statscounter_recursclients) == 0); 956 INSIST(!ISC_LINK_LINKED(qctx->client, rlink)); 957 if (!test->do_cancel) { 958 /* 959 * In the normal case the client's timestamp is updated 960 * and the query handling has been resumed from the 961 * expected point. 962 */ 963 INSIST(qctx->client->now != 0); 964 INSIST(asdata.lasthookpoint == test->hookpoint2); 965 } 966 } else { 967 INSIST(qctx->client->query.hookactx == NULL); 968 } 969 970 /* 971 * Confirm SERVFAIL has been sent if it was expected. 972 * Also, the last-generated qctx should have detach_client being true. 973 */ 974 if (expect_servfail) { 975 INSIST(ns_stats_get_counter( 976 qctx->client->manager->sctx->nsstats, 977 ns_statscounter_servfail) == srvfail_cnt + 1); 978 if (test->do_cancel) { 979 /* qctx was created on resume and copied in hook */ 980 INSIST(asdata.qctx.detach_client); 981 } else { 982 INSIST(qctx->detach_client); 983 } 984 } 985 986 /* 987 * Cleanup. Note that we've kept 'qctx' until now; otherwise 988 * qctx->client may have been invalidated while we still need it. 989 */ 990 ns_test_qctx_destroy(&qctx); 991 ns_hooktable_free(mctx, (void **)&ns__hook_table); 992 if (!test->quota_ok) { 993 isc_quota_release(&sctx->recursionquota); 994 } 995 } 996 997 ISC_LOOP_TEST_IMPL(ns__query_hookasync) { 998 size_t i; 999 1000 const ns__query_hookasync_test_params_t tests[] = { 1001 { 1002 NS_TEST_ID("normal case"), 1003 NS_QUERY_START_BEGIN, 1004 NS_QUERY_START_BEGIN, 1005 hook_async_query_start_begin, 1006 ISC_R_SUCCESS, 1007 true, 1008 false, 1009 }, 1010 { 1011 NS_TEST_ID("quota fail"), 1012 NS_QUERY_START_BEGIN, 1013 NS_QUERY_START_BEGIN, 1014 hook_async_query_start_begin, 1015 ISC_R_SUCCESS, 1016 false, 1017 false, 1018 }, 1019 { 1020 NS_TEST_ID("start fail"), 1021 NS_QUERY_START_BEGIN, 1022 NS_QUERY_START_BEGIN, 1023 hook_async_query_start_begin, 1024 ISC_R_FAILURE, 1025 true, 1026 false, 1027 }, 1028 { 1029 NS_TEST_ID("query cancel"), 1030 NS_QUERY_START_BEGIN, 1031 NS_QUERY_START_BEGIN, 1032 hook_async_query_start_begin, 1033 ISC_R_SUCCESS, 1034 true, 1035 true, 1036 }, 1037 /* 1038 * The rest of the test case just confirms supported hookpoints 1039 * with the same test logic. 1040 */ 1041 { 1042 NS_TEST_ID("async from setup"), 1043 NS_QUERY_SETUP, 1044 NS_QUERY_SETUP, 1045 hook_async_query_setup, 1046 ISC_R_SUCCESS, 1047 true, 1048 false, 1049 }, 1050 { 1051 NS_TEST_ID("async from lookup"), 1052 NS_QUERY_LOOKUP_BEGIN, 1053 NS_QUERY_LOOKUP_BEGIN, 1054 hook_async_query_lookup_begin, 1055 ISC_R_SUCCESS, 1056 true, 1057 false, 1058 }, 1059 { 1060 NS_TEST_ID("async from resume"), 1061 NS_QUERY_RESUME_BEGIN, 1062 NS_QUERY_RESUME_BEGIN, 1063 hook_async_query_resume_begin, 1064 ISC_R_SUCCESS, 1065 true, 1066 false, 1067 }, 1068 { 1069 NS_TEST_ID("async from resume restored"), 1070 NS_QUERY_RESUME_RESTORED, 1071 NS_QUERY_RESUME_BEGIN, 1072 hook_async_query_resume_begin, 1073 ISC_R_SUCCESS, 1074 true, 1075 false, 1076 }, 1077 { 1078 NS_TEST_ID("async from gotanswer"), 1079 NS_QUERY_GOT_ANSWER_BEGIN, 1080 NS_QUERY_GOT_ANSWER_BEGIN, 1081 hook_async_query_got_answer_begin, 1082 ISC_R_SUCCESS, 1083 true, 1084 false, 1085 }, 1086 { 1087 NS_TEST_ID("async from respond any"), 1088 NS_QUERY_RESPOND_ANY_BEGIN, 1089 NS_QUERY_RESPOND_ANY_BEGIN, 1090 hook_async_query_respond_any_begin, 1091 ISC_R_SUCCESS, 1092 true, 1093 false, 1094 }, 1095 { 1096 NS_TEST_ID("async from add answer"), 1097 NS_QUERY_ADDANSWER_BEGIN, 1098 NS_QUERY_ADDANSWER_BEGIN, 1099 hook_async_query_addanswer_begin, 1100 ISC_R_SUCCESS, 1101 true, 1102 false, 1103 }, 1104 { 1105 NS_TEST_ID("async from notfound"), 1106 NS_QUERY_NOTFOUND_BEGIN, 1107 NS_QUERY_NOTFOUND_BEGIN, 1108 hook_async_query_notfound_begin, 1109 ISC_R_SUCCESS, 1110 true, 1111 false, 1112 }, 1113 { 1114 NS_TEST_ID("async from prep delegation"), 1115 NS_QUERY_PREP_DELEGATION_BEGIN, 1116 NS_QUERY_PREP_DELEGATION_BEGIN, 1117 hook_async_query_prep_delegation_begin, 1118 ISC_R_SUCCESS, 1119 true, 1120 false, 1121 }, 1122 { 1123 NS_TEST_ID("async from zone delegation"), 1124 NS_QUERY_ZONE_DELEGATION_BEGIN, 1125 NS_QUERY_ZONE_DELEGATION_BEGIN, 1126 hook_async_query_zone_delegation_begin, 1127 ISC_R_SUCCESS, 1128 true, 1129 false, 1130 }, 1131 { 1132 NS_TEST_ID("async from delegation"), 1133 NS_QUERY_DELEGATION_BEGIN, 1134 NS_QUERY_DELEGATION_BEGIN, 1135 hook_async_query_delegation_begin, 1136 ISC_R_SUCCESS, 1137 true, 1138 false, 1139 }, 1140 { 1141 NS_TEST_ID("async from async delegation"), 1142 NS_QUERY_DELEGATION_RECURSE_BEGIN, 1143 NS_QUERY_DELEGATION_RECURSE_BEGIN, 1144 hook_async_query_delegation_recurse_begin, 1145 ISC_R_SUCCESS, 1146 true, 1147 false, 1148 }, 1149 { 1150 NS_TEST_ID("async from nodata"), 1151 NS_QUERY_NODATA_BEGIN, 1152 NS_QUERY_NODATA_BEGIN, 1153 hook_async_query_nodata_begin, 1154 ISC_R_SUCCESS, 1155 true, 1156 false, 1157 }, 1158 { 1159 NS_TEST_ID("async from nxdomain"), 1160 NS_QUERY_NXDOMAIN_BEGIN, 1161 NS_QUERY_NXDOMAIN_BEGIN, 1162 hook_async_query_nxdomain_begin, 1163 ISC_R_SUCCESS, 1164 true, 1165 false, 1166 }, 1167 { 1168 NS_TEST_ID("async from ncache"), 1169 NS_QUERY_NCACHE_BEGIN, 1170 NS_QUERY_NCACHE_BEGIN, 1171 hook_async_query_ncache_begin, 1172 ISC_R_SUCCESS, 1173 true, 1174 false, 1175 }, 1176 { 1177 NS_TEST_ID("async from CNAME"), 1178 NS_QUERY_CNAME_BEGIN, 1179 NS_QUERY_CNAME_BEGIN, 1180 hook_async_query_cname_begin, 1181 ISC_R_SUCCESS, 1182 true, 1183 false, 1184 }, 1185 { 1186 NS_TEST_ID("async from DNAME"), 1187 NS_QUERY_DNAME_BEGIN, 1188 NS_QUERY_DNAME_BEGIN, 1189 hook_async_query_dname_begin, 1190 ISC_R_SUCCESS, 1191 true, 1192 false, 1193 }, 1194 { 1195 NS_TEST_ID("async from prep response"), 1196 NS_QUERY_PREP_RESPONSE_BEGIN, 1197 NS_QUERY_PREP_RESPONSE_BEGIN, 1198 hook_async_query_response_begin, 1199 ISC_R_SUCCESS, 1200 true, 1201 false, 1202 }, 1203 { 1204 NS_TEST_ID("async from respond"), 1205 NS_QUERY_RESPOND_BEGIN, 1206 NS_QUERY_RESPOND_BEGIN, 1207 hook_async_query_respond_begin, 1208 ISC_R_SUCCESS, 1209 true, 1210 false, 1211 }, 1212 { 1213 NS_TEST_ID("async from done begin"), 1214 NS_QUERY_DONE_BEGIN, 1215 NS_QUERY_DONE_BEGIN, 1216 hook_async_query_done_begin, 1217 ISC_R_SUCCESS, 1218 true, 1219 false, 1220 }, 1221 { 1222 NS_TEST_ID("async from done send"), 1223 NS_QUERY_DONE_SEND, 1224 NS_QUERY_DONE_BEGIN, 1225 hook_async_query_done_begin, 1226 ISC_R_SUCCESS, 1227 true, 1228 false, 1229 }, 1230 }; 1231 1232 for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { 1233 run_hookasync_test(&tests[i]); 1234 } 1235 1236 isc_loop_teardown(mainloop, shutdown_interfacemgr, NULL); 1237 isc_loopmgr_shutdown(loopmgr); 1238 } 1239 1240 /***** 1241 ***** tests for higher level ("e2e") behavior of ns_query_hookasync(). 1242 ***** It exercises overall behavior for some selected cases, while 1243 ***** ns__query_hookasync_test exercises implementation details for a 1244 ***** simple scenario and for all supported hook points. 1245 *****/ 1246 1247 /*% 1248 * Structure containing parameters for ns__query_hookasync_e2e_test(). 1249 */ 1250 typedef struct { 1251 const ns_test_id_t id; /* libns test identifier */ 1252 const char *qname; /* QNAME */ 1253 ns_hookpoint_t hookpoint; /* hook point specified for resume */ 1254 isc_result_t start_result; /* result of 'runasync' */ 1255 bool do_cancel; /* true if query should be canceled 1256 * in test */ 1257 dns_rcode_t expected_rcode; 1258 } ns__query_hookasync_e2e_test_params_t; 1259 1260 /* data structure passed from tests to hooks */ 1261 typedef struct hookasync_e2e_data { 1262 bool async; /* true if in a hook-triggered 1263 * asynchronous process */ 1264 ns_hook_resume_t *rev; /* resume state sent on completion */ 1265 ns_hookpoint_t hookpoint; /* specifies where to resume */ 1266 isc_result_t start_result; /* result of 'runasync' */ 1267 dns_rcode_t expected_rcode; 1268 bool done; /* if SEND_DONE hook is called */ 1269 } hookasync_e2e_data_t; 1270 1271 /* Cancel callback. Just need to be defined, it doesn't have to do anything. */ 1272 static void 1273 cancel_e2ehookactx(ns_hookasync_t *ctx) { 1274 UNUSED(ctx); 1275 } 1276 1277 /* 'runasync' callback passed to ns_query_hookasync */ 1278 static isc_result_t 1279 test_hookasync_e2e(query_ctx_t *qctx, isc_mem_t *memctx, void *arg, 1280 isc_loop_t *loop, isc_job_cb cb, void *evarg, 1281 ns_hookasync_t **ctxp) { 1282 ns_hookasync_t *ctx = NULL; 1283 ns_hook_resume_t *rev = NULL; 1284 hookasync_e2e_data_t *asdata = arg; 1285 1286 if (asdata->start_result != ISC_R_SUCCESS) { 1287 return asdata->start_result; 1288 } 1289 1290 ctx = isc_mem_get(memctx, sizeof(*ctx)); 1291 rev = isc_mem_get(memctx, sizeof(*rev)); 1292 *rev = (ns_hook_resume_t){ 1293 .hookpoint = asdata->hookpoint, 1294 .saved_qctx = qctx, 1295 .ctx = ctx, 1296 .loop = loop, 1297 .cb = cb, 1298 .arg = evarg, 1299 }; 1300 1301 asdata->rev = rev; 1302 1303 *ctx = (ns_hookasync_t){ 1304 .destroy = destroy_hookactx, 1305 .cancel = cancel_e2ehookactx, 1306 .private = asdata, 1307 }; 1308 isc_mem_attach(memctx, &ctx->mctx); 1309 1310 *ctxp = ctx; 1311 return ISC_R_SUCCESS; 1312 } 1313 1314 static ns_hookresult_t 1315 hook_async_e2e(void *arg, void *data, isc_result_t *resultp) { 1316 query_ctx_t *qctx = arg; 1317 hookasync_e2e_data_t *asdata = data; 1318 isc_result_t result; 1319 1320 if (!asdata->async) { 1321 /* Initial call to the hook; start async event */ 1322 result = ns_query_hookasync(qctx, test_hookasync_e2e, asdata); 1323 if (result != ISC_R_SUCCESS) { 1324 *resultp = result; 1325 return NS_HOOK_RETURN; 1326 } 1327 1328 asdata->async = true; 1329 asdata->rev->origresult = *resultp; /* save it for resume */ 1330 *resultp = ISC_R_UNSET; 1331 return NS_HOOK_RETURN; 1332 } else { 1333 /* Resume from the completion of async event */ 1334 asdata->async = false; 1335 /* Don't touch 'resultp' */ 1336 return NS_HOOK_CONTINUE; 1337 } 1338 } 1339 1340 /* 1341 * Check whether the final response has expected the RCODE according to 1342 * the test scenario. 1343 */ 1344 static ns_hookresult_t 1345 hook_donesend(void *arg, void *data, isc_result_t *resultp) { 1346 query_ctx_t *qctx = arg; 1347 hookasync_e2e_data_t *asdata = data; 1348 1349 INSIST(qctx->client->message->rcode == asdata->expected_rcode); 1350 asdata->done = true; /* Let the test know this hook is called */ 1351 *resultp = ISC_R_UNSET; 1352 return NS_HOOK_CONTINUE; 1353 } 1354 1355 static void 1356 run_hookasync_e2e_test(const ns__query_hookasync_e2e_test_params_t *test) { 1357 query_ctx_t *qctx = NULL; 1358 isc_result_t result; 1359 hookasync_e2e_data_t asdata = { 1360 .async = false, 1361 .hookpoint = test->hookpoint, 1362 .start_result = test->start_result, 1363 .expected_rcode = test->expected_rcode, 1364 .done = false, 1365 }; 1366 const ns_hook_t donesend_hook = { 1367 .action = hook_donesend, 1368 .action_data = &asdata, 1369 }; 1370 const ns_hook_t hook = { 1371 .action = hook_async_e2e, 1372 .action_data = &asdata, 1373 }; 1374 const ns_test_qctx_create_params_t qctx_params = { 1375 .qname = test->qname, 1376 .qtype = dns_rdatatype_a, 1377 .with_cache = true, 1378 }; 1379 1380 ns__hook_table = NULL; 1381 ns_hooktable_create(mctx, &ns__hook_table); 1382 ns_hook_add(ns__hook_table, mctx, test->hookpoint, &hook); 1383 ns_hook_add(ns__hook_table, mctx, NS_QUERY_DONE_SEND, &donesend_hook); 1384 1385 result = ns_test_qctx_create(&qctx_params, &qctx); 1386 INSIST(result == ISC_R_SUCCESS); 1387 1388 isc_sockaddr_any(&qctx->client->peeraddr); /* for sortlist */ 1389 qctx->client->sendcb = send_noop; 1390 1391 /* Load a zone. it should have ns.foo/A */ 1392 result = ns_test_serve_zone("foo", TESTS_DIR "/testdata/query/foo.db", 1393 qctx->client->view); 1394 INSIST(result == ISC_R_SUCCESS); 1395 1396 /* 1397 * We expect to have a response sent all cases, so we need to 1398 * setup reqhandle (which will be detached on the send). 1399 */ 1400 isc_nmhandle_attach(qctx->client->handle, &qctx->client->reqhandle); 1401 1402 /* Handle the query. hook-based async event will be triggered. */ 1403 qctx->client->state = NS_CLIENTSTATE_WORKING; 1404 ns__query_start(qctx); 1405 1406 /* If specified cancel the query at this point. */ 1407 if (test->do_cancel) { 1408 ns_query_cancel(qctx->client); 1409 } 1410 1411 if (test->start_result == ISC_R_SUCCESS) { 1412 /* 1413 * If async event has started, manually invoke the done event. 1414 */ 1415 INSIST(asdata.async); 1416 asdata.rev->cb(asdata.rev); 1417 1418 /* 1419 * Usually 'async' is reset to false on the 2nd call to 1420 * the hook. But the hook isn't called if the query is 1421 * canceled. 1422 */ 1423 INSIST(asdata.done == !test->do_cancel); 1424 INSIST(asdata.async == test->do_cancel); 1425 } else { 1426 INSIST(!asdata.async); 1427 } 1428 1429 /* Cleanup */ 1430 ns_test_qctx_destroy(&qctx); 1431 ns_test_cleanup_zone(); 1432 ns_hooktable_free(mctx, (void **)&ns__hook_table); 1433 } 1434 1435 ISC_LOOP_TEST_IMPL(ns__query_hookasync_e2e) { 1436 const ns__query_hookasync_e2e_test_params_t tests[] = { 1437 { 1438 NS_TEST_ID("positive answer"), 1439 "ns.foo", 1440 NS_QUERY_GOT_ANSWER_BEGIN, 1441 ISC_R_SUCCESS, 1442 false, 1443 dns_rcode_noerror, 1444 }, 1445 { 1446 NS_TEST_ID("NXDOMAIN"), 1447 "notexist.foo", 1448 NS_QUERY_NXDOMAIN_BEGIN, 1449 ISC_R_SUCCESS, 1450 false, 1451 dns_rcode_nxdomain, 1452 }, 1453 { 1454 NS_TEST_ID("async fail"), 1455 "ns.foo", 1456 NS_QUERY_DONE_BEGIN, 1457 ISC_R_FAILURE, 1458 false, 1459 -1, 1460 }, 1461 { 1462 NS_TEST_ID("cancel query"), 1463 "ns.foo", 1464 NS_QUERY_DONE_BEGIN, 1465 ISC_R_SUCCESS, 1466 true, 1467 -1, 1468 }, 1469 }; 1470 1471 for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { 1472 run_hookasync_e2e_test(&tests[i]); 1473 } 1474 1475 isc_loop_teardown(mainloop, shutdown_interfacemgr, NULL); 1476 isc_loopmgr_shutdown(loopmgr); 1477 } 1478 1479 ISC_TEST_LIST_START 1480 ISC_TEST_ENTRY_CUSTOM(ns__query_sfcache, setup_server, teardown_server) 1481 ISC_TEST_ENTRY_CUSTOM(ns__query_start, setup_server, teardown_server) 1482 ISC_TEST_ENTRY_CUSTOM(ns__query_hookasync, setup_server, teardown_server) 1483 ISC_TEST_ENTRY_CUSTOM(ns__query_hookasync_e2e, setup_server, teardown_server) 1484 ISC_TEST_LIST_END 1485 1486 ISC_TEST_MAIN 1487