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