Hi,For the first question about being practical, I took a stab at enabling dynlib support in the existing build (diff attached in unbound-dynlib.debdiff). Because of the split unbound library and unbound daemon build, and because building dynlib modules requires more header files (and symbols) than just what is exported, the packaging gets a bit messy.
The shared library can't be used (without some renaming taking place) because it doesn't expose all of the necessary symbols; if `--enable-allsymbols` is used for the build, then that effectively breaks the library-only and library+daemon build, because they are currently built with different features, which means the libraries will have different code compiled in. This leaves using a static library. However, the static library build from the daemon build must be used, because otherwise one of the structures has a difference in the number of fields (because of the different featuresets), and this would either result in unexpected behavior or a segfault. Also, PIC would need to be enabled for the static build.
In addition to the above, more header files from unbound would need to be packaged. In the debdiff, I've included the header files from 3 directories that might be used for dynlib modules.
As for an example module, unbound does have an example module at https://github.com/NLnetLabs/unbound/blob/master/dynlibmod/examples/helloworld.c. I've attached a slightly modified version here where the include paths are corrected. This can be compiled with `gcc -I /usr/include/unbound -shared -Wall -Werror -fPIC -o libhelloworld.so helloworld.c -fPIC -l:libunbound.a` after installing the build with the debdiff along with the private-dev package.
I personally am looking at writing a Rust library (with bindgen generating the C <-> Rust translation code) to log DNS requests into a DB. It appears to be able to do the basics (get loaded by unbound, parse a DNS query), so it seems to be partially working at least.
On 2/28/26 22:36, Michael Tokarev wrote:
Control: tag -1 + moreinfo On 01.03.2026 03:18, Saikrishna Arcot wrote: []It would be nice if unbound could be compiled with `--with- dynlibmodule`. This would allow users to compile C/C++ module that can be loaded and used by unbound. Unbound already has support for Python modules.Hi! Thank you for the report and for the request. Is this a practical wish? -- do you have an example module in mind? It'd be nice if there's some module I can try/test when enabling this feature. Thanks, /mjt
-- Saikrishna Arcot
diff --git a/debian/control b/debian/control
index 709930721..145e1a185 100644
--- a/debian/control
+++ b/debian/control
@@ -48,6 +48,21 @@ Description: static library, header files, and docs for
libunbound
hostnames to IP addresses and back and obtain other information from the
DNS. Cryptographic validation of results is performed with DNSSEC.
+Package: libunbound-private-dev
+Section: libdevel
+Architecture: any
+Depends:
+ libunbound8 (= ${binary:Version}),
+ ${misc:Depends},
+Multi-Arch: same
+Description: private header files for libunbound
+ Private header files for libunbound. This will be useful for compiling dynlib
+ modules.
+ .
+ libunbound performs and validates DNS lookups; it can be used to convert
+ hostnames to IP addresses and back and obtain other information from the
+ DNS. Cryptographic validation of results is performed with DNSSEC.
+
Package: libunbound8
Section: libs
Architecture: any
diff --git a/debian/rules b/debian/rules
index 9be9296b3..3a076fb1b 100755
--- a/debian/rules
+++ b/debian/rules
@@ -22,6 +22,8 @@ CONFIGURE_ARGS = \
--with-pidfile=/run/unbound.pid \
--with-libevent \
--enable-tfo-client \
+ --with-dynlibmodule \
+ --enable-pic=yes \
# this is used by unbound-host and unbound-anchor
CONFIGURE_ARGS += --with-rootkey-file=/usr/share/dns/root.key
@@ -102,11 +104,11 @@ override_dh_auto_install: override_dh_auto_build
$(if ${libonly},, ${MAKE} -Cb/unbound install
DESTDIR=${CURDIR}/debian/tmp)
# this overrides the library (static & shared), headers and the manpages
${MAKE} -Cb/libunbound install-lib DESTDIR=${CURDIR}/debian/tmp
-# as of 1.23, ./configure does not honour --disable-static
- rm -f debian/tmp/usr/lib/${DEB_HOST_MULTIARCH}/libunbound.a
-# drop static-build deps from the .pc file
- sed -r -i '/^(Libs|Requires)\.private/D' \
- debian/tmp/usr/lib/${DEB_HOST_MULTIARCH}/pkgconfig/libunbound.pc
+ # override with the version of libunbound that was compiled for the
daemon
+ install -Dp -m 0644 b/unbound/.libs/libunbound.a
debian/tmp/usr/lib/${DEB_HOST_MULTIARCH}/libunbound.a
+ install -m 0755 -d debian/libunbound-private-dev/usr/include/unbound
+ find util services sldns dynlibmod -name '*.h' -exec install -Dp -m
0644 {} debian/libunbound-private-dev/usr/include/unbound/{} \;
+ install -Dp -m 0644 b/unbound/config.h
debian/libunbound-private-dev/usr/include/unbound
ifeq (,${libonly})
install -Dp -m 0644 debian/apparmor-profile
debian/unbound/etc/apparmor.d/usr.sbin.unbound
/**
* \file
*
* This is an example to show how dynamic libraries can be made to work with
* unbound. To build a .so file simply run:
* gcc -I../.. -shared -Wall -Werror -fpic -o helloworld.so helloworld.c
* And to build for windows, first make unbound with the --with-dynlibmod
* switch, then use this command:
* x86_64-w64-mingw32-gcc -m64 -I../.. -shared -Wall -Werror -fpic
* -o helloworld.dll helloworld.c -L../.. -l:libunbound.dll.a
* to cross-compile a 64-bit Windows DLL. The libunbound.dll.a is produced
* by the compile step that makes unbound.exe and allows the dynlib dll to
* access definitions in unbound.exe.
*/
#include <stddef.h>
#include <stdint.h>
#include "config.h"
#include "util/module.h"
#include "sldns/parseutil.h"
#include "dynlibmod/dynlibmod.h"
/* Declare the EXPORT macro that expands to exporting the symbol for DLLs when
* compiling for Windows. All procedures marked with EXPORT in this example are
* called directly by the dynlib module and must be present for the module to
* load correctly. */
#ifdef HAVE_WINDOWS_H
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
/* Forward declare a callback, implemented at the bottom of this file */
int reply_callback(struct query_info* qinfo,
struct module_qstate* qstate, struct reply_info* rep, int rcode,
struct edns_data* edns, struct edns_option** opt_list_out,
struct comm_reply* repinfo, struct regional* region,
struct timeval* start_time, int id, void* callback);
/* Init is called when the module is first loaded. It should be used to set up
* the environment for this module and do any other initialisation required. */
EXPORT int init(struct module_env* env, int id) {
log_info("dynlib: hello world from init");
struct dynlibmod_env* de = (struct dynlibmod_env*) env->modinfo[id];
de->inplace_cb_register_wrapped(&reply_callback,
inplace_cb_reply,
NULL, env, id);
struct dynlibmod_env* local_env = env->modinfo[id];
local_env->dyn_env = NULL;
return 1;
}
/* Deinit is run as the program is shutting down. It should be used to clean up
* the environment and any left over data. */
EXPORT void deinit(struct module_env* env, int id) {
log_info("dynlib: hello world from deinit");
struct dynlibmod_env* de = (struct dynlibmod_env*) env->modinfo[id];
de->inplace_cb_delete_wrapped(env, inplace_cb_reply, id);
if (de->dyn_env != NULL) free(de->dyn_env);
}
/* Operate is called every time a query passes by this module. The event can be
* used to determine which direction in the module chain it came from. */
EXPORT void operate(struct module_qstate* qstate, enum module_ev event,
int id, struct outbound_entry* entry) {
log_info("dynlib: hello world from operate");
log_info("dynlib: incoming query: %s %s(%d) %s(%d)",
qstate->qinfo.qname,
sldns_lookup_by_id(sldns_rr_classes, qstate->qinfo.qclass)->name,
qstate->qinfo.qclass,
sldns_rr_descript(qstate->qinfo.qtype)->_name,
qstate->qinfo.qtype);
if (event == module_event_new || event == module_event_pass) {
qstate->ext_state[id] = module_wait_module;
struct dynlibmod_env* env = qstate->env->modinfo[id];
if (env->dyn_env == NULL) {
env->dyn_env = calloc(3, sizeof(int));
((int *)env->dyn_env)[0] = 42;
((int *)env->dyn_env)[1] = 102;
((int *)env->dyn_env)[2] = 192;
} else {
log_err("dynlib: already has data!");
qstate->ext_state[id] = module_error;
}
} else if (event == module_event_moddone) {
qstate->ext_state[id] = module_finished;
} else {
qstate->ext_state[id] = module_error;
}
}
/* Inform super is called when a query is completed or errors out, but only if
* a sub-query has been registered to it by this module. Look at
* mesh_attach_sub in services/mesh.h to see how this is done. */
EXPORT void inform_super(struct module_qstate* qstate, int id,
struct module_qstate* super) {
log_info("dynlib: hello world from inform_super");
}
/* Clear is called once a query is complete and the response has been sent
* back. It is used to clear up any per-query allocations. */
EXPORT void clear(struct module_qstate* qstate, int id) {
log_info("dynlib: hello world from clear");
struct dynlibmod_env* env = qstate->env->modinfo[id];
if (env->dyn_env != NULL) {
free(env->dyn_env);
env->dyn_env = NULL;
}
}
/* Get mem is called when Unbound is printing performance information. This
* only happens explicitly and is only used to show memory usage to the user. */
EXPORT size_t get_mem(struct module_env* env, int id) {
log_info("dynlib: hello world from get_mem");
return 0;
}
/* The callback that was forward declared earlier. It is registered in the init
* procedure to run when a query is being replied to. */
int reply_callback(struct query_info* qinfo,
struct module_qstate* qstate, struct reply_info* rep, int rcode,
struct edns_data* edns, struct edns_option** opt_list_out,
struct comm_reply* repinfo, struct regional* region,
struct timeval* start_time, int id, void* callback) {
log_info("dynlib: hello world from callback");
struct dynlibmod_env* env = qstate->env->modinfo[id];
if (env->dyn_env != NULL) {
log_info("dynlib: numbers gotten from query: %d, %d, and %d",
((int *)env->dyn_env)[0],
((int *)env->dyn_env)[1],
((int *)env->dyn_env)[2]);
}
return 0;
}
OpenPGP_0x391B18159E7A9E84_and_old_rev.asc
Description: OpenPGP public key
OpenPGP_signature.asc
Description: OpenPGP digital signature

