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 <equinox@opensourcerouting.org>
This commit is contained in:
David Lamparter 2016-05-31 19:25:46 +02:00
parent 4c0a782d47
commit 30771d65b2
6 changed files with 329 additions and 4 deletions

View file

@ -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}

View file

@ -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

View file

@ -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();

View file

@ -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);

159
lib/module.c Normal file
View file

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <dlfcn.h>
#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

104
lib/module.h Normal file
View file

@ -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 <stdint.h>
#include <stdbool.h>
#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 */