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