From 45d6379135504814ab723b57f0eb8be23393a51d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 09:24:22 +0200 Subject: Adding upstream version 1:9.16.44. Signed-off-by: Daniel Baumann --- lib/ns/tests/Kyuafile | 18 + lib/ns/tests/Makefile.in | 85 +++ lib/ns/tests/listenlist_test.c | 140 ++++ lib/ns/tests/notify_test.c | 172 +++++ lib/ns/tests/nstest.c | 1018 ++++++++++++++++++++++++++++++ lib/ns/tests/nstest.h | 165 +++++ lib/ns/tests/plugin_test.c | 209 ++++++ lib/ns/tests/query_test.c | 632 +++++++++++++++++++ lib/ns/tests/testdata/notify/notify1.msg | 3 + lib/ns/tests/testdata/notify/zone1.db | 26 + lib/ns/tests/testdata/query/foo.db | 20 + 11 files changed, 2488 insertions(+) create mode 100644 lib/ns/tests/Kyuafile create mode 100644 lib/ns/tests/Makefile.in create mode 100644 lib/ns/tests/listenlist_test.c create mode 100644 lib/ns/tests/notify_test.c create mode 100644 lib/ns/tests/nstest.c create mode 100644 lib/ns/tests/nstest.h create mode 100644 lib/ns/tests/plugin_test.c create mode 100644 lib/ns/tests/query_test.c create mode 100644 lib/ns/tests/testdata/notify/notify1.msg create mode 100644 lib/ns/tests/testdata/notify/zone1.db create mode 100644 lib/ns/tests/testdata/query/foo.db (limited to 'lib/ns/tests') diff --git a/lib/ns/tests/Kyuafile b/lib/ns/tests/Kyuafile new file mode 100644 index 0000000..22cdcff --- /dev/null +++ b/lib/ns/tests/Kyuafile @@ -0,0 +1,18 @@ +-- Copyright (C) Internet Systems Consortium, Inc. ("ISC") +-- +-- SPDX-License-Identifier: MPL-2.0 +-- +-- This Source Code Form is subject to the terms of the Mozilla Public +-- License, v. 2.0. If a copy of the MPL was not distributed with this +-- file, you can obtain one at https://mozilla.org/MPL/2.0/. +-- +-- See the COPYRIGHT file distributed with this work for additional +-- information regarding copyright ownership. + +syntax(2) +test_suite('bind9') + +tap_test_program{name='listenlist_test'} +tap_test_program{name='notify_test'} +tap_test_program{name='plugin_test'} +tap_test_program{name='query_test'} diff --git a/lib/ns/tests/Makefile.in b/lib/ns/tests/Makefile.in new file mode 100644 index 0000000..9adcb37 --- /dev/null +++ b/lib/ns/tests/Makefile.in @@ -0,0 +1,85 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +@BIND9_MAKE_INCLUDES@ + +WRAP_OPTIONS = -Wl,--wrap=isc__nmhandle_detach -Wl,--wrap=isc__nmhandle_attach + +CINCLUDES = -I. -Iinclude ${NS_INCLUDES} ${DNS_INCLUDES} ${ISC_INCLUDES} \ + ${OPENSSL_CFLAGS} \ + @CMOCKA_CFLAGS@ +CDEFINES = -DTESTS="\"${top_builddir}/lib/ns/tests/\"" -DNAMED_PLUGINDIR=\"${plugindir}\" + +ISCLIBS = ../../isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@ +ISCDEPLIBS = ../../isc/libisc.@A@ +DNSLIBS = ../../dns/libdns.@A@ @NO_LIBTOOL_DNSLIBS@ +DNSDEPLIBS = ../../dns/libdns.@A@ +NSLIBS = ../libns.@A@ +NSDEPLIBS = ../libns.@A@ + +LIBS = @LIBS@ @CMOCKA_LIBS@ + +SO_CFLAGS = @CFLAGS@ @SO_CFLAGS@ +SO_LDFLAGS = @LDFLAGS@ @SO_LDFLAGS@ + +OBJS = nstest.@O@ +SRCS = nstest.c \ + listenlist_test.c \ + notify_test.c \ + plugin_test.c \ + query_test.c + +SUBDIRS = +TARGETS = listenlist_test@EXEEXT@ \ + notify_test@EXEEXT@ \ + plugin_test@EXEEXT@ \ + query_test@EXEEXT@ + +LD_WRAP_TESTS=@LD_WRAP_TESTS@ + +@BIND9_MAKE_RULES@ + +listenlist_test@EXEEXT@: listenlist_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS} + if test "${LD_WRAP_TESTS}" = true -a -z "${LIBTOOL}"; then WRAP="${WRAP_OPTIONS}"; fi; \ + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} $${WRAP} -o $@ listenlist_test.@O@ nstest.@O@ \ + ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} + +notify_test@EXEEXT@: notify_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS} + if test "${LD_WRAP_TESTS}" = true -a -z "${LIBTOOL}"; then WRAP="${WRAP_OPTIONS}"; fi; \ + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} $${WRAP} -o $@ notify_test.@O@ nstest.@O@ \ + ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} + +plugin_test@EXEEXT@: plugin_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS} + if test "${LD_WRAP_TESTS}" = true -a -z "${LIBTOOL}"; then WRAP="${WRAP_OPTIONS}"; fi; \ + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} $${WRAP} -o $@ plugin_test.@O@ nstest.@O@ \ + ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} + +query_test@EXEEXT@: query_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS} + if test "${LD_WRAP_TESTS}" = true -a -z "${LIBTOOL}"; then WRAP="${WRAP_OPTIONS}"; fi; \ + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} $${WRAP} -o $@ query_test.@O@ nstest.@O@ \ + ${NSLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} + +unit:: + sh ${top_builddir}/unit/unittest.sh + +clean distclean:: + rm -f ${TARGETS} + rm -f atf.out diff --git a/lib/ns/tests/listenlist_test.c b/lib/ns/tests/listenlist_test.c new file mode 100644 index 0000000..72cb36e --- /dev/null +++ b/lib/ns/tests/listenlist_test.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include + +#include + +#include + +#include "nstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = ns_test_begin(NULL, true); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + ns_test_end(); + + return (0); +} + +/* test that ns_listenlist_default() works */ +static void +ns_listenlist_default_test(void **state) { + isc_result_t result; + in_port_t port = 5300 + isc_random8(); + ns_listenlist_t *list = NULL; + ns_listenelt_t *elt; + int count; + + UNUSED(state); + + result = ns_listenlist_default(mctx, port, -1, false, &list); + assert_int_equal(result, ISC_R_SUCCESS); + assert_non_null(list); + + assert_false(ISC_LIST_EMPTY(list->elts)); + + count = 0; + elt = ISC_LIST_HEAD(list->elts); + while (elt != NULL) { + ns_listenelt_t *next = ISC_LIST_NEXT(elt, link); + dns_acl_t *acl = NULL; + + dns_acl_attach(elt->acl, &acl); + ISC_LIST_UNLINK(list->elts, elt, link); + ns_listenelt_destroy(elt); + elt = next; + + assert_true(dns_acl_isnone(acl)); + dns_acl_detach(&acl); + count++; + } + + assert_true(ISC_LIST_EMPTY(list->elts)); + assert_int_equal(count, 1); + + ns_listenlist_detach(&list); + + result = ns_listenlist_default(mctx, port, -1, true, &list); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_false(ISC_LIST_EMPTY(list->elts)); + + /* This time just use ns_listenlist_detach() to destroy elements */ + count = 0; + elt = ISC_LIST_HEAD(list->elts); + while (elt != NULL) { + ns_listenelt_t *next = ISC_LIST_NEXT(elt, link); + assert_true(dns_acl_isany(elt->acl)); + elt = next; + count++; + } + + assert_int_equal(count, 1); + + ns_listenlist_detach(&list); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(ns_listenlist_default_test, + _setup, _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* HAVE_CMOCKA */ diff --git a/lib/ns/tests/notify_test.c b/lib/ns/tests/notify_test.c new file mode 100644 index 0000000..82880fb --- /dev/null +++ b/lib/ns/tests/notify_test.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#if HAVE_CMOCKA + +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "nstest.h" + +#if defined(USE_LIBTOOL) || LD_WRAP +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = ns_test_begin(NULL, true); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + ns_test_end(); + + return (0); +} + +static void +check_response(isc_buffer_t *buf) { + isc_result_t result; + dns_message_t *message = NULL; + char rcodebuf[20]; + isc_buffer_t b; + + dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &message); + + result = dns_message_parse(message, buf, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_init(&b, rcodebuf, sizeof(rcodebuf)); + result = dns_rcode_totext(message->rcode, &b); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_int_equal(message->rcode, dns_rcode_noerror); + + dns_message_detach(&message); +} + +/* test ns_notify_start() */ +static void +notify_start(void **state) { + isc_result_t result; + ns_client_t *client = NULL; + isc_nmhandle_t *handle = NULL; + dns_message_t *nmsg = NULL; + unsigned char ndata[4096]; + isc_buffer_t nbuf; + size_t nsize; + + UNUSED(state); + + result = ns_test_getclient(NULL, false, &client); + assert_int_equal(result, ISC_R_SUCCESS); + + result = ns_test_makeview("view", false, &client->view); + assert_int_equal(result, ISC_R_SUCCESS); + + result = ns_test_serve_zone("example.com", "testdata/notify/zone1.db", + client->view); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Create a NOTIFY message by parsing a file in testdata. + * (XXX: use better message mocking method when available.) + */ + + result = ns_test_getdata("testdata/notify/notify1.msg", ndata, + sizeof(ndata), &nsize); + assert_int_equal(result, ISC_R_SUCCESS); + isc_buffer_init(&nbuf, ndata, nsize); + isc_buffer_add(&nbuf, nsize); + + dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &nmsg); + + result = dns_message_parse(nmsg, &nbuf, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Set up client object with this message and test the NOTIFY + * handler. + */ + if (client->message != NULL) { + dns_message_detach(&client->message); + } + client->message = nmsg; + nmsg = NULL; + client->sendcb = check_response; + ns_notify_start(client, client->handle); + + /* + * Clean up + */ + ns_test_cleanup_zone(); + + handle = client->handle; + isc_nmhandle_detach(&client->handle); + isc_nmhandle_detach(&handle); +} +#endif /* if defined(USE_LIBTOOL) || LD_WRAP */ + +int +main(void) { +#if defined(USE_LIBTOOL) || LD_WRAP + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(notify_start, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +#else /* if defined(USE_LIBTOOL) || LD_WRAP */ + print_message("1..0 # Skip notify_test requires libtool or LD_WRAP\n"); +#endif /* if defined(USE_LIBTOOL) || LD_WRAP */ +} +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* HAVE_CMOCKA */ diff --git a/lib/ns/tests/nstest.c b/lib/ns/tests/nstest.c new file mode 100644 index 0000000..df15408 --- /dev/null +++ b/lib/ns/tests/nstest.c @@ -0,0 +1,1018 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include "nstest.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +isc_mem_t *mctx = NULL; +isc_log_t *lctx = NULL; +isc_taskmgr_t *taskmgr = NULL; +isc_task_t *maintask = NULL; +isc_timermgr_t *timermgr = NULL; +isc_socketmgr_t *socketmgr = NULL; +isc_nm_t *netmgr = NULL; +dns_zonemgr_t *zonemgr = NULL; +dns_dispatchmgr_t *dispatchmgr = NULL; +ns_clientmgr_t *clientmgr = NULL; +ns_interfacemgr_t *interfacemgr = NULL; +ns_server_t *sctx = NULL; +bool app_running = false; +int ncpus; +bool debug_mem_record = true; +static atomic_bool run_managers = false; + +static bool dst_active = false; +static bool test_running = false; + +static dns_zone_t *served_zone = NULL; + +/* + * We don't want to use netmgr-based client accounting, we need to emulate it. + */ +atomic_uint_fast32_t client_refs[32]; +atomic_uintptr_t client_addrs[32]; + +void +__wrap_isc__nmhandle_attach(isc_nmhandle_t *source, isc_nmhandle_t **targetp); +void +__wrap_isc__nmhandle_detach(isc_nmhandle_t **handlep); + +void +__wrap_isc__nmhandle_attach(isc_nmhandle_t *source, isc_nmhandle_t **targetp) { + ns_client_t *client = (ns_client_t *)source; + int i; + + for (i = 0; i < 32; i++) { + if (atomic_load(&client_addrs[i]) == (uintptr_t)client) { + break; + } + } + INSIST(i < 32); + INSIST(atomic_load(&client_refs[i]) > 0); + + atomic_fetch_add(&client_refs[i], 1); + + *targetp = source; + return; +} + +void +__wrap_isc__nmhandle_detach(isc_nmhandle_t **handlep) { + isc_nmhandle_t *handle = *handlep; + ns_client_t *client = (ns_client_t *)handle; + int i; + + *handlep = NULL; + + for (i = 0; i < 32; i++) { + if (atomic_load(&client_addrs[i]) == (uintptr_t)client) { + break; + } + } + INSIST(i < 32); + + if (atomic_fetch_sub(&client_refs[i], 1) == 1) { + dns_view_detach(&client->view); + client->state = 4; + ns__client_reset_cb(client); + ns__client_put_cb(client); + isc_mem_put(mctx, client, sizeof(ns_client_t)); + } + + return; +} + +#ifdef USE_LIBTOOL +void +isc__nmhandle_attach(isc_nmhandle_t *source, isc_nmhandle_t **targetp) { + __wrap_isc__nmhandle_attach(source, targetp); +} +void +isc__nmhandle_detach(isc_nmhandle_t **handle) { + __wrap_isc__nmhandle_detach(handle); +} +#endif /* USE_LIBTOOL */ + +/* + * Logging categories: this needs to match the list in lib/ns/log.c. + */ +static isc_logcategory_t categories[] = { { "", 0 }, + { "client", 0 }, + { "network", 0 }, + { "update", 0 }, + { "queries", 0 }, + { "unmatched", 0 }, + { "update-security", 0 }, + { "query-errors", 0 }, + { NULL, 0 } }; + +static isc_result_t +matchview(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr, + dns_message_t *message, dns_aclenv_t *env, isc_result_t *sigresultp, + dns_view_t **viewp) { + UNUSED(srcaddr); + UNUSED(destaddr); + UNUSED(message); + UNUSED(env); + UNUSED(sigresultp); + UNUSED(viewp); + + return (ISC_R_NOTIMPLEMENTED); +} + +/* + * These need to be shut down from a running task. + */ +static atomic_bool shutdown_done = false; +static void +shutdown_managers(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + if (interfacemgr != NULL) { + ns_interfacemgr_shutdown(interfacemgr); + ns_interfacemgr_detach(&interfacemgr); + } + + if (dispatchmgr != NULL) { + dns_dispatchmgr_destroy(&dispatchmgr); + } + + atomic_store(&shutdown_done, true); + atomic_store(&run_managers, false); + + isc_event_free(&event); +} + +static void +cleanup_managers(void) { + atomic_store(&shutdown_done, false); + + if (maintask != NULL) { + isc_task_shutdown(maintask); + isc_task_destroy(&maintask); + } + + while (atomic_load(&run_managers) && !atomic_load(&shutdown_done)) { + /* + * There's no straightforward way to determine + * whether all the clients have shut down, so + * we'll just sleep for a bit and hope. + */ + ns_test_nap(500000); + } + + if (sctx != NULL) { + ns_server_detach(&sctx); + } + if (interfacemgr != NULL) { + ns_interfacemgr_detach(&interfacemgr); + } + if (socketmgr != NULL) { + isc_socketmgr_destroy(&socketmgr); + } + + isc_managers_destroy(netmgr == NULL ? NULL : &netmgr, + taskmgr == NULL ? NULL : &taskmgr); + + if (timermgr != NULL) { + isc_timermgr_destroy(&timermgr); + } + if (app_running) { + isc_app_finish(); + } +} + +static void +scan_interfaces(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + ns_interfacemgr_scan(interfacemgr, true); + isc_event_free(&event); +} + +static isc_result_t +create_managers(void) { + isc_result_t result; + in_port_t port = 5300 + isc_random8(); + ns_listenlist_t *listenon = NULL; + isc_event_t *event = NULL; + ncpus = isc_os_ncpus(); + + CHECK(isc_managers_create(mctx, ncpus, 0, &netmgr, &taskmgr)); + CHECK(isc_task_create_bound(taskmgr, 0, &maintask, 0)); + isc_taskmgr_setexcltask(taskmgr, maintask); + CHECK(isc_task_onshutdown(maintask, shutdown_managers, NULL)); + + CHECK(isc_timermgr_create(mctx, &timermgr)); + + CHECK(isc_socketmgr_create(mctx, &socketmgr)); + + CHECK(ns_server_create(mctx, matchview, &sctx)); + + CHECK(dns_dispatchmgr_create(mctx, &dispatchmgr)); + + CHECK(ns_interfacemgr_create(mctx, sctx, taskmgr, timermgr, socketmgr, + netmgr, dispatchmgr, maintask, ncpus, NULL, + ncpus, &interfacemgr)); + + CHECK(ns_listenlist_default(mctx, port, -1, true, &listenon)); + ns_interfacemgr_setlistenon4(interfacemgr, listenon); + ns_listenlist_detach(&listenon); + + event = isc_event_allocate(mctx, maintask, ISC_TASKEVENT_TEST, + scan_interfaces, NULL, sizeof(isc_event_t)); + isc_task_send(maintask, &event); + + /* + * There's no straightforward way to determine + * whether the interfaces have been scanned, + * we'll just sleep for a bit and hope. + */ + ns_test_nap(500000); + ns_interface_t *ifp = ns__interfacemgr_getif(interfacemgr); + clientmgr = ifp->clientmgr; + + atomic_store(&run_managers, true); + + return (ISC_R_SUCCESS); + +cleanup: + cleanup_managers(); + return (result); +} + +isc_result_t +ns_test_begin(FILE *logfile, bool start_managers) { + isc_result_t result; + + INSIST(!test_running); + test_running = true; + + if (start_managers) { + isc_resourcevalue_t files; + + /* + * The 'listenlist_test', 'notify_test', and 'query_test' + * tests need more than 256 descriptors with 8 cpus. + * Bump up to at least 1024. + */ + result = isc_resource_getcurlimit(isc_resource_openfiles, + &files); + if (result == ISC_R_SUCCESS) { + if (files < 1024) { + files = 1024; + (void)isc_resource_setlimit( + isc_resource_openfiles, files); + } + } + CHECK(isc_app_start()); + } + if (debug_mem_record) { + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + } + + INSIST(mctx == NULL); + isc_mem_create(&mctx); + + if (!dst_active) { + CHECK(dst_lib_init(mctx, NULL)); + dst_active = true; + } + + if (logfile != NULL) { + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + + INSIST(lctx == NULL); + isc_log_create(mctx, &lctx, &logconfig); + + isc_log_registercategories(lctx, categories); + isc_log_setcontext(lctx); + dns_log_init(lctx); + dns_log_setcontext(lctx); + + destination.file.stream = logfile; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC, + ISC_LOG_DYNAMIC, &destination, 0); + CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL)); + } + + dns_result_register(); + + if (start_managers) { + CHECK(create_managers()); + } + + /* + * atf-run changes us to a /tmp directory, so tests + * that access test data files must first chdir to the proper + * location. + */ + if (chdir(TESTS) == -1) { + CHECK(ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); + +cleanup: + ns_test_end(); + return (result); +} + +void +ns_test_end(void) { + cleanup_managers(); + + dst_lib_destroy(); + dst_active = false; + + if (lctx != NULL) { + isc_log_destroy(&lctx); + } + + if (mctx != NULL) { + isc_mem_destroy(&mctx); + } + + test_running = false; +} + +isc_result_t +ns_test_makeview(const char *name, bool with_cache, dns_view_t **viewp) { + dns_cache_t *cache = NULL; + dns_view_t *view = NULL; + isc_result_t result; + + CHECK(dns_view_create(mctx, dns_rdataclass_in, name, &view)); + + if (with_cache) { + CHECK(dns_cache_create(mctx, mctx, taskmgr, timermgr, + dns_rdataclass_in, "", "rbt", 0, NULL, + &cache)); + dns_view_setcache(view, cache, false); + /* + * Reference count for "cache" is now at 2, so decrement it in + * order for the cache to be automatically freed when "view" + * gets freed. + */ + dns_cache_detach(&cache); + } + + *viewp = view; + + return (ISC_R_SUCCESS); + +cleanup: + if (view != NULL) { + dns_view_detach(&view); + } + return (result); +} + +/* + * Create a zone with origin 'name', return a pointer to the zone object in + * 'zonep'. If 'view' is set, add the zone to that view; otherwise, create + * a new view for the purpose. + * + * If the created view is going to be needed by the caller subsequently, + * then 'keepview' should be set to true; this will prevent the view + * from being detached. In this case, the caller is responsible for + * detaching the view. + */ +isc_result_t +ns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view, + bool keepview) { + isc_result_t result; + dns_zone_t *zone = NULL; + isc_buffer_t buffer; + dns_fixedname_t fixorigin; + dns_name_t *origin; + + if (view == NULL) { + CHECK(dns_view_create(mctx, dns_rdataclass_in, "view", &view)); + } else if (!keepview) { + keepview = true; + } + + zone = *zonep; + if (zone == NULL) { + CHECK(dns_zone_create(&zone, mctx)); + } + + isc_buffer_constinit(&buffer, name, strlen(name)); + isc_buffer_add(&buffer, strlen(name)); + origin = dns_fixedname_initname(&fixorigin); + CHECK(dns_name_fromtext(origin, &buffer, dns_rootname, 0, NULL)); + CHECK(dns_zone_setorigin(zone, origin)); + dns_zone_setview(zone, view); + dns_zone_settype(zone, dns_zone_primary); + dns_zone_setclass(zone, view->rdclass); + dns_view_addzone(view, zone); + + if (!keepview) { + dns_view_detach(&view); + } + + *zonep = zone; + + return (ISC_R_SUCCESS); + +cleanup: + if (zone != NULL) { + dns_zone_detach(&zone); + } + if (view != NULL) { + dns_view_detach(&view); + } + return (result); +} + +isc_result_t +ns_test_setupzonemgr(void) { + isc_result_t result; + REQUIRE(zonemgr == NULL); + + result = dns_zonemgr_create(mctx, taskmgr, timermgr, socketmgr, + &zonemgr); + return (result); +} + +isc_result_t +ns_test_managezone(dns_zone_t *zone) { + isc_result_t result; + REQUIRE(zonemgr != NULL); + + result = dns_zonemgr_setsize(zonemgr, 1); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_zonemgr_managezone(zonemgr, zone); + return (result); +} + +void +ns_test_releasezone(dns_zone_t *zone) { + REQUIRE(zonemgr != NULL); + dns_zonemgr_releasezone(zonemgr, zone); +} + +void +ns_test_closezonemgr(void) { + REQUIRE(zonemgr != NULL); + + dns_zonemgr_shutdown(zonemgr); + dns_zonemgr_detach(&zonemgr); +} + +isc_result_t +ns_test_serve_zone(const char *zonename, const char *filename, + dns_view_t *view) { + isc_result_t result; + dns_db_t *db = NULL; + + /* + * Prepare zone structure for further processing. + */ + result = ns_test_makezone(zonename, &served_zone, view, true); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Start zone manager. + */ + result = ns_test_setupzonemgr(); + if (result != ISC_R_SUCCESS) { + goto free_zone; + } + + /* + * Add the zone to the zone manager. + */ + result = ns_test_managezone(served_zone); + if (result != ISC_R_SUCCESS) { + goto close_zonemgr; + } + + view->nocookieudp = 512; + + /* + * Set path to the master file for the zone and then load it. + */ + dns_zone_setfile(served_zone, filename, dns_masterformat_text, + &dns_master_style_default); + result = dns_zone_load(served_zone, false); + if (result != ISC_R_SUCCESS) { + goto release_zone; + } + + /* + * The zone should now be loaded; test it. + */ + result = dns_zone_getdb(served_zone, &db); + if (result != ISC_R_SUCCESS) { + goto release_zone; + } + if (db != NULL) { + dns_db_detach(&db); + } + + return (ISC_R_SUCCESS); + +release_zone: + ns_test_releasezone(served_zone); +close_zonemgr: + ns_test_closezonemgr(); +free_zone: + dns_zone_detach(&served_zone); + + return (result); +} + +void +ns_test_cleanup_zone(void) { + ns_test_releasezone(served_zone); + ns_test_closezonemgr(); + + dns_zone_detach(&served_zone); +} + +isc_result_t +ns_test_getclient(ns_interface_t *ifp0, bool tcp, ns_client_t **clientp) { + isc_result_t result; + ns_client_t *client = isc_mem_get(mctx, sizeof(ns_client_t)); + int i; + + UNUSED(ifp0); + UNUSED(tcp); + + result = ns__client_setup(client, clientmgr, true); + + for (i = 0; i < 32; i++) { + if (atomic_load(&client_addrs[i]) == (uintptr_t)NULL || + atomic_load(&client_addrs[i]) == (uintptr_t)client) + { + break; + } + } + REQUIRE(i < 32); + + atomic_store(&client_refs[i], 2); + atomic_store(&client_addrs[i], (uintptr_t)client); + client->handle = (isc_nmhandle_t *)client; /* Hack */ + *clientp = client; + + return (result); +} + +/*% + * Synthesize a DNS message based on supplied QNAME, QTYPE and flags, then + * parse it and store the results in client->message. + */ +static isc_result_t +attach_query_msg_to_client(ns_client_t *client, const char *qnamestr, + dns_rdatatype_t qtype, unsigned int qflags) { + dns_rdataset_t *qrdataset = NULL; + dns_message_t *message = NULL; + unsigned char query[65536]; + dns_name_t *qname = NULL; + isc_buffer_t querybuf; + dns_compress_t cctx; + isc_result_t result; + + REQUIRE(client != NULL); + REQUIRE(qnamestr != NULL); + + /* + * Create a new DNS message holding a query. + */ + dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &message); + + /* + * Set query ID to a random value. + */ + message->id = isc_random16(); + + /* + * Set query flags as requested by the caller. + */ + message->flags = qflags; + + /* + * Allocate structures required to construct the query. + */ + result = dns_message_gettemprdataset(message, &qrdataset); + if (result != ISC_R_SUCCESS) { + goto destroy_message; + } + result = dns_message_gettempname(message, &qname); + if (result != ISC_R_SUCCESS) { + goto put_rdataset; + } + + /* + * Convert "qnamestr" to a DNS name, create a question rdataset of + * class IN and type "qtype", link the two and add the result to the + * QUESTION section of the query. + */ + result = dns_name_fromstring(qname, qnamestr, 0, mctx); + if (result != ISC_R_SUCCESS) { + goto put_name; + } + dns_rdataset_makequestion(qrdataset, dns_rdataclass_in, qtype); + ISC_LIST_APPEND(qname->list, qrdataset, link); + dns_message_addname(message, qname, DNS_SECTION_QUESTION); + + /* + * Render the query. + */ + dns_compress_init(&cctx, -1, mctx); + isc_buffer_init(&querybuf, query, sizeof(query)); + result = dns_message_renderbegin(message, &cctx, &querybuf); + if (result != ISC_R_SUCCESS) { + goto destroy_message; + } + result = dns_message_rendersection(message, DNS_SECTION_QUESTION, 0); + if (result != ISC_R_SUCCESS) { + goto destroy_message; + } + result = dns_message_renderend(message); + if (result != ISC_R_SUCCESS) { + goto destroy_message; + } + dns_compress_invalidate(&cctx); + + /* + * Destroy the created message as it was rendered into "querybuf" and + * the latter is all we are going to need from now on. + */ + dns_message_detach(&message); + + /* + * Parse the rendered query, storing results in client->message. + */ + isc_buffer_first(&querybuf); + return (dns_message_parse(client->message, &querybuf, 0)); + +put_name: + dns_message_puttempname(message, &qname); +put_rdataset: + dns_message_puttemprdataset(message, &qrdataset); +destroy_message: + dns_message_detach(&message); + + return (result); +} + +/*% + * A hook action which stores the query context pointed to by "arg" at + * "data". Causes execution to be interrupted at hook insertion + * point. + */ +static ns_hookresult_t +extract_qctx(void *arg, void *data, isc_result_t *resultp) { + query_ctx_t **qctxp; + query_ctx_t *qctx; + + REQUIRE(arg != NULL); + REQUIRE(data != NULL); + REQUIRE(resultp != NULL); + + /* + * qctx is a stack variable in lib/ns/query.c. Its contents need to be + * duplicated or otherwise they will become invalidated once the stack + * gets unwound. + */ + qctx = isc_mem_get(mctx, sizeof(*qctx)); + if (qctx != NULL) { + memmove(qctx, (query_ctx_t *)arg, sizeof(*qctx)); + } + + qctxp = (query_ctx_t **)data; + /* + * If memory allocation failed, the supplied pointer will simply be set + * to NULL. We rely on the user of this hook to react properly. + */ + *qctxp = qctx; + *resultp = ISC_R_UNSET; + + return (NS_HOOK_RETURN); +} + +/*% + * Initialize a query context for "client" and store it in "qctxp". + * + * Requires: + * + * \li "client->message" to hold a parsed DNS query. + */ +static isc_result_t +create_qctx_for_client(ns_client_t *client, query_ctx_t **qctxp) { + ns_hooktable_t *saved_hook_table = NULL, *query_hooks = NULL; + const ns_hook_t hook = { + .action = extract_qctx, + .action_data = qctxp, + }; + + REQUIRE(client != NULL); + REQUIRE(qctxp != NULL); + REQUIRE(*qctxp == NULL); + + /* + * Call ns_query_start() to initialize a query context for given + * client, but first hook into query_setup() so that we can just + * extract an initialized query context, without kicking off any + * further processing. Make sure we do not overwrite any previously + * set hooks. + */ + + ns_hooktable_create(mctx, &query_hooks); + ns_hook_add(query_hooks, mctx, NS_QUERY_SETUP, &hook); + + saved_hook_table = ns__hook_table; + ns__hook_table = query_hooks; + + ns_query_start(client, client->handle); + + ns__hook_table = saved_hook_table; + ns_hooktable_free(mctx, (void **)&query_hooks); + + isc_nmhandle_detach(&client->reqhandle); + + if (*qctxp == NULL) { + return (ISC_R_NOMEMORY); + } else { + return (ISC_R_SUCCESS); + } +} + +isc_result_t +ns_test_qctx_create(const ns_test_qctx_create_params_t *params, + query_ctx_t **qctxp) { + ns_client_t *client = NULL; + isc_result_t result; + isc_nmhandle_t *handle = NULL; + + REQUIRE(params != NULL); + REQUIRE(params->qname != NULL); + REQUIRE(qctxp != NULL); + REQUIRE(*qctxp == NULL); + + /* + * Allocate and initialize a client structure. + */ + result = ns_test_getclient(NULL, false, &client); + if (result != ISC_R_SUCCESS) { + return (result); + } + TIME_NOW(&client->tnow); + + /* + * Every client needs to belong to a view. + */ + result = ns_test_makeview("view", params->with_cache, &client->view); + if (result != ISC_R_SUCCESS) { + goto detach_client; + } + + /* + * Synthesize a DNS query using given QNAME, QTYPE and flags, storing + * it in client->message. + */ + result = attach_query_msg_to_client(client, params->qname, + params->qtype, params->qflags); + if (result != ISC_R_SUCCESS) { + goto detach_view; + } + + /* + * Allow recursion for the client. As NS_CLIENTATTR_RA normally gets + * set in ns__client_request(), i.e. earlier than the unit tests hook + * into the call chain, just set it manually. + */ + client->attributes |= NS_CLIENTATTR_RA; + + /* + * Create a query context for a client sending the previously + * synthesized query. + */ + result = create_qctx_for_client(client, qctxp); + if (result != ISC_R_SUCCESS) { + goto detach_query; + } + + /* + * The reference count for "client" is now at 2, so we need to + * decrement it in order for it to drop to zero when "qctx" gets + * destroyed. + */ + handle = client->handle; + isc_nmhandle_detach(&handle); + + return (ISC_R_SUCCESS); + +detach_query: + dns_message_detach(&client->message); +detach_view: + dns_view_detach(&client->view); +detach_client: + isc_nmhandle_detach(&client->handle); + + return (result); +} + +void +ns_test_qctx_destroy(query_ctx_t **qctxp) { + query_ctx_t *qctx; + + REQUIRE(qctxp != NULL); + REQUIRE(*qctxp != NULL); + + qctx = *qctxp; + *qctxp = NULL; + + if (qctx->zone != NULL) { + dns_zone_detach(&qctx->zone); + } + if (qctx->db != NULL) { + dns_db_detach(&qctx->db); + } + if (qctx->client != NULL) { + isc_nmhandle_detach(&qctx->client->handle); + } + + isc_mem_put(mctx, qctx, sizeof(*qctx)); +} + +ns_hookresult_t +ns_test_hook_catch_call(void *arg, void *data, isc_result_t *resultp) { + UNUSED(arg); + UNUSED(data); + + *resultp = ISC_R_UNSET; + + return (NS_HOOK_RETURN); +} + +/* + * Sleep for 'usec' microseconds. + */ +void +ns_test_nap(uint32_t usec) { + struct timespec ts; + + ts.tv_sec = usec / 1000000; + ts.tv_nsec = (usec % 1000000) * 1000; + nanosleep(&ts, NULL); +} + +isc_result_t +ns_test_loaddb(dns_db_t **db, dns_dbtype_t dbtype, const char *origin, + const char *testfile) { + isc_result_t result; + dns_fixedname_t fixed; + dns_name_t *name; + + name = dns_fixedname_initname(&fixed); + + result = dns_name_fromstring(name, origin, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_create(mctx, "rbt", name, dbtype, dns_rdataclass_in, 0, + NULL, db); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_db_load(*db, testfile, dns_masterformat_text, 0); + return (result); +} + +static int +fromhex(char c) { + if (c >= '0' && c <= '9') { + return (c - '0'); + } else if (c >= 'a' && c <= 'f') { + return (c - 'a' + 10); + } else if (c >= 'A' && c <= 'F') { + return (c - 'A' + 10); + } + + printf("bad input format: %02x\n", c); + exit(3); +} + +isc_result_t +ns_test_getdata(const char *file, unsigned char *buf, size_t bufsiz, + size_t *sizep) { + isc_result_t result; + unsigned char *bp; + char *rp, *wp; + char s[BUFSIZ]; + size_t len, i; + FILE *f = NULL; + int n; + + result = isc_stdio_open(file, "r", &f); + if (result != ISC_R_SUCCESS) { + return (result); + } + + bp = buf; + while (fgets(s, sizeof(s), f) != NULL) { + rp = s; + wp = s; + len = 0; + while (*rp != '\0') { + if (*rp == '#') { + break; + } + if (*rp != ' ' && *rp != '\t' && *rp != '\r' && + *rp != '\n') + { + *wp++ = *rp; + len++; + } + rp++; + } + if (len == 0U) { + continue; + } + if (len % 2 != 0U) { + CHECK(ISC_R_UNEXPECTEDEND); + } + if (len > bufsiz * 2) { + CHECK(ISC_R_NOSPACE); + } + rp = s; + for (i = 0; i < len; i += 2) { + n = fromhex(*rp++); + n *= 16; + n += fromhex(*rp++); + *bp++ = n; + } + } + + *sizep = bp - buf; + + result = ISC_R_SUCCESS; + +cleanup: + isc_stdio_close(f); + return (result); +} diff --git a/lib/ns/tests/nstest.h b/lib/ns/tests/nstest.h new file mode 100644 index 0000000..87a26a6 --- /dev/null +++ b/lib/ns/tests/nstest.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +typedef struct ns_test_id { + const char *description; + int lineno; +} ns_test_id_t; + +#define NS_TEST_ID(desc) \ + { \ + .description = desc, .lineno = __LINE__ \ + } + +#define CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +extern isc_mem_t *mctx; +extern isc_log_t *lctx; +extern isc_taskmgr_t *taskmgr; +extern isc_task_t *maintask; +extern isc_timermgr_t *timermgr; +extern isc_socketmgr_t *socketmgr; +extern dns_zonemgr_t *zonemgr; +extern dns_dispatchmgr_t *dispatchmgr; +extern ns_clientmgr_t *clientmgr; +extern ns_interfacemgr_t *interfacemgr; +extern ns_server_t *sctx; +extern bool app_running; +extern int ncpus; +extern bool debug_mem_record; + +#ifdef NETMGR_TRACE +#define FLARG \ + , const char *file __attribute__((unused)), \ + unsigned int line __attribute__((unused)), \ + const char *func __attribute__((unused)) +#else +#define FLARG +#endif + +isc_result_t +ns_test_begin(FILE *logfile, bool create_managers); + +void +ns_test_end(void); + +/*% + * Create a view. If "with_cache" is set to true, a cache database will + * also be created and attached to the created view. + */ +isc_result_t +ns_test_makeview(const char *name, bool with_cache, dns_view_t **viewp); + +isc_result_t +ns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view, + bool keepview); + +isc_result_t +ns_test_setupzonemgr(void); + +isc_result_t +ns_test_managezone(dns_zone_t *zone); + +void +ns_test_releasezone(dns_zone_t *zone); + +void +ns_test_closezonemgr(void); + +/*% + * Load data for zone "zonename" from file "filename" and start serving it to + * clients matching "view". Only one zone loaded using this function can be + * served at any given time. + */ +isc_result_t +ns_test_serve_zone(const char *zonename, const char *filename, + dns_view_t *view); + +/*% + * Release the zone loaded by ns_test_serve_zone(). + */ +void +ns_test_cleanup_zone(void); + +void +ns_test_nap(uint32_t usec); + +isc_result_t +ns_test_loaddb(dns_db_t **db, dns_dbtype_t dbtype, const char *origin, + const char *testfile); + +isc_result_t +ns_test_getdata(const char *file, unsigned char *buf, size_t bufsiz, + size_t *sizep); + +isc_result_t +ns_test_getclient(ns_interface_t *ifp0, bool tcp, ns_client_t **clientp); + +/*% + * Structure containing parameters for ns_test_qctx_create(). + */ +typedef struct ns_test_qctx_create_params { + const char *qname; + dns_rdatatype_t qtype; + unsigned int qflags; + bool with_cache; +} ns_test_qctx_create_params_t; + +/*% + * Prepare a query context identical with one that would be prepared if a query + * with given QNAME, QTYPE and flags was received from a client. Recursion is + * assumed to be allowed for this client. If "with_cache" is set to true, + * a cache database will be created and associated with the view matching the + * incoming query. + */ +isc_result_t +ns_test_qctx_create(const ns_test_qctx_create_params_t *params, + query_ctx_t **qctxp); + +/*% + * Destroy a query context created by ns_test_qctx_create(). + */ +void +ns_test_qctx_destroy(query_ctx_t **qctxp); + +/*% + * A hook callback interrupting execution at given hook's insertion point. + */ +ns_hookresult_t +ns_test_hook_catch_call(void *arg, void *data, isc_result_t *resultp); diff --git a/lib/ns/tests/plugin_test.c b/lib/ns/tests/plugin_test.c new file mode 100644 index 0000000..7870f7a --- /dev/null +++ b/lib/ns/tests/plugin_test.c @@ -0,0 +1,209 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include + +ISC_PLATFORM_NORETURN_PRE void +_fail(const char *const file, const int line) ISC_PLATFORM_NORETURN_POST; + +#include + +#include "nstest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = ns_test_begin(NULL, false); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + if (*state != NULL) { + isc_mem_free(mctx, *state); + } + + ns_test_end(); + + return (0); +} + +/*% + * Structure containing parameters for run_full_path_test(). + */ +typedef struct { + const ns_test_id_t id; /* libns test identifier */ + const char *input; /* source string - plugin name or path + * */ + size_t output_size; /* size of target char array to + * allocate */ + isc_result_t result; /* expected return value */ + const char *output; /* expected output string */ +} ns_plugin_expandpath_test_params_t; + +/*% + * Perform a single ns_plugin_expandpath() check using given parameters. + */ +static void +run_full_path_test(const ns_plugin_expandpath_test_params_t *test, + void **state) { + char **target = (char **)state; + isc_result_t result; + + REQUIRE(test != NULL); + REQUIRE(test->id.description != NULL); + REQUIRE(test->input != NULL); + REQUIRE(test->result != ISC_R_SUCCESS || test->output != NULL); + + /* + * Prepare a target buffer of given size. Store it in 'state' so that + * it can get cleaned up by _teardown() if the test fails. + */ + *target = isc_mem_allocate(mctx, test->output_size); + + /* + * Call ns_plugin_expandpath(). + */ + result = ns_plugin_expandpath(test->input, *target, test->output_size); + + /* + * Check return value. + */ + if (result != test->result) { + fail_msg("# test \"%s\" on line %d: " + "expected result %d (%s), got %d (%s)", + test->id.description, test->id.lineno, test->result, + isc_result_totext(test->result), result, + isc_result_totext(result)); + } + + /* + * Check output string if return value indicates success. + */ + if (result == ISC_R_SUCCESS && strcmp(*target, test->output) != 0) { + fail_msg("# test \"%s\" on line %d: " + "expected output \"%s\", got \"%s\"", + test->id.description, test->id.lineno, test->output, + *target); + } + + isc_mem_free(mctx, *target); +} + +/* test ns_plugin_expandpath() */ +static void +ns_plugin_expandpath_test(void **state) { + size_t i; + + const ns_plugin_expandpath_test_params_t tests[] = { + { + NS_TEST_ID("correct use with an absolute path"), + .input = "/usr/lib/named/foo.so", + .output_size = PATH_MAX, + .result = ISC_R_SUCCESS, + .output = "/usr/lib/named/foo.so", + }, + { + NS_TEST_ID("correct use with a relative path"), + .input = "../../foo.so", + .output_size = PATH_MAX, + .result = ISC_R_SUCCESS, + .output = "../../foo.so", + }, + { + NS_TEST_ID("correct use with a filename"), + .input = "foo.so", + .output_size = PATH_MAX, + .result = ISC_R_SUCCESS, +#ifndef WIN32 + .output = NAMED_PLUGINDIR "/foo.so", +#else /* ifndef WIN32 */ + .output = "foo.so", +#endif /* ifndef WIN32 */ + }, + { + NS_TEST_ID("no space at all in target buffer"), + .input = "/usr/lib/named/foo.so", + .output_size = 0, + .result = ISC_R_NOSPACE, + }, + { + NS_TEST_ID("target buffer too small to fit input"), + .input = "/usr/lib/named/foo.so", + .output_size = 1, + .result = ISC_R_NOSPACE, + }, + { + NS_TEST_ID("target buffer too small to fit NULL byte"), + .input = "/foo.so", + .output_size = 7, + .result = ISC_R_NOSPACE, + }, +#ifndef WIN32 + { + NS_TEST_ID("target buffer too small to fit full path"), + .input = "foo.so", + .output_size = 7, + .result = ISC_R_NOSPACE, + }, +#endif /* ifndef WIN32 */ + }; + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + run_full_path_test(&tests[i], state); + } +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(ns_plugin_expandpath_test, + _setup, _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +} +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/ns/tests/query_test.c b/lib/ns/tests/query_test.c new file mode 100644 index 0000000..b3e8fc1 --- /dev/null +++ b/lib/ns/tests/query_test.c @@ -0,0 +1,632 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include + +#if HAVE_CMOCKA + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include +#include +#include + +#include "nstest.h" + +#if defined(USE_LIBTOOL) || LD_WRAP +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = ns_test_begin(NULL, true); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + ns_test_end(); + + return (0); +} + +/***** +***** ns__query_sfcache() tests +*****/ + +/*% + * Structure containing parameters for ns__query_sfcache_test(). + */ +typedef struct { + const ns_test_id_t id; /* libns test identifier */ + unsigned int qflags; /* query flags */ + bool cache_entry_present; /* whether a SERVFAIL + * cache entry + * matching the query + * should be + * present */ + uint32_t cache_entry_flags; /* NS_FAILCACHE_* flags to + * set for + * the SERVFAIL cache entry + * */ + bool servfail_expected; /* whether a cached + * SERVFAIL is + * expected to be returned + * */ +} ns__query_sfcache_test_params_t; + +/*% + * Perform a single ns__query_sfcache() check using given parameters. + */ +static void +run_sfcache_test(const ns__query_sfcache_test_params_t *test) { + ns_hooktable_t *query_hooks = NULL; + query_ctx_t *qctx = NULL; + isc_result_t result; + const ns_hook_t hook = { + .action = ns_test_hook_catch_call, + }; + + REQUIRE(test != NULL); + REQUIRE(test->id.description != NULL); + REQUIRE(test->cache_entry_present || test->cache_entry_flags == 0); + + /* + * Interrupt execution if ns_query_done() is called. + */ + + ns_hooktable_create(mctx, &query_hooks); + ns_hook_add(query_hooks, mctx, NS_QUERY_DONE_BEGIN, &hook); + ns__hook_table = query_hooks; + + /* + * Construct a query context for a ./NS query with given flags. + */ + { + const ns_test_qctx_create_params_t qctx_params = { + .qname = ".", + .qtype = dns_rdatatype_ns, + .qflags = test->qflags, + .with_cache = true, + }; + + result = ns_test_qctx_create(&qctx_params, &qctx); + assert_int_equal(result, ISC_R_SUCCESS); + } + + /* + * If this test wants a SERVFAIL cache entry matching the query to + * exist, create it. + */ + if (test->cache_entry_present) { + isc_interval_t hour; + isc_time_t expire; + + isc_interval_set(&hour, 3600, 0); + result = isc_time_nowplusinterval(&expire, &hour); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_badcache_add(qctx->client->view->failcache, dns_rootname, + dns_rdatatype_ns, true, + test->cache_entry_flags, &expire); + } + + /* + * Check whether ns__query_sfcache() behaves as expected. + */ + ns__query_sfcache(qctx); + + if (test->servfail_expected) { + if (qctx->result != DNS_R_SERVFAIL) { + fail_msg("# test \"%s\" on line %d: " + "expected SERVFAIL, got %s", + test->id.description, test->id.lineno, + isc_result_totext(qctx->result)); + } + } else { + if (qctx->result != ISC_R_SUCCESS) { + fail_msg("# test \"%s\" on line %d: " + "expected success, got %s", + test->id.description, test->id.lineno, + isc_result_totext(qctx->result)); + } + } + + /* + * Clean up. + */ + ns_test_qctx_destroy(&qctx); + ns_hooktable_free(mctx, (void **)&query_hooks); +} + +/* test ns__query_sfcache() */ +static void +ns__query_sfcache_test(void **state) { + size_t i; + + const ns__query_sfcache_test_params_t tests[] = { + /* + * Sanity check for an empty SERVFAIL cache. + */ + { + NS_TEST_ID("query: RD=1, CD=0; cache: empty"), + .qflags = DNS_MESSAGEFLAG_RD, + .cache_entry_present = false, + .servfail_expected = false, + }, + /* + * Query: RD=1, CD=0. Cache entry: CD=0. Should SERVFAIL. + */ + { + NS_TEST_ID("query: RD=1, CD=0; cache: CD=0"), + .qflags = DNS_MESSAGEFLAG_RD, + .cache_entry_present = true, + .cache_entry_flags = 0, + .servfail_expected = true, + }, + /* + * Query: RD=1, CD=1. Cache entry: CD=0. Should not SERVFAIL: + * failed validation should not influence CD=1 queries. + */ + { + NS_TEST_ID("query: RD=1, CD=1; cache: CD=0"), + .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD, + .cache_entry_present = true, + .cache_entry_flags = 0, + .servfail_expected = false, + }, + /* + * Query: RD=1, CD=1. Cache entry: CD=1. Should SERVFAIL: + * SERVFAIL responses elicited by CD=1 queries can be + * "replayed" for other CD=1 queries during the lifetime of the + * SERVFAIL cache entry. + */ + { + NS_TEST_ID("query: RD=1, CD=1; cache: CD=1"), + .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD, + .cache_entry_present = true, + .cache_entry_flags = NS_FAILCACHE_CD, + .servfail_expected = true, + }, + /* + * Query: RD=1, CD=0. Cache entry: CD=1. Should SERVFAIL: if + * a CD=1 query elicited a SERVFAIL, a CD=0 query for the same + * QNAME and QTYPE will SERVFAIL as well. + */ + { + NS_TEST_ID("query: RD=1, CD=0; cache: CD=1"), + .qflags = DNS_MESSAGEFLAG_RD, + .cache_entry_present = true, + .cache_entry_flags = NS_FAILCACHE_CD, + .servfail_expected = true, + }, + /* + * Query: RD=0, CD=0. Cache entry: CD=0. Should not SERVFAIL + * despite a matching entry being present as the SERVFAIL cache + * should not be consulted for non-recursive queries. + */ + { + NS_TEST_ID("query: RD=0, CD=0; cache: CD=0"), + .qflags = 0, + .cache_entry_present = true, + .cache_entry_flags = 0, + .servfail_expected = false, + }, + }; + + UNUSED(state); + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + run_sfcache_test(&tests[i]); + } +} + +/***** +***** ns__query_start() tests +*****/ + +/*% + * Structure containing parameters for ns__query_start_test(). + */ +typedef struct { + const ns_test_id_t id; /* libns test identifier */ + const char *qname; /* QNAME */ + dns_rdatatype_t qtype; /* QTYPE */ + unsigned int qflags; /* query flags */ + bool disable_name_checks; /* if set to true, owner + * name + * checks will + * be disabled for the + * view created + * */ + bool recursive_service; /* if set to true, the view + * created will + * have a cache + * database + * attached */ + const char *auth_zone_origin; /* origin name of the zone + * the + * created view will be + * authoritative for */ + const char *auth_zone_path; /* path to load the + * authoritative + * zone from */ + enum { /* expected result: */ + NS__QUERY_START_R_INVALID, + NS__QUERY_START_R_REFUSE, /* query should be REFUSED */ + NS__QUERY_START_R_CACHE, /* query should be answered from + * cache */ + NS__QUERY_START_R_AUTH, /* query should be answered using + * authoritative data */ + } expected_result; +} ns__query_start_test_params_t; + +/*% + * Perform a single ns__query_start() check using given parameters. + */ +static void +run_start_test(const ns__query_start_test_params_t *test) { + ns_hooktable_t *query_hooks = NULL; + query_ctx_t *qctx = NULL; + isc_result_t result; + const ns_hook_t hook = { + .action = ns_test_hook_catch_call, + }; + + REQUIRE(test != NULL); + REQUIRE(test->id.description != NULL); + REQUIRE((test->auth_zone_origin == NULL && + test->auth_zone_path == NULL) || + (test->auth_zone_origin != NULL && + test->auth_zone_path != NULL)); + + /* + * Interrupt execution if query_lookup() or ns_query_done() is called. + */ + ns_hooktable_create(mctx, &query_hooks); + ns_hook_add(query_hooks, mctx, NS_QUERY_LOOKUP_BEGIN, &hook); + ns_hook_add(query_hooks, mctx, NS_QUERY_DONE_BEGIN, &hook); + ns__hook_table = query_hooks; + + /* + * Construct a query context using the supplied parameters. + */ + { + const ns_test_qctx_create_params_t qctx_params = { + .qname = test->qname, + .qtype = test->qtype, + .qflags = test->qflags, + .with_cache = test->recursive_service, + }; + result = ns_test_qctx_create(&qctx_params, &qctx); + assert_int_equal(result, ISC_R_SUCCESS); + } + + /* + * Enable view->checknames by default, disable if requested. + */ + qctx->client->view->checknames = !test->disable_name_checks; + + /* + * Load zone from file and attach it to the client's view, if + * requested. + */ + if (test->auth_zone_path != NULL) { + result = ns_test_serve_zone(test->auth_zone_origin, + test->auth_zone_path, + qctx->client->view); + assert_int_equal(result, ISC_R_SUCCESS); + } + + /* + * Check whether ns__query_start() behaves as expected. + */ + ns__query_start(qctx); + + switch (test->expected_result) { + case NS__QUERY_START_R_REFUSE: + if (qctx->result != DNS_R_REFUSED) { + fail_msg("# test \"%s\" on line %d: " + "expected REFUSED, got %s", + test->id.description, test->id.lineno, + isc_result_totext(qctx->result)); + } + if (qctx->zone != NULL) { + fail_msg("# test \"%s\" on line %d: " + "no zone was expected to be attached to " + "query context, but some was", + test->id.description, test->id.lineno); + } + if (qctx->db != NULL) { + fail_msg("# test \"%s\" on line %d: " + "no database was expected to be attached to " + "query context, but some was", + test->id.description, test->id.lineno); + } + break; + case NS__QUERY_START_R_CACHE: + if (qctx->result != ISC_R_SUCCESS) { + fail_msg("# test \"%s\" on line %d: " + "expected success, got %s", + test->id.description, test->id.lineno, + isc_result_totext(qctx->result)); + } + if (qctx->zone != NULL) { + fail_msg("# test \"%s\" on line %d: " + "no zone was expected to be attached to " + "query context, but some was", + test->id.description, test->id.lineno); + } + if (qctx->db == NULL || qctx->db != qctx->client->view->cachedb) + { + fail_msg("# test \"%s\" on line %d: " + "cache database was expected to be " + "attached to query context, but it was not", + test->id.description, test->id.lineno); + } + break; + case NS__QUERY_START_R_AUTH: + if (qctx->result != ISC_R_SUCCESS) { + fail_msg("# test \"%s\" on line %d: " + "expected success, got %s", + test->id.description, test->id.lineno, + isc_result_totext(qctx->result)); + } + if (qctx->zone == NULL) { + fail_msg("# test \"%s\" on line %d: " + "a zone was expected to be attached to query " + "context, but it was not", + test->id.description, test->id.lineno); + } + if (qctx->db == qctx->client->view->cachedb) { + fail_msg("# test \"%s\" on line %d: " + "cache database was not expected to be " + "attached to query context, but it is", + test->id.description, test->id.lineno); + } + break; + case NS__QUERY_START_R_INVALID: + fail_msg("# test \"%s\" on line %d has no expected result set", + test->id.description, test->id.lineno); + break; + default: + UNREACHABLE(); + } + + /* + * Clean up. + */ + if (test->auth_zone_path != NULL) { + ns_test_cleanup_zone(); + } + ns_test_qctx_destroy(&qctx); + ns_hooktable_free(mctx, (void **)&query_hooks); +} + +/* test ns__query_start() */ +static void +ns__query_start_test(void **state) { + size_t i; + + const ns__query_start_test_params_t tests[] = { + /* + * Recursive foo/A query to a server without recursive service + * and no zones configured. Query should be REFUSED. + */ + { + NS_TEST_ID("foo/A, no cache, no auth"), + .qname = "foo", + .qtype = dns_rdatatype_a, + .qflags = DNS_MESSAGEFLAG_RD, + .recursive_service = false, + .expected_result = NS__QUERY_START_R_REFUSE, + }, + /* + * Recursive foo/A query to a server with recursive service and + * no zones configured. Query should be answered from cache. + */ + { + NS_TEST_ID("foo/A, cache, no auth"), + .qname = "foo", + .qtype = dns_rdatatype_a, + .recursive_service = true, + .expected_result = NS__QUERY_START_R_CACHE, + }, + /* + * Recursive foo/A query to a server with recursive service and + * zone "foo" configured. Query should be answered from + * authoritative data. + */ + { + NS_TEST_ID("foo/A, RD=1, cache, auth for foo"), + .qname = "foo", + .qtype = dns_rdatatype_a, + .qflags = DNS_MESSAGEFLAG_RD, + .recursive_service = true, + .auth_zone_origin = "foo", + .auth_zone_path = "testdata/query/foo.db", + .expected_result = NS__QUERY_START_R_AUTH, + }, + /* + * Recursive bar/A query to a server without recursive service + * and zone "foo" configured. Query should be REFUSED. + */ + { + NS_TEST_ID("bar/A, RD=1, no cache, auth for foo"), + .qname = "bar", + .qtype = dns_rdatatype_a, + .qflags = DNS_MESSAGEFLAG_RD, + .recursive_service = false, + .auth_zone_origin = "foo", + .auth_zone_path = "testdata/query/foo.db", + .expected_result = NS__QUERY_START_R_REFUSE, + }, + /* + * Recursive bar/A query to a server with recursive service and + * zone "foo" configured. Query should be answered from + * cache. + */ + { + NS_TEST_ID("bar/A, RD=1, cache, auth for foo"), + .qname = "bar", + .qtype = dns_rdatatype_a, + .qflags = DNS_MESSAGEFLAG_RD, + .recursive_service = true, + .auth_zone_origin = "foo", + .auth_zone_path = "testdata/query/foo.db", + .expected_result = NS__QUERY_START_R_CACHE, + }, + /* + * Recursive bar.foo/DS query to a server with recursive + * service and zone "foo" configured. Query should be answered + * from authoritative data. + */ + { + NS_TEST_ID("bar.foo/DS, RD=1, cache, auth for foo"), + .qname = "bar.foo", + .qtype = dns_rdatatype_ds, + .qflags = DNS_MESSAGEFLAG_RD, + .recursive_service = true, + .auth_zone_origin = "foo", + .auth_zone_path = "testdata/query/foo.db", + .expected_result = NS__QUERY_START_R_AUTH, + }, + /* + * Non-recursive bar.foo/DS query to a server with recursive + * service and zone "foo" configured. Query should be answered + * from authoritative data. + */ + { + NS_TEST_ID("bar.foo/DS, RD=0, cache, auth for foo"), + .qname = "bar.foo", + .qtype = dns_rdatatype_ds, + .qflags = 0, + .recursive_service = true, + .auth_zone_origin = "foo", + .auth_zone_path = "testdata/query/foo.db", + .expected_result = NS__QUERY_START_R_AUTH, + }, + /* + * Recursive foo/DS query to a server with recursive service + * and zone "foo" configured. Query should be answered from + * cache. + */ + { + NS_TEST_ID("foo/DS, RD=1, cache, auth for foo"), + .qname = "foo", + .qtype = dns_rdatatype_ds, + .qflags = DNS_MESSAGEFLAG_RD, + .recursive_service = true, + .auth_zone_origin = "foo", + .auth_zone_path = "testdata/query/foo.db", + .expected_result = NS__QUERY_START_R_CACHE, + }, + /* + * Non-recursive foo/DS query to a server with recursive + * service and zone "foo" configured. Query should be answered + * from authoritative data. + */ + { + NS_TEST_ID("foo/DS, RD=0, cache, auth for foo"), + .qname = "foo", + .qtype = dns_rdatatype_ds, + .qflags = 0, + .recursive_service = true, + .auth_zone_origin = "foo", + .auth_zone_path = "testdata/query/foo.db", + .expected_result = NS__QUERY_START_R_AUTH, + }, + /* + * Recursive _foo/A query to a server with recursive service, + * no zones configured and owner name checks disabled. Query + * should be answered from cache. + */ + { + NS_TEST_ID("_foo/A, cache, no auth, name checks off"), + .qname = "_foo", + .qtype = dns_rdatatype_a, + .qflags = DNS_MESSAGEFLAG_RD, + .disable_name_checks = true, + .recursive_service = true, + .expected_result = NS__QUERY_START_R_CACHE, + }, + /* + * Recursive _foo/A query to a server with recursive service, + * no zones configured and owner name checks enabled. Query + * should be REFUSED. + */ + { + NS_TEST_ID("_foo/A, cache, no auth, name checks on"), + .qname = "_foo", + .qtype = dns_rdatatype_a, + .qflags = DNS_MESSAGEFLAG_RD, + .disable_name_checks = false, + .recursive_service = true, + .expected_result = NS__QUERY_START_R_REFUSE, + }, + }; + + UNUSED(state); + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + run_start_test(&tests[i]); + } +} +#endif /* if defined(USE_LIBTOOL) || LD_WRAP */ + +int +main(void) { +#if defined(USE_LIBTOOL) || LD_WRAP + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(ns__query_sfcache_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(ns__query_start_test, _setup, + _teardown), + }; + + return (cmocka_run_group_tests(tests, NULL, NULL)); +#else /* if defined(USE_LIBTOOL) || LD_WRAP */ + print_message("1..0 # Skip query_test requires libtool or LD_WRAP\n"); +#endif /* if defined(USE_LIBTOOL) || LD_WRAP */ +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + + return (SKIPPED_TEST_EXIT_CODE); +} + +#endif /* HAVE_CMOCKA */ diff --git a/lib/ns/tests/testdata/notify/notify1.msg b/lib/ns/tests/testdata/notify/notify1.msg new file mode 100644 index 0000000..a84193b --- /dev/null +++ b/lib/ns/tests/testdata/notify/notify1.msg @@ -0,0 +1,3 @@ +# notify for example.com +10 a6 10 10 00 01 00 00 00 00 00 00 07 65 78 61 +6d 70 6c 65 03 63 6f 6d 00 00 06 00 01 diff --git a/lib/ns/tests/testdata/notify/zone1.db b/lib/ns/tests/testdata/notify/zone1.db new file mode 100644 index 0000000..b218d6d --- /dev/null +++ b/lib/ns/tests/testdata/notify/zone1.db @@ -0,0 +1,26 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 1000 +@ in soa localhost. postmaster.localhost. ( + 1993050801 ;serial + 3600 ;refresh + 1800 ;retry + 604800 ;expiration + 3600 ) ;minimum + in ns ns.example.com. + in ns ns2.example.com. + in ns ns3.example.com. +ns in a 10.0.0.1 +ns2 in a 10.0.0.2 +ns3 in a 10.0.0.3 + +a in a 1.2.3.4 diff --git a/lib/ns/tests/testdata/query/foo.db b/lib/ns/tests/testdata/query/foo.db new file mode 100644 index 0000000..5fb2c42 --- /dev/null +++ b/lib/ns/tests/testdata/query/foo.db @@ -0,0 +1,20 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 3600 +@ IN SOA localhost. postmaster.localhost. ( + 1 ;serial + 3600 ;refresh + 1800 ;retry + 604800 ;expiration + 3600 ) ;minimum + IN NS ns +ns IN A 127.0.0.1 -- cgit v1.2.3