add zone-specific plugin instance

The zone object now has its own hooktable and plugins, which are
initialized during zone initialization.
This commit is contained in:
Colin Vidal 2025-05-27 11:46:21 +02:00
parent 0247506ddc
commit 5893770cd9
9 changed files with 286 additions and 70 deletions

View file

@ -31,6 +31,8 @@
#include <dns/stats.h>
#include <dns/types.h>
#include <isccfg/cfg.h>
#include <ns/interfacemgr.h>
#include <ns/server.h>
#include <ns/stats.h>
@ -407,3 +409,13 @@ named_server_togglememprof(isc_lex_t *lex);
*/
const char *
named_server_getmemprof(void);
/*%
* Helper callback function to register a plugin, called via
* cfg_pluginlist_foreach(). Note: if registration of any
* plugin fails, subsequent ones will not be attempted.
*/
isc_result_t
named_register_one_plugin(const cfg_obj_t *config, const cfg_obj_t *obj,
const char *plugin_path, const char *parameters,
void *callback_data);

View file

@ -81,3 +81,16 @@ named_zone_templateopts(const cfg_obj_t *config, const cfg_obj_t *zoptions);
* the template options and return them. If no such template is found,
* return NULL.
*/
isc_result_t
named_zone_loadplugins(dns_zone_t *zone, const cfg_obj_t *config,
const cfg_obj_t *zoptions);
/*%<
* Load plugins that should run for this specific zone. Take care of cleaning
* up any pre-existing plugins first, if the zone is re-used.
*
* Require:
* \li 'zone' to be a valid zone
* \li 'config' to be a valid named.conf configuration tree
* \li 'zoptions' to be a valid zone configuration tree
*/

View file

@ -3712,18 +3712,13 @@ create_mapped_acl(void) {
return result;
}
/*%
* A callback for the cfg_pluginlist_foreach() call in configure_view() below.
* If registering any plugin fails, registering subsequent ones is not
* attempted.
*/
static isc_result_t
register_one_plugin(const cfg_obj_t *config, const cfg_obj_t *obj,
const char *plugin_path, const char *parameters,
void *callback_data) {
dns_view_t *view = callback_data;
isc_result_t
named_register_one_plugin(const cfg_obj_t *config, const cfg_obj_t *obj,
const char *plugin_path, const char *parameters,
void *callback_data) {
char full_path[PATH_MAX];
isc_result_t result;
ns_hook_data_t *hookdata = callback_data;
result = ns_plugin_expandpath(plugin_path, full_path,
sizeof(full_path));
@ -3738,7 +3733,7 @@ register_one_plugin(const cfg_obj_t *config, const cfg_obj_t *obj,
result = ns_plugin_register(full_path, parameters, config,
cfg_obj_file(obj), cfg_obj_line(obj),
isc_g_mctx, named_g_aclconfctx, view);
isc_g_mctx, named_g_aclconfctx, hookdata);
if (result != ISC_R_SUCCESS) {
isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER,
ISC_LOG_ERROR,
@ -5422,16 +5417,20 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
}
if (plugin_list != NULL) {
ns_hook_data_t hookdata = {};
INSIST(view->hooktable == NULL);
CHECK(ns_hooktable_create(view->mctx,
(ns_hooktable_t **)&view->hooktable));
ns_hooktable_create(view->mctx, &hookdata.hooktable);
view->hooktable = hookdata.hooktable;
view->hooktable_free = ns_hooktable_free;
ns_plugins_create(view->mctx, (ns_plugins_t **)&view->plugins);
ns_plugins_create(view->mctx, &hookdata.plugins);
view->plugins = hookdata.plugins;
view->plugins_free = ns_plugins_free;
CHECK(cfg_pluginlist_foreach(config, plugin_list,
register_one_plugin, view));
named_register_one_plugin,
&hookdata));
}
/*
@ -6661,6 +6660,8 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
dns_zone_rekey(zone, fullsign, false);
}
result = named_zone_loadplugins(zone, config, zoptions);
cleanup:
if (zone != NULL) {
dns_zone_detach(&zone);

View file

@ -43,6 +43,7 @@
#include <dns/zone.h>
#include <ns/client.h>
#include <ns/hooks.h>
#include <named/config.h>
#include <named/globals.h>
@ -2097,3 +2098,42 @@ named_zone_templateopts(const cfg_obj_t *config, const cfg_obj_t *zoptions) {
return NULL;
}
isc_result_t
named_zone_loadplugins(dns_zone_t *zone, const cfg_obj_t *config,
const cfg_obj_t *zoptions) {
isc_result_t result = ISC_R_SUCCESS;
const cfg_obj_t *pluginlist = NULL;
/*
* If the zone previously had any loaded plugins, unload them:
* they might not be needed anymore, or they might hold state
* that's now obsolete.
*/
dns_zone_unloadplugins(zone);
/*
* Load zone-specific plugin instances.
*/
if (zoptions != NULL) {
(void)cfg_map_get(zoptions, "plugin", &pluginlist);
}
if (pluginlist != NULL) {
ns_hook_data_t hookdata = {};
isc_mem_t *zmctx = dns_zone_getmctx(zone);
ns_hooktable_create(zmctx, &hookdata.hooktable);
dns_zone_sethooktable(zone, hookdata.hooktable,
ns_hooktable_free);
ns_plugins_create(zmctx, &hookdata.plugins);
dns_zone_setplugins(zone, hookdata.plugins, ns_plugins_free);
result = cfg_pluginlist_foreach(config, pluginlist,
named_register_one_plugin,
&hookdata);
}
return result;
}

View file

@ -2754,6 +2754,49 @@ dns_zone_getkeystores(dns_zone_t *zone);
* initialized.
*/
void *
dns_zone_gethooktable(dns_zone_t *zone);
/**<
* Returns the zone hooktable
*
* Requires:
* \li 'zone' to be a valid zone.
*/
void
dns_zone_sethooktable(dns_zone_t *zone, void *hooktable,
void (*hooktable_free)(isc_mem_t *, void **));
/**<
* Initialize zone hooktable and free callback
*
* Requires:
* \li 'zone' to be a valid zone.
* \li 'hooktable' to be initialized.
* \li 'hooktable_free' to be valid.
*/
void
dns_zone_setplugins(dns_zone_t *zone, void *plugins,
void (*plugins_free)(isc_mem_t *, void **));
/**<
* Initialize zone plugins owning list and free callback
*
* Requires:
* \li 'zone' to be a valid zone.
* \li 'plugins' to be initialized.
* \li 'plugins_free' to be valid.
*/
void
dns_zone_unloadplugins(dns_zone_t *zone);
/**<
* Unload all plugins attached to this zone, and free the hooktable as well as
* the plugins list.
*
* Requires:
* \li 'zone' to be a valid zone.
*/
#if DNS_ZONE_TRACE
#define dns_zone_ref(ptr) dns_zone__ref(ptr, __func__, __FILE__, __LINE__)
#define dns_zone_unref(ptr) dns_zone__unref(ptr, __func__, __FILE__, __LINE__)

View file

@ -530,6 +530,14 @@ struct dns_zone {
*/
dns_skr_t *skr;
dns_skrbundle_t *skrbundle;
/*
* Plugin-related data structures
*/
void *plugins;
void (*plugins_free)(isc_mem_t *, void **);
void *hooktable;
void (*hooktable_free)(isc_mem_t *, void **);
};
#define zonediff_init(z, d) \
@ -1376,6 +1384,7 @@ zone_free(dns_zone_t *zone) {
if (zone->gluecachestats != NULL) {
isc_stats_detach(&zone->gluecachestats);
}
dns_zone_unloadplugins(zone);
/* last stuff */
ZONEDB_DESTROYLOCK(&zone->dblock);
@ -24864,3 +24873,48 @@ dns_zone_setrad(dns_zone_t *zone, dns_name_t *name) {
}
rcu_read_unlock();
}
void *
dns_zone_gethooktable(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return zone->hooktable;
}
void
dns_zone_sethooktable(dns_zone_t *zone, void *hooktable,
void (*hooktable_free)(isc_mem_t *, void **)) {
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(zone->hooktable == NULL);
REQUIRE(zone->hooktable_free == NULL);
zone->hooktable = hooktable;
zone->hooktable_free = hooktable_free;
}
void
dns_zone_setplugins(dns_zone_t *zone, void *plugins,
void (*plugins_free)(isc_mem_t *, void **)) {
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(zone->plugins == NULL);
REQUIRE(zone->plugins_free == NULL);
zone->plugins = plugins;
zone->plugins_free = plugins_free;
}
void
dns_zone_unloadplugins(dns_zone_t *zone) {
if (zone->hooktable != NULL) {
INSIST(zone->hooktable_free);
zone->hooktable_free(zone->mctx, &zone->hooktable);
INSIST(zone->hooktable == NULL);
zone->hooktable_free = NULL;
}
if (zone->plugins != NULL) {
INSIST(zone->plugins_free);
zone->plugins_free(zone->mctx, &zone->plugins);
INSIST(zone->plugins == NULL);
zone->plugins_free = NULL;
}
}

View file

@ -224,12 +224,12 @@ unload_plugin(ns_plugin_t **pluginp) {
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, void *actx, dns_view_t *view) {
isc_mem_t *mctx, void *actx, ns_hook_data_t *hookdata) {
isc_result_t result;
ns_plugin_t *plugin = NULL;
REQUIRE(mctx != NULL);
REQUIRE(view != NULL);
REQUIRE(hookdata != NULL);
isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
"loading plugin '%s'", modpath);
@ -240,9 +240,9 @@ ns_plugin_register(const char *modpath, const char *parameters, const void *cfg,
"registering plugin '%s'", modpath);
CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line, mctx,
actx, view->hooktable, &plugin->inst));
actx, hookdata->hooktable, &plugin->inst));
ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link);
ISC_LIST_APPEND(*hookdata->plugins, plugin, link);
cleanup:
if (result != ISC_R_SUCCESS && plugin != NULL) {
@ -281,7 +281,7 @@ ns_hooktable_init(ns_hooktable_t *hooktable) {
}
}
isc_result_t
void
ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) {
ns_hooktable_t *hooktable = NULL;
@ -292,8 +292,6 @@ ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) {
ns_hooktable_init(hooktable);
*tablep = hooktable;
return ISC_R_SUCCESS;
}
void

View file

@ -447,6 +447,15 @@ typedef struct ns_hook_resume {
void *arg; /* argument to pass to the callback */
} ns_hook_resume_t;
/*
* Wrapper struct holding hook/plugins owning data structures used and owned by
* zones and views having registered plugins.
*/
typedef struct ns_hook_data {
ns_hooktable_t *hooktable;
ns_plugins_t *plugins;
} ns_hook_data_t;
/*
* Plugin API version
*
@ -535,7 +544,7 @@ ns_plugin_expandpath(const char *src, char *dst, size_t dstsize);
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, void *actx, dns_view_t *view);
isc_mem_t *mctx, void *actx, ns_hook_data_t *hookdata);
/*%<
* Load the plugin module specified from the file 'modpath', and
* register an instance using 'parameters'.
@ -603,7 +612,7 @@ ns_hooktable_init(ns_hooktable_t *hooktable);
* Initialize a hook table.
*/
isc_result_t
void
ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep);
/*%<
* Allocate and initialize a hook table.

View file

@ -259,18 +259,80 @@ acquire_recursionquota(ns_client_t *client);
static void
release_recursionquota(ns_client_t *client);
/*
* Return the hooktable in use with 'qctx', or if there isn't one
* set, return the default hooktable.
*/
static ns_hooktable_t *
get_hooktab(query_ctx_t *qctx) {
if (qctx == NULL || qctx->view == NULL || qctx->view->hooktable == NULL)
{
return ns__hook_table;
static ns_hookresult_t
ns__query_callhook(uint8_t id, query_ctx_t *qctx, isc_result_t *result,
ns_hooktable_t *hooktab) {
isc_result_t hookresult = *result;
ns_hook_t *hook;
if (hooktab == NULL) {
return NS_HOOK_CONTINUE;
}
return qctx->view->hooktable;
hook = ISC_LIST_HEAD((*hooktab)[id]);
while (hook != NULL) {
ns_hook_action_t func = hook->action;
void *data = hook->action_data;
INSIST(func != NULL);
switch (func(qctx, data, &hookresult)) {
case NS_HOOK_CONTINUE:
hook = ISC_LIST_NEXT(hook, link);
break;
case NS_HOOK_RETURN:
*result = hookresult;
return NS_HOOK_RETURN;
default:
UNREACHABLE();
}
}
return NS_HOOK_CONTINUE;
}
static void
ns__query_callhook_noreturn(uint8_t id, query_ctx_t *qctx,
ns_hooktable_t *hooktab) {
ns_hook_t *hook;
isc_result_t dummyres;
if (hooktab == NULL) {
return;
}
hook = ISC_LIST_HEAD((*hooktab)[id]);
while (hook != NULL) {
ns_hook_action_t func = hook->action;
void *data = hook->action_data;
INSIST(func != NULL);
func(qctx, data, &dummyres);
hook = ISC_LIST_NEXT(hook, link);
}
}
static ns_hooktable_t *
ns__zone_hooktab(query_ctx_t *qctx) {
ns_hooktable_t *hooktab = NULL;
if (qctx && qctx->zone) {
hooktab = dns_zone_gethooktable(qctx->zone);
}
return hooktab;
}
static ns_hooktable_t *
ns__view_hooktab(query_ctx_t *qctx) {
ns_hooktable_t *hooktab = NULL;
if (qctx && qctx->view) {
hooktab = qctx->view->hooktable;
}
return hooktab;
}
/*
@ -283,28 +345,22 @@ get_hooktab(query_ctx_t *qctx) {
* is a macro instead of a static function; it needs to be able to use
* 'goto cleanup' regardless of the return value.)
*/
#define CALL_HOOK(_id, _qctx) \
do { \
isc_result_t _res = result; \
ns_hooktable_t *_tab = get_hooktab(_qctx); \
ns_hook_t *_hook; \
_hook = ISC_LIST_HEAD((*_tab)[_id]); \
while (_hook != NULL) { \
ns_hook_action_t _func = _hook->action; \
void *_data = _hook->action_data; \
INSIST(_func != NULL); \
switch (_func(_qctx, _data, &_res)) { \
case NS_HOOK_CONTINUE: \
_hook = ISC_LIST_NEXT(_hook, link); \
break; \
case NS_HOOK_RETURN: \
result = _res; \
goto cleanup; \
default: \
UNREACHABLE(); \
} \
} \
} while (false)
#define CALL_HOOK(_id, _qctx) \
if (ns__query_callhook(_id, _qctx, &result, \
ns__zone_hooktab(_qctx)) == NS_HOOK_RETURN) \
{ \
goto cleanup; \
} \
if (ns__query_callhook(_id, _qctx, &result, \
ns__view_hooktab(_qctx)) == NS_HOOK_RETURN) \
{ \
goto cleanup; \
} \
if (ns__query_callhook(_id, _qctx, &result, ns__hook_table) == \
NS_HOOK_RETURN) \
{ \
goto cleanup; \
}
/*
* Call the specified hook function in every configured module that
@ -315,20 +371,10 @@ get_hooktab(query_ctx_t *qctx) {
* (This could be implemented as a static void function, but is left as a
* macro for symmetry with CALL_HOOK above.)
*/
#define CALL_HOOK_NORETURN(_id, _qctx) \
do { \
isc_result_t _res; \
ns_hooktable_t *_tab = get_hooktab(_qctx); \
ns_hook_t *_hook; \
_hook = ISC_LIST_HEAD((*_tab)[_id]); \
while (_hook != NULL) { \
ns_hook_action_t _func = _hook->action; \
void *_data = _hook->action_data; \
INSIST(_func != NULL); \
_func(_qctx, _data, &_res); \
_hook = ISC_LIST_NEXT(_hook, link); \
} \
} while (false)
#define CALL_HOOK_NORETURN(_id, _qctx) \
ns__query_callhook_noreturn(_id, _qctx, ns__zone_hooktab(_qctx)); \
ns__query_callhook_noreturn(_id, _qctx, ns__view_hooktab(_qctx)); \
ns__query_callhook_noreturn(_id, _qctx, ns__hook_table);
/*
* The functions defined below implement the query logic that previously lived