2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-30 22:15:20 +00:00

[master] expanded libns unit tests

4772.	[test]		Expanded unit testing framework for libns, using
			hooks to interrupt query flow and inspect state
			at specified locations. [RT #46173]
This commit is contained in:
Michał Kępień
2017-10-11 15:02:50 -07:00
parent b2597ce86b
commit defa292088
12 changed files with 1360 additions and 141 deletions

View File

@@ -1,3 +1,7 @@
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]

17
configure vendored
View File

@@ -940,6 +940,7 @@ infodir
docdir
oldincludedir
includedir
runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -1103,6 +1104,7 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1355,6 +1357,15 @@ do
| -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=* \
@@ -1492,7 +1503,7 @@ fi
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.
@@ -1645,6 +1656,7 @@ Fine tuning of the installation directories:
--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]
@@ -11466,7 +11478,7 @@ fi
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
@@ -22225,6 +22237,7 @@ if test "no" != "$atf"; then
$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

View File

@@ -62,7 +62,7 @@ AC_ARG_ENABLE(developer, [ --enable-developer enable developer build setti
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
@@ -4771,6 +4771,7 @@ ATFLIBS=
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)

142
lib/ns/hooks.h Normal file
View File

@@ -0,0 +1,142 @@
/*
* 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 */

View File

@@ -16,6 +16,7 @@
#include <isc/netaddr.h>
#include <dns/rdataset.h>
#include <dns/resolver.h>
#include <dns/rpz.h>
#include <dns/types.h>
@@ -92,6 +93,55 @@ struct ns_query {
#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);
@@ -104,4 +154,16 @@ ns_query_start(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 */

View File

@@ -62,6 +62,8 @@
#include <ns/stats.h>
#include <ns/xfrout.h>
#include "hooks.h"
#if 0
/*
* It has been recommended that DNS64 be changed to return excluded
@@ -243,17 +245,32 @@ static void
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.
@@ -310,53 +327,6 @@ log_noexistnodata(void *val, int level, const char *fmt, ...)
* 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);
@@ -367,9 +337,6 @@ qctx_init(ns_client_t *client, dns_fetchevent_t *event,
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);
@@ -384,9 +351,6 @@ query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
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);
@@ -5121,12 +5085,12 @@ query_trace(query_ctx_t *qctx) {
/*
* 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
@@ -5137,14 +5101,6 @@ query_setup(ns_client_t *client, dns_rdatatype_t qtype) {
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.
*/
@@ -5152,11 +5108,19 @@ query_setup(ns_client_t *client, dns_rdatatype_t qtype) {
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));
}
/*%
@@ -5166,10 +5130,10 @@ query_setup(ns_client_t *client, dns_rdatatype_t qtype) {
* 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;
@@ -5206,6 +5170,12 @@ query_start(query_ctx_t *qctx) {
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;
}
@@ -5218,8 +5188,10 @@ query_start(query_ctx_t *qctx) {
(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;
@@ -5232,6 +5204,10 @@ query_start(query_ctx_t *qctx) {
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) {
@@ -5247,6 +5223,10 @@ query_start(query_ctx_t *qctx) {
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);
}
@@ -5255,6 +5235,11 @@ query_start(query_ctx_t *qctx) {
}
}
}
/*
* 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)) {
@@ -5269,12 +5254,17 @@ query_start(query_ctx_t *qctx) {
}
} 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;
@@ -5285,6 +5275,10 @@ query_start(query_ctx_t *qctx) {
}
}
/*
* 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) {
@@ -5326,6 +5320,8 @@ query_lookup(query_ctx_t *qctx) {
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);
@@ -5884,10 +5880,11 @@ query_resume(query_ctx_t *qctx) {
* 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;
@@ -10317,7 +10314,7 @@ query_glueanswer(query_ctx_t *qctx) {
*
* - 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().
*/
@@ -10326,6 +10323,8 @@ query_done(query_ctx_t *qctx) {
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.
*/
@@ -10352,7 +10351,7 @@ query_done(query_ctx_t *qctx) {
*/
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) {

View File

@@ -32,11 +32,13 @@ LIBS = @LIBS@ @ATFLIBS@
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@
@@ -50,6 +52,11 @@ notify_test@EXEEXT@: notify_test.@O@ nstest.@O@ ${NSDEPLIBS} ${ISCDEPLIBS} ${DNS
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

View File

@@ -28,49 +28,6 @@
#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;
@@ -113,7 +70,12 @@ ATF_TC_BODY(notify_start, tc) {
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.
@@ -136,7 +98,6 @@ ATF_TC_BODY(notify_start, tc) {
* 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);
}
@@ -148,7 +109,7 @@ ATF_TC_BODY(notify_start, tc) {
/*
* Clean up
*/
cleanup_zone();
ns_test_cleanup_zone();
ns_client_detach(&client);

View File

@@ -22,6 +22,7 @@
#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>
@@ -29,6 +30,7 @@
#include <isc/timer.h>
#include <isc/util.h>
#include <dns/cache.h>
#include <dns/db.h>
#include <dns/dispatch.h>
#include <dns/fixedname.h>
@@ -42,6 +44,8 @@
#include <ns/interfacemgr.h>
#include <ns/server.h>
#include "../hooks.h"
#include "nstest.h"
isc_mem_t *mctx = NULL;
@@ -63,6 +67,8 @@ isc_boolean_t run_managers = ISC_FALSE;
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.
*/
@@ -275,6 +281,8 @@ ns_test_begin(FILE *logfile, isc_boolean_t start_managers) {
if (chdir(TESTS) == -1)
CHECK(ISC_R_FAILURE);
ns__hook_table = NULL;
return (ISC_R_SUCCESS);
cleanup:
@@ -306,15 +314,29 @@ ns_test_end(void) {
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);
@@ -417,6 +439,79 @@ ns_test_closezonemgr(void) {
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)
@@ -435,6 +530,291 @@ ns_test_getclient(ns_interface_t *ifp0, isc_boolean_t tcp,
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.
*/

View File

@@ -26,6 +26,13 @@
#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); \
@@ -55,8 +62,13 @@ ns_test_begin(FILE *logfile, isc_boolean_t create_managers);
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,
@@ -74,6 +86,21 @@ ns_test_releasezone(dns_zone_t *zone);
void
ns_test_closezonemgr(void);
/*%
* Load data for zone "zonename" from file "filename" and start serving it to
* clients matching "view". Only one zone loaded using this function can be
* served at any given time.
*/
isc_result_t
ns_test_serve_zone(const char *zonename, const char *filename,
dns_view_t *view);
/*%
* Release the zone loaded by ns_test_serve_zone().
*/
void
ns_test_cleanup_zone(void);
void
ns_test_nap(isc_uint32_t usec);
@@ -88,3 +115,37 @@ ns_test_getdata(const char *file, unsigned char *buf,
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);

574
lib/ns/tests/query_test.c Normal file
View File

@@ -0,0 +1,574 @@
/*
* 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());
}

15
lib/ns/tests/testdata/query/foo.db vendored Normal file
View File

@@ -0,0 +1,15 @@
; 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