add hook statement to configuration parser

- allow multiple "hook" statements at global or view level
- add "optional bracketed text" type for optional parameter list
- load hook module from specified path rather than hardcoded path
- add a hooktable pointer (and a callback for freeing it) to the
  view structure
- change the hooktable functions so they no longer update ns__hook_table
  by default, and modify PROCESS_HOOK so it uses the view hooktable, if
  set, rather than ns__hook_table. (ns__hook_table is retained for
  use by unit tests.)
- update the filter-aaaa system test to load filter-aaaa.so
- add a prereq script to check for dlopen support before running
  the filter-aaaa system test

not yet done:
- configuration parameters are not being passed to the filter-aaaa
  module; the filter-aaaa ACL and filter-aaaa-on-{v4,v6} settings are
  still stored in dns_view
This commit is contained in:
Evan Hunt 2018-08-12 11:19:36 -07:00
parent e2ac439e28
commit d2f4644388
22 changed files with 289 additions and 87 deletions

View file

@ -77,10 +77,19 @@ hook_register(const char *parameters, const char *file, unsigned long line,
UNUSED(parameters);
UNUSED(instp);
isc_log_write(hctx->lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
"loading params for 'filter-aaaa' module from %s:%lu",
file, line);
if (parameters != NULL) {
isc_log_write(hctx->lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
"loading params for 'filter-aaaa' "
"module from %s:%lu",
file, line);
} else {
isc_log_write(hctx->lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
"loading 'filter-aaaa' "
"module from %s:%lu, no parameters",
file, line);
}
/*
* TODO:

View file

@ -1535,6 +1535,50 @@ configure_dyndb(const cfg_obj_t *dyndb, isc_mem_t *mctx,
name, isc_result_totext(result));
return (result);
}
static isc_result_t
configure_hook(ns_hooktable_t *hooktable, const cfg_obj_t *hook,
ns_hookctx_t *hctx)
{
isc_result_t result = ISC_R_SUCCESS;
const cfg_obj_t *obj;
const char *type, *library;
/* Get the path to the hook module. */
obj = cfg_tuple_get(hook, "type");
type = cfg_obj_asstring(obj);
/* Only query hooks are supported currently. */
if (strcasecmp(type, "query") != 0) {
cfg_obj_log(obj, named_g_lctx, ISC_LOG_ERROR,
"unsupported hook type");
return (ISC_R_FAILURE);
}
library = cfg_obj_asstring(cfg_tuple_get(hook, "library"));
obj = cfg_tuple_get(hook, "parameters");
if (obj != NULL && cfg_obj_isstring(obj)) {
result = ns_hookmodule_load(library,
cfg_obj_asstring(obj),
cfg_obj_file(obj),
cfg_obj_line(obj),
hctx, hooktable);
} else {
result = ns_hookmodule_load(library, NULL,
cfg_obj_file(hook),
cfg_obj_line(hook),
hctx, hooktable);
}
if (result != ISC_R_SUCCESS) {
isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR,
"%s: hook module configuration failed: %s",
library, isc_result_totext(result));
}
return (result);
}
#endif
@ -3675,7 +3719,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist,
const cfg_obj_t *dlvobj = NULL;
unsigned int dlzargc;
char **dlzargv;
const cfg_obj_t *dyndb_list;
const cfg_obj_t *dyndb_list, *hook_list;
const cfg_obj_t *disabled;
const cfg_obj_t *obj, *obj2;
const cfg_listelt_t *element;
@ -5271,10 +5315,11 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist,
* Load DynDB modules.
*/
dyndb_list = NULL;
if (voptions != NULL)
if (voptions != NULL) {
(void)cfg_map_get(voptions, "dyndb", &dyndb_list);
else
} else {
(void)cfg_map_get(config, "dyndb", &dyndb_list);
}
#ifdef HAVE_DLOPEN
for (element = cfg_list_first(dyndb_list);
@ -5294,21 +5339,37 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist,
CHECK(configure_dyndb(dyndb, mctx, dctx));
}
#endif
/*
* XXX:
* temporary! this forces loading of filter-aaaa.so from the
* current working directory, if present. later this will
* happen via configuration as dyndb does above. we don't
* bother checking whether it succeeded; if it doesn't,
* filter-aaaa simply won't work.
* Load hook modules.
*/
if (hctx == NULL) {
CHECK(ns_hook_createctx(mctx, &hctx));
hook_list = NULL;
if (voptions != NULL) {
(void)cfg_map_get(voptions, "hook", &hook_list);
} else {
(void)cfg_map_get(config, "hook", &hook_list);
}
#ifdef HAVE_DLOPEN
for (element = cfg_list_first(hook_list);
element != NULL;
element = cfg_list_next(element))
{
const cfg_obj_t *hook = cfg_listelt_value(element);
if (view->hooktable == NULL) {
ns_hooktable_create(view->mctx,
(ns_hooktable_t **) &view->hooktable);
view->hooktable_free = ns_hooktable_free;
}
if (hctx == NULL) {
CHECK(ns_hook_createctx(mctx, &hctx));
}
CHECK(configure_hook(view->hooktable, hook, hctx));
}
ns_hooktable_init(NULL);
(void) ns_hookmodule_load("/tmp/filter-aaaa.so", "", "<none>", 0,
hctx, NULL);
#endif
/*

View file

@ -25,6 +25,8 @@ options {
minimal-responses no;
};
hook query "../../../../hooks/lib/filter-aaaa.so";
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;

View file

@ -25,6 +25,8 @@ options {
minimal-responses no;
};
hook query "../../../../hooks/lib/filter-aaaa.so";
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;

View file

@ -25,6 +25,8 @@ options {
minimal-responses no;
};
hook query "../../../../hooks/lib/filter-aaaa.so";
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;

View file

@ -25,6 +25,8 @@ options {
minimal-responses no;
};
hook query "../../../../hooks/lib/filter-aaaa.so";
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;

View file

@ -25,6 +25,8 @@ options {
minimal-responses no;
};
hook query "../../../../hooks/lib/filter-aaaa.so";
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;

View file

@ -25,6 +25,8 @@ options {
minimal-responses no;
};
hook query "../../../../hooks/lib/filter-aaaa.so";
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;

View file

@ -25,6 +25,8 @@ options {
minimal-responses no;
};
hook query "../../../../hooks/lib/filter-aaaa.so";
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;

View file

@ -25,6 +25,8 @@ options {
minimal-responses no;
};
hook query "../../../../hooks/lib/filter-aaaa.so";
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;

View file

@ -30,6 +30,8 @@ options {
minimal-responses no;
};
hook query "../../../../hooks/lib/filter-aaaa.so";
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;

View file

@ -0,0 +1,19 @@
#!/bin/sh
#
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
SYSTEMTESTTOP=..
. $SYSTEMTESTTOP/conf.sh
$FEATURETEST --have-dlopen || {
echo_i "dlopen() not supported - skipping filter-aaaa test"
exit 255
}
exit 0

View file

@ -238,6 +238,9 @@ struct dns_view {
dns_dtenv_t *dtenv; /* Dnstap environment */
dns_dtmsgtype_t dttypes; /* Dnstap message types
to log */
void *hooktable; /* ns_hooktable */
void (*hooktable_free)(isc_mem_t *, void **);
};
#define DNS_VIEW_MAGIC ISC_MAGIC('V','i','e','w')

View file

@ -259,6 +259,9 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
view->dtenv = NULL;
view->dttypes = 0;
view->hooktable = NULL;
view->hooktable_free = NULL;
isc_mutex_init(&view->new_zone_lock);
result = dns_order_create(view->mctx, &view->order);
@ -550,6 +553,9 @@ destroy(dns_view_t *view) {
isc_mutex_destroy(&view->lock);
isc_mem_free(view->mctx, view->nta_file);
isc_mem_free(view->mctx, view->name);
if (view->hooktable != NULL && view->hooktable_free != NULL) {
view->hooktable_free(view->mctx, &view->hooktable);
}
isc_mem_putanddetach(&view->mctx, view, sizeof(*view));
}

View file

@ -302,6 +302,7 @@ LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_astring;
LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_ustring;
LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_sstring;
LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_bracketed_text;
LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_optional_bracketed_text;
LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_sockaddr;
LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_sockaddrdscp;
LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr;

View file

@ -109,6 +109,7 @@ static cfg_type_t cfg_type_dnstap;
static cfg_type_t cfg_type_dnstapoutput;
static cfg_type_t cfg_type_dyndb;
static cfg_type_t cfg_type_filter_aaaa;
static cfg_type_t cfg_type_hook;
static cfg_type_t cfg_type_ixfrdifftype;
static cfg_type_t cfg_type_key;
static cfg_type_t cfg_type_logfile;
@ -1007,6 +1008,7 @@ namedconf_or_view_clauses[] = {
{ "dyndb", &cfg_type_dyndb, CFG_CLAUSEFLAG_MULTI },
{ "key", &cfg_type_key, CFG_CLAUSEFLAG_MULTI },
{ "managed-keys", &cfg_type_managedkeys, CFG_CLAUSEFLAG_MULTI },
{ "hook", &cfg_type_hook, CFG_CLAUSEFLAG_MULTI },
{ "server", &cfg_type_server, CFG_CLAUSEFLAG_MULTI },
{ "trusted-keys", &cfg_type_dnsseckeys, CFG_CLAUSEFLAG_MULTI },
{ "zone", &cfg_type_zone, CFG_CLAUSEFLAG_MULTI },
@ -2394,6 +2396,29 @@ static cfg_type_t cfg_type_dyndb = {
&cfg_rep_tuple, dyndb_fields
};
/*%
* The "hook" statement syntax.
* Currently only one hook type is supported: query.
*/
static const char *hook_enums[] = {
"query", NULL
};
static cfg_type_t cfg_type_hooktype = {
"hooktype", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum,
&cfg_rep_string, hook_enums
};
static cfg_tuplefielddef_t hook_fields[] = {
{ "type", &cfg_type_hooktype, 0 },
{ "library", &cfg_type_astring, 0 },
{ "parameters", &cfg_type_optional_bracketed_text, 0 },
{ NULL, NULL, 0 }
};
static cfg_type_t cfg_type_hook = {
"hook", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple,
&cfg_rep_tuple, hook_fields
};
/*%
* Clauses that can be found within the 'key' statement.
*/

View file

@ -1134,7 +1134,6 @@ doc_btext(cfg_printer_t *pctx, const cfg_type_t *type) {
cfg_print_cstr(pctx, "{ <unspecified-text> }");
}
bool
cfg_is_enum(const char *s, const char *const *enums) {
const char * const *p;
@ -1275,6 +1274,51 @@ LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_bracketed_text = {
&cfg_rep_string, NULL
};
/*
* Optional bracketed text
*/
static isc_result_t
parse_optional_btext(cfg_parser_t *pctx, const cfg_type_t *type,
cfg_obj_t **ret)
{
isc_result_t result;
UNUSED(type);
CHECK(cfg_peektoken(pctx, ISC_LEXOPT_BTEXT));
if (pctx->token.type == isc_tokentype_btext) {
CHECK(cfg_parse_obj(pctx, &cfg_type_bracketed_text, ret));
} else {
CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret));
}
cleanup:
return (result);
}
static void
print_optional_btext(cfg_printer_t *pctx, const cfg_obj_t *obj) {
if (obj->type == &cfg_type_void) {
return;
}
pctx->indent++;
cfg_print_cstr(pctx, "{");
cfg_print_chars(pctx, obj->value.string.base, obj->value.string.length);
print_close(pctx);
}
static void
doc_optional_btext(cfg_printer_t *pctx, const cfg_type_t *type) {
UNUSED(type);
cfg_print_cstr(pctx, "[ { <unspecified-text> } ]");
}
cfg_type_t cfg_type_optional_bracketed_text = {
"optional_btext", parse_optional_btext, print_optional_btext,
doc_optional_btext, NULL, NULL
};
/*
* Booleans
*/
@ -1485,7 +1529,7 @@ print_list(cfg_printer_t *pctx, const cfg_obj_t *obj) {
isc_result_t
cfg_parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type,
cfg_obj_t **ret)
cfg_obj_t **ret)
{
isc_result_t result;

View file

@ -335,10 +335,6 @@ ns_hookmodule_load(const char *libname, const char *parameters,
isc_result_t result;
ns_hook_module_t *module = NULL;
if (hooktable == NULL) {
hooktable = ns__hook_table;
}
REQUIRE(NS_HOOKCTX_VALID(hctx));
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
@ -420,27 +416,32 @@ ns_hooktable_init(ns_hooktable_t *hooktable) {
RUNTIME_CHECK(isc_once_do(&once, init_modules) == ISC_R_SUCCESS);
if (hooktable == NULL) {
hooktable = ns__hook_table;
}
for (i = 0; i < NS_QUERY_HOOKS_COUNT; i++) {
ISC_LIST_INIT((*hooktable)[i]);
}
}
ns_hooktable_t *
ns_hooktable_save() {
return (ns__hook_table);
isc_result_t
ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) {
ns_hooktable_t *hooktable;
REQUIRE(tablep != NULL && *tablep == NULL);
hooktable = isc_mem_get(mctx, sizeof(ns_hooktable_t));
ns_hooktable_init(hooktable);
*tablep = hooktable;
return (ISC_R_SUCCESS);
}
void
ns_hooktable_reset(ns_hooktable_t *hooktable) {
if (hooktable != NULL) {
ns__hook_table = hooktable;
} else {
ns__hook_table = &hooktab;
}
ns_hooktable_free(isc_mem_t *mctx, void **tablep) {
REQUIRE(tablep != NULL && *tablep != NULL);
isc_mem_put(mctx, *tablep, sizeof(ns_hooktable_t));
*tablep = NULL;
}
void

View file

@ -196,13 +196,13 @@ typedef struct ns_hook {
ISC_LINK(struct ns_hook) link;
} ns_hook_t;
/*
* ns__hook_table is a globally visible pointer to the active hook
* table. It's initialized to point to 'hooktab', which is the default
* global hook table.
*/
typedef ISC_LIST(ns_hook_t) ns_hooklist_t;
typedef ns_hooklist_t ns_hooktable_t[NS_QUERY_HOOKS_COUNT];
/*
* ns__hook_table is a global hook table, which is used if view->hooktable
* is NULL. It's intended only for use by unit tests.
*/
LIBNS_EXTERNAL_DATA extern ns_hooktable_t *ns__hook_table;
/*!
@ -281,7 +281,7 @@ typedef int ns_hook_version_t(unsigned int *flags);
* true, we continue processing.
*/
#define _NS_PROCESS_HOOK(table, id, data, ...) \
if (table != NULL) { \
if (table != NULL) { \
ns_hook_t *_hook = ISC_LIST_HEAD((*table)[id]); \
isc_result_t _result; \
\
@ -298,9 +298,9 @@ typedef int ns_hook_version_t(unsigned int *flags);
} \
}
#define NS_PROCESS_HOOK(table, id, data) \
#define NS_PROCESS_HOOK(table, id, data, ...) \
_NS_PROCESS_HOOK(table, id, data, _result)
#define NS_PROCESS_HOOK_VOID(table, id, data) \
#define NS_PROCESS_HOOK_VOID(table, id, data, ...) \
_NS_PROCESS_HOOK(table, id, data)
isc_result_t
@ -321,8 +321,7 @@ ns_hook_add(ns_hooktable_t *hooktable, ns_hookpoint_t hookpoint,
ns_hook_t *hook);
/*%
* Append hook function 'hook' to the list of hooks at 'hookpoint' in
* 'hooktable'. If 'hooktable' is NULL, the global hook table
* ns__hook_table is used.
* 'hooktable'.
*
* Requires:
*\li 'hook' is not NULL
@ -331,26 +330,23 @@ ns_hook_add(ns_hooktable_t *hooktable, ns_hookpoint_t hookpoint,
*
*/
ns_hooktable_t *
ns_hooktable_save(void);
/*%
* Returns a pointer to the current global hook table so it can
* be restored after replacing it.
*/
void
ns_hooktable_reset(ns_hooktable_t *hooktable);
/*%
* Set the global hooks table pointer to 'hooktable'.
*
* If 'hooktable' is NULL, restores the default global hook table.
*/
void
ns_hooktable_init(ns_hooktable_t *hooktable);
/*%
* Initialize a hook table. If 'hooktable' is NULL, initialize
* the global hooktable ns__hook_table.
* Initialize a hook table.
*/
isc_result_t
ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep);
/*%
* Allocate and initialize a hook table.
*/
void
ns_hooktable_free(isc_mem_t *mctx, void **tablep);
/*%
* Free a hook table.
*/
#endif /* NS_HOOKS_H */

View file

@ -238,10 +238,33 @@ static void
log_noexistnodata(void *val, int level, const char *fmt, ...)
ISC_FORMAT_PRINTF(3, 4);
#define PROCESS_HOOK(...) \
NS_PROCESS_HOOK(ns__hook_table, __VA_ARGS__)
#define PROCESS_HOOK_VOID(...) \
NS_PROCESS_HOOK_VOID(ns__hook_table, __VA_ARGS__)
#define PROCESS_HOOK(_id, _qctx, ...) \
do { \
ns_hooktable_t *_tab = ns__hook_table; \
query_ctx_t *_q = (_qctx); \
if (_q != NULL && \
_q->client != NULL && \
_q->client->view != NULL && \
_q->client->view->hooktable != NULL) \
{ \
_tab = _q->client->view->hooktable; \
} \
NS_PROCESS_HOOK(_tab, _id, _q, __VA_ARGS__); \
} while (false)
#define PROCESS_HOOK_VOID(_id, _qctx, ...) \
do { \
ns_hooktable_t *_tab = ns__hook_table; \
query_ctx_t *_q = (_qctx); \
if (_q != NULL && \
_q->client != NULL && \
_q->client->view != NULL && \
_q->client->view->hooktable != NULL) \
{ \
_tab = _q->client->view->hooktable; \
} \
NS_PROCESS_HOOK_VOID(_tab, _id, _q, __VA_ARGS__); \
} while (false)
/*
* The functions defined below implement the query logic that previously lived

View file

@ -693,11 +693,12 @@ create_qctx_for_client(ns_client_t *client, query_ctx_t **qctxp) {
ns_hooktable_init(&query_hooks);
ns_hook_add(&query_hooks, NS_QUERY_SETUP_QCTX_INITIALIZED, &hook);
saved_hook_table = ns_hooktable_save();
saved_hook_table = ns__hook_table;
ns__hook_table = &query_hooks;
ns_hooktable_reset(&query_hooks);
ns_query_start(client);
ns_hooktable_reset(saved_hook_table);
ns__hook_table = saved_hook_table;
if (*qctxp == NULL) {
return (ISC_R_NOMEMORY);

View file

@ -84,6 +84,9 @@ static void
run_sfcache_test(const ns__query_sfcache_test_params_t *test) {
query_ctx_t *qctx = NULL;
isc_result_t result;
ns_hook_t hook = {
.callback = ns_test_hook_catch_call,
};
REQUIRE(test != NULL);
REQUIRE(test->id.description != NULL);
@ -93,14 +96,9 @@ run_sfcache_test(const ns__query_sfcache_test_params_t *test) {
/*
* Interrupt execution if ns_query_done() is called.
*/
ns_hook_t hook = {
.callback = ns_test_hook_catch_call,
};
ns_hooktable_t query_hooks;
ns_hooktable_init(&query_hooks);
ns_hook_add(&query_hooks, NS_QUERY_DONE_BEGIN, &hook);
ns_hooktable_reset(&query_hooks);
ns_hooktable_init(ns__hook_table);
ns_hook_add(ns__hook_table, NS_QUERY_DONE_BEGIN, &hook);
/*
* Construct a query context for a ./NS query with given flags.
@ -283,6 +281,9 @@ static void
run_start_test(const ns__query_start_test_params_t *test) {
query_ctx_t *qctx = NULL;
isc_result_t result;
ns_hook_t hook = {
.callback = ns_test_hook_catch_call,
};
REQUIRE(test != NULL);
REQUIRE(test->id.description != NULL);
@ -294,16 +295,10 @@ run_start_test(const ns__query_start_test_params_t *test) {
/*
* Interrupt execution if query_lookup() or ns_query_done() is called.
*/
ns_hook_t hook = {
.callback = ns_test_hook_catch_call,
};
ns_hooktable_t query_hooks;
ns_hooktable_init(&query_hooks);
ns_hook_add(&query_hooks, NS_QUERY_LOOKUP_BEGIN, &hook);
ns_hook_add(&query_hooks, NS_QUERY_DONE_BEGIN, &hook);
ns_hooktable_reset(&query_hooks);
ns_hooktable_init(ns__hook_table);
ns_hook_add(ns__hook_table, NS_QUERY_LOOKUP_BEGIN, &hook);
ns_hook_add(ns__hook_table, NS_QUERY_DONE_BEGIN, &hook);
/*
* Construct a query context using the supplied parameters.