diff --git a/bin/tests/system/Makefile.am b/bin/tests/system/Makefile.am index 6c4e1eb541..a4a3abb5a5 100644 --- a/bin/tests/system/Makefile.am +++ b/bin/tests/system/Makefile.am @@ -9,7 +9,7 @@ dist-hook: sed -n "s|^Would remove \(.*\)|$(distdir)/\1|p" | \ xargs -I{} rm -rf "{}" -SUBDIRS = dyndb/driver dlzexternal/driver +SUBDIRS = dyndb/driver dlzexternal/driver hooks/driver AM_CPPFLAGS += \ $(LIBISC_CFLAGS) @@ -109,6 +109,7 @@ TESTS += \ include-multiplecfg \ inline \ integrity \ + hooks \ keepalive \ legacy \ limits \ diff --git a/bin/tests/system/hooks/clean.sh b/bin/tests/system/hooks/clean.sh new file mode 100644 index 0000000000..271efd68f9 --- /dev/null +++ b/bin/tests/system/hooks/clean.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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. + +rm -f */named.run +rm -f */named.conf +rm -f */named.memstats diff --git a/bin/tests/system/hooks/driver/Makefile.am b/bin/tests/system/hooks/driver/Makefile.am new file mode 100644 index 0000000000..08187bf789 --- /dev/null +++ b/bin/tests/system/hooks/driver/Makefile.am @@ -0,0 +1,13 @@ +include $(top_srcdir)/Makefile.top + +AM_CPPFLAGS += \ + $(LIBISC_CFLAGS) \ + $(LIBDNS_CFLAGS) \ + $(LIBNS_CFLAGS) \ + $(LIBISCCFG_CFLAGS) + +hooks_LTLIBRARIES = test-async.la +hooksdir = $(abs_builddir) + +test_async_la_SOURCES = test-async.c +test_async_la_LDFLAGS = -avoid-version -module -shared -export-dynamic diff --git a/bin/tests/system/hooks/driver/test-async.c b/bin/tests/system/hooks/driver/test-async.c new file mode 100644 index 0000000000..8449c9b140 --- /dev/null +++ b/bin/tests/system/hooks/driver/test-async.c @@ -0,0 +1,364 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 */ + +/* aliases for the exported symbols */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) { \ + goto cleanup; \ + } \ + } while (0) + +/* + * Persistent data for use by this module. This will be associated + * with client object address in the hash table, and will remain + * accessible until the client object is detached. + */ +typedef struct async_instance { + ns_plugin_t *module; + isc_mem_t *mctx; + isc_mempool_t *datapool; + isc_ht_t *ht; + isc_mutex_t hlock; + isc_log_t *lctx; +} async_instance_t; + +typedef struct state { + bool async; + ns_hook_resevent_t *rev; + ns_hookpoint_t hookpoint; + isc_result_t origresult; +} state_t; + +/* + * Forward declarations of functions referenced in install_hooks(). + */ +static ns_hookresult_t +async_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp); +static ns_hookresult_t +async_query_done_begin(void *arg, void *cbdata, isc_result_t *resp); +static ns_hookresult_t +async_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp); + +/*% + * Register the functions to be called at each hook point in 'hooktable', using + * memory context 'mctx' for allocating copies of stack-allocated structures + * passed to ns_hook_add(). Make sure 'inst' will be passed as the 'cbdata' + * argument to every callback. + */ +static void +install_hooks(ns_hooktable_t *hooktable, isc_mem_t *mctx, + async_instance_t *inst) { + const ns_hook_t async_init = { + .action = async_qctx_initialize, + .action_data = inst, + }; + const ns_hook_t async_donebegin = { + .action = async_query_done_begin, + .action_data = inst, + }; + const ns_hook_t async_destroy = { + .action = async_qctx_destroy, + .action_data = inst, + }; + + ns_hook_add(hooktable, mctx, NS_QUERY_QCTX_INITIALIZED, &async_init); + ns_hook_add(hooktable, mctx, NS_QUERY_DONE_BEGIN, &async_donebegin); + ns_hook_add(hooktable, mctx, NS_QUERY_QCTX_DESTROYED, &async_destroy); +} + +static void +logmsg(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_INFO, fmt, ap); + va_end(ap); +} + +/** +** Mandatory plugin API functions: +** +** - plugin_destroy +** - plugin_register +** - plugin_version +** - plugin_check +**/ + +/* + * Called by ns_plugin_register() to initialize the plugin and + * register hook functions into the view hook table. + */ +isc_result_t +plugin_register(const char *parameters, const void *cfg, const char *cfg_file, + unsigned long cfg_line, isc_mem_t *mctx, isc_log_t *lctx, + void *actx, ns_hooktable_t *hooktable, void **instp) { + async_instance_t *inst = NULL; + isc_result_t result; + + UNUSED(parameters); + UNUSED(cfg); + UNUSED(actx); + + isc_log_write(lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_INFO, + "registering 'test-async' module from %s:%lu", cfg_file, + cfg_line); + + inst = isc_mem_get(mctx, sizeof(*inst)); + *inst = (async_instance_t){ .mctx = NULL }; + isc_mem_attach(mctx, &inst->mctx); + + isc_mempool_create(mctx, sizeof(state_t), &inst->datapool); + CHECK(isc_ht_init(&inst->ht, mctx, 16)); + isc_mutex_init(&inst->hlock); + + /* + * Set hook points in the view's hooktable. + */ + install_hooks(hooktable, mctx, inst); + + *instp = inst; + + return (ISC_R_SUCCESS); + +cleanup: + if (result != ISC_R_SUCCESS && inst != NULL) { + plugin_destroy((void **)&inst); + } + + return (result); +} + +isc_result_t +plugin_check(const char *parameters, const void *cfg, const char *cfg_file, + unsigned long cfg_line, isc_mem_t *mctx, isc_log_t *lctx, + void *actx) { + UNUSED(parameters); + UNUSED(cfg); + UNUSED(cfg_file); + UNUSED(cfg_line); + UNUSED(mctx); + UNUSED(lctx); + UNUSED(actx); + + return (ISC_R_SUCCESS); +} + +/* + * Called by ns_plugins_free(); frees memory allocated by + * the module when it was registered. + */ +void +plugin_destroy(void **instp) { + async_instance_t *inst = (async_instance_t *)*instp; + + if (inst->ht != NULL) { + isc_ht_destroy(&inst->ht); + isc_mutex_destroy(&inst->hlock); + } + if (inst->datapool != NULL) { + isc_mempool_destroy(&inst->datapool); + } + + isc_mem_putanddetach(&inst->mctx, inst, sizeof(*inst)); + *instp = NULL; + + return; +} + +/* + * Returns plugin API version for compatibility checks. + */ +int +plugin_version(void) { + return (NS_PLUGIN_VERSION); +} + +static state_t * +client_state_get(const query_ctx_t *qctx, async_instance_t *inst) { + state_t *state = NULL; + isc_result_t result; + + LOCK(&inst->hlock); + result = isc_ht_find(inst->ht, (const unsigned char *)&qctx->client, + sizeof(qctx->client), (void **)&state); + UNLOCK(&inst->hlock); + + return (result == ISC_R_SUCCESS ? state : NULL); +} + +static void +client_state_create(const query_ctx_t *qctx, async_instance_t *inst) { + state_t *state = NULL; + isc_result_t result; + + state = isc_mempool_get(inst->datapool); + if (state == NULL) { + return; + } + + LOCK(&inst->hlock); + result = isc_ht_add(inst->ht, (const unsigned char *)&qctx->client, + sizeof(qctx->client), state); + UNLOCK(&inst->hlock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); +} + +static void +client_state_destroy(const query_ctx_t *qctx, async_instance_t *inst) { + state_t *state = client_state_get(qctx, inst); + isc_result_t result; + + if (state == NULL) { + return; + } + + LOCK(&inst->hlock); + result = isc_ht_delete(inst->ht, (const unsigned char *)&qctx->client, + sizeof(qctx->client)); + UNLOCK(&inst->hlock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc_mempool_put(inst->datapool, state); +} + +static ns_hookresult_t +async_qctx_initialize(void *arg, void *cbdata, isc_result_t *resp) { + query_ctx_t *qctx = (query_ctx_t *)arg; + async_instance_t *inst = (async_instance_t *)cbdata; + state_t *state = NULL; + + logmsg("qctx init hook"); + *resp = ISC_R_UNSET; + + state = client_state_get(qctx, inst); + if (state == NULL) { + client_state_create(qctx, inst); + } + + return (NS_HOOK_CONTINUE); +} + +static void +cancelasync(ns_hookasync_t *hctx) { + UNUSED(hctx); + logmsg("cancelasync"); +} + +static void +destroyasync(ns_hookasync_t **ctxp) { + ns_hookasync_t *ctx = *ctxp; + + logmsg("destroyasync"); + *ctxp = NULL; + isc_mem_putanddetach(&ctx->mctx, ctx, sizeof(*ctx)); +} + +static isc_result_t +doasync(query_ctx_t *qctx, isc_mem_t *mctx, void *arg, isc_task_t *task, + isc_taskaction_t action, void *evarg, ns_hookasync_t **ctxp) { + ns_hook_resevent_t *rev = (ns_hook_resevent_t *)isc_event_allocate( + mctx, task, NS_EVENT_HOOKASYNCDONE, action, evarg, + sizeof(*rev)); + ns_hookasync_t *ctx = isc_mem_get(mctx, sizeof(*ctx)); + state_t *state = (state_t *)arg; + + logmsg("doasync"); + *ctx = (ns_hookasync_t){ .mctx = NULL }; + isc_mem_attach(mctx, &ctx->mctx); + ctx->cancel = cancelasync; + ctx->destroy = destroyasync; + + rev->hookpoint = state->hookpoint; + rev->origresult = state->origresult; + qctx->result = DNS_R_NOTIMP; + rev->saved_qctx = qctx; + rev->ctx = ctx; + + state->rev = rev; + + isc_task_send(task, (isc_event_t **)&rev); + + *ctxp = ctx; + return (ISC_R_SUCCESS); +} + +static ns_hookresult_t +async_query_done_begin(void *arg, void *cbdata, isc_result_t *resp) { + query_ctx_t *qctx = (query_ctx_t *)arg; + async_instance_t *inst = (async_instance_t *)cbdata; + state_t *state = client_state_get(qctx, inst); + + UNUSED(qctx); + UNUSED(cbdata); + UNUSED(state); + + logmsg("done begin hook"); + if (state->async) { + /* resuming */ + state->async = false; + return (NS_HOOK_CONTINUE); + } + + /* initial call */ + state->async = true; + state->hookpoint = NS_QUERY_DONE_BEGIN; + state->origresult = *resp; + ns_query_hookasync(qctx, doasync, state); + return (NS_HOOK_RETURN); +} + +static ns_hookresult_t +async_qctx_destroy(void *arg, void *cbdata, isc_result_t *resp) { + query_ctx_t *qctx = (query_ctx_t *)arg; + async_instance_t *inst = (async_instance_t *)cbdata; + + logmsg("qctx destroy hook"); + *resp = ISC_R_UNSET; + + if (!qctx->detach_client) { + return (NS_HOOK_CONTINUE); + } + + client_state_destroy(qctx, inst); + + return (NS_HOOK_CONTINUE); +} diff --git a/bin/tests/system/hooks/ns1/example.db b/bin/tests/system/hooks/ns1/example.db new file mode 100644 index 0000000000..ab0218c020 --- /dev/null +++ b/bin/tests/system/hooks/ns1/example.db @@ -0,0 +1,19 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; 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 http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 120 +@ SOA ns.unsigned. hostmaster.ns.unsigned. ( 1 3600 1200 604800 60 ) +@ NS ns +@ MX 10 mx + +ns A 10.53.0.1 + AAAA fd92:7065:b8e:ffff::1 + +a A 1.1.1.1 +mx A 2.2.2.2 diff --git a/bin/tests/system/hooks/ns1/named.conf.in b/bin/tests/system/hooks/ns1/named.conf.in new file mode 100644 index 0000000000..6696675ccd --- /dev/null +++ b/bin/tests/system/hooks/ns1/named.conf.in @@ -0,0 +1,39 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + recursion no; + dnssec-validation yes; + notify yes; + minimal-responses no; +}; + +plugin query "../driver/.libs/test-async.so"; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-sha256; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "example.com" { + type primary; + file "example.db"; +}; diff --git a/bin/tests/system/hooks/setup.sh b/bin/tests/system/hooks/setup.sh new file mode 100644 index 0000000000..110e0e3446 --- /dev/null +++ b/bin/tests/system/hooks/setup.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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. + +. ../conf.sh + +copy_setports ns1/named.conf.in ns1/named.conf diff --git a/bin/tests/system/hooks/tests.sh b/bin/tests/system/hooks/tests.sh new file mode 100644 index 0000000000..284980c5bb --- /dev/null +++ b/bin/tests/system/hooks/tests.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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. + +. ../conf.sh + +status=0 +n=0 + +rm -f dig.out.* + +DIGOPTS="+tcp +noadd +nosea +nostat +nocmd -p ${PORT}" +RNDCCMD="$RNDC -c ../common/rndc.conf -p ${CONTROLPORT} -s" + +n=$((n+1)) +echo_i "checking asynchronous hook action resumes correctly ($n)" +ret=0 +$DIG $DIGOPTS example.com @10.53.0.1 > dig.out.ns1.test$n || ret=1 +# the test-async plugin changes the status of any postiive answer to NOTIMP +grep -q "status: NOTIMP" dig.out.ns1.test$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + + + +echo_i "exit status: $status" +[ $status -eq 0 ] || exit 1 diff --git a/configure.ac b/configure.ac index 625bc08b08..47459bc118 100644 --- a/configure.ac +++ b/configure.ac @@ -1633,7 +1633,8 @@ AC_CONFIG_FILES([bin/tests/Makefile bin/tests/system/Makefile bin/tests/system/conf.sh bin/tests/system/dyndb/driver/Makefile - bin/tests/system/dlzexternal/driver/Makefile]) + bin/tests/system/dlzexternal/driver/Makefile + bin/tests/system/hooks/driver/Makefile]) AC_CONFIG_FILES([bin/tests/system/ifconfig.sh], [chmod +x bin/tests/system/ifconfig.sh]) diff --git a/util/copyrights b/util/copyrights index dd95e6bb6c..a29a86d66d 100644 --- a/util/copyrights +++ b/util/copyrights @@ -478,6 +478,10 @@ ./bin/tests/system/glue/noglue.good X 2000,2001,2018,2019,2020 ./bin/tests/system/glue/setup.sh SH 2001,2004,2007,2012,2016,2018,2019,2020 ./bin/tests/system/glue/tests.sh SH 2000,2001,2003,2004,2007,2012,2013,2016,2017,2018,2019,2020 +./bin/tests/system/hooks/clean.sh SH 2020 +./bin/tests/system/hooks/driver/test-async.c C 2020 +./bin/tests/system/hooks/setup.sh SH 2020 +./bin/tests/system/hooks/tests.sh SH 2020 ./bin/tests/system/idna/clean.sh SH 2018,2019,2020 ./bin/tests/system/idna/setup.sh SH 2018,2019,2020 ./bin/tests/system/idna/tests.sh SH 2018,2019,2020