From 30771d65b23e4ef15a8298357b70fc083c699750 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Tue, 31 May 2016 19:25:46 +0200 Subject: [PATCH] lib: dynamic module loading This adds a "-M" option to each daemon, to load dynamic modules at startup. Modules are by default located in /usr/lib/frr/modules (lib64 if appropriate). Unloading or loading at runtime is not supported at this point to keep things simple. Signed-off-by: David Lamparter --- configure.ac | 20 ++++++ lib/Makefile.am | 5 +- lib/libfrr.c | 32 +++++++++- lib/libfrr.h | 13 +++- lib/module.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/module.h | 104 +++++++++++++++++++++++++++++++ 6 files changed, 329 insertions(+), 4 deletions(-) create mode 100644 lib/module.c create mode 100644 lib/module.h diff --git a/configure.ac b/configure.ac index e46e44a8b7..2a8cab34f1 100755 --- a/configure.ac +++ b/configure.ac @@ -55,6 +55,13 @@ dnl XXX add --pkgsrcrcdir to autoconf standard directory list somehow AC_SUBST(pkgsrcdir) AC_SUBST(pkgsrcrcdir) +AC_ARG_WITH([moduledir], [AS_HELP_STRING([--with-moduledir=DIR], [module directory (${libdir}/frr/modules)])], [ + moduledir="$withval" +], [ + moduledir="\${libdir}/frr/modules" +]) +AC_SUBST([moduledir], [$moduledir]) + AC_ARG_ENABLE(tcmalloc, AS_HELP_STRING([--enable-tcmalloc], [Turn on tcmalloc]), [case "${enableval}" in @@ -1347,6 +1354,14 @@ int main(void); AC_DEFINE_UNQUOTED(AS_TR_CPP(SNMP_${SNMP_METHOD}),,SNMP method to interface with snmpd) fi +dnl ------ +dnl dlopen +dnl ------ +AC_SEARCH_LIBS(dlopen, [dl dld], [], [ + AC_MSG_ERROR([unable to find the dlopen()]) +]) + + dnl --------------------------- dnl sockaddr and netinet checks dnl --------------------------- @@ -1646,14 +1661,18 @@ AC_DEFINE_UNQUOTED(VTYSH_BIN_PATH, "$vtysh_bin",path to vtysh binary) CFG_SYSCONF="$sysconfdir" CFG_SBIN="$sbindir" CFG_STATE="$frr_statedir" +CFG_MODULE="$moduledir" for I in 1 2 3 4 5 6 7 8 9 10; do eval CFG_SYSCONF="\"$CFG_SYSCONF\"" eval CFG_SBIN="\"$CFG_SBIN\"" eval CFG_STATE="\"$CFG_STATE\"" + eval CFG_MODULE="\"$CFG_MODULE\"" done AC_SUBST(CFG_SYSCONF) AC_SUBST(CFG_SBIN) AC_SUBST(CFG_STATE) +AC_SUBST(CFG_MODULE) +AC_DEFINE_UNQUOTED(MODULE_PATH, "$CFG_MODULE", path to modules) dnl --------------------------- dnl Check htonl works correctly @@ -1728,6 +1747,7 @@ linker flags : ${LDFLAGS} ${LIBS} ${LIBCAP} ${LIBREADLINE} ${LIBM} state file directory : ${frr_statedir} config file directory : `eval echo \`echo ${sysconfdir}\`` example directory : `eval echo \`echo ${exampledir}\`` +module directory : ${CFG_MODULE} user to run as : ${enable_user} group to run as : ${enable_group} group for vty sockets : ${enable_vty_group} diff --git a/lib/Makefile.am b/lib/Makefile.am index 1a8c7af42b..5731440640 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -31,7 +31,9 @@ libfrr_la_SOURCES = \ spf_backoff.c \ libfrr.c \ strlcpy.c \ - strlcat.c + strlcat.c \ + module.c \ + # end BUILT_SOURCES = route_types.h gitversion.h command_parse.h command_lex.h @@ -54,6 +56,7 @@ pkginclude_HEADERS = \ monotime.h \ spf_backoff.h \ srcdest_table.h \ + module.h \ libfrr.h \ # end diff --git a/lib/libfrr.c b/lib/libfrr.c index b7ce0679c3..5c047040ae 100644 --- a/lib/libfrr.c +++ b/lib/libfrr.c @@ -28,6 +28,7 @@ #include "memory_vty.h" #include "zclient.h" #include "log_int.h" +#include "module.h" const char frr_sysconfdir[] = SYSCONFDIR; const char frr_vtydir[] = DAEMON_VTY_DIR; @@ -64,14 +65,16 @@ static const struct option lo_always[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { "daemon", no_argument, NULL, 'd' }, + { "module", no_argument, NULL, 'M' }, { "vty_socket", required_argument, NULL, OPTION_VTYSOCK }, { NULL } }; static const struct optspec os_always = { - "hvdi:", + "hvdM:", " -h, --help Display this help and exit\n" " -v, --version Print program version\n" " -d, --daemon Runs in daemon mode\n" + " -M, --module Load specified module\n" " --vty_socket Override vty socket path\n", lo_always }; @@ -184,12 +187,18 @@ void frr_help_exit(int status) exit(status); } +struct option_chain { + struct option_chain *next; + const char *arg; +}; +static struct option_chain *modules = NULL, **modnext = &modules; static int errors = 0; static int frr_opt(int opt) { static int vty_port_set = 0; static int vty_addr_set = 0; + struct option_chain *oc; char *err; switch (opt) { @@ -203,6 +212,13 @@ static int frr_opt(int opt) case 'd': di->daemon_mode = 1; break; + case 'M': + oc = XMALLOC(MTYPE_TMP, sizeof(*oc)); + oc->arg = optarg; + oc->next = NULL; + *modnext = oc; + modnext = &oc->next; + break; case 'i': if (di->flags & FRR_NO_CFG_PID_DRY) return 1; @@ -298,6 +314,9 @@ int frr_getopt(int argc, char * const argv[], int *longindex) struct thread_master *frr_init(void) { struct thread_master *master; + struct option_chain *oc; + struct frrmod_runtime *module; + char moderr[256]; srandom(time(NULL)); @@ -307,6 +326,17 @@ struct thread_master *frr_init(void) zlog_set_level (ZLOG_DEST_SYSLOG, zlog_default->default_lvl); #endif + frrmod_init(di->module); + while (modules) { + modules = (oc = modules)->next; + module = frrmod_load(oc->arg, moderr, sizeof(moderr)); + if (!module) { + fprintf(stderr, "%s\n", moderr); + exit(1); + } + XFREE(MTYPE_TMP, oc); + } + zprivs_init(di->privs); master = thread_master_create(); diff --git a/lib/libfrr.h b/lib/libfrr.h index d37f406f5b..71225fad38 100644 --- a/lib/libfrr.h +++ b/lib/libfrr.h @@ -26,6 +26,7 @@ #include "thread.h" #include "log.h" #include "getopt.h" +#include "module.h" #define FRR_NO_PRIVSEP (1 << 0) #define FRR_NO_TCPVTY (1 << 1) @@ -40,6 +41,7 @@ struct frr_daemon_info { const char *name; const char *logname; unsigned short instance; + struct frrmod_runtime *module; char *vty_addr; int vty_port; @@ -67,15 +69,22 @@ struct frr_daemon_info { * i.e. "ZEBRA" or "BGP" * * note that this macro is also a latch-on point for other changes (e.g. - * upcoming plugin support) that need to place some per-daemon things. Each + * upcoming module support) that need to place some per-daemon things. Each * daemon should have one of these. */ #define FRR_DAEMON_INFO(execname, constname, ...) \ static struct frr_daemon_info execname ##_di = { \ .name = # execname, \ .logname = # constname, \ + .module = THIS_MODULE, \ __VA_ARGS__ \ - }; + }; \ + FRR_COREMOD_SETUP( \ + .name = # execname, \ + .description = # execname " daemon", \ + .version = FRR_VERSION, \ + ) \ + /* end */ extern void frr_preinit(struct frr_daemon_info *daemon, int argc, char **argv); diff --git a/lib/module.c b/lib/module.c new file mode 100644 index 0000000000..4ebe3c0da2 --- /dev/null +++ b/lib/module.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "module.h" +#include "memory.h" +#include "version.h" + +DEFINE_MTYPE_STATIC(LIB, MODULE_LOADNAME, "Module loading name") +DEFINE_MTYPE_STATIC(LIB, MODULE_LOADARGS, "Module loading arguments") + +static struct frrmod_info frrmod_default_info = { + .name = "libfrr", + .version = FRR_VERSION, + .description = "libfrr core module", +}; +union _frrmod_runtime_u frrmod_default = { + .r.info = &frrmod_default_info, + .r.finished_loading = 1, +}; + +// if defined(HAVE_SYS_WEAK_ALIAS_ATTRIBUTE) +// union _frrmod_runtime_u _frrmod_this_module +// __attribute__((weak, alias("frrmod_default"))); +// elif defined(HAVE_SYS_WEAK_ALIAS_PRAGMA) +#pragma weak _frrmod_this_module = frrmod_default +// else +// error need weak symbol support +// endif + +struct frrmod_runtime *frrmod_list = &frrmod_default.r; +static struct frrmod_runtime **frrmod_last = &frrmod_default.r.next; +static const char *execname = NULL; + +void frrmod_init(struct frrmod_runtime *modinfo) +{ + modinfo->finished_loading = 1; + *frrmod_last = modinfo; + frrmod_last = &modinfo->next; + + execname = modinfo->info->name; +} + +struct frrmod_runtime *frrmod_load(const char *spec, + char *err, size_t err_len) +{ + void *handle = NULL; + char name[PATH_MAX], fullpath[PATH_MAX], *args; + struct frrmod_runtime *rtinfo, **rtinfop; + const struct frrmod_info *info; + + snprintf(name, sizeof(name), "%s", spec); + args = strchr(name, ':'); + if (args) + *args++ = '\0'; + + if (!strchr(name, '/')) { + if (!handle && execname) { + snprintf(fullpath, sizeof(fullpath), "%s/%s_%s.so", + MODULE_PATH, execname, name); + handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL); + } + if (!handle) { + snprintf(fullpath, sizeof(fullpath), "%s/%s.so", + MODULE_PATH, name); + handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL); + } + } + if (!handle) { + snprintf(fullpath, sizeof(fullpath), "%s", name); + handle = dlopen(fullpath, RTLD_NOW | RTLD_GLOBAL); + } + if (!handle) { + if (err) + snprintf(err, err_len, + "loading module \"%s\" failed: %s", + name, dlerror()); + return NULL; + } + + rtinfop = dlsym(handle, "frr_module"); + if (!rtinfop) { + dlclose(handle); + if (err) + snprintf(err, err_len, + "\"%s\" is not a Quagga module: %s", + name, dlerror()); + return NULL; + } + rtinfo = *rtinfop; + rtinfo->load_name = XSTRDUP(MTYPE_MODULE_LOADNAME, name); + rtinfo->dl_handle = handle; + if (args) + rtinfo->load_args = XSTRDUP(MTYPE_MODULE_LOADARGS, args); + info = rtinfo->info; + + if (rtinfo->finished_loading) { + dlclose(handle); + if (err) + snprintf(err, err_len, + "module \"%s\" already loaded", + name); + goto out_fail; + } + + if (info->init && info->init()) { + dlclose(handle); + if (err) + snprintf(err, err_len, + "module \"%s\" initialisation failed", + name); + goto out_fail; + } + + rtinfo->finished_loading = 1; + + *frrmod_last = rtinfo; + frrmod_last = &rtinfo->next; + return rtinfo; + +out_fail: + if (rtinfo->load_args) + XFREE(MTYPE_MODULE_LOADARGS, rtinfo->load_args); + XFREE(MTYPE_MODULE_LOADNAME, rtinfo->load_name); + return NULL; +} + +#if 0 +void frrmod_unload(struct frrmod_runtime *module) +{ +} +#endif diff --git a/lib/module.h b/lib/module.h new file mode 100644 index 0000000000..cb66e60976 --- /dev/null +++ b/lib/module.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef _FRR_MODULE_H +#define _FRR_MODULE_H + +#include +#include + +#if !defined(__GNUC__) +# error module code needs GCC visibility extensions +#elif __GNUC__ < 4 +# error module code needs GCC visibility extensions +#else +# define DSO_PUBLIC __attribute__ ((visibility ("default"))) +# define DSO_SELF __attribute__ ((visibility ("protected"))) +# define DSO_LOCAL __attribute__ ((visibility ("hidden"))) +#endif + +struct frrmod_runtime; + +struct frrmod_info { + /* single-line few-word title */ + const char *name; + /* human-readable version number, should not contain spaces */ + const char *version; + /* one-paragraph description */ + const char *description; + + int (*init)(void); +}; + +/* primary entry point structure to be present in loadable module under + * "_frrmod_this_module" dlsym() name + * + * note space for future extensions is reserved below, so other modules + * (e.g. memory management, hooks) can add fields + * + * const members/info are in frrmod_info. + */ +struct frrmod_runtime { + struct frrmod_runtime *next; + + const struct frrmod_info *info; + void *dl_handle; + bool finished_loading; + + char *load_name; + char *load_args; +}; + +/* space-reserving foo */ +struct _frrmod_runtime_size { + struct frrmod_runtime r; + /* this will barf if frrmod_runtime exceeds 1024 bytes ... */ + uint8_t space[1024 - sizeof(struct frrmod_runtime)]; +}; +union _frrmod_runtime_u { + struct frrmod_runtime r; + struct _frrmod_runtime_size s; +}; + +extern union _frrmod_runtime_u _frrmod_this_module; +#define THIS_MODULE (&_frrmod_this_module.r) + +#define FRR_COREMOD_SETUP(...) \ + static const struct frrmod_info _frrmod_info = { __VA_ARGS__ }; \ + DSO_LOCAL union _frrmod_runtime_u _frrmod_this_module = { \ + .r.info = &_frrmod_info, \ + }; +#define FRR_MODULE_SETUP(...) \ + FRR_COREMOD_SETUP(__VA_ARGS__) \ + DSO_SELF struct frrmod_runtime *frr_module = &_frrmod_this_module.r; + +extern struct frrmod_runtime *frrmod_list; + +extern void frrmod_init(struct frrmod_runtime *modinfo); +extern struct frrmod_runtime *frrmod_load(const char *spec, + char *err, size_t err_len); +#if 0 +/* not implemented yet */ +extern void frrmod_unload(struct frrmod_runtime *module); +#endif + +#endif /* _FRR_MODULE_H */ -- 2.39.5