From 1762437121c91c6972d3fb8c22266d0c8d837f0c Mon Sep 17 00:00:00 2001 From: PMunch Date: Thu, 17 Oct 2019 15:10:33 +0200 Subject: [PATCH 1/7] Add dynamic library support --- Makefile.in | 26 +++- config.h.in | 3 + configure | 35 +++++ configure.ac | 22 +++ dynlibmod/dynlibmod.c | 236 ++++++++++++++++++++++++++++++++ dynlibmod/dynlibmod.h | 71 ++++++++++ dynlibmod/examples/helloworld.c | 45 ++++++ services/modstack.c | 9 ++ util/config_file.c | 4 + util/config_file.h | 3 + util/configlexer.lex | 2 + util/configparser.y | 18 ++- util/fptr_wlist.c | 21 +++ 13 files changed, 490 insertions(+), 5 deletions(-) create mode 100644 dynlibmod/dynlibmod.c create mode 100644 dynlibmod/dynlibmod.h create mode 100644 dynlibmod/examples/helloworld.c diff --git a/Makefile.in b/Makefile.in index d9d4fe7ad..21367eec3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -25,6 +25,7 @@ DNSTAP_SRC=@DNSTAP_SRC@ DNSTAP_OBJ=@DNSTAP_OBJ@ DNSCRYPT_SRC=@DNSCRYPT_SRC@ DNSCRYPT_OBJ=@DNSCRYPT_OBJ@ +WITH_DYNLIBMODULE=@WITH_DYNLIBMODULE@ WITH_PYTHONMODULE=@WITH_PYTHONMODULE@ WITH_PYUNBOUND=@WITH_PYUNBOUND@ PY_MAJOR_VERSION=@PY_MAJOR_VERSION@ @@ -87,6 +88,12 @@ LINTFLAGS+="-D__uint16_t=uint16_t" "-DEVP_PKEY_ASN1_METHOD=int" "-D_RuneLocale=i INSTALL=$(SHELL) $(srcdir)/install-sh +DYNLIBMOD_SRC=dynlibmod/dynlibmod.c +DYNLIBMOD_OBJ=@DYNLIBMOD_OBJ@ +DYNLIBMOD_HEADER=@DYNLIBMOD_HEADER@ +DYNLIBMOD_EXTRALIBS=@DYNLIBMOD_EXTRALIBS@ + + #pythonmod.c is not here, it is mentioned by itself in its own rules, #makedepend fails on missing interface.h otherwise. PYTHONMOD_SRC=pythonmod/pythonmod_utils.c @@ -139,7 +146,7 @@ autotrust.lo val_anchor.lo \ validator.lo val_kcache.lo val_kentry.lo val_neg.lo val_nsec3.lo val_nsec.lo \ val_secalgo.lo val_sigcrypt.lo val_utils.lo dns64.lo cachedb.lo redis.lo authzone.lo \ $(SUBNET_OBJ) $(PYTHONMOD_OBJ) $(CHECKLOCK_OBJ) $(DNSTAP_OBJ) $(DNSCRYPT_OBJ) \ -$(IPSECMOD_OBJ) $(IPSET_OBJ) respip.lo +$(IPSECMOD_OBJ) $(IPSET_OBJ) $(DYNLIBMOD_OBJ) respip.lo COMMON_OBJ_WITHOUT_UB_EVENT=$(COMMON_OBJ_WITHOUT_NETCALL) netevent.lo listen_dnsport.lo \ outside_network.lo COMMON_OBJ=$(COMMON_OBJ_WITHOUT_UB_EVENT) ub_event.lo @@ -326,7 +333,7 @@ libunbound.la: $(LIBUNBOUND_OBJ_LINK) $(LINK_LIB) $(UBSYMS) -o $@ $(LIBUNBOUND_OBJ_LINK) -rpath $(libdir) $(SSLLIB) $(LIBS) unbound$(EXEEXT): $(DAEMON_OBJ_LINK) libunbound.la - $(LINK) -o $@ $(DAEMON_OBJ_LINK) $(EXTRALINK) $(SSLLIB) $(LIBS) + $(LINK) -o $@ $(DAEMON_OBJ_LINK) $(EXTRALINK) $(SSLLIB) $(LIBS) $(DYNLIBMOD_EXTRALIBS) unbound-checkconf$(EXEEXT): $(CHECKCONF_OBJ_LINK) libunbound.la $(LINK) -o $@ $(CHECKCONF_OBJ_LINK) $(EXTRALINK) $(SSLLIB) $(LIBS) @@ -453,6 +460,7 @@ clean: rm -f unbound$(EXEEXT) unbound-checkconf$(EXEEXT) unbound-host$(EXEEXT) unbound-control$(EXEEXT) unbound-anchor$(EXEEXT) unbound-control-setup libunbound.la unbound.h rm -f $(ALL_SRC:.c=.lint) rm -f _unbound.la libunbound/python/libunbound_wrap.c libunbound/python/unbound.py pythonmod/interface.h pythonmod/unboundmodule.py + rm -f libunbound.a rm -rf autom4te.cache .libs build doc/html doc/xml realclean: clean @@ -622,6 +630,7 @@ depend: -e 's?$$(srcdir)/pythonmod/pythonmod.h?$$(PYTHONMOD_HEADER)?g' \ -e 's?$$(srcdir)/edns-subnet/subnetmod.h $$(srcdir)/edns-subnet/subnet-whitelist.h $$(srcdir)/edns-subnet/edns-subnet.h $$(srcdir)/edns-subnet/addrtree.h?$$(SUBNET_HEADER)?g' \ -e 's?$$(srcdir)/ipsecmod/ipsecmod.h $$(srcdir)/ipsecmod/ipsecmod-whitelist.h?$$(IPSECMOD_HEADER)?g' \ + -e 's?$$(srcdir)/dynlibmod/dynlibmod.h?$$(DYNLIBMOD_HEADER)?g' \ -e 's!\(.*\)\.o[ :]*!\1.lo \1.o: !g' \ > $(DEPEND_TMP) cp $(DEPEND_TARGET) $(DEPEND_TMP2) @@ -783,7 +792,7 @@ modstack.lo modstack.o: $(srcdir)/services/modstack.c config.h $(srcdir)/service $(srcdir)/services/view.h $(PYTHONMOD_HEADER) $(srcdir)/ipsecmod/ipsecmod.h \ $(srcdir)/edns-subnet/subnetmod.h $(srcdir)/util/alloc.h $(srcdir)/util/net_help.h \ $(srcdir)/util/storage/slabhash.h $(srcdir)/edns-subnet/addrtree.h $(srcdir)/edns-subnet/edns-subnet.h \ - $(srcdir)/ipset/ipset.h + $(srcdir)/ipset/ipset.h $(srcdir)/dynlibmod/dynlibmod.h view.lo view.o: $(srcdir)/services/view.c config.h $(srcdir)/services/view.h $(srcdir)/util/rbtree.h \ $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/services/localzone.h $(srcdir)/util/storage/dnstree.h \ $(srcdir)/util/module.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/data/msgreply.h \ @@ -865,7 +874,8 @@ fptr_wlist.lo fptr_wlist.o: $(srcdir)/util/fptr_wlist.c config.h $(srcdir)/util/ $(srcdir)/libunbound/unbound-event.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \ $(srcdir)/util/config_file.h $(srcdir)/respip/respip.h $(PYTHONMOD_HEADER) \ $(srcdir)/ipsecmod/ipsecmod.h $(srcdir)/edns-subnet/subnetmod.h $(srcdir)/util/net_help.h \ - $(srcdir)/edns-subnet/addrtree.h $(srcdir)/edns-subnet/edns-subnet.h $(srcdir)/ipset/ipset.h + $(srcdir)/edns-subnet/addrtree.h $(srcdir)/edns-subnet/edns-subnet.h $(srcdir)/ipset/ipset.h \ + $(srcdir)/dynlibmod/dynlibmod.h locks.lo locks.o: $(srcdir)/util/locks.c config.h $(srcdir)/util/locks.h $(srcdir)/util/log.h log.lo log.o: $(srcdir)/util/log.c config.h $(srcdir)/util/log.h $(srcdir)/util/locks.h $(srcdir)/sldns/sbuffer.h mini_event.lo mini_event.o: $(srcdir)/util/mini_event.c config.h $(srcdir)/util/mini_event.h @@ -1072,6 +1082,14 @@ dnstap.lo dnstap.o: $(srcdir)/dnstap/dnstap.c config.h $(srcdir)/sldns/sbuffer. dnstap/dnstap.pb-c.h dnstap.pb-c.lo dnstap.pb-c.o: dnstap/dnstap.pb-c.c dnstap/dnstap.pb-c.h \ +dynlibmod.lo dynlibmod.o: $(srcdir)/dynlibmod/dynlibmod.c config.h $(srcdir)/dynlibmod/dynlibmod.h \ + $(srcdir)/util/module.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \ + $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/data/msgparse.h \ + $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/rbtree.h\ + $(srcdir)/util/storage/dnstree.h $(srcdir)/util/fptr_wlist.h $(srcdir)/util/netevent.h \ + $(srcdir)/dnscrypt/dnscrypt.h $(srcdir)/util/tube.h \ + $(srcdir)/services/mesh.h $(srcdir)/services/modstack.h $(srcdir)/util/regional.h $(srcdir)/util/net_help.h \ + $(srcdir)/util/config_file.h $(srcdir)/services/cache/dns.h $(srcdir)/sldns/wire2str.h ipsecmod.lo ipsecmod.o: $(srcdir)/ipsecmod/ipsecmod.c config.h $(srcdir)/ipsecmod/ipsecmod.h \ $(srcdir)/util/module.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \ $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/data/msgparse.h \ diff --git a/config.h.in b/config.h.in index d8ec50a6d..a07b970bc 100644 --- a/config.h.in +++ b/config.h.in @@ -836,6 +836,9 @@ /* Define if you want Python module. */ #undef WITH_PYTHONMODULE +/* Define if you want dynamic library module. */ +#undef WITH_DYNLIBMODULE + /* Define if you want PyUnbound. */ #undef WITH_PYUNBOUND diff --git a/configure b/configure index 4e222879e..41984f652 100755 --- a/configure +++ b/configure @@ -697,6 +697,10 @@ PYTHON_LDFLAGS PYTHON_CPPFLAGS PYTHON PYTHON_VERSION +DYNLIBMOD_EXTRALIBS +DYNLIBMOD_HEADER +DYNLIBMOD_OBJ +WITH_DYNLIBMODULE PTHREAD_CFLAGS_ONLY PTHREAD_CFLAGS PTHREAD_LIBS @@ -851,6 +855,7 @@ enable_alloc_nonregional with_pthreads with_solaris_threads with_syslog_facility +with_dynlibmodule with_pyunbound with_pythonmodule enable_swig_version_check @@ -1607,6 +1612,8 @@ Optional Packages: --with-solaris-threads use solaris native thread library. --with-syslog-facility=LOCAL0 - LOCAL7 set SYSLOG_FACILITY, default DAEMON + --with-dynlibmodule build dynamic library module, or + --without-dynlibmodule to disable it. (default=no) --with-pyunbound build PyUnbound, or --without-pyunbound to skip it. (default=no) --with-pythonmodule build Python module, or --without-pythonmodule to @@ -17123,6 +17130,34 @@ cat >>confdefs.h <<_ACEOF _ACEOF +# Check for dynamic library module + +# Check whether --with-dynlibmodule was given. +if test "${with_dynlibmodule+set}" = set; then : + withval=$with_dynlibmodule; +else + withval="no" +fi + + +if test x_$withval != x_no; then + +$as_echo "#define WITH_DYNLIBMODULE 1" >>confdefs.h + + WITH_DYNLIBMODULE=yes + + DYNLIBMOD_OBJ="dynlibmod.lo" + + DYNLIBMOD_HEADER='$(srcdir)/dynlibmod/dynlibmod.h' + + if test $on_mingw = "no"; then + DYNLIBMOD_EXTRALIBS="-ldl -export-dynamic" + else + DYNLIBMOD_EXTRALIBS="-Wl,--export-all-symbols,--out-implib,libunbound.a" + fi + +fi + # Check for PyUnbound # Check whether --with-pyunbound was given. diff --git a/configure.ac b/configure.ac index 216a78c65..7d108d002 100644 --- a/configure.ac +++ b/configure.ac @@ -616,6 +616,28 @@ case "${UNBOUND_SYSLOG_FACILITY}" in esac AC_DEFINE_UNQUOTED(UB_SYSLOG_FACILITY,${UNBOUND_SYSLOG_FACILITY},[the SYSLOG_FACILITY to use, default LOG_DAEMON]) +# Check for dynamic library module +AC_ARG_WITH(dynlibmodule, + AC_HELP_STRING([--with-dynlibmodule], + [build dynamic library module, or --without-dynlibmodule to disable it. (default=no)]), + [], [ withval="no" ]) + +if test x_$withval != x_no; then + AC_DEFINE(WITH_DYNLIBMODULE, 1, [Define if you want dynlib module.]) + WITH_DYNLIBMODULE=yes + AC_SUBST(WITH_DYNLIBMODULE) + DYNLIBMOD_OBJ="dynlibmod.lo" + AC_SUBST(DYNLIBMOD_OBJ) + DYNLIBMOD_HEADER='$(srcdir)/dynlibmod/dynlibmod.h' + AC_SUBST(DYNLIBMOD_HEADER) + if test $on_mingw = "no"; then + DYNLIBMOD_EXTRALIBS="-ldl -export-dynamic" + else + DYNLIBMOD_EXTRALIBS="-Wl,--export-all-symbols,--out-implib,libunbound.a" + fi + AC_SUBST(DYNLIBMOD_EXTRALIBS) +fi + # Check for PyUnbound AC_ARG_WITH(pyunbound, AC_HELP_STRING([--with-pyunbound], diff --git a/dynlibmod/dynlibmod.c b/dynlibmod/dynlibmod.c new file mode 100644 index 000000000..47d315306 --- /dev/null +++ b/dynlibmod/dynlibmod.c @@ -0,0 +1,236 @@ +#include "config.h" +#include "util/module.h" +#include "util/config_file.h" +#include "dynlibmod/dynlibmod.h" + +#if HAVE_WINDOWS_H +#include +#define __DYNSYM FARPROC +#define __LOADSYM GetProcAddress +void log_dlerror() { + DWORD dwLastError = GetLastError(); + LPSTR MessageBuffer; + DWORD dwBufferLength; + DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM ; + if(dwBufferLength = FormatMessageA( + dwFormatFlags, + NULL, // module to get message from (NULL == system) + dwLastError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language + (LPSTR) &MessageBuffer, + 0, + NULL + )) + { + DWORD dwBytesWritten; + + // + // Output message string on stderr. + // + log_info("dynlibmod: %s (%ld)", MessageBuffer, dwLastError); + //WriteFile( + // GetStdHandle(STD_ERROR_HANDLE), + // MessageBuffer, + // dwBufferLength, + // &dwBytesWritten, + // NULL + // ); + + // + // Free the buffer allocated by the system. + // + LocalFree(MessageBuffer); + } + +} +#else +#include +#define __DYNSYM void* +#define __LOADSYM dlsym +void log_dlerror() { + log_err("dynlibmod: %s", dlerror()); +} +#endif + +/** + * Global state for the module. + */ + +typedef int (*func_init_t)(int, struct config_file*); +typedef int (*func_deinit_t)(int); +typedef int (*func_operate_t)(int, enum module_ev event, struct module_qstate* qstate, void*); +typedef int (*func_inform_t)(int, struct module_qstate* qstate, struct module_qstate* super, void*); +struct dynlibmod_env { + + /** Dynamic library filename. */ + const char* fname; + + /** Module init function */ + func_init_t func_init; + /** Module deinit function */ + func_deinit_t func_deinit; + /** Module operate function */ + func_operate_t func_operate; + /** Module super_inform function */ + func_inform_t func_inform; + + /** Module qstate. */ + struct module_qstate* qstate; +}; + +struct dynlibmod_qstate { + + /** Module per query data. */ + void* data; +}; + +/** dynlib module init */ +int dynlibmod_init(struct module_env* env, int id) { + struct dynlibmod_env* de = (struct dynlibmod_env*)calloc(1, sizeof(struct dynlibmod_env)); + if (!de) + { + log_err("dynlibmod: malloc failure"); + return 0; + } + + env->modinfo[id] = (void*) de; + + de->fname = env->cfg->dynlib_file; + if (de->fname == NULL || de->fname[0] == 0) { + log_err("dynlibmod: no dynamic library given."); + return 0; + } + log_info("Trying to load library %s", de->fname); +#ifndef HAVE_WINDOWS_H + void* dynamic_library = dlopen(de->fname, RTLD_LAZY | RTLD_GLOBAL); +#else + HMODULE dynamic_library = LoadLibrary(de->fname); +#endif + if (dynamic_library == NULL) { + log_dlerror(); + log_err("dynlibmod: unable to load dynamic library."); + return 0; + } else { + __DYNSYM initializer = __LOADSYM(dynamic_library,"init"); + if (initializer == NULL) { + log_err("dynlibmod: unable to load init procedure from dynamic library."); +#ifndef HAVE_WINDOWS_H + log_err("dynlibmod: %s", dlerror()); +#endif + return 0; + } else { + de->func_init = (func_init_t) initializer; + } + __DYNSYM deinitializer = __LOADSYM(dynamic_library,"deinit"); + if (deinitializer == NULL) { + log_err("dynlibmod: unable to load deinit procedure from dynamic library."); +#ifndef HAVE_WINDOWS_H + log_err("dynlibmod: %s", dlerror()); +#endif + return 0; + } else { + de->func_deinit = (func_deinit_t) deinitializer; + } + __DYNSYM operate = __LOADSYM(dynamic_library,"operate"); + if (operate == NULL) { + log_err("dynlibmod: unable to load operate procedure from dynamic library."); +#ifndef HAVE_WINDOWS_H + log_err("dynlibmod: %s", dlerror()); +#endif + return 0; + } else { + de->func_operate = (func_operate_t) operate; + } + __DYNSYM inform = __LOADSYM(dynamic_library,"inform_super"); + if (inform == NULL) { + log_err("dynlibmod: unable to load inform_super procedure from dynamic library."); +#ifndef HAVE_WINDOWS_H + log_err("dynlibmod: %s", dlerror()); +#endif + return 0; + } else { + de->func_inform = (func_inform_t) inform; + } + } + de->func_init(id, env->cfg); + return 1; +} + +/** dynlib module deinit */ +void dynlibmod_deinit(struct module_env* env, int id) { + struct dynlibmod_env* de = env->modinfo[id]; + if(de == NULL) + return; + de->func_deinit(id); + de->fname = NULL; + free(de); +} + +/** dynlib module operate on a query */ +void dynlibmod_operate(struct module_qstate* qstate, enum module_ev event, + int id, struct outbound_entry* outbound) { + struct dynlibmod_env* de = qstate->env->modinfo[id]; + struct dynlibmod_qstate* dq = (struct dynlibmod_qstate*)qstate->minfo[id]; + + void * data = dq == NULL ? NULL : dq->data; + int ret = de->func_operate(id, event, qstate, data); + if (ret != 1) { + log_err("dynlibmod: dynamic library returned bad code from operate %d.", ret); + qstate->ext_state[id] = module_error; + } +} + +/** dynlib module */ +void dynlibmod_inform_super(struct module_qstate* qstate, int id, + struct module_qstate* super) { + struct dynlibmod_env* de = qstate->env->modinfo[id]; + struct dynlibmod_qstate* dq = (struct dynlibmod_qstate*)qstate->minfo[id]; + + void * data = dq == NULL ? NULL : dq->data; + int ret = de->func_inform(id, qstate, super, data); + if (ret != 1) { + log_err("dynlibmod: dynamic library returned bad code from inform_super %d.", ret); + qstate->ext_state[id] = module_error; + } +} + +/** dynlib module cleanup query state */ +void dynlibmod_clear(struct module_qstate* qstate, int id) { + struct dynlibmod_qstate* dq; + if (qstate == NULL) + return; + + dq = (struct dynlibmod_qstate*)qstate->minfo[id]; + verbose(VERB_ALGO, "dynlibmod: clear, id: %d, dq:%p", id, dq); + if(dq != NULL) { + /* Free qstate */ + free(dq); + } + + qstate->minfo[id] = NULL; +} + +/** dynlib module alloc size routine */ +size_t dynlibmod_get_mem(struct module_env* env, int id) { + struct dynlibmod_env* de = (struct dynlibmod_env*)env->modinfo[id]; + verbose(VERB_ALGO, "dynlibmod: get_mem, id: %d, de:%p", id, de); + if(!de) + return 0; + return sizeof(*de); +} + +/** + * The module function block + */ +static struct module_func_block dynlibmod_block = { + "dynlib", + &dynlibmod_init, &dynlibmod_deinit, &dynlibmod_operate, &dynlibmod_inform_super, + &dynlibmod_clear, &dynlibmod_get_mem +}; + +struct module_func_block* dynlibmod_get_funcblock(void) +{ + return &dynlibmod_block; +} diff --git a/dynlibmod/dynlibmod.h b/dynlibmod/dynlibmod.h new file mode 100644 index 000000000..ac5d737b4 --- /dev/null +++ b/dynlibmod/dynlibmod.h @@ -0,0 +1,71 @@ +/* + * dynlibmod.h: module header file + * + * Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) + * Marek Vavrusa (xvavru00 AT stud.fit.vutbr.cz) + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the organization nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \file + * Dynamic loading module for unbound. Loads dynamic library. + */ +#ifndef DYNLIBMOD_H +#define DYNLIBMOD_H +#include "util/module.h" +#include "services/outbound_list.h" + +/** + * Get the module function block. + * @return: function block with function pointers to module methods. + */ +struct module_func_block* dynlibmod_get_funcblock(void); + +/** dynlib module init */ +int dynlibmod_init(struct module_env* env, int id); + +/** dynlib module deinit */ +void dynlibmod_deinit(struct module_env* env, int id); + +/** dynlib module operate on a query */ +void dynlibmod_operate(struct module_qstate* qstate, enum module_ev event, + int id, struct outbound_entry* outbound); + +/** dynlib module */ +void dynlibmod_inform_super(struct module_qstate* qstate, int id, + struct module_qstate* super); + +/** dynlib module cleanup query state */ +void dynlibmod_clear(struct module_qstate* qstate, int id); + +/** dynlib module alloc size routine */ +size_t dynlibmod_get_mem(struct module_env* env, int id); + +#endif /* DYNLIBMOD_H */ diff --git a/dynlibmod/examples/helloworld.c b/dynlibmod/examples/helloworld.c new file mode 100644 index 000000000..76c1df215 --- /dev/null +++ b/dynlibmod/examples/helloworld.c @@ -0,0 +1,45 @@ +/* + * 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.a + * to cross-compile a 64-bit Windows DLL. + */ + +#include "../../config.h" +#include "../../util/module.h" + +#ifdef HAVE_WINDOWS_H +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +EXPORT int init(int id, struct config_file* cfg) { + log_info("Hello world from init"); + return 1; +} + +EXPORT int deinit(int id) { + log_info("Hello world from deinit"); + return 1; +} + +EXPORT int operate(int id, enum module_ev event, struct module_qstate* qstate, void* data) { + log_info("Hello world from operate"); + if (event == module_event_new || event == module_event_pass) { + qstate->ext_state[id] = module_wait_module; + } else if (event == module_event_moddone) { + qstate->ext_state[id] = module_finished; + } else { + qstate->ext_state[id] = module_error; + } + return 1; +} + +EXPORT int inform_super(int id, struct module_qstate* qstate, struct module_qstate* super, void* data) { + log_info("Hello world from inform_super"); + return 1; +} diff --git a/services/modstack.c b/services/modstack.c index 68e592814..a600549b1 100644 --- a/services/modstack.c +++ b/services/modstack.c @@ -51,6 +51,9 @@ #ifdef WITH_PYTHONMODULE #include "pythonmod/pythonmod.h" #endif +#ifdef WITH_DYNLIBMODULE +#include "dynlibmod/dynlibmod.h" +#endif #ifdef USE_CACHEDB #include "cachedb/cachedb.h" #endif @@ -140,6 +143,9 @@ module_list_avail(void) #ifdef WITH_PYTHONMODULE "python", #endif +#ifdef WITH_DYNLIBMODULE + "dynlib", +#endif #ifdef USE_CACHEDB "cachedb", #endif @@ -171,6 +177,9 @@ module_funcs_avail(void) #ifdef WITH_PYTHONMODULE &pythonmod_get_funcblock, #endif +#ifdef WITH_DYNLIBMODULE + &dynlibmod_get_funcblock, +#endif #ifdef USE_CACHEDB &cachedb_get_funcblock, #endif diff --git a/util/config_file.c b/util/config_file.c index 119b22238..7a8fb0bbc 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -264,6 +264,7 @@ config_create(void) cfg->unblock_lan_zones = 0; cfg->insecure_lan_zones = 0; cfg->python_script = NULL; + cfg->dynlib_file = NULL; cfg->remote_control_enable = 0; cfg->control_ifs.first = NULL; cfg->control_ifs.last = NULL; @@ -610,6 +611,7 @@ int config_set_option(struct config_file* cfg, const char* opt, else S_STR("control-cert-file:", control_cert_file) else S_STR("module-config:", module_conf) else S_STRLIST("python-script:", python_script) + else S_STR("dynlib-file:", dynlib_file) else S_YNO("disable-dnssec-lame-check:", disable_dnssec_lame_check) #ifdef CLIENT_SUBNET /* Can't set max subnet prefix here, since that value is used when @@ -1062,6 +1064,7 @@ config_get_option(struct config_file* cfg, const char* opt, else O_YNO(opt, "insecure-lan-zones", insecure_lan_zones) else O_DEC(opt, "max-udp-size", max_udp_size) else O_LST(opt, "python-script", python_script) + else O_STR(opt, "dynlib-file", dynlib_file) else O_YNO(opt, "disable-dnssec-lame-check", disable_dnssec_lame_check) else O_DEC(opt, "ip-ratelimit", ip_ratelimit) else O_DEC(opt, "ratelimit", ratelimit) @@ -1398,6 +1401,7 @@ config_delete(struct config_file* cfg) free(cfg->version); free(cfg->module_conf); free(cfg->outgoing_avail_ports); + free(cfg->dynlib_file); config_delstrlist(cfg->caps_whitelist); config_delstrlist(cfg->private_address); config_delstrlist(cfg->private_domain); diff --git a/util/config_file.h b/util/config_file.h index b3ef930a0..6706ab08b 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -439,6 +439,9 @@ struct config_file { /** Python script file */ struct config_strlist* python_script; + /** Dynamic library file */ + char* dynlib_file; + /** Use systemd socket activation. */ int use_systemd; diff --git a/util/configlexer.lex b/util/configlexer.lex index a86ddf55d..1010dcda9 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -404,6 +404,8 @@ control-key-file{COLON} { YDVAR(1, VAR_CONTROL_KEY_FILE) } control-cert-file{COLON} { YDVAR(1, VAR_CONTROL_CERT_FILE) } python-script{COLON} { YDVAR(1, VAR_PYTHON_SCRIPT) } python{COLON} { YDVAR(0, VAR_PYTHON) } +dynlib-file{COLON} { YDVAR(1, VAR_DYNLIB_FILE) } +dynlib{COLON} { YDVAR(0, VAR_DYNLIB) } domain-insecure{COLON} { YDVAR(1, VAR_DOMAIN_INSECURE) } minimal-responses{COLON} { YDVAR(1, VAR_MINIMAL_RESPONSES) } rrset-roundrobin{COLON} { YDVAR(1, VAR_RRSET_ROUNDROBIN) } diff --git a/util/configparser.y b/util/configparser.y index 10227a2ff..5f58d75b3 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -168,6 +168,7 @@ extern struct config_parser_state* cfg_parser; %token VAR_STREAM_WAIT_SIZE VAR_TLS_CIPHERS VAR_TLS_CIPHERSUITES %token VAR_TLS_SESSION_TICKET_KEYS %token VAR_IPSET VAR_IPSET_NAME_V4 VAR_IPSET_NAME_V6 +%token VAR_DYNLIB VAR_DYNLIB_FILE %% toplevelvars: /* empty */ | toplevelvars toplevelvar ; @@ -175,7 +176,7 @@ toplevelvar: serverstart contents_server | stubstart contents_stub | forwardstart contents_forward | pythonstart contents_py | rcstart contents_rc | dtstart contents_dt | viewstart contents_view | dnscstart contents_dnsc | cachedbstart contents_cachedb | - ipsetstart contents_ipset | authstart contents_auth + ipsetstart contents_ipset | authstart contents_auth | dynlibstart contents_dl ; /* server: declaration */ @@ -2741,6 +2742,21 @@ py_script: VAR_PYTHON_SCRIPT STRING_ARG if(!cfg_strlist_append_ex(&cfg_parser->cfg->python_script, $2)) yyerror("out of memory"); } +dynlibstart: VAR_DYNLIB + { + OUTYY(("\nP(dynlib:)\n")); + } + ; +contents_dl: contents_dl content_dl + | ; +content_dl: dl_file + ; +dl_file: VAR_DYNLIB_FILE STRING_ARG + { + OUTYY(("P(dynlib-file:%s)\n", $2)); + free(cfg_parser->cfg->dynlib_file); + cfg_parser->cfg->dynlib_file = $2; + } server_disable_dnssec_lame_check: VAR_DISABLE_DNSSEC_LAME_CHECK STRING_ARG { OUTYY(("P(disable_dnssec_lame_check:%s)\n", $2)); diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index f5da501de..20eaeb137 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -81,6 +81,9 @@ #ifdef WITH_PYTHONMODULE #include "pythonmod/pythonmod.h" #endif +#ifdef WITH_DYNLIBMODULE +#include "dynlibmod/dynlibmod.h" +#endif #ifdef USE_CACHEDB #include "cachedb/cachedb.h" #endif @@ -379,6 +382,9 @@ fptr_whitelist_mod_init(int (*fptr)(struct module_env* env, int id)) #ifdef WITH_PYTHONMODULE else if(fptr == &pythonmod_init) return 1; #endif +#ifdef WITH_DYNLIBMODULE + else if(fptr == &dynlibmod_init) return 1; +#endif #ifdef USE_CACHEDB else if(fptr == &cachedb_init) return 1; #endif @@ -404,6 +410,9 @@ fptr_whitelist_mod_deinit(void (*fptr)(struct module_env* env, int id)) #ifdef WITH_PYTHONMODULE else if(fptr == &pythonmod_deinit) return 1; #endif +#ifdef WITH_DYNLIBMODULE + else if(fptr == &dynlibmod_deinit) return 1; +#endif #ifdef USE_CACHEDB else if(fptr == &cachedb_deinit) return 1; #endif @@ -430,6 +439,9 @@ fptr_whitelist_mod_operate(void (*fptr)(struct module_qstate* qstate, #ifdef WITH_PYTHONMODULE else if(fptr == &pythonmod_operate) return 1; #endif +#ifdef WITH_DYNLIBMODULE + else if(fptr == &dynlibmod_operate) return 1; +#endif #ifdef USE_CACHEDB else if(fptr == &cachedb_operate) return 1; #endif @@ -456,6 +468,9 @@ fptr_whitelist_mod_inform_super(void (*fptr)( #ifdef WITH_PYTHONMODULE else if(fptr == &pythonmod_inform_super) return 1; #endif +#ifdef WITH_DYNLIBMODULE + else if(fptr == &dynlibmod_inform_super) return 1; +#endif #ifdef USE_CACHEDB else if(fptr == &cachedb_inform_super) return 1; #endif @@ -482,6 +497,9 @@ fptr_whitelist_mod_clear(void (*fptr)(struct module_qstate* qstate, #ifdef WITH_PYTHONMODULE else if(fptr == &pythonmod_clear) return 1; #endif +#ifdef WITH_DYNLIBMODULE + else if(fptr == &dynlibmod_clear) return 1; +#endif #ifdef USE_CACHEDB else if(fptr == &cachedb_clear) return 1; #endif @@ -507,6 +525,9 @@ fptr_whitelist_mod_get_mem(size_t (*fptr)(struct module_env* env, int id)) #ifdef WITH_PYTHONMODULE else if(fptr == &pythonmod_get_mem) return 1; #endif +#ifdef WITH_DYNLIBMODULE + else if(fptr == &dynlibmod_get_mem) return 1; +#endif #ifdef USE_CACHEDB else if(fptr == &cachedb_get_mem) return 1; #endif From 8eeb910e3d07f1a1b1f540ffc0fc34c5c2097f8f Mon Sep 17 00:00:00 2001 From: PMunch Date: Mon, 21 Oct 2019 14:20:33 +0200 Subject: [PATCH 2/7] Improve dynlib module and add documentation Dynamic library module is now only a thin wrapper that loads dynamic libraries and forwards all function calls directly to the loaded module. This meant adding get_mem and clear, and get_mem calls have been added in the expected places. Documentation has also been added to the example.conf and the unbound.conf manpage. --- daemon/remote.c | 10 +++ doc/example.conf.in | 10 +++ doc/unbound.conf.5.in | 20 ++++- dynlibmod/dynlibmod.c | 135 ++++++++++++++------------------ dynlibmod/dynlibmod.h | 3 +- dynlibmod/examples/helloworld.c | 21 +++-- libunbound/unbound.h | 1 + smallapp/unbound-control.c | 3 + util/shm_side/shm_main.c | 4 + 9 files changed, 119 insertions(+), 88 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 1689154f5..21ab2f3bc 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -803,6 +803,9 @@ print_mem(RES* ssl, struct worker* worker, struct daemon* daemon, size_t dnscrypt_shared_secret = 0; size_t dnscrypt_nonce = 0; #endif /* USE_DNSCRYPT */ +#ifdef WITH_DYNLIBMODULE + size_t dynlib = 0; +#endif /* WITH_DYNLIBMODULE */ msg = slabhash_get_mem(daemon->env->msg_cache); rrset = slabhash_get_mem(&daemon->env->rrset_cache->table); val = mod_get_mem(&worker->env, "validator"); @@ -821,6 +824,9 @@ print_mem(RES* ssl, struct worker* worker, struct daemon* daemon, dnscrypt_nonce = slabhash_get_mem(daemon->dnscenv->nonces_cache); } #endif /* USE_DNSCRYPT */ +#ifdef WITH_DYNLIBMODULE + dynlib = mod_get_mem(&worker->env, "dynlib"); +#endif /* WITH_DYNLIBMODULE */ if(!print_longnum(ssl, "mem.cache.rrset"SQ, rrset)) return 0; @@ -848,6 +854,10 @@ print_mem(RES* ssl, struct worker* worker, struct daemon* daemon, dnscrypt_nonce)) return 0; #endif /* USE_DNSCRYPT */ +#ifdef WITH_DYNLIBMODULE + if(!print_longnum(ssl, "mem.mod.dynlibmod"SQ, dynlib)) + return 0; +#endif /* WITH_DYNLIBMODULE */ if(!print_longnum(ssl, "mem.streamwait"SQ, (size_t)s->svr.mem_stream_wait)) return 0; diff --git a/doc/example.conf.in b/doc/example.conf.in index 9d8edbf9e..ab6b43baa 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -837,6 +837,16 @@ python: # Script file to load # python-script: "@UNBOUND_SHARE_DIR@/ubmodule-tst.py" +# Dynamic library config section. To enable: +# o use --with-dynlibmodule to configure before compiling. +# o list dynlib in the module-config string (above) to enable. +# It can be placed anywhere, the dynlib module is only a very thin wrapper +# to load modules dynamically. +# o and give a dynlib-file to run. +dynlib: + # Script file to load + # dynlib-file: "@UNBOUND_SHARE_DIR@/dynlib.so" + # Remote control config section. remote-control: # Enable remote control with unbound-control(8) here. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index b7ff72326..434467995 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -940,7 +940,9 @@ EDNS client subnet support the default is "subnetcache validator iterator". Most modules that need to be listed here have to be listed at the beginning of the line. The cachedb module has to be listed just before the iterator. The python module can be listed in different places, it then processes the -output of the module it is just before. +output of the module it is just before. The dynlib module can be listed pretty +much anywhere, it is only a very thin wrapper that allows dynamic libraries to +run in its place. .TP .B trust\-anchor\-file: \fI File with trusted keys for validation. Both DS and DNSKEY entries can appear @@ -1782,6 +1784,22 @@ directory. .B python\-script: \fI\fR The script file to load. Repeat this option for every python module instance added to the \fBmodule\-config:\fR option. +.SS "Dynamic Library Module Options" +.LP +The +.B dynlib: +clause gives the settings for the \fIdynlib\fR(1) module. This module is only +a very small wrapper that allows dynamic modules to be loaded on runtime +instead of being compiled into the application. To enable the dynlib module it +has to be compiled into the daemon, and the word "dynlib" has to be put in the +\fBmodule\-config:\fR option. +.LP +The \fBdynlib\-file:\fR path should be specified as an absolute path relative +to the new path set by \fBchroot:\fR option, or as a relative path to the +working directory. +.TP +.B dynlib\-file: \fI\fR +The dynamic library file to load. .SS "DNS64 Module Options" .LP The dns64 module must be configured in the \fBmodule\-config:\fR "dns64 diff --git a/dynlibmod/dynlibmod.c b/dynlibmod/dynlibmod.c index 47d315306..3592f7de7 100644 --- a/dynlibmod/dynlibmod.c +++ b/dynlibmod/dynlibmod.c @@ -5,6 +5,7 @@ #if HAVE_WINDOWS_H #include +#define __DYNMOD HMODULE #define __DYNSYM FARPROC #define __LOADSYM GetProcAddress void log_dlerror() { @@ -25,43 +26,39 @@ void log_dlerror() { )) { DWORD dwBytesWritten; - - // - // Output message string on stderr. - // - log_info("dynlibmod: %s (%ld)", MessageBuffer, dwLastError); - //WriteFile( - // GetStdHandle(STD_ERROR_HANDLE), - // MessageBuffer, - // dwBufferLength, - // &dwBytesWritten, - // NULL - // ); - - // - // Free the buffer allocated by the system. - // + log_err("dynlibmod: %s (%ld)", MessageBuffer, dwLastError); LocalFree(MessageBuffer); } } +HMODULE open_library(const char* fname) { + return LoadLibrary(fname); +} #else #include +#define __DYNMOD void* #define __DYNSYM void* #define __LOADSYM dlsym void log_dlerror() { log_err("dynlibmod: %s", dlerror()); } +void* open_library(const char* fname) { + return dlopen(fname, RTLD_LAZY | RTLD_GLOBAL); +} #endif + /** * Global state for the module. */ -typedef int (*func_init_t)(int, struct config_file*); -typedef int (*func_deinit_t)(int); -typedef int (*func_operate_t)(int, enum module_ev event, struct module_qstate* qstate, void*); -typedef int (*func_inform_t)(int, struct module_qstate* qstate, struct module_qstate* super, void*); +typedef void (*func_init_t)(struct module_env*, int); +typedef void (*func_deinit_t)(struct module_env*, int); +typedef void (*func_operate_t)(struct module_qstate*, enum module_ev, int, struct outbound_entry*); +typedef void (*func_inform_t)(struct module_qstate*, int, struct module_qstate*); +typedef void (*func_clear_t)(struct module_qstate*, int); +typedef size_t (*func_get_mem_t)(struct module_env*, int); + struct dynlibmod_env { /** Dynamic library filename. */ @@ -75,20 +72,19 @@ struct dynlibmod_env { func_operate_t func_operate; /** Module super_inform function */ func_inform_t func_inform; + /** Module clear function */ + func_clear_t func_clear; + /** Module get_mem function */ + func_get_mem_t func_get_mem; /** Module qstate. */ struct module_qstate* qstate; }; -struct dynlibmod_qstate { - - /** Module per query data. */ - void* data; -}; - /** dynlib module init */ int dynlibmod_init(struct module_env* env, int id) { struct dynlibmod_env* de = (struct dynlibmod_env*)calloc(1, sizeof(struct dynlibmod_env)); + __DYNMOD dynamic_library; if (!de) { log_err("dynlibmod: malloc failure"); @@ -102,59 +98,63 @@ int dynlibmod_init(struct module_env* env, int id) { log_err("dynlibmod: no dynamic library given."); return 0; } - log_info("Trying to load library %s", de->fname); -#ifndef HAVE_WINDOWS_H - void* dynamic_library = dlopen(de->fname, RTLD_LAZY | RTLD_GLOBAL); -#else - HMODULE dynamic_library = LoadLibrary(de->fname); -#endif + verbose(VERB_ALGO, "dynlibmod: Trying to load library %s", de->fname); + dynamic_library = open_library(de->fname); if (dynamic_library == NULL) { log_dlerror(); - log_err("dynlibmod: unable to load dynamic library."); + log_err("dynlibmod: unable to load dynamic library \"%s\".", de->fname); return 0; } else { __DYNSYM initializer = __LOADSYM(dynamic_library,"init"); if (initializer == NULL) { - log_err("dynlibmod: unable to load init procedure from dynamic library."); -#ifndef HAVE_WINDOWS_H - log_err("dynlibmod: %s", dlerror()); -#endif + log_dlerror(); + log_err("dynlibmod: unable to load init procedure from dynamic library \"%s\".", de->fname); return 0; } else { de->func_init = (func_init_t) initializer; } __DYNSYM deinitializer = __LOADSYM(dynamic_library,"deinit"); if (deinitializer == NULL) { - log_err("dynlibmod: unable to load deinit procedure from dynamic library."); -#ifndef HAVE_WINDOWS_H - log_err("dynlibmod: %s", dlerror()); -#endif + log_dlerror(); + log_err("dynlibmod: unable to load deinit procedure from dynamic library \"%s\".", de->fname); return 0; } else { de->func_deinit = (func_deinit_t) deinitializer; } __DYNSYM operate = __LOADSYM(dynamic_library,"operate"); if (operate == NULL) { - log_err("dynlibmod: unable to load operate procedure from dynamic library."); -#ifndef HAVE_WINDOWS_H - log_err("dynlibmod: %s", dlerror()); -#endif + log_dlerror(); + log_err("dynlibmod: unable to load operate procedure from dynamic library \"%s\".", de->fname); return 0; } else { de->func_operate = (func_operate_t) operate; } __DYNSYM inform = __LOADSYM(dynamic_library,"inform_super"); if (inform == NULL) { - log_err("dynlibmod: unable to load inform_super procedure from dynamic library."); -#ifndef HAVE_WINDOWS_H - log_err("dynlibmod: %s", dlerror()); -#endif + log_dlerror(); + log_err("dynlibmod: unable to load inform_super procedure from dynamic library \"%s\".", de->fname); return 0; } else { de->func_inform = (func_inform_t) inform; } + __DYNSYM clear = __LOADSYM(dynamic_library,"clear"); + if (clear == NULL) { + log_dlerror(); + log_err("dynlibmod: unable to load clear procedure from dynamic library \"%s\".", de->fname); + return 0; + } else { + de->func_clear = (func_clear_t) clear; + } + __DYNSYM get_mem = __LOADSYM(dynamic_library,"get_mem"); + if (get_mem == NULL) { + log_dlerror(); + log_err("dynlibmod: unable to load get_mem procedure from dynamic library \"%s\".", de->fname); + return 0; + } else { + de->func_get_mem = (func_get_mem_t) get_mem; + } } - de->func_init(id, env->cfg); + de->func_init(env, id); return 1; } @@ -163,7 +163,7 @@ void dynlibmod_deinit(struct module_env* env, int id) { struct dynlibmod_env* de = env->modinfo[id]; if(de == NULL) return; - de->func_deinit(id); + de->func_deinit(env, id); de->fname = NULL; free(de); } @@ -172,44 +172,23 @@ void dynlibmod_deinit(struct module_env* env, int id) { void dynlibmod_operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* outbound) { struct dynlibmod_env* de = qstate->env->modinfo[id]; - struct dynlibmod_qstate* dq = (struct dynlibmod_qstate*)qstate->minfo[id]; - void * data = dq == NULL ? NULL : dq->data; - int ret = de->func_operate(id, event, qstate, data); - if (ret != 1) { - log_err("dynlibmod: dynamic library returned bad code from operate %d.", ret); - qstate->ext_state[id] = module_error; - } + de->func_operate(qstate, event, id, outbound); } /** dynlib module */ void dynlibmod_inform_super(struct module_qstate* qstate, int id, struct module_qstate* super) { struct dynlibmod_env* de = qstate->env->modinfo[id]; - struct dynlibmod_qstate* dq = (struct dynlibmod_qstate*)qstate->minfo[id]; - void * data = dq == NULL ? NULL : dq->data; - int ret = de->func_inform(id, qstate, super, data); - if (ret != 1) { - log_err("dynlibmod: dynamic library returned bad code from inform_super %d.", ret); - qstate->ext_state[id] = module_error; - } + de->func_inform(qstate, id, super); } /** dynlib module cleanup query state */ void dynlibmod_clear(struct module_qstate* qstate, int id) { - struct dynlibmod_qstate* dq; - if (qstate == NULL) - return; + struct dynlibmod_env* de = qstate->env->modinfo[id]; - dq = (struct dynlibmod_qstate*)qstate->minfo[id]; - verbose(VERB_ALGO, "dynlibmod: clear, id: %d, dq:%p", id, dq); - if(dq != NULL) { - /* Free qstate */ - free(dq); - } - - qstate->minfo[id] = NULL; + de->func_clear(qstate, id); } /** dynlib module alloc size routine */ @@ -218,7 +197,9 @@ size_t dynlibmod_get_mem(struct module_env* env, int id) { verbose(VERB_ALGO, "dynlibmod: get_mem, id: %d, de:%p", id, de); if(!de) return 0; - return sizeof(*de); + + size_t size = de->func_get_mem(env, id); + return size + sizeof(*de); } /** diff --git a/dynlibmod/dynlibmod.h b/dynlibmod/dynlibmod.h index ac5d737b4..1d826a1c8 100644 --- a/dynlibmod/dynlibmod.h +++ b/dynlibmod/dynlibmod.h @@ -1,8 +1,7 @@ /* * dynlibmod.h: module header file * - * Copyright (c) 2009, Zdenek Vasicek (vasicek AT fit.vutbr.cz) - * Marek Vavrusa (xvavru00 AT stud.fit.vutbr.cz) + * Copyright (c) 2019, Peter Munch-Ellingsen (peterme AT peterme.net) * * This software is open source. * diff --git a/dynlibmod/examples/helloworld.c b/dynlibmod/examples/helloworld.c index 76c1df215..6479d8460 100644 --- a/dynlibmod/examples/helloworld.c +++ b/dynlibmod/examples/helloworld.c @@ -17,17 +17,15 @@ #define EXPORT #endif -EXPORT int init(int id, struct config_file* cfg) { +EXPORT void init(struct module_env* env, int id) { log_info("Hello world from init"); - return 1; } -EXPORT int deinit(int id) { +EXPORT void deinit(struct module_env* env, int id) { log_info("Hello world from deinit"); - return 1; } -EXPORT int operate(int id, enum module_ev event, struct module_qstate* qstate, void* data) { +EXPORT void operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* entry) { log_info("Hello world from operate"); if (event == module_event_new || event == module_event_pass) { qstate->ext_state[id] = module_wait_module; @@ -36,10 +34,17 @@ EXPORT int operate(int id, enum module_ev event, struct module_qstate* qstate, v } else { qstate->ext_state[id] = module_error; } - return 1; } -EXPORT int inform_super(int id, struct module_qstate* qstate, struct module_qstate* super, void* data) { +EXPORT void inform_super(struct module_qstate* qstate, int id, struct module_qstate* super) { log_info("Hello world from inform_super"); - return 1; +} + +EXPORT void clear(struct module_qstate* qstate, int id) { + log_info("Hello world from clear"); +} + +EXPORT size_t get_mem(struct module_env* env, int id) { + log_info("Hello world from get_mem"); + return 0; } diff --git a/libunbound/unbound.h b/libunbound/unbound.h index 682ba5530..0527d7b7d 100644 --- a/libunbound/unbound.h +++ b/libunbound/unbound.h @@ -641,6 +641,7 @@ struct ub_shm_stat_info { long long respip; long long dnscrypt_shared_secret; long long dnscrypt_nonce; + long long dynlib; } mem; }; diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c index 01e2385fa..c074b782f 100644 --- a/smallapp/unbound-control.c +++ b/smallapp/unbound-control.c @@ -261,6 +261,9 @@ static void print_mem(struct ub_shm_stat_info* shm_stat, #ifdef USE_IPSECMOD PR_LL("mem.mod.ipsecmod", shm_stat->mem.ipsecmod); #endif +#ifdef WITH_DYNLIBMODULE + PR_LL("mem.mod.dynlib", shm_stat->mem.dynlib); +#endif #ifdef USE_DNSCRYPT PR_LL("mem.cache.dnscrypt_shared_secret", shm_stat->mem.dnscrypt_shared_secret); diff --git a/util/shm_side/shm_main.c b/util/shm_side/shm_main.c index a783c099b..0842b9b3b 100644 --- a/util/shm_side/shm_main.c +++ b/util/shm_side/shm_main.c @@ -280,6 +280,10 @@ void shm_main_run(struct worker *worker) #ifdef USE_IPSECMOD shm_stat->mem.ipsecmod = (long long)mod_get_mem(&worker->env, "ipsecmod"); +#endif +#ifdef WITH_DYNLIBMODULE + shm_stat->mem.dynlib = (long long)mod_get_mem(&worker->env, + "dynlib"); #endif } From f177dc974c8797f02ed5c37d2b0aa3103a64a708 Mon Sep 17 00:00:00 2001 From: PMunch Date: Mon, 21 Oct 2019 15:02:03 +0200 Subject: [PATCH 3/7] Add support for multiple dynamic modules Allows the use of multiple dynamic modules. Simply add more "dynlib" entries to the "modules-config" and the same amount of "dynlib-file" entries in the dynlib configuration block. --- doc/example.conf.in | 3 ++- doc/unbound.conf.5.in | 6 ++++-- dynlibmod/dynlibmod.c | 38 ++++++++++++++++++++++++++------------ util/config_file.c | 6 +++--- util/config_file.h | 2 +- util/configparser.y | 4 ++-- 6 files changed, 38 insertions(+), 21 deletions(-) diff --git a/doc/example.conf.in b/doc/example.conf.in index ab6b43baa..5820e5e0a 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -842,7 +842,8 @@ python: # o list dynlib in the module-config string (above) to enable. # It can be placed anywhere, the dynlib module is only a very thin wrapper # to load modules dynamically. -# o and give a dynlib-file to run. +# o and give a dynlib-file to run. If more than one dynlib entry is listed in +# the module-config then you need one dynlib-file per instance. dynlib: # Script file to load # dynlib-file: "@UNBOUND_SHARE_DIR@/dynlib.so" diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 434467995..d0583d500 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -1792,14 +1792,16 @@ clause gives the settings for the \fIdynlib\fR(1) module. This module is only a very small wrapper that allows dynamic modules to be loaded on runtime instead of being compiled into the application. To enable the dynlib module it has to be compiled into the daemon, and the word "dynlib" has to be put in the -\fBmodule\-config:\fR option. +\fBmodule\-config:\fR option. Multiple instances of dynamic libraries are +supported by adding the word "dynlib" more than once. .LP The \fBdynlib\-file:\fR path should be specified as an absolute path relative to the new path set by \fBchroot:\fR option, or as a relative path to the working directory. .TP .B dynlib\-file: \fI\fR -The dynamic library file to load. +The dynamic library file to load. Repeat this option for every dynlib module +instance added to the \fBmodule\-config:\fR option. .SS "DNS64 Module Options" .LP The dns64 module must be configured in the \fBmodule\-config:\fR "dns64 diff --git a/dynlibmod/dynlibmod.c b/dynlibmod/dynlibmod.c index 3592f7de7..54b2a5816 100644 --- a/dynlibmod/dynlibmod.c +++ b/dynlibmod/dynlibmod.c @@ -81,34 +81,48 @@ struct dynlibmod_env { struct module_qstate* qstate; }; +/** + * counter for dynamic library module instances + * incremeted by dynlibmod_init + */ +int dynlib_mod_count = 0; + /** dynlib module init */ int dynlibmod_init(struct module_env* env, int id) { + int dynlib_mod_idx = dynlib_mod_count++; + struct config_strlist* cfg_item = env->cfg->dynlib_file; struct dynlibmod_env* de = (struct dynlibmod_env*)calloc(1, sizeof(struct dynlibmod_env)); __DYNMOD dynamic_library; if (!de) { - log_err("dynlibmod: malloc failure"); + log_err("dynlibmod[%d]: malloc failure", dynlib_mod_idx); return 0; } env->modinfo[id] = (void*) de; - de->fname = env->cfg->dynlib_file; - if (de->fname == NULL || de->fname[0] == 0) { - log_err("dynlibmod: no dynamic library given."); + de->fname = NULL; + for(int i = dynlib_mod_idx; + i != 0 && cfg_item != NULL; + i--, cfg_item = cfg_item->next) {} + + if (cfg_item == NULL || cfg_item->str == NULL || cfg_item->str[0] == 0) { + log_err("dynlibmod[%d]: no dynamic library given.", dynlib_mod_idx); return 0; + } else { + de->fname = cfg_item->str; } - verbose(VERB_ALGO, "dynlibmod: Trying to load library %s", de->fname); + verbose(VERB_ALGO, "dynlibmod[%d]: Trying to load library %s", dynlib_mod_idx, de->fname); dynamic_library = open_library(de->fname); if (dynamic_library == NULL) { log_dlerror(); - log_err("dynlibmod: unable to load dynamic library \"%s\".", de->fname); + log_err("dynlibmod[%d]: unable to load dynamic library \"%s\".", dynlib_mod_idx, de->fname); return 0; } else { __DYNSYM initializer = __LOADSYM(dynamic_library,"init"); if (initializer == NULL) { log_dlerror(); - log_err("dynlibmod: unable to load init procedure from dynamic library \"%s\".", de->fname); + log_err("dynlibmod[%d]: unable to load init procedure from dynamic library \"%s\".", dynlib_mod_idx, de->fname); return 0; } else { de->func_init = (func_init_t) initializer; @@ -116,7 +130,7 @@ int dynlibmod_init(struct module_env* env, int id) { __DYNSYM deinitializer = __LOADSYM(dynamic_library,"deinit"); if (deinitializer == NULL) { log_dlerror(); - log_err("dynlibmod: unable to load deinit procedure from dynamic library \"%s\".", de->fname); + log_err("dynlibmod[%d]: unable to load deinit procedure from dynamic library \"%s\".", dynlib_mod_idx, de->fname); return 0; } else { de->func_deinit = (func_deinit_t) deinitializer; @@ -124,7 +138,7 @@ int dynlibmod_init(struct module_env* env, int id) { __DYNSYM operate = __LOADSYM(dynamic_library,"operate"); if (operate == NULL) { log_dlerror(); - log_err("dynlibmod: unable to load operate procedure from dynamic library \"%s\".", de->fname); + log_err("dynlibmod[%d]: unable to load operate procedure from dynamic library \"%s\".", dynlib_mod_idx, de->fname); return 0; } else { de->func_operate = (func_operate_t) operate; @@ -132,7 +146,7 @@ int dynlibmod_init(struct module_env* env, int id) { __DYNSYM inform = __LOADSYM(dynamic_library,"inform_super"); if (inform == NULL) { log_dlerror(); - log_err("dynlibmod: unable to load inform_super procedure from dynamic library \"%s\".", de->fname); + log_err("dynlibmod[%d]: unable to load inform_super procedure from dynamic library \"%s\".", dynlib_mod_idx, de->fname); return 0; } else { de->func_inform = (func_inform_t) inform; @@ -140,7 +154,7 @@ int dynlibmod_init(struct module_env* env, int id) { __DYNSYM clear = __LOADSYM(dynamic_library,"clear"); if (clear == NULL) { log_dlerror(); - log_err("dynlibmod: unable to load clear procedure from dynamic library \"%s\".", de->fname); + log_err("dynlibmod[%d]: unable to load clear procedure from dynamic library \"%s\".", dynlib_mod_idx, de->fname); return 0; } else { de->func_clear = (func_clear_t) clear; @@ -148,7 +162,7 @@ int dynlibmod_init(struct module_env* env, int id) { __DYNSYM get_mem = __LOADSYM(dynamic_library,"get_mem"); if (get_mem == NULL) { log_dlerror(); - log_err("dynlibmod: unable to load get_mem procedure from dynamic library \"%s\".", de->fname); + log_err("dynlibmod[%d]: unable to load get_mem procedure from dynamic library \"%s\".", dynlib_mod_idx, de->fname); return 0; } else { de->func_get_mem = (func_get_mem_t) get_mem; diff --git a/util/config_file.c b/util/config_file.c index 7a8fb0bbc..1afeb46bf 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -611,7 +611,7 @@ int config_set_option(struct config_file* cfg, const char* opt, else S_STR("control-cert-file:", control_cert_file) else S_STR("module-config:", module_conf) else S_STRLIST("python-script:", python_script) - else S_STR("dynlib-file:", dynlib_file) + else S_STRLIST("dynlib-file:", dynlib_file) else S_YNO("disable-dnssec-lame-check:", disable_dnssec_lame_check) #ifdef CLIENT_SUBNET /* Can't set max subnet prefix here, since that value is used when @@ -1064,7 +1064,7 @@ config_get_option(struct config_file* cfg, const char* opt, else O_YNO(opt, "insecure-lan-zones", insecure_lan_zones) else O_DEC(opt, "max-udp-size", max_udp_size) else O_LST(opt, "python-script", python_script) - else O_STR(opt, "dynlib-file", dynlib_file) + else O_LST(opt, "dynlib-file", dynlib_file) else O_YNO(opt, "disable-dnssec-lame-check", disable_dnssec_lame_check) else O_DEC(opt, "ip-ratelimit", ip_ratelimit) else O_DEC(opt, "ratelimit", ratelimit) @@ -1401,7 +1401,6 @@ config_delete(struct config_file* cfg) free(cfg->version); free(cfg->module_conf); free(cfg->outgoing_avail_ports); - free(cfg->dynlib_file); config_delstrlist(cfg->caps_whitelist); config_delstrlist(cfg->private_address); config_delstrlist(cfg->private_domain); @@ -1441,6 +1440,7 @@ config_delete(struct config_file* cfg) config_deldblstrlist(cfg->ratelimit_for_domain); config_deldblstrlist(cfg->ratelimit_below_domain); config_delstrlist(cfg->python_script); + config_delstrlist(cfg->dynlib_file); #ifdef USE_IPSECMOD free(cfg->ipsecmod_hook); config_delstrlist(cfg->ipsecmod_whitelist); diff --git a/util/config_file.h b/util/config_file.h index 6706ab08b..972c70fb8 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -440,7 +440,7 @@ struct config_file { struct config_strlist* python_script; /** Dynamic library file */ - char* dynlib_file; + struct config_strlist* dynlib_file; /** Use systemd socket activation. */ int use_systemd; diff --git a/util/configparser.y b/util/configparser.y index 5f58d75b3..f180f188f 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -2754,8 +2754,8 @@ content_dl: dl_file dl_file: VAR_DYNLIB_FILE STRING_ARG { OUTYY(("P(dynlib-file:%s)\n", $2)); - free(cfg_parser->cfg->dynlib_file); - cfg_parser->cfg->dynlib_file = $2; + if(!cfg_strlist_append_ex(&cfg_parser->cfg->dynlib_file, $2)) + yyerror("out of memory"); } server_disable_dnssec_lame_check: VAR_DISABLE_DNSSEC_LAME_CHECK STRING_ARG { From f79811435ba13cab2e397be4918fdd89530dbc86 Mon Sep 17 00:00:00 2001 From: PMunch Date: Tue, 22 Oct 2019 08:42:00 +0200 Subject: [PATCH 4/7] Cleanup some minor things in dynlibmod --- dynlibmod/dynlibmod.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/dynlibmod/dynlibmod.c b/dynlibmod/dynlibmod.c index 54b2a5816..977f45047 100644 --- a/dynlibmod/dynlibmod.c +++ b/dynlibmod/dynlibmod.c @@ -25,12 +25,12 @@ void log_dlerror() { NULL )) { - DWORD dwBytesWritten; log_err("dynlibmod: %s (%ld)", MessageBuffer, dwLastError); LocalFree(MessageBuffer); } } + HMODULE open_library(const char* fname) { return LoadLibrary(fname); } @@ -42,6 +42,7 @@ HMODULE open_library(const char* fname) { void log_dlerror() { log_err("dynlibmod: %s", dlerror()); } + void* open_library(const char* fname) { return dlopen(fname, RTLD_LAZY | RTLD_GLOBAL); } @@ -60,10 +61,8 @@ typedef void (*func_clear_t)(struct module_qstate*, int); typedef size_t (*func_get_mem_t)(struct module_env*, int); struct dynlibmod_env { - /** Dynamic library filename. */ const char* fname; - /** Module init function */ func_init_t func_init; /** Module deinit function */ @@ -76,19 +75,11 @@ struct dynlibmod_env { func_clear_t func_clear; /** Module get_mem function */ func_get_mem_t func_get_mem; - - /** Module qstate. */ - struct module_qstate* qstate; }; -/** - * counter for dynamic library module instances - * incremeted by dynlibmod_init - */ -int dynlib_mod_count = 0; - /** dynlib module init */ int dynlibmod_init(struct module_env* env, int id) { + static int dynlib_mod_count; int dynlib_mod_idx = dynlib_mod_count++; struct config_strlist* cfg_item = env->cfg->dynlib_file; struct dynlibmod_env* de = (struct dynlibmod_env*)calloc(1, sizeof(struct dynlibmod_env)); From d104d3be2243dbf0d678ee5e939acb78637f58e4 Mon Sep 17 00:00:00 2001 From: PMunch Date: Fri, 1 Nov 2019 10:44:26 +0100 Subject: [PATCH 5/7] Add inplace callback to dynlibmod, improve example This adds the possibility to properly register inplace callbacks in the dynamic library module. It works by creating a wrapper procedure that is available to the dynamic library and will call the given callback through a whitelisted callback function. The dynamic library example has already been improved to include comments and some simple examples on allocating and deallocating memory and registering callbacks. --- dynlibmod/dynlibmod.c | 108 +++++++++++++++++++++++--------- dynlibmod/dynlibmod.h | 67 ++++++++++++++++++++ dynlibmod/examples/helloworld.c | 76 +++++++++++++++++++++- util/fptr_wlist.c | 24 +++++++ 4 files changed, 243 insertions(+), 32 deletions(-) diff --git a/dynlibmod/dynlibmod.c b/dynlibmod/dynlibmod.c index 977f45047..7c77009d7 100644 --- a/dynlibmod/dynlibmod.c +++ b/dynlibmod/dynlibmod.c @@ -48,35 +48,6 @@ void* open_library(const char* fname) { } #endif - -/** - * Global state for the module. - */ - -typedef void (*func_init_t)(struct module_env*, int); -typedef void (*func_deinit_t)(struct module_env*, int); -typedef void (*func_operate_t)(struct module_qstate*, enum module_ev, int, struct outbound_entry*); -typedef void (*func_inform_t)(struct module_qstate*, int, struct module_qstate*); -typedef void (*func_clear_t)(struct module_qstate*, int); -typedef size_t (*func_get_mem_t)(struct module_env*, int); - -struct dynlibmod_env { - /** Dynamic library filename. */ - const char* fname; - /** Module init function */ - func_init_t func_init; - /** Module deinit function */ - func_deinit_t func_deinit; - /** Module operate function */ - func_operate_t func_operate; - /** Module super_inform function */ - func_inform_t func_inform; - /** Module clear function */ - func_clear_t func_clear; - /** Module get_mem function */ - func_get_mem_t func_get_mem; -}; - /** dynlib module init */ int dynlibmod_init(struct module_env* env, int id) { static int dynlib_mod_count; @@ -159,6 +130,8 @@ int dynlibmod_init(struct module_env* env, int id) { de->func_get_mem = (func_get_mem_t) get_mem; } } + de->inplace_cb_delete_wrapped = &inplace_cb_delete_wrapped; + de->inplace_cb_register_wrapped = &inplace_cb_register_wrapped; de->func_init(env, id); return 1; } @@ -207,6 +180,83 @@ size_t dynlibmod_get_mem(struct module_env* env, int id) { return size + sizeof(*de); } +int dynlib_inplace_cb_reply_generic(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, int id, + void* callback) { + struct cb_pair* cb_pair = (struct cb_pair*) callback; + ((inplace_cb_reply_func_type*) cb_pair->cb)(qinfo, qstate, rep, rcode, edns, opt_list_out, repinfo, region, id, cb_pair->cb_arg); +} + +int dynlib_inplace_cb_query_generic(struct query_info* qinfo, uint16_t flags, + struct module_qstate* qstate, struct sockaddr_storage* addr, + socklen_t addrlen, uint8_t* zone, size_t zonelen, struct regional* region, + int id, void* callback) { + struct cb_pair* cb_pair = (struct cb_pair*) callback; + ((inplace_cb_query_func_type*) cb_pair->cb)(qinfo, flags, qstate, addr, addrlen, zone, zonelen, region, id, cb_pair->cb_arg); +} + +int dynlib_inplace_cb_edns_back_parsed(struct module_qstate* qstate, + int id, void* cb_args) { + struct cb_pair* cb_pair = (struct cb_pair*) cb_args; + ((inplace_cb_edns_back_parsed_func_type*) cb_pair->cb)(qstate, id, cb_pair->cb_arg); +} + +int dynlib_inplace_cb_query_response(struct module_qstate* qstate, + struct dns_msg* response, int id, void* cb_args) { + struct cb_pair* cb_pair = (struct cb_pair*) cb_args; + ((inplace_cb_query_response_func_type*) cb_pair->cb)(qstate, response, id, cb_pair->cb_arg); +} + +int +inplace_cb_register_wrapped(void* cb, enum inplace_cb_list_type type, void* cbarg, + struct module_env* env, int id) { + struct cb_pair* cb_pair = malloc(sizeof(struct cb_pair)); + cb_pair->cb = cb; + cb_pair->cb_arg = cbarg; + if(type >= inplace_cb_reply && type <= inplace_cb_reply_servfail) { + return inplace_cb_register(&dynlib_inplace_cb_reply_generic, type, (void*) cb_pair, env, id); + } else if(type == inplace_cb_query) { + return inplace_cb_register(&dynlib_inplace_cb_query_generic, type, (void*) cb_pair, env, id); + } else if(type == inplace_cb_query_response) { + return inplace_cb_register(&dynlib_inplace_cb_query_response, type, (void*) cb_pair, env, id); + } else if(type == inplace_cb_edns_back_parsed) { + return inplace_cb_register(&dynlib_inplace_cb_edns_back_parsed, type, (void*) cb_pair, env, id); + } else { + return 0; + } +} + +void +inplace_cb_delete_wrapped(struct module_env* env, enum inplace_cb_list_type type, + int id) { + struct inplace_cb* temp = env->inplace_cb_lists[type]; + struct inplace_cb* prev = NULL; + + while(temp) { + if(temp->id == id) { + if(!prev) { + env->inplace_cb_lists[type] = temp->next; + free(temp->cb_arg); + free(temp); + temp = env->inplace_cb_lists[type]; + } + else { + prev->next = temp->next; + free(temp->cb_arg); + free(temp); + temp = prev->next; + } + } + else { + prev = temp; + temp = temp->next; + } + } +} + + /** * The module function block */ diff --git a/dynlibmod/dynlibmod.h b/dynlibmod/dynlibmod.h index 1d826a1c8..a8ba23cc9 100644 --- a/dynlibmod/dynlibmod.h +++ b/dynlibmod/dynlibmod.h @@ -67,4 +67,71 @@ void dynlibmod_clear(struct module_qstate* qstate, int id); /** dynlib module alloc size routine */ size_t dynlibmod_get_mem(struct module_env* env, int id); +int dynlib_inplace_cb_reply_generic(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, int id, + void* callback); + +int dynlib_inplace_cb_query_generic(struct query_info* qinfo, uint16_t flags, + struct module_qstate* qstate, struct sockaddr_storage* addr, + socklen_t addrlen, uint8_t* zone, size_t zonelen, struct regional* region, + int id, void* callback); + +int dynlib_inplace_cb_edns_back_parsed(struct module_qstate* qstate, + int id, void* cb_args); + +int dynlib_inplace_cb_query_response(struct module_qstate* qstate, + struct dns_msg* response, int id, void* cb_args); + +int +inplace_cb_register_wrapped(void* cb, enum inplace_cb_list_type type, void* cbarg, + struct module_env* env, int id); + +void +inplace_cb_delete_wrapped(struct module_env* env, enum inplace_cb_list_type type, + int id); + +struct cb_pair { + void *cb; + void *cb_arg; +}; + +/** + * Global state for the module. + */ + +typedef void (*func_init_t)(struct module_env*, int); +typedef void (*func_deinit_t)(struct module_env*, int); +typedef void (*func_operate_t)(struct module_qstate*, enum module_ev, int, struct outbound_entry*); +typedef void (*func_inform_t)(struct module_qstate*, int, struct module_qstate*); +typedef void (*func_clear_t)(struct module_qstate*, int); +typedef size_t (*func_get_mem_t)(struct module_env*, int); +typedef void (*inplace_cb_delete_wrapped_t)(struct module_env*, enum inplace_cb_list_type, int); +typedef int (*inplace_cb_register_wrapped_t)(void*, enum inplace_cb_list_type, void*, struct module_env*, int); + + +struct dynlibmod_env { + /** Dynamic library filename. */ + const char* fname; + /** Module init function */ + func_init_t func_init; + /** Module deinit function */ + func_deinit_t func_deinit; + /** Module operate function */ + func_operate_t func_operate; + /** Module super_inform function */ + func_inform_t func_inform; + /** Module clear function */ + func_clear_t func_clear; + /** Module get_mem function */ + func_get_mem_t func_get_mem; + /** Wrapped inplace callback functions to circumvent callback whitelisting */ + inplace_cb_delete_wrapped_t inplace_cb_delete_wrapped; + inplace_cb_register_wrapped_t inplace_cb_register_wrapped; + /** Pointer to any data the dynamic library might want to keep */ + void *dyn_env; +}; + + #endif /* DYNLIBMOD_H */ diff --git a/dynlibmod/examples/helloworld.c b/dynlibmod/examples/helloworld.c index 6479d8460..3d7708af0 100644 --- a/dynlibmod/examples/helloworld.c +++ b/dynlibmod/examples/helloworld.c @@ -4,31 +4,70 @@ * 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.a + * x86_64-w64-mingw32-gcc -m64 -I../.. -shared -Wall -Werror -fpic + * -o helloworld.dll helloworld.c -L../.. -l:libunbound.a * to cross-compile a 64-bit Windows DLL. */ #include "../../config.h" #include "../../util/module.h" +#include "../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, 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 void init(struct module_env* env, int id) { log_info("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; } +/* 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("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); } -EXPORT void operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* entry) { +/* 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("Hello world from operate"); 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("Already has data!"); + qstate->ext_state[id] = module_error; + } } else if (event == module_event_moddone) { qstate->ext_state[id] = module_finished; } else { @@ -36,15 +75,46 @@ EXPORT void operate(struct module_qstate* qstate, enum module_ev event, int id, } } -EXPORT void inform_super(struct module_qstate* qstate, int id, struct module_qstate* super) { +/* 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("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("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("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, int id, + void* callback) { + log_info("Hello world from callback"); + struct dynlibmod_env* env = qstate->env->modinfo[id]; + if (env->dyn_env != NULL) { + log_info("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; +} diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index 20eaeb137..dfd05957f 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -585,18 +585,30 @@ int fptr_whitelist_inplace_cb_reply_generic(inplace_cb_reply_func_type* fptr, if(type == inplace_cb_reply) { #ifdef WITH_PYTHONMODULE if(fptr == &python_inplace_cb_reply_generic) return 1; +#endif +#ifdef WITH_DYNLIBMODULE + if(fptr == &dynlib_inplace_cb_reply_generic) return 1; #endif } else if(type == inplace_cb_reply_cache) { #ifdef WITH_PYTHONMODULE if(fptr == &python_inplace_cb_reply_generic) return 1; +#endif +#ifdef WITH_DYNLIBMODULE + if(fptr == &dynlib_inplace_cb_reply_generic) return 1; #endif } else if(type == inplace_cb_reply_local) { #ifdef WITH_PYTHONMODULE if(fptr == &python_inplace_cb_reply_generic) return 1; +#endif +#ifdef WITH_DYNLIBMODULE + if(fptr == &dynlib_inplace_cb_reply_generic) return 1; #endif } else if(type == inplace_cb_reply_servfail) { #ifdef WITH_PYTHONMODULE if(fptr == &python_inplace_cb_reply_generic) return 1; +#endif +#ifdef WITH_DYNLIBMODULE + if(fptr == &dynlib_inplace_cb_reply_generic) return 1; #endif } return 0; @@ -611,6 +623,10 @@ int fptr_whitelist_inplace_cb_query(inplace_cb_query_func_type* fptr) #ifdef WITH_PYTHONMODULE if(fptr == &python_inplace_cb_query_generic) return 1; +#endif +#ifdef WITH_DYNLIBMODULE + if(fptr == &dynlib_inplace_cb_query_generic) + return 1; #endif (void)fptr; return 0; @@ -624,6 +640,10 @@ int fptr_whitelist_inplace_cb_edns_back_parsed( return 1; #else (void)fptr; +#endif +#ifdef WITH_DYNLIBMODULE + if(fptr == &dynlib_inplace_cb_edns_back_parsed) + return 1; #endif return 0; } @@ -636,6 +656,10 @@ int fptr_whitelist_inplace_cb_query_response( return 1; #else (void)fptr; +#endif +#ifdef WITH_DYNLIBMODULE + if(fptr == &dynlib_inplace_cb_query_response) + return 1; #endif return 0; } From 5eabc429bce05e40f1101e6a0b164e30de4bc9be Mon Sep 17 00:00:00 2001 From: PMunch Date: Mon, 4 Nov 2019 16:03:04 +0100 Subject: [PATCH 6/7] Add "dynlib" prefix to example output, log queries This adds the "dynlib: " prefix to all messages created by the `helloworld.c` dynamic library example. It also adds logging of queries that pass through `operate`. --- dynlibmod/examples/helloworld.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/dynlibmod/examples/helloworld.c b/dynlibmod/examples/helloworld.c index 3d7708af0..d001b3ee8 100644 --- a/dynlibmod/examples/helloworld.c +++ b/dynlibmod/examples/helloworld.c @@ -11,6 +11,7 @@ #include "../../config.h" #include "../../util/module.h" +#include "../../sldns/parseutil.h" #include "../dynlibmod.h" /* Declare the EXPORT macro that expands to exporting the symbol for DLLs when @@ -33,7 +34,7 @@ int reply_callback(struct query_info* qinfo, /* 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 void init(struct module_env* env, int id) { - log_info("Hello world from init"); + 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, @@ -45,7 +46,7 @@ EXPORT void init(struct module_env* env, int id) { /* 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("Hello world from deinit"); + 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); @@ -55,7 +56,13 @@ EXPORT void deinit(struct module_env* env, int id) { * 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("Hello world from operate"); + 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]; @@ -65,7 +72,7 @@ EXPORT void operate(struct module_qstate* qstate, enum module_ev event, ((int *)env->dyn_env)[1] = 102; ((int *)env->dyn_env)[2] = 192; } else { - log_err("Already has data!"); + log_err("dynlib: already has data!"); qstate->ext_state[id] = module_error; } } else if (event == module_event_moddone) { @@ -80,13 +87,13 @@ EXPORT void operate(struct module_qstate* qstate, enum module_ev event, * 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("Hello world from inform_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("Hello world from clear"); + log_info("dynlib: hello world from clear"); struct dynlibmod_env* env = qstate->env->modinfo[id]; if (env->dyn_env != NULL) { free(env->dyn_env); @@ -97,7 +104,7 @@ EXPORT void clear(struct module_qstate* qstate, int id) { /* 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("Hello world from get_mem"); + log_info("dynlib: hello world from get_mem"); return 0; } @@ -108,10 +115,10 @@ int reply_callback(struct query_info* qinfo, struct edns_data* edns, struct edns_option** opt_list_out, struct comm_reply* repinfo, struct regional* region, int id, void* callback) { - log_info("Hello world from callback"); + log_info("dynlib: hello world from callback"); struct dynlibmod_env* env = qstate->env->modinfo[id]; if (env->dyn_env != NULL) { - log_info("Numbers gotten from query: %d, %d, and %d", + 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]); From 8802509a61bd6eb026811fecf474b207b3f42716 Mon Sep 17 00:00:00 2001 From: PMunch Date: Wed, 20 Nov 2019 15:11:51 +0100 Subject: [PATCH 7/7] Fix return code of init to mirror native modules The return code of the init procedure was just set to be 1 in the dynamic library loading module. This ha been rectified and it will now return whatever is returned from the loaded module. --- dynlibmod/dynlibmod.c | 3 +-- dynlibmod/dynlibmod.h | 2 +- dynlibmod/examples/helloworld.c | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dynlibmod/dynlibmod.c b/dynlibmod/dynlibmod.c index 7c77009d7..5a93a7ce5 100644 --- a/dynlibmod/dynlibmod.c +++ b/dynlibmod/dynlibmod.c @@ -132,8 +132,7 @@ int dynlibmod_init(struct module_env* env, int id) { } de->inplace_cb_delete_wrapped = &inplace_cb_delete_wrapped; de->inplace_cb_register_wrapped = &inplace_cb_register_wrapped; - de->func_init(env, id); - return 1; + return de->func_init(env, id); } /** dynlib module deinit */ diff --git a/dynlibmod/dynlibmod.h b/dynlibmod/dynlibmod.h index a8ba23cc9..1097db1e7 100644 --- a/dynlibmod/dynlibmod.h +++ b/dynlibmod/dynlibmod.h @@ -101,7 +101,7 @@ struct cb_pair { * Global state for the module. */ -typedef void (*func_init_t)(struct module_env*, int); +typedef int (*func_init_t)(struct module_env*, int); typedef void (*func_deinit_t)(struct module_env*, int); typedef void (*func_operate_t)(struct module_qstate*, enum module_ev, int, struct outbound_entry*); typedef void (*func_inform_t)(struct module_qstate*, int, struct module_qstate*); diff --git a/dynlibmod/examples/helloworld.c b/dynlibmod/examples/helloworld.c index d001b3ee8..2ec50223e 100644 --- a/dynlibmod/examples/helloworld.c +++ b/dynlibmod/examples/helloworld.c @@ -33,7 +33,7 @@ int reply_callback(struct query_info* qinfo, /* 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 void init(struct module_env* env, int id) { +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, @@ -41,6 +41,7 @@ EXPORT void init(struct module_env* env, int id) { 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