mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
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:
commit
7d1e2e6692
21 changed files with 2017 additions and 62 deletions
6
CHANGES
6
CHANGES
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
14
bin/tests/system/hooks/clean.sh
Normal file
14
bin/tests/system/hooks/clean.sh
Normal 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
|
||||
13
bin/tests/system/hooks/driver/Makefile.am
Normal file
13
bin/tests/system/hooks/driver/Makefile.am
Normal 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
|
||||
364
bin/tests/system/hooks/driver/test-async.c
Normal file
364
bin/tests/system/hooks/driver/test-async.c
Normal 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);
|
||||
}
|
||||
19
bin/tests/system/hooks/ns1/example.db
Normal file
19
bin/tests/system/hooks/ns1/example.db
Normal 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
|
||||
39
bin/tests/system/hooks/ns1/named.conf.in
Normal file
39
bin/tests/system/hooks/ns1/named.conf.in
Normal 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";
|
||||
};
|
||||
14
bin/tests/system/hooks/setup.sh
Normal file
14
bin/tests/system/hooks/setup.sh
Normal 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
|
||||
34
bin/tests/system/hooks/tests.sh
Normal file
34
bin/tests/system/hooks/tests.sh
Normal 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
|
||||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
25
lib/ns/include/ns/events.h
Normal file
25
lib/ns/include/ns/events.h
Normal 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 */
|
||||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
385
lib/ns/query.c
385
lib/ns/query.c
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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, "a);
|
||||
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("a);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue