+4772. [test] Expanded unit testing framework for libns, using
+ hooks to interrupt query flow and inspect state
+ at specified locations. [RT #46173]
+
4771. [bug] When sending RFC 5011 refresh queries, disregard
cached DNSKEY rrsets. [RT #46251]
docdir
oldincludedir
includedir
+runstatedir
localstatedir
sharedstatedir
sysconfdir
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
+ -runstatedir | --runstatedir | --runstatedi | --runstated \
+ | --runstate | --runstat | --runsta | --runst | --runs \
+ | --run | --ru | --r)
+ ac_prev=runstatedir ;;
+ -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+ | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+ | --run=* | --ru=* | --r=*)
+ runstatedir=$ac_optarg ;;
+
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir
+ libdir localedir mandir runstatedir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
XTARGETS=
case "$enable_developer" in
yes)
- STD_CDEFINES="$STD_CDEFINES -DISC_MEM_DEFAULTFILL=1 -DISC_LIST_CHECKINIT=1"
+ STD_CDEFINES="$STD_CDEFINES -DISC_MEM_DEFAULTFILL=1 -DISC_LIST_CHECKINIT=1 -DNS_HOOKS_ENABLE=1"
test "${enable_fixed_rrset+set}" = set || enable_fixed_rrset=yes
test "${enable_querytrace+set}" = set || enable_querytrace=yes
test "${with_atf+set}" = set || with_atf=yes
$as_echo "#define ATF_TEST 1" >>confdefs.h
STD_CINCLUDES="$STD_CINCLUDES -I$atf/include"
+ STD_CDEFINES="$STD_CDEFINES -DNS_HOOKS_ENABLE=1"
ATFBIN="$atf/bin"
ATFLIBS="-L$atf/lib -latf-c"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for exp in -lm" >&5
XTARGETS=
case "$enable_developer" in
yes)
- STD_CDEFINES="$STD_CDEFINES -DISC_MEM_DEFAULTFILL=1 -DISC_LIST_CHECKINIT=1"
+ STD_CDEFINES="$STD_CDEFINES -DISC_MEM_DEFAULTFILL=1 -DISC_LIST_CHECKINIT=1 -DNS_HOOKS_ENABLE=1"
test "${enable_fixed_rrset+set}" = set || enable_fixed_rrset=yes
test "${enable_querytrace+set}" = set || enable_querytrace=yes
test "${with_atf+set}" = set || with_atf=yes
if test "no" != "$atf"; then
AC_DEFINE(ATF_TEST, 1, [define if ATF unit tests are to be built.])
STD_CINCLUDES="$STD_CINCLUDES -I$atf/include"
+ STD_CDEFINES="$STD_CDEFINES -DNS_HOOKS_ENABLE=1"
ATFBIN="$atf/bin"
ATFLIBS="-L$atf/lib -latf-c"
AC_CHECK_LIB(m, exp, libm=yes, libm=no)
--- /dev/null
+/*
+ * Copyright (C) 2017 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/.
+ */
+
+#ifndef NS_HOOKS_H
+#define NS_HOOKS_H 1
+
+#ifdef NS_HOOKS_ENABLE
+
+/*! \file */
+
+#include <isc/result.h>
+
+/*
+ * Hooks provide a way of running a callback function once a certain place in
+ * code is reached. Current use is limited to libns unit tests and thus:
+ *
+ * - hook-related types and macros are not placed in libns header files,
+ * - hook-related code is compiled away unless --enable-developer is used,
+ * - hook-related macro names are prefixed with "NS_".
+ *
+ * However, the implementation is pretty generic and could be repurposed for
+ * general use, e.g. as part of libisc, after some further customization.
+ *
+ * Hooks are created by inserting a macro into any function returning
+ * isc_result_t (NS_PROCESS_HOOK()) or void (NS_PROCESS_HOOK_VOID()). Each
+ * hook has an identifier, which is an integer that is an index into the hook
+ * table. In an attempt to keep things as simple as possible, current
+ * implementation:
+ *
+ * - uses hook tables which are statically-sized arrays only allowing a
+ * single callback to be invoked for each hook identifier,
+ * - only supports replacing whole hook tables.
+ *
+ * Hook callbacks are functions which:
+ *
+ * - return a boolean value; if ISC_TRUE is returned by the callback, the
+ * function into which the hook is inserted will return at hook insertion
+ * point; if ISC_FALSE is returned by the callback, execution of the
+ * function into which the hook is inserted continues normally,
+ *
+ * - accept three pointers as arguments:
+ *
+ * - a pointer specified by the hook itself,
+ * - a pointer specified upon inserting the callback into the hook table,
+ * - a pointer to isc_result_t which will be returned by the function
+ * into which the hook is inserted if the callback returns ISC_TRUE.
+ *
+ * In order for a hook callback to be called for a given hook, a pointer to
+ * that callback (along with an optional pointer to callback-specific data) has
+ * to be inserted into the hook table entry for that hook.
+ *
+ * Consider the following sample code:
+ *
+ * ----------------------------------------------------------------------------
+ * const ns_hook_t *foo_hook_table = NULL;
+ *
+ * isc_result_t
+ * foo_bar(void) {
+ * int val = 42;
+ * ...
+ * NS_PROCESS_HOOK(foo_hook_table, FOO_EXTRACT_VAL, &val);
+ * ...
+ * printf("This message may not be printed due to use of hooks.");
+ *
+ * return (ISC_R_SUCCESS);
+ * }
+ *
+ * isc_boolean_t
+ * cause_failure(void *hook_data, void *callback_data, isc_result_t *resultp) {
+ * ...
+ * *resultp = ISC_R_FAILURE;
+ *
+ * return (ISC_TRUE);
+ * }
+ *
+ * void
+ * test_foo_bar(void) {
+ * isc_boolean_t foo_bar_called = ISC_FALSE;
+ * const ns_hook_t my_hooks[FOO_HOOKS_COUNT] = {
+ * [FOO_EXTRACT_VAL] = {
+ * .callback = cause_failure,
+ * .callback_data = &foo_bar_called,
+ * },
+ * };
+ *
+ * foo_hook_table = my_hooks;
+ *
+ * foo_bar();
+ * }
+ * ----------------------------------------------------------------------------
+ *
+ * When test_foo_bar() is called, the hook table is first replaced. Then
+ * foo_bar() gets invoked. Once execution reaches the insertion point for hook
+ * FOO_EXTRACT_VAL, cause_failure() will be called with &val as hook_data and
+ * &foo_bar_called as callback_data. It can do whatever it pleases with these
+ * two values. Eventually, cause_failure() sets *resultp to ISC_R_FAILURE and
+ * returns ISC_TRUE, which causes foo_bar() to return ISC_R_FAILURE and never
+ * execute the printf() call below hook insertion point.
+ */
+
+enum {
+ NS_QUERY_SETUP_QCTX_INITIALIZED,
+ NS_QUERY_LOOKUP_BEGIN,
+ NS_QUERY_DONE_BEGIN,
+ NS_QUERY_HOOKS_COUNT
+};
+
+typedef isc_boolean_t
+(*ns_hook_cb_t)(void *hook_data, void *callback_data, isc_result_t *resultp);
+
+typedef struct ns_hook {
+ ns_hook_cb_t callback;
+ void *callback_data;
+} ns_hook_t;
+
+#define _NS_PROCESS_HOOK(table, id, data, ...) \
+ if (table != NULL) { \
+ ns_hook_cb_t _callback = table[id].callback; \
+ void *_callback_data = table[id].callback_data; \
+ isc_result_t _result; \
+ \
+ if (_callback != NULL && \
+ _callback(data, _callback_data, &_result)) { \
+ return __VA_ARGS__; \
+ } \
+ }
+
+#define NS_PROCESS_HOOK(table, id, data) \
+ _NS_PROCESS_HOOK(table, id, data, _result)
+
+#define NS_PROCESS_HOOK_VOID(table, id, data) \
+ _NS_PROCESS_HOOK(table, id, data)
+
+LIBNS_EXTERNAL_DATA extern const ns_hook_t *ns__hook_table;
+
+#endif /* NS_HOOKS_ENABLE */
+#endif /* NS_HOOKS_H */
#include <isc/netaddr.h>
#include <dns/rdataset.h>
+#include <dns/resolver.h>
#include <dns/rpz.h>
#include <dns/types.h>
#define NS_QUERYATTR_RRL_CHECKED 0x10000
#define NS_QUERYATTR_REDIRECT 0x20000
+/* query context structure */
+
+typedef struct query_ctx {
+ isc_buffer_t *dbuf; /* name buffer */
+ dns_name_t *fname; /* found name from DB lookup */
+ dns_rdataset_t *rdataset; /* found rdataset */
+ dns_rdataset_t *sigrdataset; /* found sigrdataset */
+ dns_rdataset_t *noqname; /* rdataset needing
+ * NOQNAME proof */
+ dns_rdatatype_t qtype;
+ dns_rdatatype_t type;
+
+ unsigned int options; /* DB lookup options */
+
+ isc_boolean_t redirected; /* nxdomain redirected? */
+ isc_boolean_t is_zone; /* is DB a zone DB? */
+ isc_boolean_t is_staticstub_zone;
+ isc_boolean_t resuming; /* resumed from recursion? */
+ isc_boolean_t dns64, dns64_exclude, rpz;
+ isc_boolean_t authoritative; /* authoritative query? */
+ isc_boolean_t want_restart; /* CNAME chain or other
+ * restart needed */
+ isc_boolean_t need_wildcardproof; /* wilcard proof needed */
+ isc_boolean_t nxrewrite; /* negative answer from RPZ */
+ isc_boolean_t findcoveringnsec; /* lookup covering NSEC */
+ isc_boolean_t want_stale; /* want stale records? */
+ dns_fixedname_t wildcardname; /* name needing wcard proof */
+ dns_fixedname_t dsname; /* name needing DS */
+
+ ns_client_t *client; /* client object */
+ dns_fetchevent_t *event; /* recursion event */
+
+ dns_db_t *db; /* zone or cache database */
+ dns_dbversion_t *version; /* DB version */
+ dns_dbnode_t *node; /* DB node */
+
+ dns_db_t *zdb; /* zone DB values, saved */
+ dns_name_t *zfname; /* while searching cache */
+ dns_dbversion_t *zversion; /* for a better answer */
+ dns_rdataset_t *zrdataset;
+ dns_rdataset_t *zsigrdataset;
+
+ dns_rpz_st_t *rpz_st; /* RPZ state */
+ dns_zone_t *zone; /* zone to search */
+
+ isc_result_t result; /* query result */
+ int line; /* line to report error */
+} query_ctx_t;
+
isc_result_t
ns_query_init(ns_client_t *client);
void
ns_query_cancel(ns_client_t *client);
+/*%
+ * (Must not be used outside this module and its associated unit tests.)
+ */
+isc_result_t
+ns__query_sfcache(query_ctx_t *qctx);
+
+/*%
+ * (Must not be used outside this module and its associated unit tests.)
+ */
+isc_result_t
+ns__query_start(query_ctx_t *qctx);
+
#endif /* NS_QUERY_H */
#include <ns/stats.h>
#include <ns/xfrout.h>
+#include "hooks.h"
+
#if 0
/*
* It has been recommended that DNS64 be changed to return excluded
log_noexistnodata(void *val, int level, const char *fmt, ...)
ISC_FORMAT_PRINTF(3, 4);
-/*%
- * The structure and functions defined below implement the query logic
- * that previously lived in the single very complex function query_find().
- * The query_ctx_t structure maintains state from function to function.
- * The call flow for the general query processing algorithm is described
- * below:
+#ifdef NS_HOOKS_ENABLE
+
+LIBNS_EXTERNAL_DATA const ns_hook_t *ns__hook_table = NULL;
+
+#define PROCESS_HOOK(...) \
+ NS_PROCESS_HOOK(ns__hook_table, __VA_ARGS__)
+#define PROCESS_HOOK_VOID(...) \
+ NS_PROCESS_HOOK_VOID(ns__hook_table, __VA_ARGS__)
+
+#else
+
+#define PROCESS_HOOK(...) do {} while (0)
+#define PROCESS_HOOK_VOID(...) do {} while (0)
+
+#endif
+
+/*
+ * The functions defined below implement the query logic that previously lived
+ * in the single very complex function query_find(). The query_ctx_t structure
+ * defined in <ns/query.h> maintains state from function to function. The call
+ * flow for the general query processing algorithm is described below:
*
* 1. Set up query context and other resources for a client
* query (query_setup())
*
- * 2. Start the search (query_start())
+ * 2. Start the search (ns__query_start())
*
* 3. Identify authoritative data sources which may have an answer;
* search them (query_lookup()). If an answer is found, go to 7.
* DNS64, filter-aaaa, RPZ, RRL, and the SERVFAIL cache.)
*/
-typedef struct query_ctx {
- isc_buffer_t *dbuf; /* name buffer */
- dns_name_t *fname; /* found name from DB lookup */
- dns_rdataset_t *rdataset; /* found rdataset */
- dns_rdataset_t *sigrdataset; /* found sigrdataset */
- dns_rdataset_t *noqname; /* rdataset needing
- * NOQNAME proof */
- dns_rdatatype_t qtype;
- dns_rdatatype_t type;
-
- unsigned int options; /* DB lookup options */
-
- isc_boolean_t redirected; /* nxdomain redirected? */
- isc_boolean_t is_zone; /* is DB a zone DB? */
- isc_boolean_t is_staticstub_zone;
- isc_boolean_t resuming; /* resumed from recursion? */
- isc_boolean_t dns64, dns64_exclude, rpz;
- isc_boolean_t authoritative; /* authoritative query? */
- isc_boolean_t want_restart; /* CNAME chain or other
- * restart needed */
- isc_boolean_t need_wildcardproof; /* wilcard proof needed */
- isc_boolean_t nxrewrite; /* negative answer from RPZ */
- isc_boolean_t findcoveringnsec; /* lookup covering NSEC */
- isc_boolean_t want_stale; /* want stale records? */
- dns_fixedname_t wildcardname; /* name needing wcard proof */
- dns_fixedname_t dsname; /* name needing DS */
-
- ns_client_t *client; /* client object */
- dns_fetchevent_t *event; /* recursion event */
-
- dns_db_t *db; /* zone or cache database */
- dns_dbversion_t *version; /* DB version */
- dns_dbnode_t *node; /* DB node */
-
- dns_db_t *zdb; /* zone DB values, saved */
- dns_name_t *zfname; /* while searching cache */
- dns_dbversion_t *zversion; /* for a better answer */
- dns_rdataset_t *zrdataset;
- dns_rdataset_t *zsigrdataset;
-
- dns_rpz_st_t *rpz_st; /* RPZ state */
- dns_zone_t *zone; /* zone to search */
-
- isc_result_t result; /* query result */
- int line; /* line to report error */
-} query_ctx_t;
-
static void
query_trace(query_ctx_t *qctx);
static isc_result_t
query_setup(ns_client_t *client, dns_rdatatype_t qtype);
-static isc_result_t
-query_start(query_ctx_t *qctx);
-
static isc_result_t
query_lookup(query_ctx_t *qctx);
static isc_result_t
query_resume(query_ctx_t *qctx);
-static isc_result_t
-query_sfcache(query_ctx_t *qctx);
-
static isc_result_t
query_checkrrl(query_ctx_t *qctx, isc_result_t result);
/*
* Set up query processing for the current query of 'client'.
* Calls qctx_init() to initialize a query context, checks
- * the SERVFAIL cache, then hands off processing to query_start().
+ * the SERVFAIL cache, then hands off processing to ns__query_start().
*
* This is called only from ns_query_start(), to begin a query
* for the first time. Restarting an existing query (for
* instance, to handle CNAME lookups), is done by calling
- * query_start() again with the same query context. Resuming from
+ * ns__query_start() again with the same query context. Resuming from
* recursion is handled by query_resume().
*/
static isc_result_t
qctx_init(client, NULL, qtype, &qctx);
query_trace(&qctx);
- /*
- * Check SERVFAIL cache
- */
- result = query_sfcache(&qctx);
- if (result != ISC_R_COMPLETE) {
- return (result);
- }
-
/*
* If it's a SIG query, we'll iterate the node.
*/
qctx.qtype == dns_rdatatype_sig)
{
qctx.type = dns_rdatatype_any;
- } else {
- qctx.type = qctx.qtype;
}
- return (query_start(&qctx));
+ PROCESS_HOOK(NS_QUERY_SETUP_QCTX_INITIALIZED, &qctx);
+
+ /*
+ * Check SERVFAIL cache
+ */
+ result = ns__query_sfcache(&qctx);
+ if (result != ISC_R_COMPLETE) {
+ return (result);
+ }
+
+ return (ns__query_start(&qctx));
}
/*%
* follow a CNAME chain. Determines which authoritative database to
* search, then hands off processing to query_lookup().
*/
-static isc_result_t
-query_start(query_ctx_t *qctx) {
+isc_result_t
+ns__query_start(query_ctx_t *qctx) {
isc_result_t result;
- CCTRACE(ISC_LOG_DEBUG(3), "query_start");
+ CCTRACE(ISC_LOG_DEBUG(3), "ns__query_start");
qctx->want_restart = ISC_FALSE;
qctx->authoritative = ISC_FALSE;
qctx->version = NULL;
if (dns_rdatatype_atparent(qctx->qtype) &&
!dns_name_equal(qctx->client->query.qname, dns_rootname))
{
+ /*
+ * If authoritative data for this QTYPE is supposed to live in
+ * the parent zone, do not look for an exact match for QNAME,
+ * but rather for its containing zone (unless the QNAME is
+ * root).
+ */
qctx->options |= DNS_GETDB_NOEXACT;
}
(qctx->options & DNS_GETDB_NOEXACT) != 0))
{
/*
- * If the query type is DS, look to see if we are
- * authoritative for the child zone.
+ * This is a non-recursive QTYPE=DS query with QNAME whose
+ * parent we are not authoritative for. Check whether we are
+ * authoritative for QNAME, because if so, we need to send a
+ * "no data" response as required by RFC 4035, section 3.1.4.1.
*/
dns_db_t *tdb = NULL;
dns_zone_t *tzone = NULL;
DNS_GETDB_PARTIAL,
&tzone, &tdb, &tversion);
if (tresult == ISC_R_SUCCESS) {
+ /*
+ * We are authoritative for QNAME. Attach the relevant
+ * zone to query context, set result to ISC_R_SUCCESS.
+ */
qctx->options &= ~DNS_GETDB_NOEXACT;
query_putrdataset(qctx->client, &qctx->rdataset);
if (qctx->db != NULL) {
qctx->is_zone = ISC_TRUE;
result = ISC_R_SUCCESS;
} else {
+ /*
+ * We are not authoritative for QNAME. Clean up and
+ * leave result as it was.
+ */
if (tdb != NULL) {
dns_db_detach(&tdb);
}
}
}
}
+ /*
+ * If we did not find a database from which we can answer the query,
+ * respond with either REFUSED or SERVFAIL, depending on what the
+ * result of query_getdb() was.
+ */
if (result != ISC_R_SUCCESS) {
if (result == DNS_R_REFUSED) {
if (WANTRECURSION(qctx->client)) {
}
} else {
CCTRACE(ISC_LOG_ERROR,
- "query_start: query_getdb failed");
+ "ns__query_start: query_getdb failed");
QUERY_ERROR(qctx, DNS_R_SERVFAIL);
}
return (query_done(qctx));
}
+ /*
+ * We found a database from which we can answer the query. Update
+ * relevant query context flags if the answer is to be prepared using
+ * authoritative data.
+ */
qctx->is_staticstub_zone = ISC_FALSE;
if (qctx->is_zone) {
qctx->authoritative = ISC_TRUE;
}
}
+ /*
+ * Attach to the database which will be used to prepare the answer.
+ * Update query statistics.
+ */
if (qctx->event == NULL && qctx->client->query.restarts == 0) {
if (qctx->is_zone) {
if (qctx->zone != NULL) {
CCTRACE(ISC_LOG_DEBUG(3), "query_lookup");
+ PROCESS_HOOK(NS_QUERY_LOOKUP_BEGIN, qctx);
+
dns_clientinfomethods_init(&cm, ns_client_sourceip);
dns_clientinfo_init(&ci, qctx->client, NULL);
* 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
* from a query with CD=1, *or* if the current query has CD=0, then we just
- * return SERVFAIL again.
+ * return SERVFAIL again. This prevents a validation failure from eliciting a
+ * SERVFAIL response to a CD=1 query.
*/
-static isc_result_t
-query_sfcache(query_ctx_t *qctx) {
+isc_result_t
+ns__query_sfcache(query_ctx_t *qctx) {
isc_boolean_t failcache;
isc_uint32_t flags;
*
* - Clean up
* - If we have an answer ready (positive or negative), send it.
- * - If we need to restart for a chaining query, call query_start() again.
+ * - If we need to restart for a chaining query, call ns__query_start() again.
* - If we've started recursion, then just clean up; things will be
* restarted via fetch_callback()/query_resume().
*/
const dns_namelist_t *secs = qctx->client->message->sections;
CCTRACE(ISC_LOG_DEBUG(3), "query_done");
+ PROCESS_HOOK(NS_QUERY_DONE_BEGIN, qctx);
+
/*
* General cleanup.
*/
*/
if (qctx->want_restart && qctx->client->query.restarts < MAX_RESTARTS) {
qctx->client->query.restarts++;
- return (query_start(qctx));
+ return (ns__query_start(qctx));
}
if (qctx->want_stale) {
OBJS = nstest.@O@
SRCS = nstest.c \
listenlist_test.c \
- notify_test.c
+ notify_test.c \
+ query_test.c
SUBDIRS =
TARGETS = listenlist_test@EXEEXT@ \
- notify_test@EXEEXT@
+ notify_test@EXEEXT@ \
+ query_test
@BIND9_MAKE_RULES@
notify_test.@O@ nstest.@O@ ${NSLIBS} ${DNSLIBS} \
${ISCLIBS} ${LIBS}
+query_test@EXEEXT@: query_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS}
+ ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \
+ query_test.@O@ nstest.@O@ ${NSLIBS} ${DNSLIBS} \
+ ${ISCLIBS} ${LIBS}
+
unit::
sh ${top_srcdir}/unit/unittest.sh
#include "nstest.h"
-static dns_zone_t *zone = NULL;
-static dns_view_t *view = NULL;
-
-/*
- * Helper functions
- */
-static void
-setup_zone(const char *zonename, const char *filename) {
- isc_result_t result;
- dns_db_t *db = NULL;
-
- result = ns_test_makezone(zonename, &zone, NULL, ISC_TRUE);
- ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
- result = ns_test_setupzonemgr();
- ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
- result = ns_test_managezone(zone);
- ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
- view = dns_zone_getview(zone);
- ATF_REQUIRE(view->zonetable != NULL);
- view->nocookieudp = 512;
-
- dns_zone_setfile(zone, filename);
- result = dns_zone_load(zone);
- ATF_REQUIRE(result == ISC_R_SUCCESS);
-
- /* The zone should now be loaded; test it */
- result = dns_zone_getdb(zone, &db);
- ATF_CHECK_EQ(result, ISC_R_SUCCESS);
- ATF_CHECK(db != NULL);
- if (db != NULL) {
- dns_db_detach(&db);
- }
-}
-
-static void
-cleanup_zone() {
- ns_test_releasezone(zone);
- ns_test_closezonemgr();
-
- dns_zone_detach(&zone);
- dns_view_detach(&view);
-}
-
static void
check_response(isc_buffer_t *buf) {
isc_result_t result;
result = ns_test_getclient(NULL, ISC_FALSE, &client);
ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
- setup_zone("example.com", "testdata/notify/zone1.db");
+ result = ns_test_makeview("view", ISC_FALSE, &client->view);
+ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+
+ result = ns_test_serve_zone("example.com", "testdata/notify/zone1.db",
+ client->view);
+ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
/*
* Create a NOTIFY message by parsing a file in testdata.
* Set up client object with this message and test the NOTIFY
* handler.
*/
- dns_view_attach(view, &client->view);
if (client->message != NULL) {
dns_message_destroy(&client->message);
}
/*
* Clean up
*/
- cleanup_zone();
+ ns_test_cleanup_zone();
ns_client_detach(&client);
#include <isc/mem.h>
#include <isc/os.h>
#include <isc/print.h>
+#include <isc/random.h>
#include <isc/string.h>
#include <isc/socket.h>
#include <isc/stdio.h>
#include <isc/timer.h>
#include <isc/util.h>
+#include <dns/cache.h>
#include <dns/db.h>
#include <dns/dispatch.h>
#include <dns/fixedname.h>
#include <ns/interfacemgr.h>
#include <ns/server.h>
+#include "../hooks.h"
+
#include "nstest.h"
isc_mem_t *mctx = NULL;
static isc_boolean_t hash_active = ISC_FALSE, dst_active = ISC_FALSE;
+static dns_zone_t *served_zone = NULL;
+
/*
* Logging categories: this needs to match the list in lib/ns/log.c.
*/
if (chdir(TESTS) == -1)
CHECK(ISC_R_FAILURE);
+ ns__hook_table = NULL;
+
return (ISC_R_SUCCESS);
cleanup:
isc_mem_destroy(&mctx);
}
-/*
- * Create a view.
- */
isc_result_t
-ns_test_makeview(const char *name, dns_view_t **viewp) {
- isc_result_t result;
+ns_test_makeview(const char *name, isc_boolean_t with_cache,
+ dns_view_t **viewp)
+{
+ dns_cache_t *cache = NULL;
dns_view_t *view = NULL;
+ isc_result_t result;
CHECK(dns_view_create(mctx, dns_rdataclass_in, name, &view));
+
+ if (with_cache) {
+ CHECK(dns_cache_create(mctx, taskmgr, timermgr,
+ dns_rdataclass_in, "rbt", 0, NULL,
+ &cache));
+ dns_view_setcache(view, cache);
+ /*
+ * Reference count for "cache" is now at 2, so decrement it in
+ * order for the cache to be automatically freed when "view"
+ * gets freed.
+ */
+ dns_cache_detach(&cache);
+ }
+
*viewp = view;
return (ISC_R_SUCCESS);
dns_zonemgr_detach(&zonemgr);
}
+isc_result_t
+ns_test_serve_zone(const char *zonename, const char *filename,
+ dns_view_t *view)
+{
+ isc_result_t result;
+ dns_db_t *db = NULL;
+
+ /*
+ * Prepare zone structure for further processing.
+ */
+ result = ns_test_makezone(zonename, &served_zone, view, ISC_TRUE);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Start zone manager.
+ */
+ result = ns_test_setupzonemgr();
+ if (result != ISC_R_SUCCESS) {
+ goto free_zone;
+ }
+
+ /*
+ * Add the zone to the zone manager.
+ */
+ result = ns_test_managezone(served_zone);
+ if (result != ISC_R_SUCCESS) {
+ goto close_zonemgr;
+ }
+
+ view->nocookieudp = 512;
+
+ /*
+ * Set path to the master file for the zone and then load it.
+ */
+ dns_zone_setfile(served_zone, filename);
+ result = dns_zone_load(served_zone);
+ if (result != ISC_R_SUCCESS) {
+ goto release_zone;
+ }
+
+ /*
+ * The zone should now be loaded; test it.
+ */
+ result = dns_zone_getdb(served_zone, &db);
+ if (result != ISC_R_SUCCESS) {
+ goto release_zone;
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ return (ISC_R_SUCCESS);
+
+release_zone:
+ ns_test_releasezone(served_zone);
+close_zonemgr:
+ ns_test_closezonemgr();
+free_zone:
+ dns_zone_detach(&served_zone);
+
+ return (result);
+}
+
+void
+ns_test_cleanup_zone(void) {
+ ns_test_releasezone(served_zone);
+ ns_test_closezonemgr();
+
+ dns_zone_detach(&served_zone);
+}
+
isc_result_t
ns_test_getclient(ns_interface_t *ifp0, isc_boolean_t tcp,
ns_client_t **clientp)
return (result);
}
+/*%
+ * Synthesize a DNS message based on supplied QNAME, QTYPE and flags, then
+ * parse it and store the results in client->message.
+ */
+static isc_result_t
+attach_query_msg_to_client(ns_client_t *client, const char *qnamestr,
+ dns_rdatatype_t qtype, unsigned int qflags)
+{
+ dns_rdataset_t *qrdataset = NULL;
+ dns_message_t *message = NULL;
+ unsigned char query[65536];
+ dns_name_t *qname = NULL;
+ isc_buffer_t querybuf;
+ dns_compress_t cctx;
+ isc_result_t result;
+ isc_uint32_t qid;
+
+ REQUIRE(client != NULL);
+ REQUIRE(qnamestr != NULL);
+
+ /*
+ * Create a new DNS message holding a query.
+ */
+ result = dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &message);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Set query ID to a random value.
+ */
+ isc_random_get(&qid);
+ message->id = (dns_messageid_t)(qid & 0xffff);
+
+ /*
+ * Set query flags as requested by the caller.
+ */
+ message->flags = qflags;
+
+ /*
+ * Allocate structures required to construct the query.
+ */
+ result = dns_message_gettemprdataset(message, &qrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_message;
+ }
+ result = dns_message_gettempname(message, &qname);
+ if (result != ISC_R_SUCCESS) {
+ goto put_rdataset;
+ }
+
+ /*
+ * Convert "qnamestr" to a DNS name, create a question rdataset of
+ * class IN and type "qtype", link the two and add the result to the
+ * QUESTION section of the query.
+ */
+ result = dns_name_fromstring(qname, qnamestr, 0, mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto put_name;
+ }
+ dns_rdataset_makequestion(qrdataset, dns_rdataclass_in, qtype);
+ ISC_LIST_APPEND(qname->list, qrdataset, link);
+ dns_message_addname(message, qname, DNS_SECTION_QUESTION);
+
+ /*
+ * Render the query.
+ */
+ dns_compress_init(&cctx, -1, mctx);
+ isc_buffer_init(&querybuf, query, sizeof(query));
+ result = dns_message_renderbegin(message, &cctx, &querybuf);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_message;
+ }
+ result = dns_message_rendersection(message, DNS_SECTION_QUESTION, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_message;
+ }
+ result = dns_message_renderend(message);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_message;
+ }
+ dns_compress_invalidate(&cctx);
+
+ /*
+ * Destroy the created message as it was rendered into "querybuf" and
+ * the latter is all we are going to need from now on.
+ */
+ dns_message_destroy(&message);
+
+ /*
+ * Parse the rendered query, storing results in client->message.
+ */
+ isc_buffer_first(&querybuf);
+ return (dns_message_parse(client->message, &querybuf, 0));
+
+put_name:
+ dns_message_puttempname(message, &qname);
+put_rdataset:
+ dns_message_puttemprdataset(message, &qrdataset);
+destroy_message:
+ dns_message_destroy(&message);
+
+ return (result);
+}
+
+/*%
+ * A hook callback which stores the query context pointed to by "hook_data" at
+ * "callback_data". Causes execution to be interrupted at hook insertion
+ * point.
+ */
+static isc_boolean_t
+extract_qctx(void *hook_data, void *callback_data, isc_result_t *resultp) {
+ query_ctx_t **qctxp;
+ query_ctx_t *qctx;
+
+ REQUIRE(hook_data != NULL);
+ REQUIRE(callback_data != NULL);
+ REQUIRE(resultp != NULL);
+
+ /*
+ * qctx is a stack variable in lib/ns/query.c. Its contents need to be
+ * duplicated or otherwise they will become invalidated once the stack
+ * gets unwound.
+ */
+ qctx = isc_mem_get(mctx, sizeof(*qctx));
+ if (qctx != NULL) {
+ memmove(qctx, (query_ctx_t *)hook_data, sizeof(*qctx));
+ }
+
+ qctxp = (query_ctx_t **)callback_data;
+ /*
+ * If memory allocation failed, the supplied pointer will simply be set
+ * to NULL. We rely on the user of this hook to react properly.
+ */
+ *qctxp = qctx;
+ *resultp = ISC_R_UNSET;
+
+ return (ISC_TRUE);
+}
+
+/*%
+ * Initialize a query context for "client" and store it in "qctxp".
+ *
+ * Requires:
+ *
+ * \li "client->message" to hold a parsed DNS query.
+ */
+static isc_result_t
+create_qctx_for_client(ns_client_t *client, query_ctx_t **qctxp) {
+ const ns_hook_t *saved_hook_table;
+ const ns_hook_t query_hooks[NS_QUERY_HOOKS_COUNT] = {
+ [NS_QUERY_SETUP_QCTX_INITIALIZED] = {
+ .callback = extract_qctx,
+ .callback_data = qctxp,
+ },
+ };
+
+ REQUIRE(client != NULL);
+ REQUIRE(qctxp != NULL);
+ REQUIRE(*qctxp == NULL);
+
+ /*
+ * Call ns_query_start() to initialize a query context for given
+ * client, but first hook into query_setup() so that we can just
+ * extract an initialized query context, without kicking off any
+ * further processing. Make sure we do not overwrite any previously
+ * set hooks.
+ */
+ saved_hook_table = ns__hook_table;
+ ns__hook_table = query_hooks;
+ ns_query_start(client);
+ ns__hook_table = saved_hook_table;
+
+ if (*qctxp == NULL) {
+ return (ISC_R_NOMEMORY);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+isc_result_t
+ns_test_qctx_create(const ns_test_qctx_create_params_t *params,
+ query_ctx_t **qctxp)
+{
+ ns_client_t *client = NULL;
+ isc_result_t result;
+
+ REQUIRE(params != NULL);
+ REQUIRE(params->qname != NULL);
+ REQUIRE(qctxp != NULL);
+ REQUIRE(*qctxp == NULL);
+
+ /*
+ * Allocate and initialize a client structure.
+ */
+ result = ns_test_getclient(NULL, ISC_FALSE, &client);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ TIME_NOW(&client->tnow);
+
+ /*
+ * Every client needs to belong to a view.
+ */
+ result = ns_test_makeview("view", params->with_cache, &client->view);
+ if (result != ISC_R_SUCCESS) {
+ goto detach_client;
+ }
+
+ /*
+ * Synthesize a DNS query using given QNAME, QTYPE and flags, storing
+ * it in client->message.
+ */
+ result = attach_query_msg_to_client(client, params->qname,
+ params->qtype, params->qflags);
+ if (result != ISC_R_SUCCESS) {
+ goto detach_client;
+ }
+
+ /*
+ * Allow recursion for the client. As NS_CLIENTATTR_RA normally gets
+ * set in ns__client_request(), i.e. earlier than the unit tests hook
+ * into the call chain, just set it manually.
+ */
+ client->attributes |= NS_CLIENTATTR_RA;
+
+ /*
+ * Create a query context for a client sending the previously
+ * synthesized query.
+ */
+ result = create_qctx_for_client(client, qctxp);
+ if (result != ISC_R_SUCCESS) {
+ goto destroy_query;
+ }
+
+ /*
+ * Reference count for "client" is now at 2, so decrement it in order
+ * for it to drop to zero when "qctx" gets destroyed.
+ */
+ ns_client_detach(&client);
+
+ return (ISC_R_SUCCESS);
+
+destroy_query:
+ dns_message_destroy(&client->message);
+detach_client:
+ ns_client_detach(&client);
+
+ return (result);
+}
+
+void
+ns_test_qctx_destroy(query_ctx_t **qctxp) {
+ query_ctx_t *qctx;
+
+ REQUIRE(qctxp != NULL);
+ REQUIRE(*qctxp != NULL);
+
+ qctx = *qctxp;
+
+ ns_client_detach(&qctx->client);
+
+ if (qctx->zone != NULL) {
+ dns_zone_detach(&qctx->zone);
+ }
+ if (qctx->db != NULL) {
+ dns_db_detach(&qctx->db);
+ }
+
+ isc_mem_put(mctx, qctx, sizeof(*qctx));
+ *qctxp = NULL;
+}
+
+isc_boolean_t
+ns_test_hook_catch_call(void *hook_data, void *callback_data,
+ isc_result_t *resultp)
+{
+ UNUSED(hook_data);
+ UNUSED(callback_data);
+
+ *resultp = ISC_R_UNSET;
+
+ return (ISC_TRUE);
+}
+
/*
* Sleep for 'usec' microseconds.
*/
#include <ns/interfacemgr.h>
#include <ns/client.h>
+typedef struct ns_test_id {
+ const char *description;
+ int lineno;
+} ns_test_id_t;
+
+#define NS_TEST_ID(desc) { .description = desc, .lineno = __LINE__ }
+
#define CHECK(r) \
do { \
result = (r); \
void
ns_test_end(void);
+/*%
+ * Create a view. If "with_cache" is set to ISC_TRUE, a cache database will
+ * also be created and attached to the created view.
+ */
isc_result_t
-ns_test_makeview(const char *name, dns_view_t **viewp);
+ns_test_makeview(const char *name, isc_boolean_t with_cache,
+ dns_view_t **viewp);
isc_result_t
ns_test_makezone(const char *name, dns_zone_t **zonep, dns_view_t *view,
void
ns_test_closezonemgr(void);
+/*%
+ * Load data for zone "zonename" from file "filename" and start serving it to
+ * clients matching "view". Only one zone loaded using this function can be
+ * served at any given time.
+ */
+isc_result_t
+ns_test_serve_zone(const char *zonename, const char *filename,
+ dns_view_t *view);
+
+/*%
+ * Release the zone loaded by ns_test_serve_zone().
+ */
+void
+ns_test_cleanup_zone(void);
+
void
ns_test_nap(isc_uint32_t usec);
isc_result_t
ns_test_getclient(ns_interface_t *ifp0, isc_boolean_t tcp,
ns_client_t **clientp);
+
+/*%
+ * Structure containing parameters for ns_test_qctx_create().
+ */
+typedef struct ns_test_qctx_create_params {
+ const char *qname;
+ dns_rdatatype_t qtype;
+ unsigned int qflags;
+ isc_boolean_t with_cache;
+} ns_test_qctx_create_params_t;
+
+/*%
+ * Prepare a query context identical with one that would be prepared if a query
+ * with given QNAME, QTYPE and flags was received from a client. Recursion is
+ * assumed to be allowed for this client. If "with_cache" is set to ISC_TRUE,
+ * a cache database will be created and associated with the view matching the
+ * incoming query.
+ */
+isc_result_t
+ns_test_qctx_create(const ns_test_qctx_create_params_t *params,
+ query_ctx_t **qctxp);
+
+/*%
+ * Destroy a query context created by ns_test_qctx_create().
+ */
+void
+ns_test_qctx_destroy(query_ctx_t **qctxp);
+
+/*%
+ * A hook callback interrupting execution at given hook's insertion point.
+ */
+isc_boolean_t
+ns_test_hook_catch_call(void *hook_data, void *callback_data,
+ isc_result_t *resultp);
--- /dev/null
+/*
+ * Copyright (C) 2017 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/.
+ */
+
+/*! \file */
+
+#include <config.h>
+
+#include <atf-c.h>
+
+#include <dns/badcache.h>
+#include <dns/view.h>
+#include <isc/util.h>
+#include <ns/client.h>
+#include <ns/query.h>
+
+#include "../hooks.h"
+
+#include "nstest.h"
+
+/*****
+ ***** ns__query_sfcache() tests
+ *****/
+
+/*%
+ * Structure containing parameters for ns__query_sfcache_test().
+ */
+typedef struct {
+ const ns_test_id_t id; /* libns test identifier */
+ unsigned int qflags; /* query flags */
+ isc_boolean_t cache_entry_present; /* whether a SERVFAIL cache entry
+ matching the query should be
+ present */
+ isc_uint32_t cache_entry_flags; /* NS_FAILCACHE_* flags to set for
+ the SERVFAIL cache entry */
+ isc_boolean_t servfail_expected; /* whether a cached SERVFAIL is
+ expected to be returned */
+} ns__query_sfcache_test_params_t;
+
+/*%
+ * Perform a single ns__query_sfcache() check using given parameters.
+ */
+static void
+ns__query_sfcache_test(const ns__query_sfcache_test_params_t *test) {
+ query_ctx_t *qctx = NULL;
+ isc_result_t result;
+
+ REQUIRE(test != NULL);
+ REQUIRE(test->id.description != NULL);
+ REQUIRE(test->cache_entry_present == ISC_TRUE ||
+ test->cache_entry_flags == 0);
+
+ /*
+ * Interrupt execution if query_done() is called.
+ */
+ const ns_hook_t query_hooks[NS_QUERY_HOOKS_COUNT] = {
+ [NS_QUERY_DONE_BEGIN] = {
+ .callback = ns_test_hook_catch_call,
+ .callback_data = NULL,
+ },
+ };
+ ns__hook_table = query_hooks;
+
+ /*
+ * Construct a query context for a ./NS query with given flags.
+ */
+ {
+ const ns_test_qctx_create_params_t qctx_params = {
+ .qname = ".",
+ .qtype = dns_rdatatype_ns,
+ .qflags = test->qflags,
+ .with_cache = ISC_TRUE,
+ };
+
+ result = ns_test_qctx_create(&qctx_params, &qctx);
+ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+ }
+
+ /*
+ * If this test wants a SERVFAIL cache entry matching the query to
+ * exist, create it.
+ */
+ if (test->cache_entry_present) {
+ isc_interval_t hour;
+ isc_time_t expire;
+
+ isc_interval_set(&hour, 3600, 0);
+ result = isc_time_nowplusinterval(&expire, &hour);
+ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+
+ dns_badcache_add(qctx->client->view->failcache, dns_rootname,
+ dns_rdatatype_ns, ISC_TRUE,
+ test->cache_entry_flags, &expire);
+ }
+
+ /*
+ * Check whether ns__query_sfcache() behaves as expected.
+ */
+ ns__query_sfcache(qctx);
+
+ if (test->servfail_expected) {
+ ATF_CHECK_EQ_MSG(qctx->result, DNS_R_SERVFAIL,
+ "test \"%s\" on line %d: "
+ "expected SERVFAIL, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ } else {
+ ATF_CHECK_EQ_MSG(qctx->result, ISC_R_SUCCESS,
+ "test \"%s\" on line %d: "
+ "expected success, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ }
+
+ /*
+ * Clean up.
+ */
+ ns_test_qctx_destroy(&qctx);
+
+}
+
+ATF_TC(ns__query_sfcache);
+ATF_TC_HEAD(ns__query_sfcache, tc) {
+ atf_tc_set_md_var(tc, "descr", "ns__query_sfcache()");
+}
+ATF_TC_BODY(ns__query_sfcache, tc) {
+ isc_result_t result;
+ size_t i;
+
+ const ns__query_sfcache_test_params_t tests[] = {
+ /*
+ * Sanity check for an empty SERVFAIL cache.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=0; cache: empty"),
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .cache_entry_present = ISC_FALSE,
+ .servfail_expected = ISC_FALSE,
+ },
+ /*
+ * Query: RD=1, CD=0. Cache entry: CD=0. Should SERVFAIL.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=0; cache: CD=0"),
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .cache_entry_present = ISC_TRUE,
+ .cache_entry_flags = 0,
+ .servfail_expected = ISC_TRUE,
+ },
+ /*
+ * Query: RD=1, CD=1. Cache entry: CD=0. Should not SERVFAIL:
+ * failed validation should not influence CD=1 queries.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=1; cache: CD=0"),
+ .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD,
+ .cache_entry_present = ISC_TRUE,
+ .cache_entry_flags = 0,
+ .servfail_expected = ISC_FALSE,
+ },
+ /*
+ * Query: RD=1, CD=1. Cache entry: CD=1. Should SERVFAIL:
+ * SERVFAIL responses elicited by CD=1 queries can be
+ * "replayed" for other CD=1 queries during the lifetime of the
+ * SERVFAIL cache entry.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=1; cache: CD=1"),
+ .qflags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD,
+ .cache_entry_present = ISC_TRUE,
+ .cache_entry_flags = NS_FAILCACHE_CD,
+ .servfail_expected = ISC_TRUE,
+ },
+ /*
+ * Query: RD=1, CD=0. Cache entry: CD=1. Should SERVFAIL: if
+ * a CD=1 query elicited a SERVFAIL, a CD=0 query for the same
+ * QNAME and QTYPE will SERVFAIL as well.
+ */
+ {
+ NS_TEST_ID("query: RD=1, CD=0; cache: CD=1"),
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .cache_entry_present = ISC_TRUE,
+ .cache_entry_flags = NS_FAILCACHE_CD,
+ .servfail_expected = ISC_TRUE,
+ },
+ /*
+ * Query: RD=0, CD=0. Cache entry: CD=0. Should not SERVFAIL
+ * despite a matching entry being present as the SERVFAIL cache
+ * should not be consulted for non-recursive queries.
+ */
+ {
+ NS_TEST_ID("query: RD=0, CD=0; cache: CD=0"),
+ .qflags = 0,
+ .cache_entry_present = ISC_TRUE,
+ .cache_entry_flags = 0,
+ .servfail_expected = ISC_FALSE,
+ },
+ };
+
+ UNUSED(tc);
+
+ result = ns_test_begin(NULL, ISC_TRUE);
+ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ ns__query_sfcache_test(&tests[i]);
+ }
+
+ ns_test_end();
+}
+
+/*****
+ ***** ns__query_start() tests
+ *****/
+
+/*%
+ * Structure containing parameters for ns__query_start_test().
+ */
+typedef struct {
+ const ns_test_id_t id; /* libns test identifier */
+ const char *qname; /* QNAME */
+ dns_rdatatype_t qtype; /* QTYPE */
+ unsigned int qflags; /* query flags */
+ isc_boolean_t disable_name_checks; /* if set to ISC_TRUE, owner name
+ checks will be disabled for the
+ view created */
+ isc_boolean_t recursive_service; /* if set to ISC_TRUE, the view
+ created will have a cache
+ database attached */
+ const char *auth_zone_origin; /* origin name of the zone the
+ created view will be
+ authoritative for */
+ const char *auth_zone_path; /* path to load the authoritative
+ zone from */
+ enum { /* expected result: */
+ NS__QUERY_START_R_INVALID,
+ NS__QUERY_START_R_REFUSE, /* query should be REFUSED */
+ NS__QUERY_START_R_CACHE, /* query should be answered from
+ cache */
+ NS__QUERY_START_R_AUTH, /* query should be answered using
+ authoritative data */
+ } expected_result;
+} ns__query_start_test_params_t;
+
+/*%
+ * Perform a single ns__query_start() check using given parameters.
+ */
+static void
+ns__query_start_test(const ns__query_start_test_params_t *test) {
+ query_ctx_t *qctx = NULL;
+ isc_result_t result;
+
+ REQUIRE(test != NULL);
+ REQUIRE(test->id.description != NULL);
+ REQUIRE((test->auth_zone_origin == NULL &&
+ test->auth_zone_path == NULL) ||
+ (test->auth_zone_origin != NULL &&
+ test->auth_zone_path != NULL));
+
+ /*
+ * Interrupt execution if query_lookup() or query_done() is called.
+ */
+ const ns_hook_t query_hooks[NS_QUERY_HOOKS_COUNT] = {
+ [NS_QUERY_LOOKUP_BEGIN] = {
+ .callback = ns_test_hook_catch_call,
+ .callback_data = NULL,
+ },
+ [NS_QUERY_DONE_BEGIN] = {
+ .callback = ns_test_hook_catch_call,
+ .callback_data = NULL,
+ },
+ };
+ ns__hook_table = query_hooks;
+
+ /*
+ * Construct a query context using the supplied parameters.
+ */
+ {
+ const ns_test_qctx_create_params_t qctx_params = {
+ .qname = test->qname,
+ .qtype = test->qtype,
+ .qflags = test->qflags,
+ .with_cache = test->recursive_service,
+ };
+ result = ns_test_qctx_create(&qctx_params, &qctx);
+ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+ }
+
+ /*
+ * Enable view->checknames by default, disable if requested.
+ */
+ qctx->client->view->checknames = !test->disable_name_checks;
+
+ /*
+ * Load zone from file and attach it to the client's view, if
+ * requested.
+ */
+ if (test->auth_zone_path != NULL) {
+ result = ns_test_serve_zone(test->auth_zone_origin,
+ test->auth_zone_path,
+ qctx->client->view);
+ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+ }
+
+ /*
+ * Check whether ns__query_start() behaves as expected.
+ */
+ ns__query_start(qctx);
+
+ switch (test->expected_result) {
+ case NS__QUERY_START_R_REFUSE:
+ ATF_CHECK_EQ_MSG(qctx->result, DNS_R_REFUSED,
+ "test \"%s\" on line %d: "
+ "expected REFUSED, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ ATF_CHECK_EQ_MSG(qctx->zone, NULL,
+ "test \"%s\" on line %d: "
+ "no zone was expected to be attached to "
+ "query context, but some was",
+ test->id.description, test->id.lineno);
+ ATF_CHECK_EQ_MSG(qctx->db, NULL,
+ "test \"%s\" on line %d: "
+ "no database was expected to be attached to "
+ "query context, but some was",
+ test->id.description, test->id.lineno);
+ break;
+ case NS__QUERY_START_R_CACHE:
+ ATF_CHECK_EQ_MSG(qctx->result, ISC_R_SUCCESS,
+ "test \"%s\" on line %d: "
+ "expected success, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ ATF_CHECK_EQ_MSG(qctx->zone, NULL,
+ "test \"%s\" on line %d: "
+ "no zone was expected to be attached to "
+ "query context, but some was",
+ test->id.description, test->id.lineno);
+ ATF_CHECK_MSG((qctx->db != NULL &&
+ qctx->db == qctx->client->view->cachedb),
+ "test \"%s\" on line %d: "
+ "cache database was expected to be attached to "
+ "query context, but it was not",
+ test->id.description, test->id.lineno);
+ break;
+ case NS__QUERY_START_R_AUTH:
+ ATF_CHECK_EQ_MSG(qctx->result, ISC_R_SUCCESS,
+ "test \"%s\" on line %d: "
+ "expected success, got %s",
+ test->id.description, test->id.lineno,
+ isc_result_totext(qctx->result));
+ ATF_CHECK_MSG(qctx->zone != NULL,
+ "test \"%s\" on line %d: "
+ "a zone was expected to be attached to query "
+ "context, but it was not",
+ test->id.description, test->id.lineno);
+ ATF_CHECK_MSG((qctx->db != NULL &&
+ qctx->db != qctx->client->view->cachedb),
+ "test \"%s\" on line %d: "
+ "cache database was not expected to be attached "
+ "to query context, but it is",
+ test->id.description, test->id.lineno);
+ break;
+ case NS__QUERY_START_R_INVALID:
+ ATF_REQUIRE_MSG(ISC_FALSE,
+ "test \"%s\" on line %d has no expected "
+ "result set",
+ test->id.description, test->id.lineno);
+ break;
+ default:
+ INSIST(0);
+ break;
+ }
+
+ /*
+ * Clean up.
+ */
+ if (test->auth_zone_path != NULL) {
+ ns_test_cleanup_zone();
+ }
+ ns_test_qctx_destroy(&qctx);
+}
+
+ATF_TC(ns__query_start);
+ATF_TC_HEAD(ns__query_start, tc) {
+ atf_tc_set_md_var(tc, "descr", "ns__query_start()");
+}
+ATF_TC_BODY(ns__query_start, tc) {
+ isc_result_t result;
+ size_t i;
+
+ const ns__query_start_test_params_t tests[] = {
+ /*
+ * Recursive foo/A query to a server without recursive service
+ * and no zones configured. Query should be REFUSED.
+ */
+ {
+ NS_TEST_ID("foo/A, no cache, no auth"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = ISC_FALSE,
+ .expected_result = NS__QUERY_START_R_REFUSE,
+ },
+ /*
+ * Recursive foo/A query to a server with recursive service and
+ * no zones configured. Query should be answered from cache.
+ */
+ {
+ NS_TEST_ID("foo/A, cache, no auth"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_a,
+ .recursive_service = ISC_TRUE,
+ .expected_result = NS__QUERY_START_R_CACHE,
+ },
+ /*
+ * Recursive foo/A query to a server with recursive service and
+ * zone "foo" configured. Query should be answered from
+ * authoritative data.
+ */
+ {
+ NS_TEST_ID("foo/A, RD=1, cache, auth for foo"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = ISC_TRUE,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_AUTH,
+ },
+ /*
+ * Recursive bar/A query to a server without recursive service
+ * and zone "foo" configured. Query should be REFUSED.
+ */
+ {
+ NS_TEST_ID("bar/A, RD=1, no cache, auth for foo"),
+ .qname = "bar",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = ISC_FALSE,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_REFUSE,
+ },
+ /*
+ * Recursive bar/A query to a server with recursive service and
+ * zone "foo" configured. Query should be answered from
+ * cache.
+ */
+ {
+ NS_TEST_ID("bar/A, RD=1, cache, auth for foo"),
+ .qname = "bar",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = ISC_TRUE,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_CACHE,
+ },
+ /*
+ * Recursive bar.foo/DS query to a server with recursive
+ * service and zone "foo" configured. Query should be answered
+ * from authoritative data.
+ */
+ {
+ NS_TEST_ID("bar.foo/DS, RD=1, cache, auth for foo"),
+ .qname = "bar.foo",
+ .qtype = dns_rdatatype_ds,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = ISC_TRUE,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_AUTH,
+ },
+ /*
+ * Non-recursive bar.foo/DS query to a server with recursive
+ * service and zone "foo" configured. Query should be answered
+ * from authoritative data.
+ */
+ {
+ NS_TEST_ID("bar.foo/DS, RD=0, cache, auth for foo"),
+ .qname = "bar.foo",
+ .qtype = dns_rdatatype_ds,
+ .qflags = 0,
+ .recursive_service = ISC_TRUE,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_AUTH,
+ },
+ /*
+ * Recursive foo/DS query to a server with recursive service
+ * and zone "foo" configured. Query should be answered from
+ * cache.
+ */
+ {
+ NS_TEST_ID("foo/DS, RD=1, cache, auth for foo"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_ds,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .recursive_service = ISC_TRUE,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_CACHE,
+ },
+ /*
+ * Non-recursive foo/DS query to a server with recursive
+ * service and zone "foo" configured. Query should be answered
+ * from authoritative data.
+ */
+ {
+ NS_TEST_ID("foo/DS, RD=0, cache, auth for foo"),
+ .qname = "foo",
+ .qtype = dns_rdatatype_ds,
+ .qflags = 0,
+ .recursive_service = ISC_TRUE,
+ .auth_zone_origin = "foo",
+ .auth_zone_path = "testdata/query/foo.db",
+ .expected_result = NS__QUERY_START_R_AUTH,
+ },
+ /*
+ * Recursive _foo/A query to a server with recursive service,
+ * no zones configured and owner name checks disabled. Query
+ * should be answered from cache.
+ */
+ {
+ NS_TEST_ID("_foo/A, cache, no auth, name checks off"),
+ .qname = "_foo",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .disable_name_checks = ISC_TRUE,
+ .recursive_service = ISC_TRUE,
+ .expected_result = NS__QUERY_START_R_CACHE,
+ },
+ /*
+ * Recursive _foo/A query to a server with recursive service,
+ * no zones configured and owner name checks enabled. Query
+ * should be REFUSED.
+ */
+ {
+ NS_TEST_ID("_foo/A, cache, no auth, name checks on"),
+ .qname = "_foo",
+ .qtype = dns_rdatatype_a,
+ .qflags = DNS_MESSAGEFLAG_RD,
+ .disable_name_checks = ISC_FALSE,
+ .recursive_service = ISC_TRUE,
+ .expected_result = NS__QUERY_START_R_REFUSE,
+ },
+ };
+
+ UNUSED(tc);
+
+ result = ns_test_begin(NULL, ISC_TRUE);
+ ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
+
+ for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ ns__query_start_test(&tests[i]);
+ }
+
+ ns_test_end();
+}
+
+/*
+ * Main
+ */
+ATF_TP_ADD_TCS(tp) {
+ ATF_TP_ADD_TC(tp, ns__query_sfcache);
+ ATF_TP_ADD_TC(tp, ns__query_start);
+
+ return (atf_no_error());
+}
--- /dev/null
+; Copyright (C) 2017 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/.
+
+$TTL 3600
+@ IN SOA localhost. postmaster.localhost. (
+ 1 ;serial
+ 3600 ;refresh
+ 1800 ;retry
+ 604800 ;expiration
+ 3600 ) ;minimum
+ IN NS ns
+ns IN A 127.0.0.1