Home | History | Annotate | Line # | Download | only in ns
      1 /*	$NetBSD: hooks.h,v 1.9 2025/01/26 16:25:46 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  *
      6  * SPDX-License-Identifier: MPL-2.0
      7  *
      8  * This Source Code Form is subject to the terms of the Mozilla Public
      9  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  *
     12  * See the COPYRIGHT file distributed with this work for additional
     13  * information regarding copyright ownership.
     14  */
     15 
     16 #pragma once
     17 
     18 /*! \file */
     19 
     20 #include <stdbool.h>
     21 
     22 #include <isc/list.h>
     23 #include <isc/magic.h>
     24 #include <isc/mem.h>
     25 #include <isc/result.h>
     26 
     27 #include <dns/rdatatype.h>
     28 
     29 #include <ns/client.h>
     30 #include <ns/query.h>
     31 /*
     32  * "Hooks" are a mechanism to call a defined function or set of functions once
     33  * a certain place in code is reached.  Hook actions can inspect and alter the
     34  * state of an ongoing process, allowing processing to continue afterward or
     35  * triggering an early return.
     36  *
     37  * Currently hooks are used in two ways: in plugins, which use them to
     38  * add functionality to query processing, and in the unit tests for libns,
     39  * where they are used to inspect state before and after certain functions have
     40  * run.
     41  *
     42  * Both of these uses are limited to libns, so hooks are currently defined in
     43  * the ns/hooks.h header file, and hook-related macro and function names are
     44  * prefixed with `NS_` and `ns_`.  However, the design is fairly generic and
     45  * could be repurposed for general use, e.g. as part of libisc, after some
     46  * further customization.
     47  *
     48  * Hooks are created by defining a hook point identifier in the ns_hookpoint_t
     49  * enum below, and placing a special call at a corresponding location in the
     50  * code which invokes the action(s) for that hook; there are two such special
     51  * calls currently implemented, namely the CALL_HOOK() and CALL_HOOK_NORETURN()
     52  * macros in query.c.  The former macro contains a "goto cleanup" statement
     53  * which is inlined into the function into which the hook has been inserted;
     54  * this enables the hook action to cause the calling function to return from
     55  * the hook insertion point.  For functions returning isc_result_t, if a hook
     56  * action intends to cause a return at hook insertion point, it also has to set
     57  * the value to be returned by the calling function.
     58  *
     59  * A hook table is an array (indexed by the value of the hook point identifier)
     60  * in which each cell contains a linked list of structures, each of which
     61  * contains a function pointer to a hook action and a pointer to data which is
     62  * to be passed to the action function when it is called.
     63  *
     64  * Each view has its own separate hook table, populated by loading plugin
     65  * modules specified in the "plugin" statements in named.conf.  There is also a
     66  * special, global hook table (ns__hook_table) that is only used by libns unit
     67  * tests and whose existence can be safely ignored by plugin modules.
     68  *
     69  * Hook actions are functions which:
     70  *
     71  *   - return an ns_hookresult_t value:
     72  *       - if NS_HOOK_RETURN is returned by the hook action, the function
     73  *         into which the hook is inserted will return and no further hook
     74  *         actions at the same hook point will be invoked,
     75  *       - if NS_HOOK_CONTINUE is returned by the hook action and there are
     76  *         further hook actions set up at the same hook point, they will be
     77  *         processed; if NS_HOOK_CONTINUE is returned and there are no
     78  *         further hook actions set up at the same hook point, execution of
     79  *         the function into which the hook has been inserted will be
     80  *         resumed.
     81  *
     82  *   - accept three pointers as arguments:
     83  *       - a pointer specified by the special call at the hook insertion point,
     84  *       - a pointer specified upon inserting the action into the hook table,
     85  *       - a pointer to an isc_result_t value which will be returned by the
     86  *         function into which the hook is inserted if the action returns
     87  *         NS_HOOK_RETURN.
     88  *
     89  * In order for a hook action to be called for a given hook, a pointer to that
     90  * action function (along with an optional pointer to action-specific data) has
     91  * to be inserted into the relevant hook table entry for that hook using an
     92  * ns_hook_add() call.  If multiple actions are set up at a single hook point
     93  * (e.g. by multiple plugin modules), they are processed in FIFO order, that is
     94  * they are performed in the same order in which their relevant ns_hook_add()
     95  * calls were issued.  Since the configuration is loaded from a single thread,
     96  * this means that multiple actions at a single hook point are determined by
     97  * the order in which the relevant plugin modules were declared in the
     98  * configuration file(s).  The hook API currently does not support changing
     99  * this order.
    100  *
    101  * As an example, consider the following hypothetical function in query.c:
    102  *
    103  * ----------------------------------------------------------------------------
    104  * static isc_result_t
    105  * query_foo(query_ctx_t *qctx) {
    106  *     isc_result_t result;
    107  *
    108  *     CALL_HOOK(NS_QUERY_FOO_BEGIN, qctx);
    109  *
    110  *     ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY,
    111  *                   ISC_LOG_DEBUG(99), "Lorem ipsum dolor sit amet...");
    112  *
    113  *     result = ISC_R_COMPLETE;
    114  *
    115  *  cleanup:
    116  *     return (result);
    117  * }
    118  * ----------------------------------------------------------------------------
    119  *
    120  * and the following hook action:
    121  *
    122  * ----------------------------------------------------------------------------
    123  * static ns_hookresult_t
    124  * cause_failure(void *hook_data, void *action_data, isc_result_t *resultp) {
    125  *     UNUSED(hook_data);
    126  *     UNUSED(action_data);
    127  *
    128  *     *resultp = ISC_R_FAILURE;
    129  *
    130  *     return (NS_HOOK_RETURN);
    131  * }
    132  * ----------------------------------------------------------------------------
    133  *
    134  * If this hook action was installed in the hook table using:
    135  *
    136  * ----------------------------------------------------------------------------
    137  * const ns_hook_t foo_fail = {
    138  *     .action = cause_failure,
    139  * };
    140  *
    141  * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_fail);
    142  * ----------------------------------------------------------------------------
    143  *
    144  * then query_foo() would return ISC_R_FAILURE every time it is called due
    145  * to the cause_failure() hook action returning NS_HOOK_RETURN and setting
    146  * '*resultp' to ISC_R_FAILURE.  query_foo() would also never log the
    147  * "Lorem ipsum dolor sit amet..." message.
    148  *
    149  * Consider a different hook action:
    150  *
    151  * ----------------------------------------------------------------------------
    152  * static ns_hookresult_t
    153  * log_qtype(void *hook_data, void *action_data, isc_result_t *resultp) {
    154  *     query_ctx_t *qctx = (query_ctx_t *)hook_data;
    155  *     FILE *stream = (FILE *)action_data;
    156  *
    157  *     UNUSED(resultp);
    158  *
    159  *     fprintf(stream, "QTYPE=%u\n", qctx->qtype);
    160  *
    161  *     return (NS_HOOK_CONTINUE);
    162  * }
    163  * ----------------------------------------------------------------------------
    164  *
    165  * If this hook action was installed in the hook table instead of
    166  * cause_failure(), using:
    167  *
    168  * ----------------------------------------------------------------------------
    169  * const ns_hook_t foo_log_qtype = {
    170  *     .action = log_qtype,
    171  *     .action_data = stderr,
    172  * };
    173  *
    174  * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_log_qtype);
    175  * ----------------------------------------------------------------------------
    176  *
    177  * then the QTYPE stored in the query context passed to query_foo() would be
    178  * logged to stderr upon each call to that function; 'qctx' would be passed to
    179  * the hook action in 'hook_data' since it is specified in the CALL_HOOK() call
    180  * inside query_foo() while stderr would be passed to the hook action in
    181  * 'action_data' since it is specified in the ns_hook_t structure passed to
    182  * ns_hook_add().  As the hook action returns NS_HOOK_CONTINUE,
    183  * query_foo() would also be logging the "Lorem ipsum dolor sit amet..."
    184  * message before returning ISC_R_COMPLETE.
    185  *
    186  * ASYNCHRONOUS EVENT HANDLING IN QUERY HOOKS
    187  *
    188  * Usually a hook action works synchronously; it completes some particular
    189  * job in the middle of query processing, thus blocking the caller (and the
    190  * worker thread handling the query).  But sometimes an action can be time
    191  * consuming and the blocking behavior may not be acceptable.  For example,
    192  * a hook may need to send some kind of query (like a DB lookup) to an
    193  * external backend server and wait for the response to complete the hook's
    194  * action.  Depending on the network condition, the external server's load,
    195  * etc, it may take several seconds or more.
    196  *
    197  * In order to handle such a situation, a hook action can start an
    198  * asynchronous event by calling ns_query_hookasync().  This is similar
    199  * to ns_query_recurse(), but more generic.  ns_query_hookasync() will
    200  * call the 'runasync' function with a specified 'arg' (both passed to
    201  * ns_query_hookasync()) and a set of loop and associated event arguments
    202  * to be called to resume query handling upon completion of the
    203  * asynchronous event.
    204  *
    205  * The implementation of 'runasync' is assumed to allocate and build an
    206  * instance of ns_hook_resume_t whose callback, arg, and loop are set to
    207  * the passed values from ns_query_hookasync().  Other fields of
    208  * ns_hook_resume_t must be correctly set in the hook implementation
    209  * by the time it's sent to the specified loop:
    210  *
    211  * - hookpoint: the point from which the query handling should be resumed
    212  *   (which should usually be the hook point that triggered the asynchronous
    213  *   event).
    214  * - origresult: the result code passed to the hook action that triggers the
    215  *   asynchronous event through the 'resultp' pointer.  Some hook points need
    216  *   this value to correctly resume the query handling.
    217  * - saved_qctx: the 'qctx' passed to 'runasync'.  This holds some
    218  *   intermediate data for resolving the query, and will be used to resume the
    219  *   query handling.  The 'runasync' implementation must not modify it.
    220  *
    221  * The hook implementation should somehow maintain the created event
    222  * instance so that it can eventually send the event.
    223  *
    224  * 'runasync' then creates an instance of ns_hookasync_t with specifying its
    225  * own cancel and destroy function, and returns it to ns_query_hookasync()
    226  * in the passed pointer.
    227  *
    228  * On return from ns_query_hookasync(), the hook action MUST return
    229  * NS_HOOK_RETURN to suspend the query handling.
    230  *
    231  * On the completion of the asynchronous event, the hook implementation is
    232  * supposed to send the resumeevent to the corresponding loop.  The query
    233  * module resumes the query handling so that the hook action of the
    234  * specified hook point will be called, skipping some intermediate query
    235  * handling steps.  So, typically, the same hook action will be called
    236  * twice.  The hook implementation must somehow remember the context, and
    237  * handle the second call to complete its action using the result of the
    238  * asynchronous event.
    239  *
    240  * Example: assume the following hook-specific structure to manage
    241  * asynchronous events:
    242  *
    243  * typedef struct hookstate {
    244  * 	bool async;
    245  *	ns_hook_resume_t *rev
    246  *	ns_hookpoint_t hookpoint;
    247  *	isc_result_t origresult;
    248  * } hookstate_t;
    249  *
    250  * 'async' is supposed to be true if and only if hook-triggered
    251  * asynchronous processing is taking place.
    252  *
    253  * A hook action that uses an asynchronous event would look something
    254  * like this:
    255  *
    256  * hook_recurse(void *hook_data, void *action_data, isc_result_t *resultp) {
    257  *	hookstate_t *state = somehow_retrieve_from(action_data);
    258  *	if (state->async) {
    259  *		// just resumed from an asynchronous hook action.
    260  *		// complete the hook's action using the result of the
    261  *		// internal asynchronous event.
    262  *		state->async = false;
    263  *		return (NS_HOOK_CONTINUE);
    264  *	}
    265  *
    266  *	// Initial call to the hook action.  Start the internal
    267  *	// asynchronous event, and have the query module suspend
    268  *	// its own handling by returning NS_HOOK_RETURN.
    269  *	state->hookpoint = ...; // would be hook point for this hook
    270  *	state->origresult = *resultp;
    271  *	ns_query_hookasync(hook_data, runasync, state);
    272  *	state->async = true;
    273  *	return (NS_HOOK_RETURN);
    274  * }
    275  *
    276  * And the 'runasync' function would be something like this:
    277  *
    278  * static isc_result_t
    279  * runasync(query_ctx_t *qctx, void *arg, isc_job_cb cb, void *evarg,
    280  * 	    isc_loop_t *loop, ns_hookasync_t **ctxp) {
    281  * 	hookstate_t *state = arg;
    282  *	ns_hook_resume_t *rev isc_mem_get(mctx, sizeof(*rev));
    283  *	ns_hookasync_t *ctx = isc_mem_get(mctx, sizeof(*ctx));
    284  *
    285  *	*ctx = (ns_hookasync_t){ .private = NULL
    286  *                               .hookpoint = state->hookpoint,
    287  *                               .origresult = state->origresult,
    288  *                               .saved_ctx = qctx,
    289  *                               .ctx = ctx,
    290  *                               .loop = loop,
    291  *                               .cb = cb,
    292  *                               .arg = cbarg
    293  *	};
    294  *	isc_mem_attach(mctx, &ctx->mctx);
    295  *
    296  *	ctx->cancel = ...;  // set the cancel function, which cancels the
    297  *			    // internal asynchronous event (if necessary).
    298  *			    // it should eventually result in sending
    299  *			    // the 'rev' event to the calling loop.
    300  *	ctx->destroy = ...; // set the destroy function, which frees 'ctx'
    301  *
    302  *	state->rev = rev; // store the resume state so we can send it later
    303  *
    304  *	// initiate some asynchronous process here - for example, a
    305  *	// recursive fetch.
    306  *
    307  *	*ctxp = ctx;
    308  *	return (ISC_R_SUCCESS);
    309  * }
    310  *
    311  * Finally, in the completion handler for the asynchronous process, we
    312  * need to send a resumption event so that query processing can resume.
    313  * For example, the completion handler might call this function:
    314  *
    315  * static void
    316  * asyncproc_done(hookstate_t *state) {
    317  *	isc_async_run(state->rev->loop, state->rev->cb, state->rev->arg);
    318  * }
    319  *
    320  * Caveats:
    321  * - On resuming from a hook-initiated asynchronous process, code in
    322  *   the query module before the hook point needs to be exercised.
    323  *   So if this part has side effects, it's possible that the resuming
    324  *   doesn't work well.  Currently, NS_QUERY_RESPOND_ANY_FOUND is
    325  *   explicitly prohibited to be used as the resume point.
    326  * - In general, hooks other than those called at the beginning of the
    327  *   caller function may not work safely with asynchronous processing for
    328  *   the reason stated in the previous bullet.  For example, a hook action
    329  *   for NS_QUERY_DONE_SEND may not be able to start an asychronous
    330  *   function safely.
    331  * - Hook-triggered asynchronous processing is not allowed to be running
    332  *   while the standard DNS recursive fetch is taking place (starting
    333  *   from a call to dns_resolver_createfetch()), as the two would be
    334  *   using some of the same context resources.  For this reason the
    335  *   NS_QUERY_NOTFOUND_RECURSE and NS_QUERY_ZEROTTL_RECURSE hook points
    336  *   are explicitly prohibited from being used for asynchronous hook
    337  *   actions.
    338  * - Specifying multiple hook actions for the same hook point at the
    339  *   same time may cause problems, as resumption from one hook action
    340  *   could cause another hook to be called twice unintentionally.
    341  *   It's generally not safe to assume such a use case works,
    342  *   especially if the hooks are developed independently. (Note that
    343  *   that's not necessarily specific to the use of asynchronous hook
    344  *   actions. As long as hook actions have side effects, including
    345  *   modifying the internal query state, it's not guaranteed safe
    346  *   to use multiple independent hooks at the same time.)
    347  */
    348 
    349 /*!
    350  * Currently-defined hook points. So long as these are unique, the order in
    351  * which they are declared is unimportant, but it currently matches the
    352  * order in which they are referenced in query.c.
    353  */
    354 typedef enum {
    355 	/* hookpoints from query.c */
    356 	NS_QUERY_QCTX_INITIALIZED,
    357 	NS_QUERY_QCTX_DESTROYED,
    358 	NS_QUERY_SETUP,
    359 	NS_QUERY_START_BEGIN,
    360 	NS_QUERY_LOOKUP_BEGIN,
    361 	NS_QUERY_RESUME_BEGIN,
    362 	NS_QUERY_RESUME_RESTORED,
    363 	NS_QUERY_GOT_ANSWER_BEGIN,
    364 	NS_QUERY_RESPOND_ANY_BEGIN,
    365 	NS_QUERY_RESPOND_ANY_FOUND,
    366 	NS_QUERY_ADDANSWER_BEGIN,
    367 	NS_QUERY_RESPOND_BEGIN,
    368 	NS_QUERY_NOTFOUND_BEGIN,
    369 	NS_QUERY_NOTFOUND_RECURSE,
    370 	NS_QUERY_PREP_DELEGATION_BEGIN,
    371 	NS_QUERY_ZONE_DELEGATION_BEGIN,
    372 	NS_QUERY_DELEGATION_BEGIN,
    373 	NS_QUERY_DELEGATION_RECURSE_BEGIN,
    374 	NS_QUERY_NODATA_BEGIN,
    375 	NS_QUERY_NXDOMAIN_BEGIN,
    376 	NS_QUERY_NCACHE_BEGIN,
    377 	NS_QUERY_ZEROTTL_RECURSE,
    378 	NS_QUERY_CNAME_BEGIN,
    379 	NS_QUERY_DNAME_BEGIN,
    380 	NS_QUERY_PREP_RESPONSE_BEGIN,
    381 	NS_QUERY_DONE_BEGIN,
    382 	NS_QUERY_DONE_SEND,
    383 
    384 	/* XXX other files could be added later */
    385 
    386 	NS_HOOKPOINTS_COUNT /* MUST BE LAST */
    387 } ns_hookpoint_t;
    388 
    389 /*
    390  * Returned by a hook action to indicate how to proceed after it has
    391  * been called: continue processing, or return immediately.
    392  */
    393 typedef enum {
    394 	NS_HOOK_CONTINUE,
    395 	NS_HOOK_RETURN,
    396 } ns_hookresult_t;
    397 
    398 typedef ns_hookresult_t (*ns_hook_action_t)(void *arg, void *data,
    399 					    isc_result_t *resultp);
    400 
    401 typedef struct ns_hook {
    402 	isc_mem_t	*mctx;
    403 	ns_hook_action_t action;
    404 	void		*action_data;
    405 	ISC_LINK(struct ns_hook) link;
    406 } ns_hook_t;
    407 
    408 typedef ISC_LIST(ns_hook_t) ns_hooklist_t;
    409 typedef ns_hooklist_t ns_hooktable_t[NS_HOOKPOINTS_COUNT];
    410 
    411 /*%
    412  * ns__hook_table is a global hook table, which is used if view->hooktable
    413  * is NULL.  It's intended only for use by unit tests.
    414  */
    415 extern ns_hooktable_t *ns__hook_table;
    416 
    417 typedef void (*ns_hook_cancelasync_t)(ns_hookasync_t *);
    418 typedef void (*ns_hook_destroyasync_t)(ns_hookasync_t **);
    419 
    420 /*%
    421  * Context for a hook-initiated asynchronous process. This works
    422  * similarly to dns_fetch_t.
    423  */
    424 struct ns_hookasync {
    425 	isc_mem_t *mctx;
    426 
    427 	/*
    428 	 * The following two are equivalent to dns_resolver_cancelfetch and
    429 	 * dns_resolver_destroyfetch, respectively, but specified as function
    430 	 * pointers since they can be hook-specific.
    431 	 */
    432 	ns_hook_cancelasync_t  cancel;
    433 	ns_hook_destroyasync_t destroy;
    434 
    435 	void *private; /* hook-specific data */
    436 };
    437 
    438 /*
    439  * isc_event to be sent on the completion of a hook-initiated asyncronous
    440  * process, similar to dns_fetchresponse_t.
    441  */
    442 typedef struct ns_hook_resume {
    443 	ns_hookasync_t *ctx;	   /* asynchronous processing context */
    444 	ns_hookpoint_t	hookpoint; /* hook point from which to resume */
    445 	isc_result_t origresult; /* result code at the point of call to hook */
    446 	query_ctx_t *saved_qctx; /* qctx at the point of call to hook */
    447 	isc_loop_t  *loop;	 /* loopmgr loop to resume in */
    448 	isc_job_cb   cb;	 /* callback function */
    449 	void	    *arg;	 /* argument to pass to the callback */
    450 } ns_hook_resume_t;
    451 
    452 /*
    453  * Plugin API version
    454  *
    455  * When the API changes, increment NS_PLUGIN_VERSION. If the
    456  * change is backward-compatible (e.g., adding a new function call
    457  * but not changing or removing an old one), increment NS_PLUGIN_AGE
    458  * as well; if not, set NS_PLUGIN_AGE to 0.
    459  */
    460 #ifndef NS_PLUGIN_VERSION
    461 #define NS_PLUGIN_VERSION 2
    462 #define NS_PLUGIN_AGE	  0
    463 #endif /* ifndef NS_PLUGIN_VERSION */
    464 
    465 typedef isc_result_t
    466 ns_plugin_register_t(const char *parameters, const void *cfg, const char *file,
    467 		     unsigned long line, isc_mem_t *mctx, isc_log_t *lctx,
    468 		     void *actx, ns_hooktable_t *hooktable, void **instp);
    469 /*%<
    470  * Called when registering a new plugin.
    471  *
    472  * 'parameters' contains the plugin configuration text.
    473  *
    474  * '*instp' will be set to the module instance handle if the function
    475  * is successful.
    476  *
    477  * Returns:
    478  *\li	#ISC_R_SUCCESS
    479  *\li	#ISC_R_NOMEMORY
    480  *\li	Other errors are possible
    481  */
    482 
    483 typedef void
    484 ns_plugin_destroy_t(void **instp);
    485 /*%<
    486  * Destroy a plugin instance.
    487  *
    488  * '*instp' must be set to NULL by the function before it returns.
    489  */
    490 
    491 typedef isc_result_t
    492 ns_plugin_check_t(const char *parameters, const void *cfg, const char *file,
    493 		  unsigned long line, isc_mem_t *mctx, isc_log_t *lctx,
    494 		  void *actx);
    495 /*%<
    496  * Check the validity of 'parameters'.
    497  */
    498 
    499 typedef int
    500 ns_plugin_version_t(void);
    501 /*%<
    502  * Return the API version number a plugin was compiled with.
    503  *
    504  * If the returned version number is no greater than
    505  * NS_PLUGIN_VERSION, and no less than NS_PLUGIN_VERSION - NS_PLUGIN_AGE,
    506  * then the module is API-compatible with named.
    507  */
    508 
    509 /*%
    510  * Prototypes for API functions to be defined in each module.
    511  */
    512 ns_plugin_check_t    plugin_check;
    513 ns_plugin_destroy_t  plugin_destroy;
    514 ns_plugin_register_t plugin_register;
    515 ns_plugin_version_t  plugin_version;
    516 
    517 isc_result_t
    518 ns_plugin_expandpath(const char *src, char *dst, size_t dstsize);
    519 /*%<
    520  * Prepare the plugin location to be passed to dlopen() based on the plugin
    521  * path or filename found in the configuration file ('src').  Store the result
    522  * in 'dst', which is 'dstsize' bytes large.
    523  *
    524  * On Unix systems, two classes of 'src' are recognized:
    525  *
    526  *   - If 'src' is an absolute or relative path, it will be copied to 'dst'
    527  *     verbatim.
    528  *
    529  *   - If 'src' is a filename (i.e. does not contain a path separator), the
    530  *     path to the directory into which named plugins are installed will be
    531  *     prepended to it and the result will be stored in 'dst'.
    532  *
    533  * Returns:
    534  *\li	#ISC_R_SUCCESS	Success
    535  *\li	#ISC_R_NOSPACE	'dst' is not large enough to hold the output string
    536  *\li	Other result	snprintf() returned a negative value
    537  */
    538 
    539 isc_result_t
    540 ns_plugin_register(const char *modpath, const char *parameters, const void *cfg,
    541 		   const char *cfg_file, unsigned long cfg_line,
    542 		   isc_mem_t *mctx, isc_log_t *lctx, void *actx,
    543 		   dns_view_t *view);
    544 /*%<
    545  * Load the plugin module specified from the file 'modpath', and
    546  * register an instance using 'parameters'.
    547  *
    548  * 'cfg_file' and 'cfg_line' specify the location of the plugin
    549  * declaration in the configuration file.
    550  *
    551  * 'cfg' and 'actx' are the configuration context and ACL configuration
    552  * context, respectively; they are passed as void * here in order to
    553  * prevent this library from having a dependency on libisccfg).
    554  *
    555  * 'instp' will be left pointing to the instance of the plugin
    556  * created by the module's plugin_register function.
    557  */
    558 
    559 isc_result_t
    560 ns_plugin_check(const char *modpath, const char *parameters, const void *cfg,
    561 		const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx,
    562 		isc_log_t *lctx, void *actx);
    563 /*%<
    564  * Open the plugin module at 'modpath' and check the validity of
    565  * 'parameters', logging any errors or warnings found, then
    566  * close it without configuring it.
    567  */
    568 
    569 void
    570 ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp);
    571 /*%<
    572  * Create and initialize a plugin list.
    573  */
    574 
    575 void
    576 ns_plugins_free(isc_mem_t *mctx, void **listp);
    577 /*%<
    578  * Close each plugin module in a plugin list, then free the list object.
    579  */
    580 
    581 void
    582 ns_hooktable_free(isc_mem_t *mctx, void **tablep);
    583 /*%<
    584  * Free a hook table.
    585  */
    586 
    587 void
    588 ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx,
    589 	    ns_hookpoint_t hookpoint, const ns_hook_t *hook);
    590 /*%<
    591  * Allocate (using memory context 'mctx') a copy of the 'hook' structure
    592  * describing a hook action and append it to the list of hooks at 'hookpoint'
    593  * in 'hooktable'.
    594  *
    595  * Requires:
    596  *\li 'hooktable' is not NULL
    597  *
    598  *\li 'mctx' is not NULL
    599  *
    600  *\li 'hookpoint' is less than NS_QUERY_HOOKS_COUNT
    601  *
    602  *\li 'hook' is not NULL
    603  */
    604 
    605 void
    606 ns_hooktable_init(ns_hooktable_t *hooktable);
    607 /*%<
    608  * Initialize a hook table.
    609  */
    610 
    611 isc_result_t
    612 ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep);
    613 /*%<
    614  * Allocate and initialize a hook table.
    615  */
    616