Merge branch '2141-asynchrony-support-for-bind-9-query-plugins' into 'main'

Resolve "asynchrony support for BIND 9 query plugins"

Closes #2141

See merge request isc-projects/bind9!4407
This commit is contained in:
Evan Hunt 2020-11-25 00:59:19 +00:00
commit 7d1e2e6692
21 changed files with 2017 additions and 62 deletions

View file

@ -1,3 +1,9 @@
5537. [func] The query plugin mechanism has been extended
to support asynchronous operations. For example, a
plugin can now trigger recursion and resume
processing when it's complete. Thanks to Jinmei
Tatuya at Infoblox. [GL #2141]
5536. [func] Dig can now report the DNS64 prefixes in use
(+dns64prefix). [GL #1154]

View file

@ -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 \

View file

@ -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

View file

@ -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

View file

@ -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 <inttypes.h>
#include <stdbool.h>
#include <string.h>
#include <isc/buffer.h>
#include <isc/hash.h>
#include <isc/ht.h>
#include <isc/lib.h>
#include <isc/log.h>
#include <isc/mem.h>
#include <isc/netaddr.h>
#include <isc/result.h>
#include <isc/types.h>
#include <isc/util.h>
#include <dns/result.h>
#include <ns/client.h>
#include <ns/events.h>
#include <ns/hooks.h>
#include <ns/log.h>
#include <ns/query.h>
#include <ns/types.h>
#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);
}

View file

@ -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

View file

@ -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";
};

View file

@ -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

View file

@ -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

View file

@ -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])

View file

@ -9,6 +9,7 @@ libns_ladir = $(includedir)/ns
libns_la_HEADERS = \
include/ns/client.h \
include/ns/events.h \
include/ns/hooks.h \
include/ns/interfacemgr.h \
include/ns/lib.h \

View file

@ -0,0 +1,25 @@
/*
* 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.
*/
#ifndef NS_EVENTS_H
#define NS_EVENTS_H 1
#include <isc/eventclass.h>
/*! \file ns/events.h
* \brief
* Registry of NS event numbers.
*/
#define NS_EVENT_CLIENTCONTROL (ISC_EVENTCLASS_NS + 0)
#define NS_EVENT_HOOKASYNCDONE (ISC_EVENTCLASS_NS + 1)
#endif /* NS_EVENTS_H */

View file

@ -16,9 +16,12 @@
#include <stdbool.h>
#include <isc/event.h>
#include <isc/list.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/result.h>
#include <isc/task.h>
#include <dns/rdatatype.h>
@ -178,13 +181,173 @@
* ns_hook_add(). As the hook action returns NS_HOOK_CONTINUE,
* query_foo() would also be logging the "Lorem ipsum dolor sit amet..."
* message before returning ISC_R_COMPLETE.
*
* ASYNCHRONOUS EVENT HANDLING IN QUERY HOOKS
*
* Usually a hook action works synchronously; it completes some particular
* job in the middle of query processing, thus blocking the caller (and the
* worker thread handling the query). But sometimes an action can be time
* consuming and the blocking behavior may not be acceptable. For example,
* a hook may need to send some kind of query (like a DB lookup) to an
* external backend server and wait for the response to complete the hook's
* action. Depending on the network condition, the external server's load,
* etc, it may take several seconds or more.
*
* In order to handle such a situation, a hook action can start an
* asynchronous event by calling ns_query_hookasync(). This is similar
* to ns_query_recurse(), but more generic. ns_query_hookasync() will
* call the 'runasync' function with a specified 'arg' (both passed to
* ns_query_hookasync()) and a set of task and associated event arguments
* to be called to resume query handling upon completion of the
* asynchronous event.
*
* The implementation of 'runasync' is assumed to allocate and build an
* instance of ns_hook_resevent_t whose action, arg, and task are set to
* the passed values from ns_query_hookasync(). Other fields of
* ns_hook_resevent_t must be correctly set in the hook implementation
* by the time it's sent to the specified task:
*
* - hookpoint: the point from which the query handling should be resumed
* (which should usually be the hook point that triggered the asynchronous
* event).
* - origresult: the result code passed to the hook action that triggers the
* asynchronous event through the 'resultp' pointer. Some hook points need
* this value to correctly resume the query handling.
* - saved_qctx: the 'qctx' passed to 'runasync'. This holds some
* intermediate data for resolving the query, and will be used to resume the
* query handling. The 'runasync' implementation must not modify it.
*
* The hook implementation should somehow maintain the created event
* instance so that it can eventually send the event.
*
* 'runasync' then creates an instance of ns_hookasync_t with specifying its
* own cancel and destroy function, and returns it to ns_query_hookasync()
* in the passed pointer.
*
* On return from ns_query_hookasync(), the hook action MUST return
* NS_HOOK_RETURN to suspend the query handling.
*
* On the completion of the asynchronous event, the hook implementation is
* supposed to send the resumeevent to the corresponding task. The query
* module resumes the query handling so that the hook action of the
* specified hook point will be called, skipping some intermediate query
* handling steps. So, typically, the same hook action will be called
* twice. The hook implementation must somehow remember the context, and
* handle the second call to complete its action using the result of the
* asynchronous event.
*
* Example: assume the following hook-specific structure to manage
* asynchronous events:
*
* typedef struct hookstate {
* bool async;
* ns_hook_resevent_t *rev
* ns_hookpoint_t hookpoint;
* isc_result_t origresult;
* } hookstate_t;
*
* 'async' is supposed to be true if and only if hook-triggered
* asynchronous processing is taking place.
*
* A hook action that uses an asynchronous event would look something
* like this:
*
* hook_recurse(void *hook_data, void *action_data, isc_result_t *resultp) {
* hookstate_t *state = somehow_retrieve_from(action_data);
* if (state->async) {
* // just resumed from an asynchronous hook action.
* // complete the hook's action using the result of the
* // internal asynchronous event.
* state->async = false;
* return (NS_HOOK_CONTINUE);
* }
*
* // Initial call to the hook action. Start the internal
* // asynchronous event, and have the query module suspend
* // its own handling by returning NS_HOOK_RETURN.
* state->hookpoint = ...; // would be hook point for this hook
* state->origresult = *resultp;
* ns_query_hookasync(hook_data, runasync, state);
* state->async = true;
* return (NS_HOOK_RETURN);
* }
*
* And the 'runasync' function would be something like this:
*
* static isc_result_t
* runasync(query_ctx_t *qctx, void *arg, isc_taskaction_t action,
* void *evarg, isc_task_t *task, ns_hookasync_t **ctxp) {
* hookstate_t *state = arg;
* ns_hook_resevent_t *rev = isc_event_allocate(
* mctx, task, NS_EVENT_HOOKASYNCDONE, action, evarg,
* sizeof(*rev));
* ns_hookasync_t *ctx = isc_mem_get(mctx, sizeof(*ctx));
*
* *ctx = (ns_hookasync_t){ .private = NULL };
* isc_mem_attach(mctx, &ctx->mctx);
* ctx->cancel = ...; // set the cancel function, which cancels the
* // internal asynchronous event (if necessary).
* // it should eventually result in sending
* // the 'rev' event to the calling task.
* ctx->destroy = ...; // set the destroy function, which frees 'ctx'
*
* rev->hookpoint = state->hookpoint;
* rev->origresult = state->origresult;
* rev->saved_qctx = qctx;
* rev->ctx = ctx;
*
* state->rev = rev; // store the resume event so we can send it later
*
* // initiate some asynchronous process here - for example, a
* // recursive fetch.
*
* *ctxp = ctx;
* return (ISC_R_SUCCESS);
* }
*
* Finally, in the completion handler for the asynchronous process, we
* need to send a resumption event so that query processing can resume.
* For example, the completion handler might call this function:
*
* static void
* asyncproc_done(hookstate_t *state) {
* isc_event_t *ev = (isc_event_t *)state->rev;
* isc_task_send(ev->ev_sender, &ev);
* }
*
* Caveats:
* - On resuming from a hook-initiated asynchronous process, code in
* the query module before the hook point needs to be exercised.
* So if this part has side effects, it's possible that the resuming
* doesn't work well. Currently, NS_QUERY_RESPOND_ANY_FOUND is
* explicitly prohibited to be used as the resume point.
* - In general, hooks other than those called at the beginning of the
* caller function may not work safely with asynchronous processing for
* the reason stated in the previous bullet. For example, a hook action
* for NS_QUERY_DONE_SEND may not be able to start an asychronous
* function safely.
* - Hook-triggered asynchronous processing is not allowed to be running
* while the standard DNS recursive fetch is taking place (starting
* from a call to dns_resolver_createfetch()), as the two would be
* using some of the same context resources. For this reason the
* NS_QUERY_NOTFOUND_RECURSE and NS_QUERY_ZEROTTL_RECURSE hook points
* are explicitly prohibited from being used for asynchronous hook
* actions.
* - Specifying multiple hook actions for the same hook point at the
* same time may cause problems, as resumption from one hook action
* could cause another hook to be called twice unintentionally.
* It's generally not safe to assume such a use case works,
* especially if the hooks are developed independently. (Note that
* that's not necessarily specific to the use of asynchronous hook
* actions. As long as hook actions have side effects, including
* modifying the internal query state, it's not guaranteed safe
* to use multiple independent hooks at the same time.)
*/
/*!
* Currently-defined hook points. So long as these are unique,
* the order in which they are declared is unimportant, but
* currently matches the order in which they are referenced in
* query.c.
* Currently-defined hook points. So long as these are unique, the order in
* which they are declared is unimportant, but it currently matches the
* order in which they are referenced in query.c.
*/
typedef enum {
/* hookpoints from query.c */
@ -249,6 +412,39 @@ typedef ns_hooklist_t ns_hooktable_t[NS_HOOKPOINTS_COUNT];
*/
LIBNS_EXTERNAL_DATA extern ns_hooktable_t *ns__hook_table;
typedef void (*ns_hook_cancelasync_t)(ns_hookasync_t *);
typedef void (*ns_hook_destroyasync_t)(ns_hookasync_t **);
/*%
* Context for a hook-initiated asynchronous process. This works
* similarly to dns_fetch_t.
*/
struct ns_hookasync {
isc_mem_t *mctx;
/*
* The following two are equivalent to dns_resolver_cancelfetch and
* dns_resolver_destroyfetch, respectively, but specified as function
* pointers since they can be hook-specific.
*/
ns_hook_cancelasync_t cancel;
ns_hook_destroyasync_t destroy;
void *private; /* hook-specific data */
};
/*
* isc_event to be sent on the completion of a hook-initiated asyncronous
* process, similar to dns_fetchevent_t.
*/
typedef struct ns_hook_resevent {
ISC_EVENT_COMMON(struct ns_hook_resevent);
ns_hookasync_t *ctx; /* asynchronous processing context */
ns_hookpoint_t hookpoint; /* hook point from which to resume */
isc_result_t origresult; /* result code at the point of call to hook */
query_ctx_t *saved_qctx; /* qctx at the point of call to hook */
} ns_hook_resevent_t;
/*
* Plugin API version
*

View file

@ -18,6 +18,7 @@
#include <isc/buffer.h>
#include <isc/netaddr.h>
#include <isc/task.h>
#include <isc/types.h>
#include <dns/rdataset.h>
@ -66,6 +67,7 @@ struct ns_query {
isc_mutex_t fetchlock;
dns_fetch_t * fetch;
dns_fetch_t * prefetch;
ns_hookasync_t * hookactx;
dns_rpz_st_t * rpz_st;
isc_bufferlist_t namebufs;
ISC_LIST(ns_dbversion_t) activeversions;
@ -174,6 +176,15 @@ struct query_ctx {
int line; /* line to report error */
};
typedef isc_result_t (*ns_query_starthookasync_t)(
query_ctx_t *qctx, isc_mem_t *mctx, void *arg, isc_task_t *task,
isc_taskaction_t action, void *evarg, ns_hookasync_t **ctxp);
/*
* The following functions are expected to be used only within query.c
* and query modules.
*/
isc_result_t
ns_query_done(query_ctx_t *qctx);
/*%<
@ -197,6 +208,34 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
* recursion completes.
*/
isc_result_t
ns_query_hookasync(query_ctx_t *qctx, ns_query_starthookasync_t runasync,
void *arg);
/*%<
* Prepare the client for an asynchronous hook action, then call the
* specified 'runasync' function to start an asynchronous process running
* in the background. This function works similarly to ns_query_recurse(),
* but is expected to be called from a query hook action to support
* asynchronous event handling in a hook. A typical use case would be for
* a plugin to initiate recursion, but it may also be used to carry out
* other time-consuming tasks without blocking the caller or the worker
* thread.
*
* The calling plugin action must pass 'qctx' as passed from the query
* module.
*
* Once a plugin action calls this function, the ownership of 'qctx' is
* essentially transferred to the query module. Regardless of the return
* value of this function, the hook must not use 'qctx' anymore.
*
* This function must not be called after ns_query_recurse() is called,
* until the fetch is completed, as it needs resources that
* ns_query_recurse() would also use.
*
* See hooks.h for details about how 'runasync' is supposed to work, and
* other aspects of hook-triggered asynchronous event handling.
*/
isc_result_t
ns_query_init(ns_client_t *client);

View file

@ -28,10 +28,9 @@
#include <dns/acl.h>
#include <dns/types.h>
#include <ns/events.h>
#include <ns/types.h>
#define NS_EVENT_CLIENTCONTROL (ISC_EVENTCLASS_NS + 0)
#define NS_SERVER_LOGQUERIES 0x00000001U /*%< log queries */
#define NS_SERVER_NOAA 0x00000002U /*%< -T noaa */
#define NS_SERVER_NOSOA 0x00000004U /*%< -T nosoa */

View file

@ -25,6 +25,7 @@ typedef struct ns_interfacemgr ns_interfacemgr_t;
typedef struct ns_query ns_query_t;
typedef struct ns_server ns_server_t;
typedef struct ns_stats ns_stats_t;
typedef struct ns_hookasync ns_hookasync_t;
typedef enum { ns_cookiealg_aes, ns_cookiealg_siphash24 } ns_cookiealg_t;

View file

@ -60,6 +60,7 @@
#include <dns/zt.h>
#include <ns/client.h>
#include <ns/events.h>
#include <ns/hooks.h>
#include <ns/interfacemgr.h>
#include <ns/log.h>
@ -182,14 +183,15 @@ client_trace(ns_client_t *client, int level, const char *message) {
#define SFCACHE_CDFLAG 0x1
/*
* These have the same semantics as:
* SAVE and RESTORE have the same semantics as:
*
* foo_attach(b, a);
* foo_detach(&a);
* foo_attach(b, &a);
* foo_detach(&b);
*
* without the locking and magic testing.
*
* We use SAVE and RESTORE as that shows the operation being performed.
* We use the names SAVE and RESTORE to show the operation being performed,
* even though the two macros are identical.
*/
#define SAVE(a, b) \
do { \
@ -223,6 +225,12 @@ static void
log_noexistnodata(void *val, int level, const char *fmt, ...)
ISC_FORMAT_PRINTF(3, 4);
static isc_result_t
query_addanswer(query_ctx_t *qctx);
static isc_result_t
query_prepare_delegation_response(query_ctx_t *qctx);
/*
* Return the hooktable in use with 'qctx', or if there isn't one
* set, return the default hooktable.
@ -249,7 +257,7 @@ get_hooktab(query_ctx_t *qctx) {
*/
#define CALL_HOOK(_id, _qctx) \
do { \
isc_result_t _res; \
isc_result_t _res = result; \
ns_hooktable_t *_tab = get_hooktab(_qctx); \
ns_hook_t *_hook; \
_hook = ISC_LIST_HEAD((*_tab)[_id]); \
@ -436,7 +444,7 @@ static void
query_addnxrrsetnsec(query_ctx_t *qctx);
static isc_result_t
query_nxdomain(query_ctx_t *qctx, bool empty_wild);
query_nxdomain(query_ctx_t *qctx, isc_result_t res);
static isc_result_t
query_redirect(query_ctx_t *qctx);
@ -625,6 +633,10 @@ ns_query_cancel(ns_client_t *client) {
client->query.fetch = NULL;
}
if (client->query.hookactx != NULL) {
client->query.hookactx->cancel(client->query.hookactx);
client->query.hookactx = NULL;
}
UNLOCK(&client->query.fetchlock);
}
@ -5159,6 +5171,52 @@ qctx_destroy(query_ctx_t *qctx) {
dns_view_detach(&qctx->view);
}
/*
* Call SAVE but set 'a' to NULL first so as not to assert.
*/
#define INITANDSAVE(a, b) \
do { \
a = NULL; \
SAVE(a, b); \
} while (0)
/*
* "save" qctx data from 'src' to 'tgt'.
* It essentially moves ownership of the data from src to tgt, so the former
* becomes unusable except for final cleanup (such as by qctx_destroy).
* Note: this function doesn't attach to the client's handle. It's the caller's
* responsibility to do it if it's necessary.
*/
static void
qctx_save(query_ctx_t *src, query_ctx_t *tgt) {
/* First copy all fields in a straightforward way */
*tgt = *src;
/* Then "move" pointers (except client and view) */
INITANDSAVE(tgt->dbuf, src->dbuf);
INITANDSAVE(tgt->fname, src->fname);
INITANDSAVE(tgt->tname, src->tname);
INITANDSAVE(tgt->rdataset, src->rdataset);
INITANDSAVE(tgt->sigrdataset, src->sigrdataset);
INITANDSAVE(tgt->noqname, src->noqname);
INITANDSAVE(tgt->event, src->event);
INITANDSAVE(tgt->db, src->db);
INITANDSAVE(tgt->version, src->version);
INITANDSAVE(tgt->node, src->node);
INITANDSAVE(tgt->zdb, src->zdb);
INITANDSAVE(tgt->znode, src->znode);
INITANDSAVE(tgt->zfname, src->zfname);
INITANDSAVE(tgt->zversion, src->zversion);
INITANDSAVE(tgt->zrdataset, src->zrdataset);
INITANDSAVE(tgt->zsigrdataset, src->zsigrdataset);
INITANDSAVE(tgt->rpz_st, src->rpz_st);
INITANDSAVE(tgt->zone, src->zone);
/* View has to stay in 'src' for qctx_destroy. */
tgt->view = NULL;
dns_view_attach(src->view, &tgt->view);
}
/*%
* Log detailed information about the query immediately after
* the client request or a return from recursion.
@ -5203,7 +5261,7 @@ query_trace(query_ctx_t *qctx) {
*/
static isc_result_t
query_setup(ns_client_t *client, dns_rdatatype_t qtype) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
query_ctx_t qctx;
qctx_init(client, NULL, qtype, &qctx);
@ -5306,7 +5364,7 @@ root_key_sentinel_detect(query_ctx_t *qctx) {
*/
isc_result_t
ns__query_start(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
CCTRACE(ISC_LOG_DEBUG(3), "ns__query_start");
qctx->want_restart = false;
qctx->authoritative = false;
@ -5518,7 +5576,7 @@ cleanup:
static isc_result_t
query_lookup(query_ctx_t *qctx) {
isc_buffer_t b;
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
dns_clientinfomethods_t cm;
dns_clientinfo_t ci;
dns_name_t *rpzqname = NULL;
@ -5873,31 +5931,12 @@ last_init(void) {
}
#endif /* ifdef ISC_MUTEX_ATOMICS */
isc_result_t
ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
dns_name_t *qdomain, dns_rdataset_t *nameservers,
bool resuming) {
isc_result_t result;
dns_rdataset_t *rdataset, *sigrdataset;
isc_sockaddr_t *peeraddr = NULL;
CTRACE(ISC_LOG_DEBUG(3), "ns_query_recurse");
/*
* Check recursion parameters from the previous query to see if they
* match. If not, update recursion parameters and proceed.
*/
if (recparam_match(&client->query.recparam, qtype, qname, qdomain)) {
ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY,
ISC_LOG_INFO, "recursion loop detected");
return (ISC_R_FAILURE);
}
recparam_update(&client->query.recparam, qtype, qname, qdomain);
if (!resuming) {
inc_stats(client, ns_statscounter_recursion);
}
/*%
* Check recursion quota before making the current client "recursing".
*/
static isc_result_t
check_recursionquota(ns_client_t *client) {
isc_result_t result = ISC_R_SUCCESS;
/*
* We are about to recurse, which means that this client will
@ -5984,6 +6023,40 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
ns_statscounter_recursclients);
}
return (result);
}
isc_result_t
ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
dns_name_t *qdomain, dns_rdataset_t *nameservers,
bool resuming) {
isc_result_t result;
dns_rdataset_t *rdataset, *sigrdataset;
isc_sockaddr_t *peeraddr = NULL;
CTRACE(ISC_LOG_DEBUG(3), "ns_query_recurse");
/*
* Check recursion parameters from the previous query to see if they
* match. If not, update recursion parameters and proceed.
*/
if (recparam_match(&client->query.recparam, qtype, qname, qdomain)) {
ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY,
ISC_LOG_INFO, "recursion loop detected");
return (ISC_R_FAILURE);
}
recparam_update(&client->query.recparam, qtype, qname, qdomain);
if (!resuming) {
inc_stats(client, ns_statscounter_recursion);
}
result = check_recursionquota(client);
if (result != ISC_R_SUCCESS) {
return (result);
}
/*
* Invoke the resolver.
*/
@ -6042,7 +6115,7 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
*/
static isc_result_t
query_resume(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
dns_name_t *tname;
isc_buffer_t b;
#ifdef WANT_QUERYTRACE
@ -6239,6 +6312,212 @@ cleanup:
return (result);
}
static void
query_hookresume(isc_task_t *task, isc_event_t *event) {
ns_hook_resevent_t *rev = (ns_hook_resevent_t *)event;
ns_hookasync_t *hctx = NULL;
ns_client_t *client = rev->ev_arg;
query_ctx_t *qctx = rev->saved_qctx;
bool canceled;
CTRACE(ISC_LOG_DEBUG(3), "query_hookresume");
REQUIRE(NS_CLIENT_VALID(client));
REQUIRE(task == client->task);
REQUIRE(event->ev_type == NS_EVENT_HOOKASYNCDONE);
LOCK(&client->query.fetchlock);
if (client->query.hookactx != NULL) {
INSIST(rev->ctx == client->query.hookactx);
client->query.hookactx = NULL;
canceled = false;
isc_stdtime_get(&client->now);
} else {
canceled = true;
}
UNLOCK(&client->query.fetchlock);
SAVE(hctx, rev->ctx);
if (client->recursionquota != NULL) {
isc_quota_detach(&client->recursionquota);
ns_stats_decrement(client->sctx->nsstats,
ns_statscounter_recursclients);
}
LOCK(&client->manager->reclock);
if (ISC_LINK_LINKED(client, rlink)) {
ISC_LIST_UNLINK(client->manager->recursing, client, rlink);
}
UNLOCK(&client->manager->reclock);
client->state = NS_CLIENTSTATE_WORKING;
if (canceled) {
/*
* Note: unlike fetch_callback, this function doesn't bother
* to check the 'shutdown' condition, as that doesn't seem to
* happen in the latest implementation.
*/
query_error(client, DNS_R_SERVFAIL, __LINE__);
/*
* There's no other place to free/release any data maintained
* in qctx. We need to do it here to prevent leak.
*/
qctx_clean(qctx);
qctx_freedata(qctx);
/*
* As we're almost done with this client, make sure any internal
* resource for hooks will be released (if necessary) via the
* QCTX_DESTROYED hook.
*/
qctx->detach_client = true;
} else {
switch (rev->hookpoint) {
case NS_QUERY_SETUP:
(void)query_setup(client, qctx->qtype);
break;
case NS_QUERY_START_BEGIN:
(void)ns__query_start(qctx);
break;
case NS_QUERY_LOOKUP_BEGIN:
(void)query_lookup(qctx);
break;
case NS_QUERY_RESUME_BEGIN:
case NS_QUERY_RESUME_RESTORED:
(void)query_resume(qctx);
break;
case NS_QUERY_GOT_ANSWER_BEGIN:
(void)query_gotanswer(qctx, rev->origresult);
break;
case NS_QUERY_RESPOND_ANY_BEGIN:
(void)query_respond_any(qctx);
break;
case NS_QUERY_ADDANSWER_BEGIN:
(void)query_addanswer(qctx);
break;
case NS_QUERY_NOTFOUND_BEGIN:
(void)query_notfound(qctx);
break;
case NS_QUERY_PREP_DELEGATION_BEGIN:
(void)query_prepare_delegation_response(qctx);
break;
case NS_QUERY_ZONE_DELEGATION_BEGIN:
(void)query_zone_delegation(qctx);
break;
case NS_QUERY_DELEGATION_BEGIN:
(void)query_delegation(qctx);
break;
case NS_QUERY_DELEGATION_RECURSE_BEGIN:
(void)query_delegation_recurse(qctx);
break;
case NS_QUERY_NODATA_BEGIN:
(void)query_nodata(qctx, rev->origresult);
break;
case NS_QUERY_NXDOMAIN_BEGIN:
(void)query_nxdomain(qctx, rev->origresult);
break;
case NS_QUERY_NCACHE_BEGIN:
(void)query_ncache(qctx, rev->origresult);
break;
case NS_QUERY_CNAME_BEGIN:
(void)query_cname(qctx);
break;
case NS_QUERY_DNAME_BEGIN:
(void)query_dname(qctx);
break;
case NS_QUERY_RESPOND_BEGIN:
(void)query_respond(qctx);
break;
case NS_QUERY_PREP_RESPONSE_BEGIN:
(void)query_prepresponse(qctx);
break;
case NS_QUERY_DONE_BEGIN:
case NS_QUERY_DONE_SEND:
(void)ns_query_done(qctx);
break;
/* Not all hookpoints can use recursion. Catch violations */
case NS_QUERY_RESPOND_ANY_FOUND: /* due to side effect */
case NS_QUERY_NOTFOUND_RECURSE: /* in recursion */
case NS_QUERY_ZEROTTL_RECURSE: /* in recursion */
default: /* catch-all just in case */
INSIST(false);
}
}
hctx->destroy(&hctx);
qctx_destroy(qctx);
isc_mem_put(client->mctx, qctx, sizeof(*qctx));
isc_event_free(&event);
isc_nmhandle_detach(&client->fetchhandle);
}
isc_result_t
ns_query_hookasync(query_ctx_t *qctx, ns_query_starthookasync_t runasync,
void *arg) {
isc_result_t result;
ns_client_t *client = qctx->client;
query_ctx_t *saved_qctx = NULL;
CTRACE(ISC_LOG_DEBUG(3), "ns_query_hookasync");
REQUIRE(NS_CLIENT_VALID(client));
REQUIRE(client->query.hookactx == NULL);
REQUIRE(client->query.fetch == NULL);
result = check_recursionquota(client);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
saved_qctx = isc_mem_get(client->mctx, sizeof(*saved_qctx));
qctx_save(qctx, saved_qctx);
result = runasync(saved_qctx, client->mctx, arg, client->task,
query_hookresume, client, &client->query.hookactx);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
/*
* Typically the runasync() function will trigger recursion, but
* there is no need to set NS_QUERYATTR_RECURSING. The calling hook
* is expected to return NS_HOOK_RETURN, and the RECURSING
* attribute won't be checked anywhere.
*
* Hook-based asynchronous processing cannot coincide with normal
* recursion, so we can safely use fetchhandle here. Unlike in
* ns_query_recurse(), we attach to the handle only if 'runasync'
* succeeds. It should be safe since we're either in the client
* task or pausing it.
*/
isc_nmhandle_attach(client->handle, &client->fetchhandle);
return (ISC_R_SUCCESS);
cleanup:
/*
* If we fail, send SERVFAIL now. It may be better to let the caller
* decide what to do on failure of this function, but hooks don't have
* access to query_error().
*/
query_error(client, DNS_R_SERVFAIL, __LINE__);
/*
* Free all resource related to the query and set detach_client,
* similar to the cancel case of query_hookresume; the callers will
* simply return on failure of this function, so there's no other
* place for this to prevent leak.
*/
if (saved_qctx != NULL) {
qctx_clean(saved_qctx);
qctx_freedata(saved_qctx);
qctx_destroy(saved_qctx);
isc_mem_put(client->mctx, saved_qctx, sizeof(*saved_qctx));
}
qctx->detach_client = true;
return (result);
}
/*%
* If the query is recursive, check the SERVFAIL cache to see whether
* identical queries have failed recently. If we find a match, and it was
@ -6947,15 +7226,12 @@ query_gotanswer(query_ctx_t *qctx, isc_result_t res) {
return (query_delegation(qctx));
case DNS_R_EMPTYNAME:
return (query_nodata(qctx, DNS_R_EMPTYNAME));
case DNS_R_NXRRSET:
return (query_nodata(qctx, DNS_R_NXRRSET));
return (query_nodata(qctx, result));
case DNS_R_EMPTYWILD:
return (query_nxdomain(qctx, true));
case DNS_R_NXDOMAIN:
return (query_nxdomain(qctx, false));
return (query_nxdomain(qctx, result));
case DNS_R_COVERINGNSEC:
return (query_coveringnsec(qctx));
@ -7090,7 +7366,7 @@ static isc_result_t
query_respond_any(query_ctx_t *qctx) {
bool found = false, hidden = false;
dns_rdatasetiter_t *rdsiter = NULL;
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
dns_rdatatype_t onetype = 0; /* type to use for minimal-any */
isc_buffer_t b;
@ -7365,7 +7641,7 @@ query_getexpire(query_ctx_t *qctx) {
static isc_result_t
query_addanswer(query_ctx_t *qctx) {
dns_rdataset_t **sigrdatasetp = NULL;
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
CCTRACE(ISC_LOG_DEBUG(3), "query_addanswer");
@ -7427,7 +7703,7 @@ cleanup:
*/
static isc_result_t
query_respond(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
CCTRACE(ISC_LOG_DEBUG(3), "query_respond");
@ -7884,7 +8160,7 @@ cleanup:
*/
static isc_result_t
query_notfound(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
CCTRACE(ISC_LOG_DEBUG(3), "query_notfound");
@ -7970,7 +8246,7 @@ cleanup:
*/
static isc_result_t
query_prepare_delegation_response(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
dns_rdataset_t **sigrdatasetp = NULL;
bool detach = false;
@ -8026,7 +8302,7 @@ cleanup:
*/
static isc_result_t
query_zone_delegation(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
CALL_HOOK(NS_QUERY_ZONE_DELEGATION_BEGIN, qctx);
@ -8124,7 +8400,7 @@ cleanup:
*/
static isc_result_t
query_delegation(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
CCTRACE(ISC_LOG_DEBUG(3), "query_delegation");
@ -8198,7 +8474,7 @@ cleanup:
*/
static isc_result_t
query_delegation_recurse(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
dns_name_t *qname = qctx->client->query.qname;
CCTRACE(ISC_LOG_DEBUG(3), "query_delegation_recurse");
@ -8721,10 +8997,11 @@ query_addnxrrsetnsec(query_ctx_t *qctx) {
* Handle NXDOMAIN and empty wildcard responses.
*/
static isc_result_t
query_nxdomain(query_ctx_t *qctx, bool empty_wild) {
query_nxdomain(query_ctx_t *qctx, isc_result_t res) {
dns_section_t section;
uint32_t ttl;
isc_result_t result;
isc_result_t result = res;
bool empty_wild = (res == DNS_R_EMPTYWILD);
CCTRACE(ISC_LOG_DEBUG(3), "query_nxdomain");
@ -9656,7 +9933,7 @@ cleanup:
*/
static isc_result_t
query_cname(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
dns_name_t *tname;
dns_rdataset_t *trdataset;
dns_rdataset_t **sigrdatasetp = NULL;
@ -9769,7 +10046,7 @@ query_dname(query_ctx_t *qctx) {
dns_namereln_t namereln;
isc_buffer_t b;
int order;
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
unsigned int nlabels;
CCTRACE(ISC_LOG_DEBUG(3), "query_dname");
@ -9998,7 +10275,7 @@ query_addcname(query_ctx_t *qctx, dns_trust_t trust, dns_ttl_t ttl) {
*/
static isc_result_t
query_prepresponse(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
CCTRACE(ISC_LOG_DEBUG(3), "query_prepresponse");
@ -10926,7 +11203,7 @@ query_glueanswer(query_ctx_t *qctx) {
isc_result_t
ns_query_done(query_ctx_t *qctx) {
isc_result_t result;
isc_result_t result = ISC_R_UNSET;
const dns_namelist_t *secs = qctx->client->message->sections;
CCTRACE(ISC_LOG_DEBUG(3), "ns_query_done");

View file

@ -117,6 +117,7 @@ isc_nmhandle_detach(isc_nmhandle_t **handlep) {
ns__client_reset_cb(client);
ns__client_put_cb(client);
isc_mem_put(mctx, client, sizeof(ns_client_t));
atomic_store(&client_addrs[i], (uintptr_t)NULL);
}
return;

View file

@ -25,12 +25,18 @@
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/quota.h>
#include <dns/badcache.h>
#include <dns/view.h>
#include <dns/zone.h>
#include <ns/client.h>
#include <ns/events.h>
#include <ns/hooks.h>
#include <ns/query.h>
#include <ns/server.h>
#include <ns/stats.h>
#include "nstest.h"
@ -55,6 +61,12 @@ _teardown(void **state) {
return (0);
}
/* can be used for client->sendcb to avoid disruption on sending a response */
static void
send_noop(isc_buffer_t *buffer) {
UNUSED(buffer);
}
/*****
***** ns__query_sfcache() tests
*****/
@ -599,6 +611,895 @@ ns__query_start_test(void **state) {
}
}
/*****
***** tests for ns_query_hookasync().
*****/
/*%
* Structure containing parameters for ns__query_hookasync_test().
*/
typedef struct {
const ns_test_id_t id; /* libns test identifier */
ns_hookpoint_t hookpoint; /* hook point specified for resume */
ns_hookpoint_t hookpoint2; /* expected hook point used after resume */
ns_hook_action_t action; /* action for the hook point */
isc_result_t start_result; /* result of 'runasync' */
bool quota_ok; /* true if recursion quota should be okay */
bool do_cancel; /* true if query should be canceled
* in test */
} ns__query_hookasync_test_params_t;
/* Data structure passed from tests to hooks */
typedef struct hookasync_data {
bool async; /* true if in a hook-triggered
* asynchronous process */
bool canceled; /* true if the query has been canceled */
isc_result_t start_result; /* result of 'runasync' */
ns_hook_resevent_t *rev; /* resume event sent on completion */
query_ctx_t qctx; /* shallow copy of qctx passed to hook */
ns_hookpoint_t hookpoint; /* specifies where to resume */
ns_hookpoint_t lasthookpoint; /* remember the last hook point called */
} hookasync_data_t;
/*
* 'destroy' callback of hook recursion ctx.
* The dynamically allocated context will be freed here, thereby proving
* this is actually called; otherwise tests would fail due to memory leak.
*/
static void
destroy_hookactx(ns_hookasync_t **ctxp) {
ns_hookasync_t *ctx = *ctxp;
*ctxp = NULL;
isc_mem_putanddetach(&ctx->mctx, ctx, sizeof(*ctx));
}
/* 'cancel' callback of hook recursion ctx. */
static void
cancel_hookactx(ns_hookasync_t *ctx) {
/* Mark the hook data so the test can confirm this is called. */
((hookasync_data_t *)ctx->private)->canceled = true;
}
/* 'runasync' callback passed to ns_query_hookasync */
static isc_result_t
test_hookasync(query_ctx_t *qctx, isc_mem_t *memctx, void *arg,
isc_task_t *task, isc_taskaction_t action, void *evarg,
ns_hookasync_t **ctxp) {
hookasync_data_t *asdata = arg;
ns_hookasync_t *ctx = NULL;
ns_hook_resevent_t *rev = NULL;
if (asdata->start_result != ISC_R_SUCCESS) {
return (asdata->start_result);
}
ctx = isc_mem_get(memctx, sizeof(*ctx));
rev = (ns_hook_resevent_t *)isc_event_allocate(
memctx, task, NS_EVENT_HOOKASYNCDONE, action, evarg,
sizeof(*rev));
rev->hookpoint = asdata->hookpoint;
rev->origresult = DNS_R_NXDOMAIN;
rev->saved_qctx = qctx;
rev->ctx = ctx;
asdata->rev = rev;
*ctx = (ns_hookasync_t){ .private = asdata };
isc_mem_attach(memctx, &ctx->mctx);
ctx->destroy = destroy_hookactx;
ctx->cancel = cancel_hookactx;
*ctxp = ctx;
return (ISC_R_SUCCESS);
}
/*
* Main logic for hook actions.
* 'hookpoint' should identify the point that calls the hook. It will be
* remembered in the hook data, so that the test can confirm which hook point
* was last used.
*/
static ns_hookresult_t
hook_recurse_common(void *arg, void *data, isc_result_t *resultp,
ns_hookpoint_t hookpoint) {
query_ctx_t *qctx = arg;
hookasync_data_t *asdata = data;
isc_result_t result;
asdata->qctx = *qctx; /* remember passed ctx for inspection */
asdata->lasthookpoint = hookpoint; /* ditto */
if (!asdata->async) {
/* Initial call to the hook; start recursion */
result = ns_query_hookasync(qctx, test_hookasync, asdata);
if (result == ISC_R_SUCCESS) {
asdata->async = true;
}
} else {
/* Resume from the completion of recursion */
asdata->async = false;
switch (hookpoint) {
case NS_QUERY_GOT_ANSWER_BEGIN:
case NS_QUERY_NODATA_BEGIN:
case NS_QUERY_NXDOMAIN_BEGIN:
case NS_QUERY_NCACHE_BEGIN:
INSIST(*resultp == DNS_R_NXDOMAIN);
break;
default:;
}
}
*resultp = ISC_R_UNSET;
return (NS_HOOK_RETURN);
}
static ns_hookresult_t
hook_recurse_query_setup(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp, NS_QUERY_SETUP));
}
static ns_hookresult_t
hook_recurse_query_start_begin(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp, NS_QUERY_START_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_lookup_begin(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp, NS_QUERY_LOOKUP_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_resume_begin(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp, NS_QUERY_RESUME_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_got_answer_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_GOT_ANSWER_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_respond_any_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_RESPOND_ANY_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_addanswer_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_ADDANSWER_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_notfound_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_NOTFOUND_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_prep_delegation_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_PREP_DELEGATION_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_zone_delegation_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_ZONE_DELEGATION_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_delegation_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_DELEGATION_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_delegation_recurse_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_DELEGATION_RECURSE_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_nodata_begin(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp, NS_QUERY_NODATA_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_nxdomain_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_NXDOMAIN_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_ncache_begin(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp, NS_QUERY_NCACHE_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_cname_begin(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp, NS_QUERY_CNAME_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_dname_begin(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp, NS_QUERY_DNAME_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_respond_begin(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_RESPOND_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_response_begin(void *arg, void *data,
isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp,
NS_QUERY_PREP_RESPONSE_BEGIN));
}
static ns_hookresult_t
hook_recurse_query_done_begin(void *arg, void *data, isc_result_t *resultp) {
return (hook_recurse_common(arg, data, resultp, NS_QUERY_DONE_BEGIN));
}
/*
* hook on destroying actx. Can't be used for recursion, but we use this
* to remember the qctx at that point.
*/
static ns_hookresult_t
ns_test_qctx_destroy_hook(void *arg, void *data, isc_result_t *resultp) {
query_ctx_t *qctx = arg;
hookasync_data_t *asdata = data;
asdata->qctx = *qctx; /* remember passed ctx for inspection */
*resultp = ISC_R_UNSET;
return (NS_HOOK_CONTINUE);
}
static void
run_hookasync_test(const ns__query_hookasync_test_params_t *test) {
query_ctx_t *qctx = NULL;
isc_result_t result;
hookasync_data_t asdata = {
.async = false,
.canceled = false,
.start_result = test->start_result,
.hookpoint = test->hookpoint,
};
const ns_hook_t testhook = {
.action = test->action,
.action_data = &asdata,
};
const ns_hook_t destroyhook = {
.action = ns_test_qctx_destroy_hook,
.action_data = &asdata,
};
isc_quota_t *quota = NULL;
isc_statscounter_t srvfail_cnt;
bool expect_servfail = false;
/*
* Prepare hooks. We always begin with ns__query_start for simplicity.
* Its action will specify various different resume points (unusual
* in practice, but that's fine for the testing purpose).
*/
ns__hook_table = NULL;
ns_hooktable_create(mctx, &ns__hook_table);
ns_hook_add(ns__hook_table, mctx, NS_QUERY_START_BEGIN, &testhook);
if (test->hookpoint2 != NS_QUERY_START_BEGIN) {
/*
* unless testing START_BEGIN itself, specify the hook for the
* expected resume point, too.
*/
ns_hook_add(ns__hook_table, mctx, test->hookpoint2, &testhook);
}
ns_hook_add(ns__hook_table, mctx, NS_QUERY_QCTX_DESTROYED,
&destroyhook);
{
const ns_test_qctx_create_params_t qctx_params = {
.qname = "test.example.com",
.qtype = dns_rdatatype_aaaa,
};
result = ns_test_qctx_create(&qctx_params, &qctx);
INSIST(result == ISC_R_SUCCESS);
qctx->client->sendcb = send_noop;
}
/*
* Set recursion quota to the lowest possible value, then make it full
* if we want to exercise a quota failure case.
*/
isc_quota_max(&sctx->recursionquota, 1);
if (!test->quota_ok) {
result = isc_quota_attach(&sctx->recursionquota, &quota);
INSIST(result == ISC_R_SUCCESS);
}
/* Remember SERVFAIL counter */
srvfail_cnt = ns_stats_get_counter(qctx->client->sctx->nsstats,
ns_statscounter_servfail);
/*
* If the query has been canceled, or recursion didn't succeed,
* SERVFAIL will have to be sent. In this case we need to have
* 'reqhandle' attach to the client's handle as it's detached in
* query_error.
*/
if (test->start_result != ISC_R_SUCCESS || !test->quota_ok ||
test->do_cancel) {
expect_servfail = true;
isc_nmhandle_attach(qctx->client->handle,
&qctx->client->reqhandle);
}
/*
* Emulate query handling from query_start.
* Specified hook should be called.
*/
qctx->client->state = NS_CLIENTSTATE_WORKING;
result = ns__query_start(qctx);
INSIST(result == ISC_R_UNSET);
/*
* hook-triggered recursion should be happening unless it hits recursion
* quota limit or 'runasync' callback fails.
*/
INSIST(asdata.async ==
(test->quota_ok && test->start_result == ISC_R_SUCCESS));
/*
* Emulate cancel if so specified.
* The cancel callback should be called.
*/
if (test->do_cancel) {
ns_query_cancel(qctx->client);
}
INSIST(asdata.canceled == test->do_cancel);
/* If recursion has started, manually invoke the 'done' event. */
if (asdata.async) {
qctx->client->now = 0; /* set to sentinel before resume */
asdata.rev->ev_action(asdata.rev->ev_sender,
(isc_event_t *)asdata.rev);
/* Confirm necessary cleanup has been performed. */
INSIST(qctx->client->query.hookactx == NULL);
INSIST(qctx->client->state == NS_CLIENTSTATE_WORKING);
INSIST(qctx->client->recursionquota == NULL);
INSIST(ns_stats_get_counter(qctx->client->sctx->nsstats,
ns_statscounter_recursclients) ==
0);
INSIST(!ISC_LINK_LINKED(qctx->client, rlink));
if (!test->do_cancel) {
/*
* In the normal case the client's timestamp is updated
* and the query handling has been resumed from the
* expected point.
*/
INSIST(qctx->client->now != 0);
INSIST(asdata.lasthookpoint == test->hookpoint2);
}
} else {
INSIST(qctx->client->query.hookactx == NULL);
}
/*
* Confirm SERVFAIL has been sent if it was expected.
* Also, the last-generated qctx should have detach_client being true.
*/
if (expect_servfail) {
INSIST(ns_stats_get_counter(qctx->client->sctx->nsstats,
ns_statscounter_servfail) ==
srvfail_cnt + 1);
if (test->do_cancel) {
/* qctx was created on resume and copied in hook */
INSIST(asdata.qctx.detach_client);
} else {
INSIST(qctx->detach_client);
}
}
/*
* Cleanup. Note that we've kept 'qctx' until now; otherwise
* qctx->client may have been invalidated while we still need it.
*/
ns_test_qctx_destroy(&qctx);
ns_hooktable_free(mctx, (void **)&ns__hook_table);
if (quota != NULL) {
isc_quota_detach(&quota);
}
}
static void
ns__query_hookasync_test(void **state) {
size_t i;
UNUSED(state);
const ns__query_hookasync_test_params_t tests[] = {
{
NS_TEST_ID("normal case"),
NS_QUERY_START_BEGIN,
NS_QUERY_START_BEGIN,
hook_recurse_query_start_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("quota fail"),
NS_QUERY_START_BEGIN,
NS_QUERY_START_BEGIN,
hook_recurse_query_start_begin,
ISC_R_SUCCESS,
false,
false,
},
{
NS_TEST_ID("start fail"),
NS_QUERY_START_BEGIN,
NS_QUERY_START_BEGIN,
hook_recurse_query_start_begin,
ISC_R_FAILURE,
true,
false,
},
{
NS_TEST_ID("query cancel"),
NS_QUERY_START_BEGIN,
NS_QUERY_START_BEGIN,
hook_recurse_query_start_begin,
ISC_R_SUCCESS,
true,
true,
},
/*
* The rest of the test case just confirms supported hookpoints
* with the same test logic.
*/
{
NS_TEST_ID("recurse from setup"),
NS_QUERY_SETUP,
NS_QUERY_SETUP,
hook_recurse_query_setup,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from lookup"),
NS_QUERY_LOOKUP_BEGIN,
NS_QUERY_LOOKUP_BEGIN,
hook_recurse_query_lookup_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from resume"),
NS_QUERY_RESUME_BEGIN,
NS_QUERY_RESUME_BEGIN,
hook_recurse_query_resume_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from resume restored"),
NS_QUERY_RESUME_RESTORED,
NS_QUERY_RESUME_BEGIN,
hook_recurse_query_resume_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from gotanswer"),
NS_QUERY_GOT_ANSWER_BEGIN,
NS_QUERY_GOT_ANSWER_BEGIN,
hook_recurse_query_got_answer_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from respond any"),
NS_QUERY_RESPOND_ANY_BEGIN,
NS_QUERY_RESPOND_ANY_BEGIN,
hook_recurse_query_respond_any_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from add answer"),
NS_QUERY_ADDANSWER_BEGIN,
NS_QUERY_ADDANSWER_BEGIN,
hook_recurse_query_addanswer_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from notfound"),
NS_QUERY_NOTFOUND_BEGIN,
NS_QUERY_NOTFOUND_BEGIN,
hook_recurse_query_notfound_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from prep delegation"),
NS_QUERY_PREP_DELEGATION_BEGIN,
NS_QUERY_PREP_DELEGATION_BEGIN,
hook_recurse_query_prep_delegation_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from zone delegation"),
NS_QUERY_ZONE_DELEGATION_BEGIN,
NS_QUERY_ZONE_DELEGATION_BEGIN,
hook_recurse_query_zone_delegation_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from delegation"),
NS_QUERY_DELEGATION_BEGIN,
NS_QUERY_DELEGATION_BEGIN,
hook_recurse_query_delegation_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from recurse delegation"),
NS_QUERY_DELEGATION_RECURSE_BEGIN,
NS_QUERY_DELEGATION_RECURSE_BEGIN,
hook_recurse_query_delegation_recurse_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from nodata"),
NS_QUERY_NODATA_BEGIN,
NS_QUERY_NODATA_BEGIN,
hook_recurse_query_nodata_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from nxdomain"),
NS_QUERY_NXDOMAIN_BEGIN,
NS_QUERY_NXDOMAIN_BEGIN,
hook_recurse_query_nxdomain_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from ncache"),
NS_QUERY_NCACHE_BEGIN,
NS_QUERY_NCACHE_BEGIN,
hook_recurse_query_ncache_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from CNAME"),
NS_QUERY_CNAME_BEGIN,
NS_QUERY_CNAME_BEGIN,
hook_recurse_query_cname_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from DNAME"),
NS_QUERY_DNAME_BEGIN,
NS_QUERY_DNAME_BEGIN,
hook_recurse_query_dname_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from prep response"),
NS_QUERY_PREP_RESPONSE_BEGIN,
NS_QUERY_PREP_RESPONSE_BEGIN,
hook_recurse_query_response_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from respond"),
NS_QUERY_RESPOND_BEGIN,
NS_QUERY_RESPOND_BEGIN,
hook_recurse_query_respond_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from done begin"),
NS_QUERY_DONE_BEGIN,
NS_QUERY_DONE_BEGIN,
hook_recurse_query_done_begin,
ISC_R_SUCCESS,
true,
false,
},
{
NS_TEST_ID("recurse from done send"),
NS_QUERY_DONE_SEND,
NS_QUERY_DONE_BEGIN,
hook_recurse_query_done_begin,
ISC_R_SUCCESS,
true,
false,
},
};
for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
run_hookasync_test(&tests[i]);
}
}
/*****
***** tests for higher level ("e2e") behavior of ns_query_hookasync().
***** It exercises overall behavior for some selected cases, while
***** ns__query_hookasync_test exercises implementation details for a
***** simple scenario and for all supported hook points.
*****/
/*%
* Structure containing parameters for ns__query_hookasync_e2e_test().
*/
typedef struct {
const ns_test_id_t id; /* libns test identifier */
const char *qname; /* QNAME */
ns_hookpoint_t hookpoint; /* hook point specified for resume */
isc_result_t start_result; /* result of 'runasync' */
bool do_cancel; /* true if query should be canceled
* in test */
dns_rcode_t expected_rcode;
} ns__query_hookasync_e2e_test_params_t;
/* data structure passed from tests to hooks */
typedef struct hookasync_e2e_data {
bool async; /* true if in a hook-triggered
* asynchronous process */
ns_hook_resevent_t *rev; /* resume event sent on completion */
ns_hookpoint_t hookpoint; /* specifies where to resume */
isc_result_t start_result; /* result of 'runasync' */
dns_rcode_t expected_rcode;
bool done; /* if SEND_DONE hook is called */
} hookasync_e2e_data_t;
/* Cancel callback. Just need to be defined, it doesn't have to do anything. */
static void
cancel_e2ehookactx(ns_hookasync_t *ctx) {
UNUSED(ctx);
}
/* 'runasync' callback passed to ns_query_hookasync */
static isc_result_t
test_hookasync_e2e(query_ctx_t *qctx, isc_mem_t *memctx, void *arg,
isc_task_t *task, isc_taskaction_t action, void *evarg,
ns_hookasync_t **ctxp) {
ns_hookasync_t *ctx = NULL;
ns_hook_resevent_t *rev = NULL;
hookasync_e2e_data_t *asdata = arg;
if (asdata->start_result != ISC_R_SUCCESS) {
return (asdata->start_result);
}
ctx = isc_mem_get(memctx, sizeof(*ctx));
rev = (ns_hook_resevent_t *)isc_event_allocate(
memctx, task, NS_EVENT_HOOKASYNCDONE, action, evarg,
sizeof(*rev));
rev->hookpoint = asdata->hookpoint;
rev->saved_qctx = qctx;
rev->ctx = ctx;
asdata->rev = rev;
*ctx = (ns_hookasync_t){ .private = asdata };
isc_mem_attach(memctx, &ctx->mctx);
ctx->destroy = destroy_hookactx;
ctx->cancel = cancel_e2ehookactx;
*ctxp = ctx;
return (ISC_R_SUCCESS);
}
static ns_hookresult_t
hook_recurse_e2e(void *arg, void *data, isc_result_t *resultp) {
query_ctx_t *qctx = arg;
hookasync_e2e_data_t *asdata = data;
isc_result_t result;
if (!asdata->async) {
/* Initial call to the hook; start recursion */
result = ns_query_hookasync(qctx, test_hookasync_e2e, asdata);
if (result != ISC_R_SUCCESS) {
*resultp = result;
return (NS_HOOK_RETURN);
}
asdata->async = true;
asdata->rev->origresult = *resultp; /* save it for resume */
*resultp = ISC_R_UNSET;
return (NS_HOOK_RETURN);
} else {
/* Resume from the completion of recursion */
asdata->async = false;
/* Don't touch 'resultp' */
return (NS_HOOK_CONTINUE);
}
}
/*
* Check whether the final response has expected the RCODE according to
* the test scenario.
*/
static ns_hookresult_t
hook_donesend(void *arg, void *data, isc_result_t *resultp) {
query_ctx_t *qctx = arg;
hookasync_e2e_data_t *asdata = data;
INSIST(qctx->client->message->rcode == asdata->expected_rcode);
asdata->done = true; /* Let the test know this hook is called */
*resultp = ISC_R_UNSET;
return (NS_HOOK_CONTINUE);
}
static void
run_hookasync_e2e_test(const ns__query_hookasync_e2e_test_params_t *test) {
query_ctx_t *qctx = NULL;
isc_result_t result;
hookasync_e2e_data_t asdata = {
.async = false,
.hookpoint = test->hookpoint,
.start_result = test->start_result,
.expected_rcode = test->expected_rcode,
.done = false,
};
const ns_hook_t donesend_hook = {
.action = hook_donesend,
.action_data = &asdata,
};
const ns_hook_t hook = {
.action = hook_recurse_e2e,
.action_data = &asdata,
};
const ns_test_qctx_create_params_t qctx_params = {
.qname = test->qname,
.qtype = dns_rdatatype_a,
.with_cache = true,
};
ns__hook_table = NULL;
ns_hooktable_create(mctx, &ns__hook_table);
ns_hook_add(ns__hook_table, mctx, test->hookpoint, &hook);
ns_hook_add(ns__hook_table, mctx, NS_QUERY_DONE_SEND, &donesend_hook);
result = ns_test_qctx_create(&qctx_params, &qctx);
INSIST(result == ISC_R_SUCCESS);
isc_sockaddr_any(&qctx->client->peeraddr); /* for sortlist */
qctx->client->sendcb = send_noop;
/* Load a zone. it should have ns.foo/A */
result = ns_test_serve_zone("foo", "testdata/query/foo.db",
qctx->client->view);
INSIST(result == ISC_R_SUCCESS);
/*
* We expect to have a response sent all cases, so we need to
* setup reqhandle (which will be detached on the send).
*/
isc_nmhandle_attach(qctx->client->handle, &qctx->client->reqhandle);
/* Handle the query. hook-based recursion will be triggered. */
qctx->client->state = NS_CLIENTSTATE_WORKING;
ns__query_start(qctx);
/* If specified cancel the query at this point. */
if (test->do_cancel) {
ns_query_cancel(qctx->client);
}
if (test->start_result == ISC_R_SUCCESS) {
/* If recursion has started, manually invoke the done event. */
INSIST(asdata.async);
asdata.rev->ev_action(asdata.rev->ev_sender,
(isc_event_t *)asdata.rev);
/*
* Usually 'async' is reset to false on the 2nd call to
* the hook. But the hook isn't called if the query is
* canceled.
*/
INSIST(asdata.done == !test->do_cancel);
INSIST(asdata.async == test->do_cancel);
} else {
INSIST(!asdata.async);
}
/* Cleanup */
ns_test_qctx_destroy(&qctx);
ns_test_cleanup_zone();
ns_hooktable_free(mctx, (void **)&ns__hook_table);
}
static void
ns__query_hookasync_e2e_test(void **state) {
UNUSED(state);
const ns__query_hookasync_e2e_test_params_t tests[] = {
{
NS_TEST_ID("positive answer"),
"ns.foo",
NS_QUERY_GOT_ANSWER_BEGIN,
ISC_R_SUCCESS,
false,
dns_rcode_noerror,
},
{
NS_TEST_ID("NXDOMAIN"),
"notexist.foo",
NS_QUERY_NXDOMAIN_BEGIN,
ISC_R_SUCCESS,
false,
dns_rcode_nxdomain,
},
{
NS_TEST_ID("recurse fail"),
"ns.foo",
NS_QUERY_DONE_BEGIN,
ISC_R_FAILURE,
false,
-1,
},
{
NS_TEST_ID("cancel query"),
"ns.foo",
NS_QUERY_DONE_BEGIN,
ISC_R_SUCCESS,
true,
-1,
},
};
for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
run_hookasync_e2e_test(&tests[i]);
}
}
int
main(void) {
const struct CMUnitTest tests[] = {
@ -606,6 +1507,10 @@ main(void) {
_teardown),
cmocka_unit_test_setup_teardown(ns__query_start_test, _setup,
_teardown),
cmocka_unit_test_setup_teardown(ns__query_hookasync_test,
_setup, _teardown),
cmocka_unit_test_setup_teardown(ns__query_hookasync_e2e_test,
_setup, _teardown),
};
return (cmocka_run_group_tests(tests, NULL, NULL));

View file

@ -82,6 +82,7 @@ ns_plugins_free
ns_query_cancel
ns_query_done
ns_query_free
ns_query_hookasync
ns_query_init
ns_query_recurse
ns_query_start

View file

@ -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
@ -2095,6 +2099,7 @@
./lib/ns/client.c C 2017,2018,2019,2020
./lib/ns/hooks.c C 2018,2019,2020
./lib/ns/include/ns/client.h C 2017,2018,2019,2020
./lib/ns/include/ns/events.h C 2020
./lib/ns/include/ns/hooks.h C 2017,2018,2019,2020
./lib/ns/include/ns/interfacemgr.h C 2017,2018,2019,2020
./lib/ns/include/ns/lib.h C 2017,2018,2019,2020