From be03ed6520227426d2c38616b4408cc340dca4da Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Mon, 21 Jul 2025 14:38:30 +0200 Subject: [PATCH 1/5] Export plugin extension in config.h Dynamically loadable libraries all use the `.so` extension on BIND9-supported platforms, except for macOS. Export the dynamic library extension of the current build platform in the generated `config.h` file, in order to let the plugin code building plugin path based on a simple plugin name. (which then would be platform-independent) --- meson.build | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7697be86d4..c4316aa05d 100644 --- a/meson.build +++ b/meson.build @@ -215,7 +215,8 @@ if host_machine.cpu_family() == 'x86' ) endif -if host_machine.system() == 'darwin' +isdarwin = host_machine.system() == 'darwin' +if isdarwin add_project_arguments( cc.get_supported_arguments( '-Wno-deprecated-declarations', # For GSS.Framework @@ -279,6 +280,13 @@ config.set_quoted('RNDC_CONFFILE', sysconfdir / 'rndc.conf') config.set_quoted('RNDC_KEYFILE', sysconfdir / 'rndc.key') config.set_quoted('NAMED_PLUGINDIR', libdir / 'bind') +if isdarwin + # Plugin extensions - macOS is the only specific case + config.set_quoted('NAMED_PLUGINEXT', '.dylib') +else + config.set_quoted('NAMED_PLUGINEXT', '.so') +endif + config.set_quoted('NAMED_LOCALSTATEDIR', localstatedir) config.set_quoted('NAMED_SYSCONFDIR', sysconfdir) config.set_quoted('NAMED_CONFFILE', sysconfdir / 'named.conf') From 7747ac8aed685bc47179d0e929f0c34e69c90cff Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Mon, 21 Jul 2025 15:06:11 +0200 Subject: [PATCH 2/5] plugin expand path automatically adds extension If a plugin is configured without the extension, `ns_plugin_expandpath()` automatically take cares of appending the suffix to the path. The way it works is by checking if a file exists at the expanded path. If it doesn't, it assumes the plugin path (or name) doesn't have the extension and append the extension (which is platform-specific) to the actual path. --- lib/ns/hooks.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/ns/hooks.c b/lib/ns/hooks.c index d11c4c72a7..fd45b7ed03 100644 --- a/lib/ns/hooks.c +++ b/lib/ns/hooks.c @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -54,9 +55,10 @@ struct ns_plugin { static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT]; ns_hooktable_t *ns__hook_table = &default_hooktable; -isc_result_t -ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) { +static isc_result_t +plugin_expandpath(const char *src, char *dst, size_t dstsize, bool appendext) { int result; + const char *ext = appendext ? NAMED_PLUGINEXT : ""; /* * On Unix systems, differentiate between paths and filenames. @@ -65,12 +67,13 @@ ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) { /* * 'src' is an absolute or relative path. Copy it verbatim. */ - result = snprintf(dst, dstsize, "%s", src); + result = snprintf(dst, dstsize, "%s%s", src, ext); } else { /* * 'src' is a filename. Prepend default plugin directory path. */ - result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src); + result = snprintf(dst, dstsize, "%s/%s%s", NAMED_PLUGINDIR, src, + ext); } if (result < 0) { @@ -82,6 +85,22 @@ ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) { } } +isc_result_t +ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) { + isc_result_t result; + + result = plugin_expandpath(src, dst, dstsize, false); + if (result != ISC_R_SUCCESS) { + return result; + } + + if (isc_file_exists(dst) == false) { + result = plugin_expandpath(src, dst, dstsize, true); + } + + return result; +} + static isc_result_t load_symbol(uv_lib_t *handle, const char *modpath, const char *symbol_name, void **symbolp) { From cdbaddb524193227b028de0a58eb806bccea8952 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Mon, 21 Jul 2025 15:58:31 +0200 Subject: [PATCH 3/5] ns_plugin_expandpath() auto-extension unit-tests Update existing ns_plugin_expandpath() unit test to cover the logic appending the plugin extension if missing. Because ns_plugin_expandpath() now relies on isc_file_exists() API, a mocked version has been added in tests/ns/plugin_test.c and relies on the linker --wrap mechanism. --- tests/ns/meson.build | 7 +++++ tests/ns/plugin_test.c | 62 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/tests/ns/meson.build b/tests/ns/meson.build index 7d4e2e30fa..ca0270ca37 100644 --- a/tests/ns/meson.build +++ b/tests/ns/meson.build @@ -14,6 +14,12 @@ foreach unit : [ 'plugin', 'query', ] + linkargs = '' + if unit == 'plugin' + linkargs = [ + '-Wl,--wrap=isc_file_exists', + ] + endif test_bin = executable( unit, files(f'@unit@_test.c', 'netmgr_wrap.c'), @@ -31,6 +37,7 @@ foreach unit : [ cmocka_dep, nghttp2_dep, ], + link_args: linkargs, ) test( diff --git a/tests/ns/plugin_test.c b/tests/ns/plugin_test.c index a7427c4c85..1de3e7c989 100644 --- a/tests/ns/plugin_test.c +++ b/tests/ns/plugin_test.c @@ -34,7 +34,16 @@ #include -#include +#include "../ns/hooks.c" + +bool +__wrap_isc_file_exists(const char *pathname); + +bool +__wrap_isc_file_exists(const char *pathname) { + UNUSED(pathname); + return mock(); +} #include @@ -43,8 +52,8 @@ */ typedef struct { const ns_test_id_t id; /* libns test identifier */ - const char *input; /* source string - plugin name or path - * */ + const char *input; /* source string - plugin name or path */ + bool exists; /* return of mocked isc_file_exists() */ size_t output_size; /* size of target char array to * allocate */ isc_result_t result; /* expected return value */ @@ -65,6 +74,10 @@ run_full_path_test(const ns_plugin_expandpath_test_params_t *test, REQUIRE(test->input != NULL); REQUIRE(test->result != ISC_R_SUCCESS || test->output != NULL); + if (test->result == ISC_R_SUCCESS) { + will_return(__wrap_isc_file_exists, test->exists); + } + /* * Prepare a target buffer of given size. Store it in 'state' so that * it can get cleaned up by _teardown() if the test fails. @@ -108,6 +121,7 @@ ISC_RUN_TEST_IMPL(ns_plugin_expandpath) { { NS_TEST_ID("correct use with an absolute path"), .input = "/usr/lib/named/foo.so", + .exists = true, .output_size = PATH_MAX, .result = ISC_R_SUCCESS, .output = "/usr/lib/named/foo.so", @@ -115,6 +129,7 @@ ISC_RUN_TEST_IMPL(ns_plugin_expandpath) { { NS_TEST_ID("correct use with a relative path"), .input = "../../foo.so", + .exists = true, .output_size = PATH_MAX, .result = ISC_R_SUCCESS, .output = "../../foo.so", @@ -122,31 +137,72 @@ ISC_RUN_TEST_IMPL(ns_plugin_expandpath) { { NS_TEST_ID("correct use with a filename"), .input = "foo.so", + .exists = true, .output_size = PATH_MAX, .result = ISC_R_SUCCESS, .output = NAMED_PLUGINDIR "/foo.so", }, + { + NS_TEST_ID("correct use with an absolute path and no " + "extension"), + .input = "/usr/lib/named/foo", + .exists = false, + .output_size = PATH_MAX, + .result = ISC_R_SUCCESS, + .output = "/usr/lib/named/foo.so", + }, + { + NS_TEST_ID("correct use with a relative path and no " + "extension"), + .input = "../../foo", + .exists = false, + .output_size = PATH_MAX, + .result = ISC_R_SUCCESS, + .output = "../../foo.so", + }, + { + NS_TEST_ID("correct use with a filename and no " + "extension"), + .input = "foo", + .exists = false, + .output_size = PATH_MAX, + .result = ISC_R_SUCCESS, + .output = NAMED_PLUGINDIR "/foo.so", + }, + { + NS_TEST_ID("correct use with a filename and no " + "extension but a name with dots"), + .input = "foo.bar", + .exists = false, + .output_size = PATH_MAX, + .result = ISC_R_SUCCESS, + .output = NAMED_PLUGINDIR "/foo.bar.so", + }, { NS_TEST_ID("no space at all in target buffer"), .input = "/usr/lib/named/foo.so", + .exists = true, .output_size = 0, .result = ISC_R_NOSPACE, }, { NS_TEST_ID("target buffer too small to fit input"), .input = "/usr/lib/named/foo.so", + .exists = true, .output_size = 1, .result = ISC_R_NOSPACE, }, { NS_TEST_ID("target buffer too small to fit NULL byte"), .input = "/foo.so", + .exists = true, .output_size = 7, .result = ISC_R_NOSPACE, }, { NS_TEST_ID("target buffer too small to fit full path"), .input = "foo.so", + .exists = true, .output_size = 7, .result = ISC_R_NOSPACE, }, From b0061843b77f7cd96022bd9531853b54fd933dc3 Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Tue, 22 Jul 2025 14:43:26 +0200 Subject: [PATCH 4/5] update test_hooks system tests Add the case where the plugin name is provided without the extension in the test_hooks system tests. --- bin/tests/system/hooks/ns1/named.conf.j2 | 6 +++++- .../hooks/{tests_async_plugin.py => tests_hooks.py} | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) rename bin/tests/system/hooks/{tests_async_plugin.py => tests_hooks.py} (72%) diff --git a/bin/tests/system/hooks/ns1/named.conf.j2 b/bin/tests/system/hooks/ns1/named.conf.j2 index 0cc4387cf1..905028ce07 100644 --- a/bin/tests/system/hooks/ns1/named.conf.j2 +++ b/bin/tests/system/hooks/ns1/named.conf.j2 @@ -10,6 +10,7 @@ * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ +{% set noextension = noextension | default(False) %} options { query-source address 10.53.0.1; @@ -24,8 +25,11 @@ options { minimal-responses no; }; - +{% if noextension %} +plugin query "@TOP_BUILDDIR@/testlib-driver-async"; +{% else %} plugin query "@TOP_BUILDDIR@/testlib-driver-async.@DYLIB@"; +{% endif %} key rndc_key { secret "1234abcd8765"; diff --git a/bin/tests/system/hooks/tests_async_plugin.py b/bin/tests/system/hooks/tests_hooks.py similarity index 72% rename from bin/tests/system/hooks/tests_async_plugin.py rename to bin/tests/system/hooks/tests_hooks.py index ac89c85ac0..b5391ee46a 100644 --- a/bin/tests/system/hooks/tests_async_plugin.py +++ b/bin/tests/system/hooks/tests_hooks.py @@ -16,8 +16,16 @@ pytest.importorskip("dns") import dns.message -def test_async_hook(): +def test_hooks(): msg = dns.message.make_query("example.com.", "A") res = isctest.query.udp(msg, "10.53.0.1") # the test-async plugin changes the status of any positive answer to NOTIMP isctest.check.notimp(res) + + +def test_hooks_noextension(ns1, templates): + templates.render("ns1/named.conf", {"noextension": True}) + with ns1.watch_log_from_here() as watcher: + ns1.rndc("reload") + watcher.wait_for_line("all zones loaded") + test_hooks() From 284806029a2dae7d0277cbeb5461ef1d6ace71dd Mon Sep 17 00:00:00 2001 From: Colin Vidal Date: Thu, 24 Jul 2025 10:51:32 +0200 Subject: [PATCH 5/5] update ARM plugin documentation Update the ARM documentation of plugin usage as the extension in the plugin library path is now optional. --- doc/arm/plugins.inc.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/arm/plugins.inc.rst b/doc/arm/plugins.inc.rst index 7e63995f3a..4a89efdb9e 100644 --- a/doc/arm/plugins.inc.rst +++ b/doc/arm/plugins.inc.rst @@ -47,8 +47,10 @@ A plugin is configured with the :any:`plugin` statement in :iscman:`named.conf`: }; -In this example, file ``library.so`` is the plugin library. ``query`` -indicates that this is a query plugin. +In this example, ``query`` indicates that this is a query plugin, +and ``library.so`` is the name of the plugin library. Note that the +library file extension (in this case, ``.so``) is optional, and can +be omitted. Multiple :any:`plugin` statements can be specified, to load different plugins or multiple instances of the same plugin.