Commit 70cc3f80 authored by Evan Hunt's avatar Evan Hunt

set up hooks.c to enable setting hook points and loading modules

- move hooks.h to public include directory
- ns_hooktable_init() initializes a hook table. if NULL is passed in, it
  initializes the global hook table
- ns_hooktable_save() saves a pointer to the current global hook table.
- ns_hooktable_reset() replaces the global hook table with different
  one
- ns_hook_add() adds hooks at specified hook points in a hook table (or
  the global hook table if the specified table is NULL)
- load and unload functions support dlopen() of hook modules (this is
  adapted from dyndb and not yet functional)
- began adding new hook points to query.c
parent 6f11f90e
......@@ -8030,6 +8030,7 @@ load_configuration(const char *filename, named_server_t *server,
* Shut down all dyndb instances.
*/
dns_dyndb_cleanup(false);
ns_hookmodule_cleanup();
/*
* Parse the global default pseudo-config file.
......@@ -9511,6 +9512,7 @@ shutdown_server(isc_task_t *task, isc_event_t *event) {
}
dns_dyndb_cleanup(true);
ns_hookmodule_cleanup();
while ((nsc = ISC_LIST_HEAD(server->cachelist)) != NULL) {
ISC_LIST_UNLINK(server->cachelist, nsc, link);
......
......@@ -11916,7 +11916,7 @@ fi
XTARGETS=
case "$enable_developer" in
yes)
STD_CDEFINES="$STD_CDEFINES -DISC_MEM_DEFAULTFILL=1 -DISC_LIST_CHECKINIT=1 -DNS_HOOKS_ENABLE=1"
STD_CDEFINES="$STD_CDEFINES -DISC_MEM_DEFAULTFILL=1 -DISC_LIST_CHECKINIT=1"
test "${enable_fixed_rrset+set}" = set || enable_fixed_rrset=yes
test "${enable_querytrace+set}" = set || enable_querytrace=yes
test "${with_cmocka+set}" = set || with_cmocka=yes
......
......@@ -76,7 +76,7 @@ AC_ARG_ENABLE(developer,
XTARGETS=
case "$enable_developer" in
yes)
STD_CDEFINES="$STD_CDEFINES -DISC_MEM_DEFAULTFILL=1 -DISC_LIST_CHECKINIT=1 -DNS_HOOKS_ENABLE=1"
STD_CDEFINES="$STD_CDEFINES -DISC_MEM_DEFAULTFILL=1 -DISC_LIST_CHECKINIT=1"
test "${enable_fixed_rrset+set}" = set || enable_fixed_rrset=yes
test "${enable_querytrace+set}" = set || enable_querytrace=yes
test "${with_cmocka+set}" = set || with_cmocka=yes
......
......@@ -190,7 +190,8 @@ cleanup:
"driver '%s': %s (%s)", instname, filename,
dlerror(), isc_result_totext(result));
if (imp != NULL)
isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t));
isc_mem_putanddetach(&imp->mctx, imp,
sizeof(dyndb_implementation_t));
if (result != ISC_R_SUCCESS && handle != NULL)
dlclose(handle);
......@@ -305,7 +306,8 @@ cleanup:
"driver '%s': %d (%s)", instname, filename,
GetLastError(), isc_result_totext(result));
if (imp != NULL)
isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t));
isc_mem_putanddetach(&imp->mctx, imp,
sizeof(dyndb_implementation_t));
if (result != ISC_R_SUCCESS && handle != NULL)
FreeLibrary(handle);
......
......@@ -40,7 +40,7 @@ struct dns_dyndbctx {
dns_zonemgr_t *zmgr;
isc_task_t *task;
isc_timermgr_t *timermgr;
bool *refvar;
bool *refvar;
};
#define DNS_DYNDBCTX_MAGIC ISC_MAGIC('D', 'd', 'b', 'c')
......@@ -71,7 +71,7 @@ typedef isc_result_t dns_dyndb_register_t(isc_mem_t *mctx,
* 'parameters' contains the driver configuration text. 'dctx' is the
* initialization context set up in dns_dyndb_createctx().
*
* '*instp' must be set to the driver instance handle if the functino
* '*instp' will be set to the driver instance handle if the function
* is successful.
*
* Returns:
......
......@@ -43,12 +43,12 @@ DNSDEPLIBS = ../../lib/dns/libdns.@A@
LIBS = @LIBS@
# Alphabetically
OBJS = client.@O@ interfacemgr.@O@ lib.@O@ \
OBJS = client.@O@ hooks.@O@ interfacemgr.@O@ lib.@O@ \
listenlist.@O@ log.@O@ notify.@O@ query.@O@ \
server.@O@ sortlist.@O@ stats.@O@ update.@O@ \
version.@O@ xfrout.@O@
SRCS = client.c interfacemgr.c lib.c listenlist.c \
SRCS = client.c hooks.c interfacemgr.c lib.c listenlist.c \
log.c notify.c query.c server.c sortlist.c stats.c \
update.c version.c xfrout.c
......
/*
* 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.
*/
/*! \file */
#include <config.h>
#include <string.h>
#if HAVE_DLFCN_H
#include <dlfcn.h>
#elif _WIN32
#include <windows.h>
#endif
#include <isc/mem.h>
#include <isc/mutex.h>
#include <isc/result.h>
#include <isc/once.h>
#include <isc/util.h>
#include <ns/hooks.h>
#include <ns/log.h>
#define CHECK(op) \
do { result = (op); \
if (result != ISC_R_SUCCESS) goto cleanup; \
} while (0)
typedef struct ns_hook_module ns_hook_module_t;
struct ns_hook_module {
isc_mem_t *mctx;
void *handle;
char *filename;
ns_hook_register_t *register_func;
ns_hook_destroy_t *destroy_func;
char *name;
void *inst;
LINK(ns_hook_module_t) link;
};
static ns_hooklist_t hooktab[NS_QUERY_HOOKS_COUNT];
LIBNS_EXTERNAL_DATA ns_hooktable_t *ns__hook_table = &hooktab;
/*
* List of hook modules.
*
* These are stored here so they can be cleaned up on shutdown.
* (The order in which they are stored is not important.)
*/
static LIST(ns_hook_module_t) hook_modules;
static isc_once_t once = ISC_ONCE_INIT;
static void
init_modules(void) {
INIT_LIST(hook_modules);
}
#if HAVE_DLFCN_H && HAVE_DLOPEN
static isc_result_t
load_symbol(void *handle, const char *filename,
const char *symbol_name, void **symbolp)
{
const char *errmsg;
void *symbol;
REQUIRE(handle != NULL);
REQUIRE(symbolp != NULL && *symbolp == NULL);
symbol = dlsym(handle, symbol_name);
if (symbol == NULL) {
errmsg = dlerror();
if (errmsg == NULL) {
errmsg = "returned function pointer is NULL";
}
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
"failed to look upsymbol %s in "
"hook module '%s': %s",
symbol_name, filename, errmsg);
return (ISC_R_FAILURE);
}
dlerror();
*symbolp = symbol;
return (ISC_R_SUCCESS);
}
static isc_result_t
load_library(isc_mem_t *mctx, const char *filename, ns_hook_module_t **hmodp) {
isc_result_t result;
void *handle = NULL;
ns_hook_module_t *hmod = NULL;
ns_hook_register_t *register_func = NULL;
ns_hook_destroy_t *destroy_func = NULL;
ns_hook_version_t *version_func = NULL;
int version, flags;
REQUIRE(hmodp != NULL && *hmodp == NULL);
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
"loading module '%s'",
filename);
flags = RTLD_NOW|RTLD_LOCAL;
#ifdef RTLD_DEEPBIND
flags |= RTLD_DEEPBIND;
#endif
handle = dlopen(filename, flags);
if (handle == NULL) {
CHECK(ISC_R_FAILURE);
}
/* Clear dlerror */
dlerror();
CHECK(load_symbol(handle, filename, "hook_version",
(void **)&version_func));
version = version_func(NULL);
if (version < (NS_HOOK_VERSION - NS_HOOK_AGE) ||
version > NS_HOOK_VERSION)
{
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
"driver API version mismatch: %d/%d",
version, NS_HOOK_VERSION);
CHECK(ISC_R_FAILURE);
}
CHECK(load_symbol(handle, filename, "hook_init",
(void **)&register_func));
CHECK(load_symbol(handle, filename, "hook_destroy",
(void **)&destroy_func));
hmod = isc_mem_get(mctx, sizeof(*hmod));
if (hmod == NULL) {
CHECK(ISC_R_NOMEMORY);
}
hmod->mctx = NULL;
isc_mem_attach(mctx, &hmod->mctx);
hmod->handle = handle;
hmod->register_func = register_func;
hmod->destroy_func = destroy_func;
hmod->inst = NULL;
ISC_LINK_INIT(hmod, link);
*hmodp = hmod;
hmod = NULL;
cleanup:
if (result != ISC_R_SUCCESS) {
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
"failed to dynamically load "
"module '%s': %s (%s)", filename,
dlerror(), isc_result_totext(result));
if (hmod != NULL) {
isc_mem_putanddetach(&hmod->mctx, hmod,
sizeof(*hmod));
}
if (handle != NULL) {
dlclose(handle);
}
}
return (result);
}
static void
unload_library(ns_hook_module_t **hmodp) {
ns_hook_module_t *hmod;
REQUIRE(hmodp != NULL && *hmodp != NULL);
hmod = *hmodp;
*hmodp = NULL;
if (hmod->handle != NULL) {
dlclose(hmod->handle);
}
if (hmod->filename != NULL) {
isc_mem_free(hmod->mctx, hmod->filename);
}
isc_mem_putanddetach(&hmod->mctx, hmod, sizeof(ns_hook_module_t));
}
#elif _WIN32
static isc_result_t
load_symbol(HMODULE handle, const char *filename,
const char *symbol_name, void **symbolp)
{
void *symbol;
REQUIRE(handle != NULL);
REQUIRE(symbolp != NULL && *symbolp == NULL);
symbol = GetProcAddress(handle, symbol_name);
if (symbol == NULL) {
int errstatus = GetLastError();
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
"failed to look up symbol %s in "
"module '%s': %d",
symbol_name, filename, errstatus);
return (ISC_R_FAILURE);
}
*symbolp = symbol;
return (ISC_R_SUCCESS);
}
static isc_result_t
load_library(isc_mem_t *mctx, const char *filename, ns_hook_module_t **hmodp) {
isc_result_t result;
HMODULE handle;
ns_hook_module_t *hmod = NULL;
ns_hook_register_t *register_func = NULL;
ns_hook_destroy_t *destroy_func = NULL;
ns_hook_version_t *version_func = NULL;
int version;
REQUIRE(hmodp != NULL && *hmodp == NULL);
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
"loading module '%s'", filename);
handle = LoadLibraryA(filename);
if (handle == NULL) {
CHECK(ISC_R_FAILURE);
}
CHECK(load_symbol(handle, filename, "hook_version",
(void **)&version_func));
version = version_func(NULL);
if (version < (NS_HOOK_VERSION - NS_HOOK_AGE) ||
version > NS_HOOK_VERSION)
{
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
"driver API version mismatch: %d/%d",
version, NS_HOOK_VERSION);
CHECK(ISC_R_FAILURE);
}
CHECK(load_symbol(handle, filename, "hook_init",
(void **)&register_func));
CHECK(load_symbol(handle, filename, "hook_destroy",
(void **)&destroy_func));
hmod = isc_mem_get(mctx, sizeof(*hmod));
if (hmod == NULL) {
CHECK(ISC_R_NOMEMORY);
}
hmod->mctx = NULL;
isc_mem_attach(mctx, &hmod->mctx);
hmod->handle = handle;
hmod->register_func = register_func;
hmod->destroy_func = destroy_func;
hmod->inst = NULL;
ISC_LINK_INIT(hmod, link);
*hmodp = hmod;
hmod = NULL;
cleanup:
if (result != ISC_R_SUCCESS) {
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
"failed to dynamically load "
"hook module '%s': %d (%s)", filename,
GetLastError(), isc_result_totext(result));
if (hmod != NULL) {
isc_mem_putanddetach(&hmod->mctx, hmod,
sizeof(*hmod));
}
if (handle != NULL) {
FreeLibrary(handle);
}
}
return (result);
}
static void
unload_library(ns_hook_module_t **hmodp) {
ns_hook_module_t *hmod;
REQUIRE(hmodp != NULL && *hmodp != NULL);
hmod = *hmodp;
*hmodp = NULL;
if (hmod->handle != NULL) {
FreeLibrary(hmod->handle);
}
if (hmod->filename != NULL) {
isc_mem_free(hmod->mctx, hmod->filename);
}
isc_mem_putanddetach(&hmod->mctx, hmod, sizeof(*hmod));
}
#else /* HAVE_DLFCN_H || _WIN32 */
static isc_result_t
load_library(isc_mem_t *mctx, const char *filename, ns_hook_module_t **hmodp) {
UNUSED(mctx);
UNUSED(filename);
UNUSED(hmodp);
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
"hook module support is not hmodlemented");
return (ISC_R_NOTIMPLEMENTED);
}
static void
unload_library(ns_hook_module_t **hmodp) {
UNUSED(hmodp);
}
#endif /* HAVE_DLFCN_H */
isc_result_t
ns_hookmodule_load(const char *libname, const char *parameters,
const char *file, unsigned long line, isc_mem_t *mctx)
{
isc_result_t result;
ns_hook_module_t *module = NULL;
RUNTIME_CHECK(isc_once_do(&once, init_modules) == ISC_R_SUCCESS);
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
"loading module '%s'", libname);
CHECK(load_library(mctx, libname, &module));
CHECK(module->register_func(mctx, parameters, file, line,
&module->inst));
APPEND(hook_modules, module, link);
result = ISC_R_SUCCESS;
cleanup:
if (result != ISC_R_SUCCESS && module != NULL) {
unload_library(&module);
}
return (result);
}
void
ns_hookmodule_cleanup(void) {
ns_hook_module_t *hmod, *prev;
RUNTIME_CHECK(isc_once_do(&once, init_modules) == ISC_R_SUCCESS);
hmod = ISC_LIST_TAIL(hook_modules);
while (hmod != NULL) {
prev = PREV(hmod, link);
UNLINK(hook_modules, hmod, link);
isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
"unloading module '%s'", hmod->name);
hmod->destroy_func(&hmod->inst);
ENSURE(hmod->inst == NULL);
unload_library(&hmod);
hmod = prev;
}
}
void
ns_hooktable_init(ns_hooktable_t *hooktable) {
int i;
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);
}
void
ns_hooktable_reset(ns_hooktable_t *hooktable) {
if (hooktable != NULL) {
ns__hook_table = hooktable;
} else {
ns__hook_table = &hooktab;
}
}
void
ns_hook_add(ns_hooktable_t *hooktable, ns_hookpoint_t hookpoint,
ns_hook_t *hook)
{
REQUIRE(hookpoint < NS_QUERY_HOOKS_COUNT);
REQUIRE(hook != NULL);
if (hooktable == NULL) {
hooktable = ns__hook_table;
}
ISC_LINK_INIT(hook, link);
ISC_LIST_APPEND((*hooktable)[hookpoint], hook, link);
}
......@@ -13,7 +13,7 @@ top_srcdir = @top_srcdir@
VERSION=@BIND9_VERSION@
HEADERS = client.h interfacemgr.h lib.h listenlist.h log.h \
HEADERS = client.h hooks.h interfacemgr.h lib.h listenlist.h log.h \
notify.h query.h server.h sortlist.h stats.h \
types.h update.h version.h xfrout.h
SUBDIRS =
......
......@@ -12,12 +12,11 @@
#ifndef NS_HOOKS_H
#define NS_HOOKS_H 1
#ifdef NS_HOOKS_ENABLE
/*! \file */
#include <stdbool.h>
#include <isc/list.h>
#include <isc/result.h>
/*
......@@ -158,40 +157,171 @@
* called this time and foo_bar() will return ISC_R_SUCCESS.
*/
enum {
typedef enum {
NS_QUERY_SETUP_QCTX_INITIALIZED,
NS_QUERY_START_BEGIN,
NS_QUERY_LOOKUP_BEGIN,
NS_QUERY_RESUME_BEGIN,
NS_QUERY_PREP_RESPONSE_BEGIN,
NS_QUERY_RESPOND_ANY_BEGIN,
NS_QUERY_RESPOND_ANY_POST_LOOKUP,
NS_QUERY_RESPOND_ANY_FOUND,
NS_QUERY_RESPOND_ANY_NOT_FOUND,
NS_QUERY_RESPOND_BEGIN,
NS_QUERY_GOT_ANSWER_BEGIN,
NS_QUERY_NOTFOUND_BEGIN,
NS_QUERY_PREP_DELEGATION_BEGIN,
NS_QUERY_ZONE_DELEGATION_BEGIN,
NS_QUERY_DELEGATION_BEGIN,
NS_QUERY_NODATA_BEGIN,
NS_QUERY_NXDOMAIN_BEGIN,
NS_QUERY_CNAME_BEGIN,
NS_QUERY_DNAME_BEGIN,
NS_QUERY_ADDITIONAL_BEGIN,
NS_QUERY_DONE_BEGIN,
NS_QUERY_DONE_SEND,
NS_QUERY_HOOKS_COUNT /* MUST BE LAST */
};
} ns_hookpoint_t;
typedef bool
(*ns_hook_cb_t)(void *hook_data, void *callback_data, isc_result_t *resultp);
/*
* API version
*
* When the API changes, increment NS_HOOK_VERSION. If the
* change is backward-compatible (e.g., adding a new function call
* but not changing or removing an old one), increment NS_HOOK_AGE
* as well; if not, set NS_HOOK_AGE to 0.
*/
#ifndef NS_HOOK_VERSION
#define NS_HOOK_VERSION 1
#define NS_HOOK_AGE 0
#endif
typedef isc_result_t ns_hook_register_t(isc_mem_t *mctx,
const char *parameters,
const char *file,
unsigned long line,
void **instp);
/*%
* Called when registering a new module.
*
* 'parameters' contains the module configuration text.
*
* '*instp' will be set to the module instance handle if the function
* is successful.
*
* Returns:
*\li #ISC_R_SUCCESS
*\li #ISC_R_NOMEMORY
*\li Other errors are possible
*/
typedef void ns_hook_destroy_t(void **instp);
/*%
* Destroy a module instance.
*
* '*instp' must be set to NULL by the function before it returns.
*/
typedef int ns_hook_version_t(unsigned int *flags);
/*%
* Return the API version number a hook module was compiled with.
*
* If the returned version number is no greater than than
* NS_HOOK_VERSION, and no less than NS_HOOK_VERSION - NS_HOOK_AGE,
* then the module is API-compatible with named.
*
* 'flags' is currently unused and may be NULL, but could be used in
* the future to pass back driver capabilities or other information.
*/
typedef struct ns_hook {
ns_hook_cb_t callback;
void *callback_data;
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];
LIBNS_EXTERNAL_DATA extern ns_hooktable_t *ns__hook_table;
/*
* Run a hook. Calls the function or functions registered at hookpoint 'id'.
* If one of them returns true, we interrupt processing and return the
* result that was returned by the hook function. If none of them return
* true, we continue processing.
*/
#define _NS_PROCESS_HOOK(table, id, data, ...) \