diff --git a/README.md b/README.md index 041a85583..972b99f9f 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,18 @@ switch is running: The script will display events of significance (table hits / misses, parser transitions, ...) for each packet. +## Loading shared objects dynamically + +Some targets (simple_switch and simple_switch_grpc) let the user load shared +libraries dynamically at runtime. This is done by using the target-specific +command-line option `--load-modules`, which takes as a parameter a +comma-separated list of shared objects. This functionality is currently only +available on systems where `dlopen` is available. Make sure that the shared +objects are visible by the dynamic loader (e.g. by setting `LD_LIBRARY_PATH` +appropriately on Linux). You can control whether this feature is available by +using `--enable-modules` / `--disable-modules` when configuring bmv2. By +default, this feature is enabled when `dlopen` is available. + ## Integrating with Mininet We will provide more information in a separate document. However you can test diff --git a/include/bm/bm_sim/target_parser.h b/include/bm/bm_sim/target_parser.h index f9a998fa2..67c78280e 100644 --- a/include/bm/bm_sim/target_parser.h +++ b/include/bm/bm_sim/target_parser.h @@ -124,6 +124,30 @@ class TargetParserBasic : public TargetParserIface { std::unique_ptr var_store; }; +//! Same as TargetParserBasic but supports the '--load-modules' command-line +//! option by default. This option is used to load shared objects dynamically at +//! runtime. Often, these objects / modules contain new primtive action or +//! extern definitions. If you plan on using '--load-modules' in your target, +//! and you plan on providing primitive / extern definitions dynamically, you +//! will need to link your executable with -rdynamic. +class TargetParserBasicWithDynModules : public TargetParserBasic { + public: + TargetParserBasicWithDynModules(); + ~TargetParserBasicWithDynModules(); + + //! See bm::TargetParserIface::parse. Make sure all possible options have been + //! registered using add_string_option(), add_int_option(), add_uint_option() + //! or add_flag_option(). + int parse(const std::vector &more_options, + std::ostream *errstream) override; + + private: + //! Name of the option. + static constexpr char load_modules_option[] = "load-modules"; + + int load_modules(std::ostream *errstream); +}; + } // namespace bm #endif // BM_BM_SIM_TARGET_PARSER_H_ diff --git a/src/bm_sim/target_parser.cpp b/src/bm_sim/target_parser.cpp index 4416d5a24..5e905d88c 100644 --- a/src/bm_sim/target_parser.cpp +++ b/src/bm_sim/target_parser.cpp @@ -18,15 +18,23 @@ * */ +#include + +#ifdef BM_HAVE_DLOPEN +#include +#endif // BM_HAVE_DLOPEN + +#include #include #include -#include -#include #include -#include #include +#include +#include +#include +#include namespace po = boost::program_options; @@ -173,4 +181,54 @@ TargetParserBasic::get_flag_option(const std::string &name, bool *v) const { return var_store->get_option(name, v); } +TargetParserBasicWithDynModules::TargetParserBasicWithDynModules() { +#ifdef BM_ENABLE_MODULES + add_string_option(load_modules_option, + "Load the given .so files (comma-separated) as modules."); +#endif // BM_ENABLE_MODULES +} + +TargetParserBasicWithDynModules::~TargetParserBasicWithDynModules() = default; + +int +TargetParserBasicWithDynModules::parse( + const std::vector &more_options, + std::ostream *errstream) { + int rc = 0; + if ((rc = TargetParserBasic::parse(more_options, errstream)) != 0) return rc; +#ifdef BM_ENABLE_MODULES + if ((rc = load_modules(errstream)) != 0) return rc; +#endif // BM_ENABLE_MODULES + return 0; +} + +int +TargetParserBasicWithDynModules::load_modules(std::ostream *errstream) { + _BM_UNUSED(errstream); +#ifdef BM_ENABLE_MODULES + std::string modules; + ReturnCode retval = get_string_option(load_modules_option, &modules); + if (retval == ReturnCode::OPTION_NOT_PROVIDED) + return 0; + if (retval != ReturnCode::SUCCESS) + return 1; // Unexpected error + std::istringstream iss(modules); + std::string module; + while (std::getline(iss, module, ',')) { +#ifdef BM_HAVE_DLOPEN + if (!dlopen(module.c_str(), RTLD_NOW | RTLD_GLOBAL)) { + *errstream << "WARNING: Skipping module: " << module << ": " + << dlerror() << std::endl; + } +#else // BM_HAVE_DLOPEN +#error modules enabled, but no loading method available +#endif // BM_HAVE_DLOPEN + } +#endif // BM_ENABLE_MODULES + return 0; +} + +/* static */ +constexpr char TargetParserBasicWithDynModules::load_modules_option[]; + } // namespace bm diff --git a/targets/simple_switch/main.cpp b/targets/simple_switch/main.cpp index 93e5a5c24..d19b71950 100644 --- a/targets/simple_switch/main.cpp +++ b/targets/simple_switch/main.cpp @@ -22,81 +22,14 @@ #include -#ifdef BM_HAVE_DLOPEN -# include -#endif // BM_HAVE_DLOPEN #include #include #include -#include -#include -#include - #include "simple_switch.h" namespace { SimpleSwitch *simple_switch; - -std::string load_modules_option = "load-modules"; - -class SimpleSwitchParser : public bm::TargetParserBasic { - public: - SimpleSwitchParser() { - add_flag_option("enable-swap", - "enable JSON swapping at runtime"); -#ifdef BM_ENABLE_MODULES - add_string_option(load_modules_option, - "load the given .so files as modules"); -#endif // BM_ENABLE_MODULES - } - - int parse(const std::vector &more_options, - std::ostream *errstream) override { - int result = ::bm::TargetParserBasic::parse(more_options, errstream); -#ifdef BM_ENABLE_MODULES - load_modules(errstream); -#endif // BM_ENABLE_MODULES - set_enable_swap(); - return result; - } - - protected: -#ifdef BM_ENABLE_MODULES - int load_modules(std::ostream *errstream) { - std::string modules; - ReturnCode retval = get_string_option(load_modules_option, &modules); - if (retval == ReturnCode::OPTION_NOT_PROVIDED) { - return 0; - } - if (retval != ReturnCode::SUCCESS) { - return -1; // Unexpected error - } - std::istringstream iss(modules); - std::string module; - while (std::getline(iss, module, ',')) { -# ifdef BM_HAVE_DLOPEN - if (!dlopen(module.c_str(), RTLD_NOW | RTLD_GLOBAL)) { - *errstream << "WARNING: Skipping module: " << module << ": " - << dlerror() << std::endl; - } -# else // BM_HAVE_DLOPEN - #error modules enabled, but no loading method available -# endif // BM_HAVE_DLOPEN - } - return 0; - } -#endif // BM_ENABLE_MODULES - - void set_enable_swap() { - bool enable_swap = false; - if (get_flag_option("enable-swap", &enable_swap) != ReturnCode::SUCCESS) - std::exit(1); - if (enable_swap) simple_switch->enable_config_swap(); - } -}; - -SimpleSwitchParser *simple_switch_parser; } // namespace namespace sswitch_runtime { @@ -106,11 +39,20 @@ shared_ptr get_handler(SimpleSwitch *sw); int main(int argc, char* argv[]) { simple_switch = new SimpleSwitch(); - simple_switch_parser = new SimpleSwitchParser(); + bm::TargetParserBasicWithDynModules simple_switch_parser; + simple_switch_parser.add_flag_option("enable-swap", + "enable JSON swapping at runtime"); int status = simple_switch->init_from_command_line_options( - argc, argv, simple_switch_parser); + argc, argv, &simple_switch_parser); if (status != 0) std::exit(status); + bool enable_swap_flag = false; + if (simple_switch_parser.get_flag_option("enable-swap", &enable_swap_flag) + != bm::TargetParserBasic::ReturnCode::SUCCESS) { + std::exit(1); + } + if (enable_swap_flag) simple_switch->enable_config_swap(); + int thrift_port = simple_switch->get_runtime_port(); bm_runtime::start_server(simple_switch, thrift_port); using ::sswitch_runtime::SimpleSwitchIf; diff --git a/targets/simple_switch_grpc/Makefile.am b/targets/simple_switch_grpc/Makefile.am index 26c3ba53a..1a8510cc4 100644 --- a/targets/simple_switch_grpc/Makefile.am +++ b/targets/simple_switch_grpc/Makefile.am @@ -27,7 +27,8 @@ libsimple_switch_grpc.la # We follow this tutorial to link with grpc++_reflection: # https://github.com/grpc/grpc/blob/master/doc/server_reflection_tutorial.md simple_switch_grpc_LDFLAGS = \ --Wl,--no-as-needed,-lgrpc++_reflection,--as-needed +-Wl,--no-as-needed,-lgrpc++_reflection,--as-needed \ +-rdynamic lib_LTLIBRARIES = libbm_grpc_dataplane.la noinst_LTLIBRARIES = libsimple_switch_grpc.la diff --git a/targets/simple_switch_grpc/main.cpp b/targets/simple_switch_grpc/main.cpp index 53fe0f44b..b6b3f001f 100644 --- a/targets/simple_switch_grpc/main.cpp +++ b/targets/simple_switch_grpc/main.cpp @@ -27,7 +27,7 @@ int main(int argc, char* argv[]) { - bm::TargetParserBasic simple_switch_parser; + bm::TargetParserBasicWithDynModules simple_switch_parser; simple_switch_parser.add_flag_option( "disable-swap", "disable JSON swapping at runtime");