From 1114b1eac0fced55d327b3bee247f73a9fb16f11 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Wed, 28 May 2025 11:54:19 +0200 Subject: [PATCH] add query unit test A new query unit test covers the logic where zone hooks must be called first, then view ones, and finally the default hooks. It also ensures that if any hook returns NS_HOOK_RETURN the chain immediately stops. --- tests/include/tests/ns.h | 6 + tests/libtest/ns.c | 5 + tests/ns/query_test.c | 262 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+) diff --git a/tests/include/tests/ns.h b/tests/include/tests/ns.h index 831ae80399..8d57c4e364 100644 --- a/tests/include/tests/ns.h +++ b/tests/include/tests/ns.h @@ -74,6 +74,12 @@ isc_result_t ns_test_serve_zone(const char *zonename, const char *filename, dns_view_t *view); +/*% + * Set a hooktable on the served zone + */ +void +ns_test_serve_zone_sethooktab(ns_hooktable_t *hooktab); + /*% * Release the zone loaded by ns_test_serve_zone(). */ diff --git a/tests/libtest/ns.c b/tests/libtest/ns.c index 9deba3bfdf..55117d1408 100644 --- a/tests/libtest/ns.c +++ b/tests/libtest/ns.c @@ -139,6 +139,11 @@ teardown_server(void **state) { static dns_zone_t *served_zone = NULL; +void +ns_test_serve_zone_sethooktab(ns_hooktable_t *hooktab) { + dns_zone_sethooktable(served_zone, hooktab, ns_hooktable_free); +} + isc_result_t ns_test_serve_zone(const char *zonename, const char *filename, dns_view_t *view) { diff --git a/tests/ns/query_test.c b/tests/ns/query_test.c index d2fa95e8cf..84f4f36704 100644 --- a/tests/ns/query_test.c +++ b/tests/ns/query_test.c @@ -1480,11 +1480,273 @@ ISC_LOOP_TEST_IMPL(ns__query_hookasync_e2e) { isc_loopmgr_shutdown(); } +/* + * Tests covering the correctness of hook call order, i.e. hooks from a zone are + * called first, then hooks from a view, then the default hook table. And any + * hook returning NS_HOOK_RETURN interrupt the whole chain + */ +typedef struct { + ns_hook_action_t zonehookactions[2]; + ns_hook_action_t viewhookactions[2]; + ns_hook_action_t defaulthookactions[2]; + const char *expected; +} ns__query_hook_test_params_t; + +static void +ns__query_test_concat(char *base, const char *tail) { + char b[512]; + + strcpy(b, base); + snprintf(base, sizeof(b), "%s%s", b, tail); +} + +static ns_hookresult_t +ns__query_test_zonehook1(void *arg, void *data, isc_result_t *resultp) { + UNUSED(arg); + UNUSED(resultp); + + ns__query_test_concat(data, "z1"); + return NS_HOOK_CONTINUE; +} + +static ns_hookresult_t +ns__query_test_zonehook2(void *arg, void *data, isc_result_t *resultp) { + UNUSED(arg); + UNUSED(resultp); + + ns__query_test_concat(data, "z2"); + return NS_HOOK_RETURN; +} + +static ns_hookresult_t +ns__query_test_viewhook1(void *arg, void *data, isc_result_t *resultp) { + UNUSED(arg); + UNUSED(resultp); + + ns__query_test_concat(data, "v1"); + return NS_HOOK_CONTINUE; +} + +static ns_hookresult_t +ns__query_test_viewhook2(void *arg, void *data, isc_result_t *resultp) { + UNUSED(arg); + UNUSED(resultp); + + ns__query_test_concat(data, "v2"); + return NS_HOOK_RETURN; +} + +static ns_hookresult_t +ns__query_test_defaulthook1(void *arg, void *data, isc_result_t *resultp) { + UNUSED(arg); + UNUSED(resultp); + + ns__query_test_concat(data, "d1"); + return NS_HOOK_CONTINUE; +} + +static ns_hookresult_t +ns__query_test_defaulthook2(void *arg, void *data, isc_result_t *resultp) { + UNUSED(arg); + UNUSED(resultp); + + ns__query_test_concat(data, "d2"); + return NS_HOOK_RETURN; +} + +static bool +ns__query_test_setup_hooks(const ns_hook_t *h1, const ns_hook_t *h2, + ns_hooktable_t **tp) { + if (h1->action || h2->action) { + INSIST(*tp == NULL); + ns_hooktable_create(isc_g_mctx, tp); + + if (h1->action) { + ns_hook_add(*tp, isc_g_mctx, NS_QUERY_NXDOMAIN_BEGIN, + h1); + } + + if (h2->action) { + ns_hook_add(*tp, isc_g_mctx, NS_QUERY_NXDOMAIN_BEGIN, + h2); + } + + return true; + } + + return false; +} + +static void +ns__query_test_run_hookchain_test(const ns__query_hook_test_params_t *test) { + isc_result_t result; + query_ctx_t *qctx = NULL; + char buffer[512] = { 0 }; + ns_hooktable_t *zone_hooktab = NULL; + ns_hooktable_t *view_hooktab = NULL; + + const ns_test_qctx_create_params_t qctx_params = { + .qname = "idontexists.foo", + .qtype = dns_rdatatype_a, + .with_cache = true, + }; + + const ns_hook_t zonehook1 = { .action = test->zonehookactions[0], + .action_data = buffer }; + + const ns_hook_t zonehook2 = { .action = test->zonehookactions[1], + .action_data = buffer }; + + const ns_hook_t viewhook1 = { .action = test->viewhookactions[0], + .action_data = buffer }; + + const ns_hook_t viewhook2 = { .action = test->viewhookactions[1], + .action_data = buffer }; + + const ns_hook_t defaulthook1 = { .action = test->defaulthookactions[0], + .action_data = buffer }; + + const ns_hook_t defaulthook2 = { .action = test->defaulthookactions[1], + .action_data = buffer }; + + /* + * Create a fake query context + */ + result = ns_test_qctx_create(&qctx_params, &qctx); + INSIST(result == ISC_R_SUCCESS); + + /* + * Load a zone + */ + result = ns_test_serve_zone("foo", TESTS_DIR "/testdata/query/foo.db", + qctx->client->inner.view); + INSIST(result == ISC_R_SUCCESS); + + /* + * Attach hooks to the zone + */ + if (ns__query_test_setup_hooks(&zonehook1, &zonehook2, &zone_hooktab)) { + ns_test_serve_zone_sethooktab(zone_hooktab); + } + + /* + * Attach hooks to the view + */ + if (ns__query_test_setup_hooks(&viewhook1, &viewhook2, &view_hooktab)) { + qctx->client->inner.view->hooktable = view_hooktab; + } + + /* + * Setup the default hook table + */ + (void)ns__query_test_setup_hooks(&defaulthook1, &defaulthook2, + &ns__hook_table); + + /* + * Handling the response + */ + qctx->client->inner.sendcb = send_noop; + isc_nmhandle_attach(qctx->client->inner.handle, + &qctx->client->inner.reqhandle); + + /* + * Run the query + */ + ns__query_start(qctx); + ns_query_done(qctx); + + /* + * Result checking + */ + assert_string_equal(buffer, test->expected); + + /* + * Cleanup + */ + ns_test_qctx_destroy(&qctx); + ns_test_cleanup_zone(); + + if (ns__hook_table) { + ns_hooktable_free(isc_g_mctx, (void **)&ns__hook_table); + } + + if (view_hooktab) { + ns_hooktable_free(isc_g_mctx, (void **)&view_hooktab); + } +} + +ISC_LOOP_TEST_IMPL(ns__query_hookchain) { + const ns__query_hook_test_params_t tests[] = { + { { ns__query_test_zonehook1, ns__query_test_zonehook1 }, + { ns__query_test_viewhook1, ns__query_test_viewhook1 }, + { ns__query_test_defaulthook1, ns__query_test_defaulthook1 }, + "z1z1v1v1d1d1" }, + { { ns__query_test_zonehook1, ns__query_test_zonehook1 }, + { ns__query_test_viewhook1, ns__query_test_viewhook1 }, + { ns__query_test_defaulthook2, ns__query_test_defaulthook1 }, + "z1z1v1v1d2" }, + { { ns__query_test_zonehook2, ns__query_test_zonehook1 }, + { ns__query_test_viewhook1, ns__query_test_viewhook1 }, + { ns__query_test_defaulthook1, ns__query_test_defaulthook1 }, + "z2" }, + { { ns__query_test_zonehook1, ns__query_test_zonehook2 }, + { ns__query_test_viewhook1, ns__query_test_viewhook1 }, + { ns__query_test_defaulthook1, ns__query_test_defaulthook1 }, + "z1z2" }, + { { ns__query_test_zonehook1, ns__query_test_zonehook1 }, + { ns__query_test_viewhook2, ns__query_test_viewhook1 }, + { ns__query_test_defaulthook1, ns__query_test_defaulthook1 }, + "z1z1v2" }, + { { ns__query_test_zonehook1, ns__query_test_zonehook1 }, + { ns__query_test_viewhook1, ns__query_test_viewhook2 }, + { ns__query_test_defaulthook1, ns__query_test_defaulthook1 }, + "z1z1v1v2" }, + { { ns__query_test_zonehook1, NULL }, + { ns__query_test_viewhook1, ns__query_test_viewhook2 }, + { ns__query_test_defaulthook1, ns__query_test_defaulthook1 }, + "z1v1v2" }, + { { NULL, NULL }, + { ns__query_test_viewhook1, ns__query_test_viewhook2 }, + { ns__query_test_defaulthook1, ns__query_test_defaulthook1 }, + "v1v2" }, + { { NULL, NULL }, + { ns__query_test_viewhook1, NULL }, + { ns__query_test_defaulthook1, ns__query_test_defaulthook1 }, + "v1d1d1" }, + { { NULL, NULL }, + { ns__query_test_viewhook1, NULL }, + { NULL, NULL }, + "v1" }, + { { NULL, NULL }, + { ns__query_test_viewhook2, NULL }, + { NULL, NULL }, + "v2" }, + { { ns__query_test_zonehook1, NULL }, + { NULL, NULL }, + { NULL, NULL }, + "z1" }, + { { NULL, NULL }, + { NULL, NULL }, + { ns__query_test_defaulthook1, NULL }, + "d1" }, + { { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, "" }, + + }; + + for (size_t i = 0; i < ARRAY_SIZE(tests); i++) { + ns__query_test_run_hookchain_test(&tests[i]); + } + + isc_loop_teardown(isc_loop_main(), shutdown_interfacemgr, NULL); + isc_loopmgr_shutdown(); +} + ISC_TEST_LIST_START ISC_TEST_ENTRY_CUSTOM(ns__query_sfcache, setup_server, teardown_server) ISC_TEST_ENTRY_CUSTOM(ns__query_start, setup_server, teardown_server) ISC_TEST_ENTRY_CUSTOM(ns__query_hookasync, setup_server, teardown_server) ISC_TEST_ENTRY_CUSTOM(ns__query_hookasync_e2e, setup_server, teardown_server) +ISC_TEST_ENTRY_CUSTOM(ns__query_hookchain, setup_server, teardown_server) ISC_TEST_LIST_END ISC_TEST_MAIN