hooks.h revision 1.5
1/*	$NetBSD: hooks.h,v 1.5 2020/05/24 19:46:29 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * See the COPYRIGHT file distributed with this work for additional
11 * information regarding copyright ownership.
12 */
13
14#ifndef NS_HOOKS_H
15#define NS_HOOKS_H 1
16
17/*! \file */
18
19#include <stdbool.h>
20
21#include <isc/list.h>
22#include <isc/magic.h>
23#include <isc/result.h>
24
25#include <dns/rdatatype.h>
26
27#include <ns/client.h>
28#include <ns/query.h>
29/*
30 * "Hooks" are a mechanism to call a defined function or set of functions once
31 * a certain place in code is reached.  Hook actions can inspect and alter the
32 * state of an ongoing process, allowing processing to continue afterward or
33 * triggering an early return.
34 *
35 * Currently hooks are used in two ways: in plugins, which use them to
36 * add functionality to query processing, and in the unit tests for libns,
37 * where they are used to inspect state before and after certain functions have
38 * run.
39 *
40 * Both of these uses are limited to libns, so hooks are currently defined in
41 * the ns/hooks.h header file, and hook-related macro and function names are
42 * prefixed with `NS_` and `ns_`.  However, the design is fairly generic and
43 * could be repurposed for general use, e.g. as part of libisc, after some
44 * further customization.
45 *
46 * Hooks are created by defining a hook point identifier in the ns_hookpoint_t
47 * enum below, and placing a special call at a corresponding location in the
48 * code which invokes the action(s) for that hook; there are two such special
49 * calls currently implemented, namely the CALL_HOOK() and CALL_HOOK_NORETURN()
50 * macros in query.c.  The former macro contains a "goto cleanup" statement
51 * which is inlined into the function into which the hook has been inserted;
52 * this enables the hook action to cause the calling function to return from
53 * the hook insertion point.  For functions returning isc_result_t, if a hook
54 * action intends to cause a return at hook insertion point, it also has to set
55 * the value to be returned by the calling function.
56 *
57 * A hook table is an array (indexed by the value of the hook point identifier)
58 * in which each cell contains a linked list of structures, each of which
59 * contains a function pointer to a hook action and a pointer to data which is
60 * to be passed to the action function when it is called.
61 *
62 * Each view has its own separate hook table, populated by loading plugin
63 * modules specified in the "plugin" statements in named.conf.  There is also a
64 * special, global hook table (ns__hook_table) that is only used by libns unit
65 * tests and whose existence can be safely ignored by plugin modules.
66 *
67 * Hook actions are functions which:
68 *
69 *   - return an ns_hookresult_t value:
70 *       - if NS_HOOK_RETURN is returned by the hook action, the function
71 *         into which the hook is inserted will return and no further hook
72 *         actions at the same hook point will be invoked,
73 *       - if NS_HOOK_CONTINUE is returned by the hook action and there are
74 *         further hook actions set up at the same hook point, they will be
75 *         processed; if NS_HOOK_CONTINUE is returned and there are no
76 *         further hook actions set up at the same hook point, execution of
77 *         the function into which the hook has been inserted will be
78 *         resumed.
79 *
80 *   - accept three pointers as arguments:
81 *       - a pointer specified by the special call at the hook insertion point,
82 *       - a pointer specified upon inserting the action into the hook table,
83 *       - a pointer to an isc_result_t value which will be returned by the
84 *         function into which the hook is inserted if the action returns
85 *         NS_HOOK_RETURN.
86 *
87 * In order for a hook action to be called for a given hook, a pointer to that
88 * action function (along with an optional pointer to action-specific data) has
89 * to be inserted into the relevant hook table entry for that hook using an
90 * ns_hook_add() call.  If multiple actions are set up at a single hook point
91 * (e.g. by multiple plugin modules), they are processed in FIFO order, that is
92 * they are performed in the same order in which their relevant ns_hook_add()
93 * calls were issued.  Since the configuration is loaded from a single thread,
94 * this means that multiple actions at a single hook point are determined by
95 * the order in which the relevant plugin modules were declared in the
96 * configuration file(s).  The hook API currently does not support changing
97 * this order.
98 *
99 * As an example, consider the following hypothetical function in query.c:
100 *
101 * ----------------------------------------------------------------------------
102 * static isc_result_t
103 * query_foo(query_ctx_t *qctx) {
104 *     isc_result_t result;
105 *
106 *     CALL_HOOK(NS_QUERY_FOO_BEGIN, qctx);
107 *
108 *     ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY,
109 *                   ISC_LOG_DEBUG(99), "Lorem ipsum dolor sit amet...");
110 *
111 *     result = ISC_R_COMPLETE;
112 *
113 *  cleanup:
114 *     return (result);
115 * }
116 * ----------------------------------------------------------------------------
117 *
118 * and the following hook action:
119 *
120 * ----------------------------------------------------------------------------
121 * static ns_hookresult_t
122 * cause_failure(void *hook_data, void *action_data, isc_result_t *resultp) {
123 *     UNUSED(hook_data);
124 *     UNUSED(action_data);
125 *
126 *     *resultp = ISC_R_FAILURE;
127 *
128 *     return (NS_HOOK_RETURN);
129 * }
130 * ----------------------------------------------------------------------------
131 *
132 * If this hook action was installed in the hook table using:
133 *
134 * ----------------------------------------------------------------------------
135 * const ns_hook_t foo_fail = {
136 *     .action = cause_failure,
137 * };
138 *
139 * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_fail);
140 * ----------------------------------------------------------------------------
141 *
142 * then query_foo() would return ISC_R_FAILURE every time it is called due
143 * to the cause_failure() hook action returning NS_HOOK_RETURN and setting
144 * '*resultp' to ISC_R_FAILURE.  query_foo() would also never log the
145 * "Lorem ipsum dolor sit amet..." message.
146 *
147 * Consider a different hook action:
148 *
149 * ----------------------------------------------------------------------------
150 * static ns_hookresult_t
151 * log_qtype(void *hook_data, void *action_data, isc_result_t *resultp) {
152 *     query_ctx_t *qctx = (query_ctx_t *)hook_data;
153 *     FILE *stream = (FILE *)action_data;
154 *
155 *     UNUSED(resultp);
156 *
157 *     fprintf(stream, "QTYPE=%u\n", qctx->qtype);
158 *
159 *     return (NS_HOOK_CONTINUE);
160 * }
161 * ----------------------------------------------------------------------------
162 *
163 * If this hook action was installed in the hook table instead of
164 * cause_failure(), using:
165 *
166 * ----------------------------------------------------------------------------
167 * const ns_hook_t foo_log_qtype = {
168 *     .action = log_qtype,
169 *     .action_data = stderr,
170 * };
171 *
172 * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_log_qtype);
173 * ----------------------------------------------------------------------------
174 *
175 * then the QTYPE stored in the query context passed to query_foo() would be
176 * logged to stderr upon each call to that function; 'qctx' would be passed to
177 * the hook action in 'hook_data' since it is specified in the CALL_HOOK() call
178 * inside query_foo() while stderr would be passed to the hook action in
179 * 'action_data' since it is specified in the ns_hook_t structure passed to
180 * ns_hook_add().  As the hook action returns NS_HOOK_CONTINUE,
181 * query_foo() would also be logging the "Lorem ipsum dolor sit amet..."
182 * message before returning ISC_R_COMPLETE.
183 */
184
185/*!
186 * Currently-defined hook points. So long as these are unique,
187 * the order in which they are declared is unimportant, but
188 * currently matches the order in which they are referenced in
189 * query.c.
190 */
191typedef enum {
192	/* hookpoints from query.c */
193	NS_QUERY_QCTX_INITIALIZED,
194	NS_QUERY_QCTX_DESTROYED,
195	NS_QUERY_SETUP,
196	NS_QUERY_START_BEGIN,
197	NS_QUERY_LOOKUP_BEGIN,
198	NS_QUERY_RESUME_BEGIN,
199	NS_QUERY_RESUME_RESTORED,
200	NS_QUERY_GOT_ANSWER_BEGIN,
201	NS_QUERY_RESPOND_ANY_BEGIN,
202	NS_QUERY_RESPOND_ANY_FOUND,
203	NS_QUERY_ADDANSWER_BEGIN,
204	NS_QUERY_RESPOND_BEGIN,
205	NS_QUERY_NOTFOUND_BEGIN,
206	NS_QUERY_NOTFOUND_RECURSE,
207	NS_QUERY_PREP_DELEGATION_BEGIN,
208	NS_QUERY_ZONE_DELEGATION_BEGIN,
209	NS_QUERY_DELEGATION_BEGIN,
210	NS_QUERY_DELEGATION_RECURSE_BEGIN,
211	NS_QUERY_NODATA_BEGIN,
212	NS_QUERY_NXDOMAIN_BEGIN,
213	NS_QUERY_NCACHE_BEGIN,
214	NS_QUERY_ZEROTTL_RECURSE,
215	NS_QUERY_CNAME_BEGIN,
216	NS_QUERY_DNAME_BEGIN,
217	NS_QUERY_PREP_RESPONSE_BEGIN,
218	NS_QUERY_DONE_BEGIN,
219	NS_QUERY_DONE_SEND,
220
221	/* XXX other files could be added later */
222
223	NS_HOOKPOINTS_COUNT /* MUST BE LAST */
224} ns_hookpoint_t;
225
226/*
227 * Returned by a hook action to indicate how to proceed after it has
228 * been called: continue processing, or return immediately.
229 */
230typedef enum {
231	NS_HOOK_CONTINUE,
232	NS_HOOK_RETURN,
233} ns_hookresult_t;
234
235typedef ns_hookresult_t (*ns_hook_action_t)(void *arg, void *data,
236					    isc_result_t *resultp);
237
238typedef struct ns_hook {
239	isc_mem_t *	 mctx;
240	ns_hook_action_t action;
241	void *		 action_data;
242	ISC_LINK(struct ns_hook) link;
243} ns_hook_t;
244
245typedef ISC_LIST(ns_hook_t) ns_hooklist_t;
246typedef ns_hooklist_t ns_hooktable_t[NS_HOOKPOINTS_COUNT];
247
248/*%
249 * ns__hook_table is a global hook table, which is used if view->hooktable
250 * is NULL.  It's intended only for use by unit tests.
251 */
252LIBNS_EXTERNAL_DATA extern ns_hooktable_t *ns__hook_table;
253
254/*
255 * Plugin API version
256 *
257 * When the API changes, increment NS_PLUGIN_VERSION. If the
258 * change is backward-compatible (e.g., adding a new function call
259 * but not changing or removing an old one), increment NS_PLUGIN_AGE
260 * as well; if not, set NS_PLUGIN_AGE to 0.
261 */
262#ifndef NS_PLUGIN_VERSION
263#define NS_PLUGIN_VERSION 1
264#define NS_PLUGIN_AGE	  0
265#endif /* ifndef NS_PLUGIN_VERSION */
266
267typedef isc_result_t
268ns_plugin_register_t(const char *parameters, const void *cfg, const char *file,
269		     unsigned long line, isc_mem_t *mctx, isc_log_t *lctx,
270		     void *actx, ns_hooktable_t *hooktable, void **instp);
271/*%<
272 * Called when registering a new plugin.
273 *
274 * 'parameters' contains the plugin configuration text.
275 *
276 * '*instp' will be set to the module instance handle if the function
277 * is successful.
278 *
279 * Returns:
280 *\li	#ISC_R_SUCCESS
281 *\li	#ISC_R_NOMEMORY
282 *\li	Other errors are possible
283 */
284
285typedef void
286ns_plugin_destroy_t(void **instp);
287/*%<
288 * Destroy a plugin instance.
289 *
290 * '*instp' must be set to NULL by the function before it returns.
291 */
292
293typedef isc_result_t
294ns_plugin_check_t(const char *parameters, const void *cfg, const char *file,
295		  unsigned long line, isc_mem_t *mctx, isc_log_t *lctx,
296		  void *actx);
297/*%<
298 * Check the validity of 'parameters'.
299 */
300
301typedef int
302ns_plugin_version_t(void);
303/*%<
304 * Return the API version number a plugin was compiled with.
305 *
306 * If the returned version number is no greater than
307 * NS_PLUGIN_VERSION, and no less than NS_PLUGIN_VERSION - NS_PLUGIN_AGE,
308 * then the module is API-compatible with named.
309 */
310
311/*%
312 * Prototypes for API functions to be defined in each module.
313 */
314ns_plugin_check_t    plugin_check;
315ns_plugin_destroy_t  plugin_destroy;
316ns_plugin_register_t plugin_register;
317ns_plugin_version_t  plugin_version;
318
319isc_result_t
320ns_plugin_expandpath(const char *src, char *dst, size_t dstsize);
321/*%<
322 * Prepare the plugin location to be passed to dlopen() based on the plugin
323 * path or filename found in the configuration file ('src').  Store the result
324 * in 'dst', which is 'dstsize' bytes large.
325 *
326 * On Unix systems, two classes of 'src' are recognized:
327 *
328 *   - If 'src' is an absolute or relative path, it will be copied to 'dst'
329 *     verbatim.
330 *
331 *   - If 'src' is a filename (i.e. does not contain a path separator), the
332 *     path to the directory into which named plugins are installed will be
333 *     prepended to it and the result will be stored in 'dst'.
334 *
335 * On Windows, 'src' is always copied to 'dst' verbatim.
336 *
337 * Returns:
338 *\li	#ISC_R_SUCCESS	Success
339 *\li	#ISC_R_NOSPACE	'dst' is not large enough to hold the output string
340 *\li	Other result	snprintf() returned a negative value
341 */
342
343isc_result_t
344ns_plugin_register(const char *modpath, const char *parameters, const void *cfg,
345		   const char *cfg_file, unsigned long cfg_line,
346		   isc_mem_t *mctx, isc_log_t *lctx, void *actx,
347		   dns_view_t *view);
348/*%<
349 * Load the plugin module specified from the file 'modpath', and
350 * register an instance using 'parameters'.
351 *
352 * 'cfg_file' and 'cfg_line' specify the location of the plugin
353 * declaration in the configuration file.
354 *
355 * 'cfg' and 'actx' are the configuration context and ACL configuration
356 * context, respectively; they are passed as void * here in order to
357 * prevent this library from having a dependency on libisccfg).
358 *
359 * 'instp' will be left pointing to the instance of the plugin
360 * created by the module's plugin_register function.
361 */
362
363isc_result_t
364ns_plugin_check(const char *modpath, const char *parameters, const void *cfg,
365		const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx,
366		isc_log_t *lctx, void *actx);
367/*%<
368 * Open the plugin module at 'modpath' and check the validity of
369 * 'parameters', logging any errors or warnings found, then
370 * close it without configuring it.
371 */
372
373void
374ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp);
375/*%<
376 * Create and initialize a plugin list.
377 */
378
379void
380ns_plugins_free(isc_mem_t *mctx, void **listp);
381/*%<
382 * Close each plugin module in a plugin list, then free the list object.
383 */
384
385void
386ns_hooktable_free(isc_mem_t *mctx, void **tablep);
387/*%<
388 * Free a hook table.
389 */
390
391void
392ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx,
393	    ns_hookpoint_t hookpoint, const ns_hook_t *hook);
394/*%<
395 * Allocate (using memory context 'mctx') a copy of the 'hook' structure
396 * describing a hook action and append it to the list of hooks at 'hookpoint'
397 * in 'hooktable'.
398 *
399 * Requires:
400 *\li 'hooktable' is not NULL
401 *
402 *\li 'mctx' is not NULL
403 *
404 *\li 'hookpoint' is less than NS_QUERY_HOOKS_COUNT
405 *
406 *\li 'hook' is not NULL
407 */
408
409void
410ns_hooktable_init(ns_hooktable_t *hooktable);
411/*%<
412 * Initialize a hook table.
413 */
414
415isc_result_t
416ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep);
417/*%<
418 * Allocate and initialize a hook table.
419 */
420#endif /* NS_HOOKS_H */
421