summaryrefslogtreecommitdiffstats
path: root/lib/ns/include/ns/hooks.h
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ns/include/ns/hooks.h')
-rw-r--r--lib/ns/include/ns/hooks.h612
1 files changed, 612 insertions, 0 deletions
diff --git a/lib/ns/include/ns/hooks.h b/lib/ns/include/ns/hooks.h
new file mode 100644
index 0000000..cb146ff
--- /dev/null
+++ b/lib/ns/include/ns/hooks.h
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/event.h>
+#include <isc/list.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/task.h>
+
+#include <dns/rdatatype.h>
+
+#include <ns/client.h>
+#include <ns/query.h>
+/*
+ * "Hooks" are a mechanism to call a defined function or set of functions once
+ * a certain place in code is reached. Hook actions can inspect and alter the
+ * state of an ongoing process, allowing processing to continue afterward or
+ * triggering an early return.
+ *
+ * Currently hooks are used in two ways: in plugins, which use them to
+ * add functionality to query processing, and in the unit tests for libns,
+ * where they are used to inspect state before and after certain functions have
+ * run.
+ *
+ * Both of these uses are limited to libns, so hooks are currently defined in
+ * the ns/hooks.h header file, and hook-related macro and function names are
+ * prefixed with `NS_` and `ns_`. However, the design is fairly generic and
+ * could be repurposed for general use, e.g. as part of libisc, after some
+ * further customization.
+ *
+ * Hooks are created by defining a hook point identifier in the ns_hookpoint_t
+ * enum below, and placing a special call at a corresponding location in the
+ * code which invokes the action(s) for that hook; there are two such special
+ * calls currently implemented, namely the CALL_HOOK() and CALL_HOOK_NORETURN()
+ * macros in query.c. The former macro contains a "goto cleanup" statement
+ * which is inlined into the function into which the hook has been inserted;
+ * this enables the hook action to cause the calling function to return from
+ * the hook insertion point. For functions returning isc_result_t, if a hook
+ * action intends to cause a return at hook insertion point, it also has to set
+ * the value to be returned by the calling function.
+ *
+ * A hook table is an array (indexed by the value of the hook point identifier)
+ * in which each cell contains a linked list of structures, each of which
+ * contains a function pointer to a hook action and a pointer to data which is
+ * to be passed to the action function when it is called.
+ *
+ * Each view has its own separate hook table, populated by loading plugin
+ * modules specified in the "plugin" statements in named.conf. There is also a
+ * special, global hook table (ns__hook_table) that is only used by libns unit
+ * tests and whose existence can be safely ignored by plugin modules.
+ *
+ * Hook actions are functions which:
+ *
+ * - return an ns_hookresult_t value:
+ * - if NS_HOOK_RETURN is returned by the hook action, the function
+ * into which the hook is inserted will return and no further hook
+ * actions at the same hook point will be invoked,
+ * - if NS_HOOK_CONTINUE is returned by the hook action and there are
+ * further hook actions set up at the same hook point, they will be
+ * processed; if NS_HOOK_CONTINUE is returned and there are no
+ * further hook actions set up at the same hook point, execution of
+ * the function into which the hook has been inserted will be
+ * resumed.
+ *
+ * - accept three pointers as arguments:
+ * - a pointer specified by the special call at the hook insertion point,
+ * - a pointer specified upon inserting the action into the hook table,
+ * - a pointer to an isc_result_t value which will be returned by the
+ * function into which the hook is inserted if the action returns
+ * NS_HOOK_RETURN.
+ *
+ * In order for a hook action to be called for a given hook, a pointer to that
+ * action function (along with an optional pointer to action-specific data) has
+ * to be inserted into the relevant hook table entry for that hook using an
+ * ns_hook_add() call. If multiple actions are set up at a single hook point
+ * (e.g. by multiple plugin modules), they are processed in FIFO order, that is
+ * they are performed in the same order in which their relevant ns_hook_add()
+ * calls were issued. Since the configuration is loaded from a single thread,
+ * this means that multiple actions at a single hook point are determined by
+ * the order in which the relevant plugin modules were declared in the
+ * configuration file(s). The hook API currently does not support changing
+ * this order.
+ *
+ * As an example, consider the following hypothetical function in query.c:
+ *
+ * ----------------------------------------------------------------------------
+ * static isc_result_t
+ * query_foo(query_ctx_t *qctx) {
+ * isc_result_t result;
+ *
+ * CALL_HOOK(NS_QUERY_FOO_BEGIN, qctx);
+ *
+ * ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY,
+ * ISC_LOG_DEBUG(99), "Lorem ipsum dolor sit amet...");
+ *
+ * result = ISC_R_COMPLETE;
+ *
+ * cleanup:
+ * return (result);
+ * }
+ * ----------------------------------------------------------------------------
+ *
+ * and the following hook action:
+ *
+ * ----------------------------------------------------------------------------
+ * static ns_hookresult_t
+ * cause_failure(void *hook_data, void *action_data, isc_result_t *resultp) {
+ * UNUSED(hook_data);
+ * UNUSED(action_data);
+ *
+ * *resultp = ISC_R_FAILURE;
+ *
+ * return (NS_HOOK_RETURN);
+ * }
+ * ----------------------------------------------------------------------------
+ *
+ * If this hook action was installed in the hook table using:
+ *
+ * ----------------------------------------------------------------------------
+ * const ns_hook_t foo_fail = {
+ * .action = cause_failure,
+ * };
+ *
+ * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_fail);
+ * ----------------------------------------------------------------------------
+ *
+ * then query_foo() would return ISC_R_FAILURE every time it is called due
+ * to the cause_failure() hook action returning NS_HOOK_RETURN and setting
+ * '*resultp' to ISC_R_FAILURE. query_foo() would also never log the
+ * "Lorem ipsum dolor sit amet..." message.
+ *
+ * Consider a different hook action:
+ *
+ * ----------------------------------------------------------------------------
+ * static ns_hookresult_t
+ * log_qtype(void *hook_data, void *action_data, isc_result_t *resultp) {
+ * query_ctx_t *qctx = (query_ctx_t *)hook_data;
+ * FILE *stream = (FILE *)action_data;
+ *
+ * UNUSED(resultp);
+ *
+ * fprintf(stream, "QTYPE=%u\n", qctx->qtype);
+ *
+ * return (NS_HOOK_CONTINUE);
+ * }
+ * ----------------------------------------------------------------------------
+ *
+ * If this hook action was installed in the hook table instead of
+ * cause_failure(), using:
+ *
+ * ----------------------------------------------------------------------------
+ * const ns_hook_t foo_log_qtype = {
+ * .action = log_qtype,
+ * .action_data = stderr,
+ * };
+ *
+ * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_log_qtype);
+ * ----------------------------------------------------------------------------
+ *
+ * then the QTYPE stored in the query context passed to query_foo() would be
+ * logged to stderr upon each call to that function; 'qctx' would be passed to
+ * the hook action in 'hook_data' since it is specified in the CALL_HOOK() call
+ * inside query_foo() while stderr would be passed to the hook action in
+ * 'action_data' since it is specified in the ns_hook_t structure passed to
+ * ns_hook_add(). As the hook action returns NS_HOOK_CONTINUE,
+ * query_foo() would also be logging the "Lorem ipsum dolor sit amet..."
+ * message before returning ISC_R_COMPLETE.
+ *
+ * ASYNCHRONOUS EVENT HANDLING IN QUERY HOOKS
+ *
+ * Usually a hook action works synchronously; it completes some particular
+ * job in the middle of query processing, thus blocking the caller (and the
+ * worker thread handling the query). But sometimes an action can be time
+ * consuming and the blocking behavior may not be acceptable. For example,
+ * a hook may need to send some kind of query (like a DB lookup) to an
+ * external backend server and wait for the response to complete the hook's
+ * action. Depending on the network condition, the external server's load,
+ * etc, it may take several seconds or more.
+ *
+ * In order to handle such a situation, a hook action can start an
+ * asynchronous event by calling ns_query_hookasync(). This is similar
+ * to ns_query_recurse(), but more generic. ns_query_hookasync() will
+ * call the 'runasync' function with a specified 'arg' (both passed to
+ * ns_query_hookasync()) and a set of task and associated event arguments
+ * to be called to resume query handling upon completion of the
+ * asynchronous event.
+ *
+ * The implementation of 'runasync' is assumed to allocate and build an
+ * instance of ns_hook_resevent_t whose action, arg, and task are set to
+ * the passed values from ns_query_hookasync(). Other fields of
+ * ns_hook_resevent_t must be correctly set in the hook implementation
+ * by the time it's sent to the specified task:
+ *
+ * - hookpoint: the point from which the query handling should be resumed
+ * (which should usually be the hook point that triggered the asynchronous
+ * event).
+ * - origresult: the result code passed to the hook action that triggers the
+ * asynchronous event through the 'resultp' pointer. Some hook points need
+ * this value to correctly resume the query handling.
+ * - saved_qctx: the 'qctx' passed to 'runasync'. This holds some
+ * intermediate data for resolving the query, and will be used to resume the
+ * query handling. The 'runasync' implementation must not modify it.
+ *
+ * The hook implementation should somehow maintain the created event
+ * instance so that it can eventually send the event.
+ *
+ * 'runasync' then creates an instance of ns_hookasync_t with specifying its
+ * own cancel and destroy function, and returns it to ns_query_hookasync()
+ * in the passed pointer.
+ *
+ * On return from ns_query_hookasync(), the hook action MUST return
+ * NS_HOOK_RETURN to suspend the query handling.
+ *
+ * On the completion of the asynchronous event, the hook implementation is
+ * supposed to send the resumeevent to the corresponding task. The query
+ * module resumes the query handling so that the hook action of the
+ * specified hook point will be called, skipping some intermediate query
+ * handling steps. So, typically, the same hook action will be called
+ * twice. The hook implementation must somehow remember the context, and
+ * handle the second call to complete its action using the result of the
+ * asynchronous event.
+ *
+ * Example: assume the following hook-specific structure to manage
+ * asynchronous events:
+ *
+ * typedef struct hookstate {
+ * bool async;
+ * ns_hook_resevent_t *rev
+ * ns_hookpoint_t hookpoint;
+ * isc_result_t origresult;
+ * } hookstate_t;
+ *
+ * 'async' is supposed to be true if and only if hook-triggered
+ * asynchronous processing is taking place.
+ *
+ * A hook action that uses an asynchronous event would look something
+ * like this:
+ *
+ * hook_recurse(void *hook_data, void *action_data, isc_result_t *resultp) {
+ * hookstate_t *state = somehow_retrieve_from(action_data);
+ * if (state->async) {
+ * // just resumed from an asynchronous hook action.
+ * // complete the hook's action using the result of the
+ * // internal asynchronous event.
+ * state->async = false;
+ * return (NS_HOOK_CONTINUE);
+ * }
+ *
+ * // Initial call to the hook action. Start the internal
+ * // asynchronous event, and have the query module suspend
+ * // its own handling by returning NS_HOOK_RETURN.
+ * state->hookpoint = ...; // would be hook point for this hook
+ * state->origresult = *resultp;
+ * ns_query_hookasync(hook_data, runasync, state);
+ * state->async = true;
+ * return (NS_HOOK_RETURN);
+ * }
+ *
+ * And the 'runasync' function would be something like this:
+ *
+ * static isc_result_t
+ * runasync(query_ctx_t *qctx, void *arg, isc_taskaction_t action,
+ * void *evarg, isc_task_t *task, ns_hookasync_t **ctxp) {
+ * hookstate_t *state = arg;
+ * ns_hook_resevent_t *rev = isc_event_allocate(
+ * mctx, task, NS_EVENT_HOOKASYNCDONE, action, evarg,
+ * sizeof(*rev));
+ * ns_hookasync_t *ctx = isc_mem_get(mctx, sizeof(*ctx));
+ *
+ * *ctx = (ns_hookasync_t){ .private = NULL };
+ * isc_mem_attach(mctx, &ctx->mctx);
+ * ctx->cancel = ...; // set the cancel function, which cancels the
+ * // internal asynchronous event (if necessary).
+ * // it should eventually result in sending
+ * // the 'rev' event to the calling task.
+ * ctx->destroy = ...; // set the destroy function, which frees 'ctx'
+ *
+ * rev->hookpoint = state->hookpoint;
+ * rev->origresult = state->origresult;
+ * rev->saved_qctx = qctx;
+ * rev->ctx = ctx;
+ *
+ * state->rev = rev; // store the resume event so we can send it later
+ *
+ * // initiate some asynchronous process here - for example, a
+ * // recursive fetch.
+ *
+ * *ctxp = ctx;
+ * return (ISC_R_SUCCESS);
+ * }
+ *
+ * Finally, in the completion handler for the asynchronous process, we
+ * need to send a resumption event so that query processing can resume.
+ * For example, the completion handler might call this function:
+ *
+ * static void
+ * asyncproc_done(hookstate_t *state) {
+ * isc_event_t *ev = (isc_event_t *)state->rev;
+ * isc_task_send(ev->ev_sender, &ev);
+ * }
+ *
+ * Caveats:
+ * - On resuming from a hook-initiated asynchronous process, code in
+ * the query module before the hook point needs to be exercised.
+ * So if this part has side effects, it's possible that the resuming
+ * doesn't work well. Currently, NS_QUERY_RESPOND_ANY_FOUND is
+ * explicitly prohibited to be used as the resume point.
+ * - In general, hooks other than those called at the beginning of the
+ * caller function may not work safely with asynchronous processing for
+ * the reason stated in the previous bullet. For example, a hook action
+ * for NS_QUERY_DONE_SEND may not be able to start an asychronous
+ * function safely.
+ * - Hook-triggered asynchronous processing is not allowed to be running
+ * while the standard DNS recursive fetch is taking place (starting
+ * from a call to dns_resolver_createfetch()), as the two would be
+ * using some of the same context resources. For this reason the
+ * NS_QUERY_NOTFOUND_RECURSE and NS_QUERY_ZEROTTL_RECURSE hook points
+ * are explicitly prohibited from being used for asynchronous hook
+ * actions.
+ * - Specifying multiple hook actions for the same hook point at the
+ * same time may cause problems, as resumption from one hook action
+ * could cause another hook to be called twice unintentionally.
+ * It's generally not safe to assume such a use case works,
+ * especially if the hooks are developed independently. (Note that
+ * that's not necessarily specific to the use of asynchronous hook
+ * actions. As long as hook actions have side effects, including
+ * modifying the internal query state, it's not guaranteed safe
+ * to use multiple independent hooks at the same time.)
+ */
+
+/*!
+ * Currently-defined hook points. So long as these are unique, the order in
+ * which they are declared is unimportant, but it currently matches the
+ * order in which they are referenced in query.c.
+ */
+typedef enum {
+ /* hookpoints from query.c */
+ NS_QUERY_QCTX_INITIALIZED,
+ NS_QUERY_QCTX_DESTROYED,
+ NS_QUERY_SETUP,
+ NS_QUERY_START_BEGIN,
+ NS_QUERY_LOOKUP_BEGIN,
+ NS_QUERY_RESUME_BEGIN,
+ NS_QUERY_RESUME_RESTORED,
+ NS_QUERY_GOT_ANSWER_BEGIN,
+ NS_QUERY_RESPOND_ANY_BEGIN,
+ NS_QUERY_RESPOND_ANY_FOUND,
+ NS_QUERY_ADDANSWER_BEGIN,
+ NS_QUERY_RESPOND_BEGIN,
+ NS_QUERY_NOTFOUND_BEGIN,
+ NS_QUERY_NOTFOUND_RECURSE,
+ NS_QUERY_PREP_DELEGATION_BEGIN,
+ NS_QUERY_ZONE_DELEGATION_BEGIN,
+ NS_QUERY_DELEGATION_BEGIN,
+ NS_QUERY_DELEGATION_RECURSE_BEGIN,
+ NS_QUERY_NODATA_BEGIN,
+ NS_QUERY_NXDOMAIN_BEGIN,
+ NS_QUERY_NCACHE_BEGIN,
+ NS_QUERY_ZEROTTL_RECURSE,
+ NS_QUERY_CNAME_BEGIN,
+ NS_QUERY_DNAME_BEGIN,
+ NS_QUERY_PREP_RESPONSE_BEGIN,
+ NS_QUERY_DONE_BEGIN,
+ NS_QUERY_DONE_SEND,
+
+ /* XXX other files could be added later */
+
+ NS_HOOKPOINTS_COUNT /* MUST BE LAST */
+} ns_hookpoint_t;
+
+/*
+ * Returned by a hook action to indicate how to proceed after it has
+ * been called: continue processing, or return immediately.
+ */
+typedef enum {
+ NS_HOOK_CONTINUE,
+ NS_HOOK_RETURN,
+} ns_hookresult_t;
+
+typedef ns_hookresult_t (*ns_hook_action_t)(void *arg, void *data,
+ isc_result_t *resultp);
+
+typedef struct ns_hook {
+ isc_mem_t *mctx;
+ ns_hook_action_t action;
+ void *action_data;
+ ISC_LINK(struct ns_hook) link;
+} ns_hook_t;
+
+typedef ISC_LIST(ns_hook_t) ns_hooklist_t;
+typedef ns_hooklist_t ns_hooktable_t[NS_HOOKPOINTS_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.
+ */
+extern ns_hooktable_t *ns__hook_table;
+
+typedef void (*ns_hook_cancelasync_t)(ns_hookasync_t *);
+typedef void (*ns_hook_destroyasync_t)(ns_hookasync_t **);
+
+/*%
+ * Context for a hook-initiated asynchronous process. This works
+ * similarly to dns_fetch_t.
+ */
+struct ns_hookasync {
+ isc_mem_t *mctx;
+
+ /*
+ * The following two are equivalent to dns_resolver_cancelfetch and
+ * dns_resolver_destroyfetch, respectively, but specified as function
+ * pointers since they can be hook-specific.
+ */
+ ns_hook_cancelasync_t cancel;
+ ns_hook_destroyasync_t destroy;
+
+ void *private; /* hook-specific data */
+};
+
+/*
+ * isc_event to be sent on the completion of a hook-initiated asyncronous
+ * process, similar to dns_fetchevent_t.
+ */
+typedef struct ns_hook_resevent {
+ ISC_EVENT_COMMON(struct ns_hook_resevent);
+ ns_hookasync_t *ctx; /* asynchronous processing context */
+ ns_hookpoint_t hookpoint; /* hook point from which to resume */
+ isc_result_t origresult; /* result code at the point of call to hook */
+ query_ctx_t *saved_qctx; /* qctx at the point of call to hook */
+} ns_hook_resevent_t;
+
+/*
+ * Plugin API version
+ *
+ * When the API changes, increment NS_PLUGIN_VERSION. If the
+ * change is backward-compatible (e.g., adding a new function call
+ * but not changing or removing an old one), increment NS_PLUGIN_AGE
+ * as well; if not, set NS_PLUGIN_AGE to 0.
+ */
+#ifndef NS_PLUGIN_VERSION
+#define NS_PLUGIN_VERSION 1
+#define NS_PLUGIN_AGE 0
+#endif /* ifndef NS_PLUGIN_VERSION */
+
+typedef isc_result_t
+ns_plugin_register_t(const char *parameters, const void *cfg, const char *file,
+ unsigned long line, isc_mem_t *mctx, isc_log_t *lctx,
+ void *actx, ns_hooktable_t *hooktable, void **instp);
+/*%<
+ * Called when registering a new plugin.
+ *
+ * 'parameters' contains the plugin 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_plugin_destroy_t(void **instp);
+/*%<
+ * Destroy a plugin instance.
+ *
+ * '*instp' must be set to NULL by the function before it returns.
+ */
+
+typedef isc_result_t
+ns_plugin_check_t(const char *parameters, const void *cfg, const char *file,
+ unsigned long line, isc_mem_t *mctx, isc_log_t *lctx,
+ void *actx);
+/*%<
+ * Check the validity of 'parameters'.
+ */
+
+typedef int
+ns_plugin_version_t(void);
+/*%<
+ * Return the API version number a plugin was compiled with.
+ *
+ * If the returned version number is no greater than
+ * NS_PLUGIN_VERSION, and no less than NS_PLUGIN_VERSION - NS_PLUGIN_AGE,
+ * then the module is API-compatible with named.
+ */
+
+/*%
+ * Prototypes for API functions to be defined in each module.
+ */
+ns_plugin_check_t plugin_check;
+ns_plugin_destroy_t plugin_destroy;
+ns_plugin_register_t plugin_register;
+ns_plugin_version_t plugin_version;
+
+isc_result_t
+ns_plugin_expandpath(const char *src, char *dst, size_t dstsize);
+/*%<
+ * Prepare the plugin location to be passed to dlopen() based on the plugin
+ * path or filename found in the configuration file ('src'). Store the result
+ * in 'dst', which is 'dstsize' bytes large.
+ *
+ * On Unix systems, two classes of 'src' are recognized:
+ *
+ * - If 'src' is an absolute or relative path, it will be copied to 'dst'
+ * verbatim.
+ *
+ * - If 'src' is a filename (i.e. does not contain a path separator), the
+ * path to the directory into which named plugins are installed will be
+ * prepended to it and the result will be stored in 'dst'.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_NOSPACE 'dst' is not large enough to hold the output string
+ *\li Other result snprintf() returned a negative value
+ */
+
+isc_result_t
+ns_plugin_register(const char *modpath, const char *parameters, const void *cfg,
+ const char *cfg_file, unsigned long cfg_line,
+ isc_mem_t *mctx, isc_log_t *lctx, void *actx,
+ dns_view_t *view);
+/*%<
+ * Load the plugin module specified from the file 'modpath', and
+ * register an instance using 'parameters'.
+ *
+ * 'cfg_file' and 'cfg_line' specify the location of the plugin
+ * declaration in the configuration file.
+ *
+ * 'cfg' and 'actx' are the configuration context and ACL configuration
+ * context, respectively; they are passed as void * here in order to
+ * prevent this library from having a dependency on libisccfg).
+ *
+ * 'instp' will be left pointing to the instance of the plugin
+ * created by the module's plugin_register function.
+ */
+
+isc_result_t
+ns_plugin_check(const char *modpath, const char *parameters, const void *cfg,
+ const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx,
+ isc_log_t *lctx, void *actx);
+/*%<
+ * Open the plugin module at 'modpath' and check the validity of
+ * 'parameters', logging any errors or warnings found, then
+ * close it without configuring it.
+ */
+
+void
+ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp);
+/*%<
+ * Create and initialize a plugin list.
+ */
+
+void
+ns_plugins_free(isc_mem_t *mctx, void **listp);
+/*%<
+ * Close each plugin module in a plugin list, then free the list object.
+ */
+
+void
+ns_hooktable_free(isc_mem_t *mctx, void **tablep);
+/*%<
+ * Free a hook table.
+ */
+
+void
+ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx,
+ ns_hookpoint_t hookpoint, const ns_hook_t *hook);
+/*%<
+ * Allocate (using memory context 'mctx') a copy of the 'hook' structure
+ * describing a hook action and append it to the list of hooks at 'hookpoint'
+ * in 'hooktable'.
+ *
+ * Requires:
+ *\li 'hooktable' is not NULL
+ *
+ *\li 'mctx' is not NULL
+ *
+ *\li 'hookpoint' is less than NS_QUERY_HOOKS_COUNT
+ *
+ *\li 'hook' is not NULL
+ */
+
+void
+ns_hooktable_init(ns_hooktable_t *hooktable);
+/*%<
+ * Initialize a hook table.
+ */
+
+isc_result_t
+ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep);
+/*%<
+ * Allocate and initialize a hook table.
+ */