diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index c5f4931330b6e758725ac7a299f5dabfa60f3bc3..f312d54ced444a15b09c8182ed303af9ce09c11b 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -5,30 +5,36 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include -#include + #include +#include +#include #include #include -#include -#include #include #include #include #include #include +#include +#include +#include #include #include #include -#include +#include + #include + #include using namespace isc::config; -using namespace isc::db; using namespace isc::data; +using namespace isc::db; using namespace isc::dhcp; using namespace isc::hooks; using namespace isc::stats; +using namespace isc::util; using namespace std; namespace { @@ -84,9 +90,10 @@ ControlledDhcpv4Srv::init(const std::string& file_name) { // Configure the server using JSON file. ConstElementPtr result = loadConfigFile(file_name); + int rcode; ConstElementPtr comment = isc::config::parseAnswer(rcode, result); - if (rcode != 0) { + if (rcode != CONTROL_RESULT_SUCCESS) { string reason = comment ? comment->stringValue() : "no details available"; isc_throw(isc::BadValue, reason); @@ -124,8 +131,6 @@ ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) { // configuration from a JSON file. isc::data::ConstElementPtr json; - isc::data::ConstElementPtr dhcp4; - isc::data::ConstElementPtr logger; isc::data::ConstElementPtr result; // Basic sanity check: file name must not be empty. @@ -164,11 +169,15 @@ ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) { "processCommand(\"config-set\", json)"); } + // @todo enable multi-threading - disabled for now + MultiThreadingMgr::instance().apply(false, + CfgMgr::instance().getCurrentCfg()->getServerThreadCount()); + // Now check is the returned result is successful (rcode=0) or not // (see @ref isc::config::parseAnswer). int rcode; ConstElementPtr comment = isc::config::parseAnswer(rcode, result); - if (rcode != 0) { + if (rcode != CONTROL_RESULT_SUCCESS) { string reason = comment ? comment->stringValue() : "no details available"; isc_throw(isc::BadValue, reason); @@ -184,38 +193,42 @@ ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) { << file_name << "': " << ex.what()); } + LOG_INFO(dhcp4_logger, DHCP4_MULTI_THREADING_INFO) + .arg(MultiThreadingMgr::instance().getMode()) + .arg(MultiThreadingMgr::instance().getPktThreadPoolSize()) + .arg(CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize()); + return (result); } - ConstElementPtr ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) { if (ControlledDhcpv4Srv::getInstance()) { ControlledDhcpv4Srv::getInstance()->shutdown(); } else { LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING); - ConstElementPtr answer = isc::config::createAnswer(1, - "Shutdown failure."); + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, + "Shutdown failure."); return (answer); } - ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down."); + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, + "Shutting down."); return (answer); } ConstElementPtr ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) { - /// @todo delete any stored CalloutHandles referring to the old libraries /// Get list of currently loaded libraries and reload them. HookLibsCollection loaded = HooksManager::getLibraryInfo(); bool status = HooksManager::loadLibraries(loaded); if (!status) { LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL); - ConstElementPtr answer = isc::config::createAnswer(1, + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "Failed to reload hooks libraries."); return (answer); } - ConstElementPtr answer = isc::config::createAnswer(0, + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, "Hooks libraries successfully reloaded."); return (answer); } @@ -223,7 +236,6 @@ ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) { ConstElementPtr ControlledDhcpv4Srv::commandConfigReloadHandler(const string&, ConstElementPtr /*args*/) { - // Get configuration file name. std::string file = ControlledDhcpv4Srv::getInstance()->getConfigFile(); try { @@ -245,7 +257,7 @@ ControlledDhcpv4Srv::commandConfigGetHandler(const string&, ConstElementPtr /*args*/) { ConstElementPtr config = CfgMgr::instance().getCurrentCfg()->toElement(); - return (createAnswer(0, config)); + return (createAnswer(CONTROL_RESULT_SUCCESS, config)); } ConstElementPtr @@ -269,6 +281,7 @@ ControlledDhcpv4Srv::commandConfigWriteHandler(const string&, if (filename.empty()) { // filename parameter was not specified, so let's use whatever we remember + // from the command-line filename = getConfigFile(); } @@ -303,7 +316,7 @@ ControlledDhcpv4Srv::commandConfigWriteHandler(const string&, ConstElementPtr ControlledDhcpv4Srv::commandConfigSetHandler(const string&, ConstElementPtr args) { - const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error + const int status_code = CONTROL_RESULT_ERROR; ConstElementPtr dhcp4; string message; @@ -374,7 +387,7 @@ ControlledDhcpv4Srv::commandConfigSetHandler(const string&, // the logging first in case there's a configuration failure. int rcode = 0; isc::config::parseAnswer(rcode, result); - if (rcode == 0) { + if (rcode == CONTROL_RESULT_SUCCESS) { CfgMgr::instance().getStagingCfg()->applyLoggingCfg(); // Use new configuration. @@ -496,7 +509,7 @@ ControlledDhcpv4Srv::commandVersionGetHandler(const string&, ConstElementPtr) { ElementPtr extended = Element::create(Dhcpv4Srv::getVersion(true)); ElementPtr arguments = Element::createMap(); arguments->set("extended", extended); - ConstElementPtr answer = isc::config::createAnswer(0, + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, Dhcpv4Srv::getVersion(false), arguments); return (answer); @@ -506,7 +519,7 @@ ConstElementPtr ControlledDhcpv4Srv::commandBuildReportHandler(const string&, ConstElementPtr) { ConstElementPtr answer = - isc::config::createAnswer(0, isc::detail::getConfigReport()); + isc::config::createAnswer(CONTROL_RESULT_SUCCESS, isc::detail::getConfigReport()); return (answer); } @@ -549,7 +562,7 @@ ControlledDhcpv4Srv::commandServerTagGetHandler(const std::string&, ConstElementPtr ControlledDhcpv4Srv::commandConfigBackendPullHandler(const std::string&, - ConstElementPtr) { + ConstElementPtr) { auto ctl_info = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo(); if (!ctl_info) { return (createAnswer(CONTROL_RESULT_EMPTY, "No config backend.")); @@ -598,7 +611,7 @@ ControlledDhcpv4Srv::commandStatusGetHandler(const string&, // todo: number of service threads. - return (createAnswer(0, status)); + return (createAnswer(CONTROL_RESULT_SUCCESS, status)); } ConstElementPtr @@ -612,8 +625,8 @@ ControlledDhcpv4Srv::processCommand(const string& command, ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance(); if (!srv) { - ConstElementPtr no_srv = isc::config::createAnswer(1, - "Server object not initialized, so can't process command '" + + ConstElementPtr no_srv = isc::config::createAnswer(CONTROL_RESULT_ERROR, + "Server object not initialized, can't process command '" + command + "', arguments: '" + txt + "'."); return (no_srv); } @@ -664,11 +677,13 @@ ControlledDhcpv4Srv::processCommand(const string& command, } else if (command == "status-get") { return (srv->commandStatusGetHandler(command, args)); } - ConstElementPtr answer = isc::config::createAnswer(1, - "Unrecognized command:" + command); - return (answer); + + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, + "Unrecognized command: " + + command)); } catch (const Exception& ex) { - return (isc::config::createAnswer(1, "Error while processing command '" + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, + "Error while processing command '" + command + "':" + ex.what() + ", params: '" + txt + "'")); } @@ -687,7 +702,7 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { if (!srv) { err << "Server object not initialized, can't process config."; - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } ConstElementPtr answer = configureDhcp4Server(*srv, config); @@ -697,12 +712,12 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { try { int rcode = 0; isc::config::parseAnswer(rcode, answer); - if (rcode != 0) { + if (rcode != CONTROL_RESULT_SUCCESS) { return (answer); } } catch (const std::exception& ex) { err << "Failed to process configuration:" << ex.what(); - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Re-open lease and host database with new parameters. @@ -714,7 +729,7 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { cfg_db->createManagers(); } catch (const std::exception& ex) { err << "Unable to open database: " << ex.what(); - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Server will start DDNS communications if its enabled. @@ -723,7 +738,7 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { } catch (const std::exception& ex) { err << "Error starting DHCP_DDNS client after server reconfiguration: " << ex.what(); - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Setup DHCPv4-over-DHCPv6 IPC @@ -733,7 +748,7 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { std::ostringstream err; err << "error starting DHCPv4-over-DHCPv6 IPC " " after server reconfiguration: " << ex.what(); - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Configure DHCP packet queueing @@ -742,13 +757,13 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl(); if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, qc)) { LOG_INFO(dhcp4_logger, DHCP4_CONFIG_PACKET_QUEUE) - .arg(IfaceMgr::instance().getPacketQueue4()->getInfoStr()); + .arg(IfaceMgr::instance().getPacketQueue4()->getInfoStr()); } } catch (const std::exception& ex) { err << "Error setting packet queue controls after server reconfiguration: " << ex.what(); - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Configuration may change active interfaces. Therefore, we have to reopen @@ -773,9 +788,10 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { err << "unable to setup timers for periodically running the" " reclamation of the expired leases: " << ex.what() << "."; - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } + // Setup config backend polling, if configured for it. auto ctl_info = CfgMgr::instance().getStagingCfg()->getConfigControlInfo(); if (ctl_info) { long fetch_time = static_cast(ctl_info->getConfigFetchWaitTime()); @@ -843,7 +859,7 @@ ControlledDhcpv4Srv::checkConfig(isc::data::ConstElementPtr config) { if (!srv) { err << "Server object not initialized, can't process config."; - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } return (configureDhcp4Server(*srv, config, true)); @@ -888,18 +904,18 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_P CommandMgr::instance().registerCommand("config-write", boost::bind(&ControlledDhcpv4Srv::commandConfigWriteHandler, this, _1, _2)); - CommandMgr::instance().registerCommand("dhcp-enable", - boost::bind(&ControlledDhcpv4Srv::commandDhcpEnableHandler, this, _1, _2)); - CommandMgr::instance().registerCommand("dhcp-disable", boost::bind(&ControlledDhcpv4Srv::commandDhcpDisableHandler, this, _1, _2)); - CommandMgr::instance().registerCommand("libreload", - boost::bind(&ControlledDhcpv4Srv::commandLibReloadHandler, this, _1, _2)); + CommandMgr::instance().registerCommand("dhcp-enable", + boost::bind(&ControlledDhcpv4Srv::commandDhcpEnableHandler, this, _1, _2)); CommandMgr::instance().registerCommand("leases-reclaim", boost::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, _1, _2)); + CommandMgr::instance().registerCommand("libreload", + boost::bind(&ControlledDhcpv4Srv::commandLibReloadHandler, this, _1, _2)); + CommandMgr::instance().registerCommand("server-tag-get", boost::bind(&ControlledDhcpv4Srv::commandServerTagGetHandler, this, _1, _2)); @@ -916,18 +932,18 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_P CommandMgr::instance().registerCommand("statistic-get", boost::bind(&StatsMgr::statisticGetHandler, _1, _2)); - CommandMgr::instance().registerCommand("statistic-reset", - boost::bind(&StatsMgr::statisticResetHandler, _1, _2)); - - CommandMgr::instance().registerCommand("statistic-remove", - boost::bind(&StatsMgr::statisticRemoveHandler, _1, _2)); - CommandMgr::instance().registerCommand("statistic-get-all", boost::bind(&StatsMgr::statisticGetAllHandler, _1, _2)); + CommandMgr::instance().registerCommand("statistic-reset", + boost::bind(&StatsMgr::statisticResetHandler, _1, _2)); + CommandMgr::instance().registerCommand("statistic-reset-all", boost::bind(&StatsMgr::statisticResetAllHandler, _1, _2)); + CommandMgr::instance().registerCommand("statistic-remove", + boost::bind(&StatsMgr::statisticRemoveHandler, _1, _2)); + CommandMgr::instance().registerCommand("statistic-remove-all", boost::bind(&StatsMgr::statisticRemoveAllHandler, _1, _2)); @@ -995,8 +1011,8 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() { ; } - server_ = NULL; // forget this instance. Noone should call any handlers at - // this stage. + server_ = NULL; // forget this instance. There should be no callback anymore + // at this stage anyway. } void ControlledDhcpv4Srv::sessionReader(void) { @@ -1133,5 +1149,5 @@ ControlledDhcpv4Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg, } } -}; // end of isc::dhcp namespace -}; // end of isc namespace +} // namespace dhcp +} // namespace isc diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h index 0111c9f7b7c745307d3037896f7bec7437849bb0..b50479681a72e8607340271ac298fe998f00ec03 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.h +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h @@ -33,7 +33,7 @@ public: uint16_t client_port = 0); /// @brief Destructor. - ~ControlledDhcpv4Srv(); + virtual ~ControlledDhcpv4Srv(); /// @brief Initializes the server. /// @@ -121,8 +121,8 @@ public: return (server_); } - private: + /// @brief Callback that will be called from iface_mgr when data /// is received over control socket. /// @@ -249,7 +249,6 @@ private: commandDhcpEnableHandler(const std::string& command, isc::data::ConstElementPtr args); - /// @Brief handler for processing 'version-get' command /// /// This handler processes version-get command, which returns @@ -416,7 +415,7 @@ private: /// @brief Static pointer to the sole instance of the DHCP server. /// /// This is required for config and command handlers to gain access to - /// the server + /// the server. Some of them need to be static methods. static ControlledDhcpv4Srv* server_; /// @brief IOService object, used for all ASIO operations. @@ -429,7 +428,7 @@ private: TimerMgrPtr timer_mgr_; }; -}; // namespace isc::dhcp -}; // namespace isc +} // namespace dhcp +} // namespace isc #endif diff --git a/src/bin/dhcp4/dhcp4_messages.cc b/src/bin/dhcp4/dhcp4_messages.cc index c9997d138ede1863641152ed32dc7b6f17644d6d..d8f6239e06aa7ac425c88e33e6ee95a78eb8ebbb 100644 --- a/src/bin/dhcp4/dhcp4_messages.cc +++ b/src/bin/dhcp4/dhcp4_messages.cc @@ -1,4 +1,4 @@ -// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes on Fri Jan 31 2020 15:04 +// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes on Wed Feb 19 2020 16:53 #include #include @@ -81,6 +81,7 @@ extern const isc::log::MessageID DHCP4_INIT_FAIL = "DHCP4_INIT_FAIL"; extern const isc::log::MessageID DHCP4_INIT_REBOOT = "DHCP4_INIT_REBOOT"; extern const isc::log::MessageID DHCP4_LEASE_ADVERT = "DHCP4_LEASE_ADVERT"; extern const isc::log::MessageID DHCP4_LEASE_ALLOC = "DHCP4_LEASE_ALLOC"; +extern const isc::log::MessageID DHCP4_MULTI_THREADING_INFO = "DHCP4_MULTI_THREADING_INFO"; extern const isc::log::MessageID DHCP4_NCR_CREATE = "DHCP4_NCR_CREATE"; extern const isc::log::MessageID DHCP4_NCR_CREATION_FAILED = "DHCP4_NCR_CREATION_FAILED"; extern const isc::log::MessageID DHCP4_NOT_RUNNING = "DHCP4_NOT_RUNNING"; @@ -223,6 +224,7 @@ const char* values[] = { "DHCP4_INIT_REBOOT", "%1: client is in INIT-REBOOT state and requests address %2", "DHCP4_LEASE_ADVERT", "%1: lease %2 will be advertised", "DHCP4_LEASE_ALLOC", "%1: lease %2 has been allocated for %3 seconds", + "DHCP4_MULTI_THREADING_INFO", "enabled: %1, number of threads: %2, queue size per thread: %3", "DHCP4_NCR_CREATE", "%1: DDNS updates enabled, therefore sending name change requests", "DHCP4_NCR_CREATION_FAILED", "%1: failed to generate name change requests for DNS: %2", "DHCP4_NOT_RUNNING", "DHCPv4 server is not running", diff --git a/src/bin/dhcp4/dhcp4_messages.h b/src/bin/dhcp4/dhcp4_messages.h index e5ff545167aadb9eaa9292bef5483c41b585a8bf..60fea8dd4217077b797487e3769813b1f971b7b0 100644 --- a/src/bin/dhcp4/dhcp4_messages.h +++ b/src/bin/dhcp4/dhcp4_messages.h @@ -1,4 +1,4 @@ -// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes on Fri Jan 31 2020 15:04 +// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes on Wed Feb 19 2020 16:53 #ifndef DHCP4_MESSAGES_H #define DHCP4_MESSAGES_H @@ -82,6 +82,7 @@ extern const isc::log::MessageID DHCP4_INIT_FAIL; extern const isc::log::MessageID DHCP4_INIT_REBOOT; extern const isc::log::MessageID DHCP4_LEASE_ADVERT; extern const isc::log::MessageID DHCP4_LEASE_ALLOC; +extern const isc::log::MessageID DHCP4_MULTI_THREADING_INFO; extern const isc::log::MessageID DHCP4_NCR_CREATE; extern const isc::log::MessageID DHCP4_NCR_CREATION_FAILED; extern const isc::log::MessageID DHCP4_NOT_RUNNING; diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index c7c2ca6c57a83962d7a127061294a2fc9b2fe3dc..cfc50d7c3a1f6cf7cc62a9a53d5cd7a78081135e 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -806,6 +806,10 @@ This is a debug message issued during the DHCPv4 server startup. It lists some information about the parameters with which the server is running. +% DHCP4_MULTI_THREADING_INFO enabled: %1, number of threads: %2, queue size per thread: %3 +This is a message listing some information about the multi-threading parameters +with which the server is running. + % DHCP4_SUBNET_DATA %1: the selected subnet details: %2 This debug message includes the details of the subnet selected for the client. The first argument includes the client and the diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index f671fe3ae1a992dce58cc8c51b47548935e63e6e..d5b2c34846b6448b357cb887e942527fb5b9ef6f 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -34,12 +34,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include @@ -47,7 +47,6 @@ #include #include #include -#include #include #include #include @@ -80,6 +79,7 @@ using namespace isc::dhcp_ddns; using namespace isc::hooks; using namespace isc::log; using namespace isc::stats; +using namespace isc::util; using namespace std; namespace { @@ -102,8 +102,8 @@ struct Dhcp4Hooks { hook_index_pkt4_receive_ = HooksManager::registerHook("pkt4_receive"); hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select"); hook_index_leases4_committed_ = HooksManager::registerHook("leases4_committed"); - hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send"); hook_index_lease4_release_ = HooksManager::registerHook("lease4_release"); + hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send"); hook_index_buffer4_send_ = HooksManager::registerHook("buffer4_send"); hook_index_lease4_decline_ = HooksManager::registerHook("lease4_decline"); hook_index_host4_identifier_ = HooksManager::registerHook("host4_identifier"); @@ -209,7 +209,7 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine, .arg(query_->getLabel()) .arg(classes.toText()); } -}; +} void Dhcpv4Exchange::initResponse() { @@ -470,14 +470,15 @@ const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_"); Dhcpv4Srv::Dhcpv4Srv(uint16_t server_port, uint16_t client_port, const bool use_bcast, const bool direct_response_desired) - : io_service_(new IOService()), shutdown_(true), alloc_engine_(), - use_bcast_(use_bcast), server_port_(server_port), - client_port_(client_port), + : io_service_(new IOService()), server_port_(server_port), + client_port_(client_port), shutdown_(true), + alloc_engine_(), use_bcast_(use_bcast), network_state_(new NetworkState(NetworkState::DHCPv4)), cb_control_(new CBControlDHCPv4()) { LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET) .arg(server_port); + try { // Port 0 is used for testing purposes where we don't open broadcast // capable sockets. So, set the packet filter handling direct traffic @@ -801,6 +802,9 @@ Dhcpv4Srv::run() { } } + // destroying the thread pool + MultiThreadingMgr::instance().apply(false, 0); + return (true); } @@ -811,11 +815,24 @@ Dhcpv4Srv::run_one() { Pkt4Ptr rsp; try { - // Set select() timeout to 1s. This value should not be modified - // because it is important that the select() returns control - // frequently so as the IOService can be polled for ready handlers. - uint32_t timeout = 1; - query = receivePacket(timeout); + bool read_pkt = true; + + // Do not read more packets from socket if there are enough + // packets to be processed in the packet thread pool queue + const int max_queue_size = CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize(); + const int thread_count = MultiThreadingMgr::instance().getPktThreadPoolSize(); + size_t pkt_queue_size = MultiThreadingMgr::instance().getPktThreadPool().count(); + if (thread_count && (pkt_queue_size >= thread_count * max_queue_size)) { + read_pkt = false; + } + + if (read_pkt) { + // Set select() timeout to 1s. This value should not be modified + // because it is important that the select() returns control + // frequently so as the IOService can be polled for ready handlers. + uint32_t timeout = 1; + query = receivePacket(timeout); + } // Log if packet has arrived. We can't log the detailed information // about the DHCP message because it hasn't been unpacked/parsed @@ -884,9 +901,33 @@ Dhcpv4Srv::run_one() { .arg(query->getLabel()); return; } else { - processPacket(query, rsp); + if (MultiThreadingMgr::instance().getMode()) { + typedef function CallBack; + boost::shared_ptr call_back = + boost::make_shared(std::bind(&Dhcpv4Srv::processPacketAndSendResponseNoThrow, + this, query, rsp)); + MultiThreadingMgr::instance().getPktThreadPool().add(call_back); + } else { + processPacketAndSendResponse(query, rsp); + } } +} +void +Dhcpv4Srv::processPacketAndSendResponseNoThrow(Pkt4Ptr& query, Pkt4Ptr& rsp) { + try { + processPacketAndSendResponse(query, rsp); + } catch (const std::exception& e) { + LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION) + .arg(e.what()); + } catch (...) { + LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION); + } +} + +void +Dhcpv4Srv::processPacketAndSendResponse(Pkt4Ptr& query, Pkt4Ptr& rsp) { + processPacket(query, rsp); if (!rsp) { return; } @@ -1178,8 +1219,16 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp, bool allow_packet_park) { // library unparks the packet. HooksManager::park("leases4_committed", query, [this, callout_handle, query, rsp]() mutable { - processPacketPktSend(callout_handle, query, rsp); - processPacketBufferSend(callout_handle, rsp); + if (MultiThreadingMgr::instance().getMode()) { + typedef function CallBack; + boost::shared_ptr call_back = + boost::make_shared(std::bind(&Dhcpv4Srv::sendResponseNoThrow, + this, callout_handle, query, rsp)); + MultiThreadingMgr::instance().getPktThreadPool().add(call_back); + } else { + processPacketPktSend(callout_handle, query, rsp); + processPacketBufferSend(callout_handle, rsp); + } }); // If we have parked the packet, let's reset the pointer to the @@ -1192,6 +1241,20 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp, bool allow_packet_park) { } } +void +Dhcpv4Srv::sendResponseNoThrow(hooks::CalloutHandlePtr& callout_handle, + Pkt4Ptr& query, Pkt4Ptr& rsp) { + try { + processPacketPktSend(callout_handle, query, rsp); + processPacketBufferSend(callout_handle, rsp); + } catch (const std::exception& e) { + LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_STD_EXCEPTION) + .arg(e.what()); + } catch (...) { + LOG_ERROR(packet4_logger, DHCP4_PACKET_PROCESS_EXCEPTION); + } +} + void Dhcpv4Srv::processPacketPktSend(hooks::CalloutHandlePtr& callout_handle, Pkt4Ptr& query, Pkt4Ptr& rsp) { diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index 13265a5e29521b73610193a9d603a62f05094d7d..350be7d52453b2121de203aa3b98abc56c9c0a94 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -9,24 +9,22 @@ #include #include -#include #include #include #include #include +#include #include #include +#include #include #include -#include #include #include #include #include #include -#include - #include #include #include @@ -164,12 +162,16 @@ private: /// @brief Pointer to the allocation engine used by the server. AllocEnginePtr alloc_engine_; + /// @brief Pointer to the DHCPv4 message sent by the client. Pkt4Ptr query_; + /// @brief Pointer to the DHCPv4 message to be sent to the client. Pkt4Ptr resp_; + /// @brief Context for use with allocation engine. AllocEngine::ClientContext4Ptr context_; + /// @brief Configured option list. /// @note The configured option list is an *ordered* list of /// @c CfgOption objects used to append options to the response. @@ -234,9 +236,9 @@ public: /// @brief Destructor. Used during DHCPv4 service shutdown. virtual ~Dhcpv4Srv(); - /// @brief Checks if the server is running in a test mode. + /// @brief Checks if the server is running in unit test mode. /// - /// @return true if the server is running in the test mode, + /// @return true if the server is running in unit test mode, /// false otherwise. bool inTestMode() const { return (server_port_ == 0); @@ -280,6 +282,32 @@ public: /// a response. void run_one(); + /// @brief Process a single incoming DHCPv4 packet and sends the response. + /// + /// It verifies correctness of the passed packet, call per-type processXXX + /// methods, generates appropriate answer, sends the answer to the client. + /// + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response + void processPacketAndSendResponse(Pkt4Ptr& query, Pkt4Ptr& rsp); + + /// @brief Process a single incoming DHCPv4 packet and sends the response. + /// + /// It verifies correctness of the passed packet, call per-type processXXX + /// methods, generates appropriate answer, sends the answer to the client. + /// + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response + void processPacketAndSendResponseNoThrow(Pkt4Ptr& query, Pkt4Ptr& rsp); + + /// @brief Process an unparked DHCPv4 packet and sends the response. + /// + /// @param callout_handle pointer to the callout handle. + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response + void sendResponseNoThrow(hooks::CalloutHandlePtr& callout_handle, + Pkt4Ptr& query, Pkt4Ptr& rsp); + /// @brief Process a single incoming DHCPv4 packet. /// /// It verifies correctness of the passed packet, call per-type processXXX @@ -351,7 +379,9 @@ public: NameChangeSender::Result result, dhcp_ddns::NameChangeRequestPtr& ncr); - /// @brief Discard all in-progress packets + /// @brief Discards cached and parked packets + /// Clears the call_handle store and packet parking lots + /// of all packets. Called during reconfigure and shutdown. void discardPackets(); protected: @@ -879,10 +909,6 @@ protected: bool& drop, bool sanity_only = false) const; - /// indicates if shutdown is in progress. Setting it to true will - /// initiate server shutdown procedure. - volatile bool shutdown_; - /// @brief dummy wrapper around IfaceMgr::receive4 /// /// This method is useful for testing purposes, where its replacement @@ -961,12 +987,6 @@ protected: void processPacketBufferSend(hooks::CalloutHandlePtr& callout_handle, Pkt4Ptr& rsp); - /// @brief Allocation Engine. - /// Pointer to the allocation engine that we are currently using - /// It must be a pointer, because we will support changing engines - /// during normal operation (e.g. to use different allocators) - boost::shared_ptr alloc_engine_; - private: /// @public @@ -984,17 +1004,27 @@ private: /// @return Option that contains netmask information static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet); - /// Should broadcast be enabled on sockets (if true). - bool use_bcast_; - protected: /// UDP port number on which server listens. uint16_t server_port_; - /// UDP port number to which server sends responses. + /// UDP port number to which server sends all responses. uint16_t client_port_; + /// Indicates if shutdown is in progress. Setting it to true will + /// initiate server shutdown procedure. + volatile bool shutdown_; + + /// @brief Allocation Engine. + /// Pointer to the allocation engine that we are currently using + /// It must be a pointer, because we will support changing engines + /// during normal operation (e.g. to use different allocators) + boost::shared_ptr alloc_engine_; + + /// Should broadcast be enabled on sockets (if true). + bool use_bcast_; + /// @brief Holds information about disabled DHCP service and/or /// disabled subnet/network scopes. NetworkStatePtr network_state_; @@ -1042,7 +1072,7 @@ public: static int getHookIndexLease4Decline(); }; -}; // namespace isc::dhcp -}; // namespace isc +} // namespace dhcp +} // namespace isc #endif // DHCP4_SRV_H diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc index a6b70ef803ed668772918280663e8c83cadda0d9..2fbb6b2d789cf713dc43d5ae992cea2e472d4f32 100644 --- a/src/bin/dhcp4/main.cc +++ b/src/bin/dhcp4/main.cc @@ -7,15 +7,16 @@ #include #include +#include #include #include #include #include -#include #include +#include #include #include -#include +#include #include @@ -31,6 +32,9 @@ using namespace std; /// instantiates ControlledDhcpv4Srv class that is responsible for establishing /// connection with msgq (receiving commands and configuration) and also /// creating Dhcpv4 server object as well. +/// +/// For detailed explanation or relations between main(), ControlledDhcpv4Srv, +/// Dhcpv4Srv and other classes, see \ref dhcpv4Session. namespace { @@ -55,9 +59,11 @@ usage() { << "(useful for testing only)" << endl; cerr << " -P number: specify non-standard client port number 1-65535 " << "(useful for testing only)" << endl; + cerr << " -N number: specify thread count 0-65535 " + << "(0 means multi-threading disabled)" << endl; exit(EXIT_FAILURE); } -} // end of anonymous namespace +} // namespace int main(int argc, char* argv[]) { @@ -66,13 +72,15 @@ main(int argc, char* argv[]) { int server_port_number = DHCP4_SERVER_PORT; // Not zero values are useful for testing only. int client_port_number = 0; + // Number of threads. 0 means multi-threading disabled + int thread_count = 0; bool verbose_mode = false; // Should server be verbose? bool check_mode = false; // Check syntax // The standard config file std::string config_file(""); - while ((ch = getopt(argc, argv, "dvVWc:p:P:t:")) != -1) { + while ((ch = getopt(argc, argv, "dvVWc:p:P:N:t:")) != -1) { switch (ch) { case 'd': verbose_mode = true; @@ -98,7 +106,7 @@ main(int argc, char* argv[]) { config_file = optarg; break; - case 'p': + case 'p': // server port number try { server_port_number = boost::lexical_cast(optarg); } catch (const boost::bad_lexical_cast &) { @@ -113,7 +121,7 @@ main(int argc, char* argv[]) { } break; - case 'P': + case 'P': // client port number try { client_port_number = boost::lexical_cast(optarg); } catch (const boost::bad_lexical_cast &) { @@ -128,6 +136,21 @@ main(int argc, char* argv[]) { } break; + case 'N': // number of threads + try { + thread_count = boost::lexical_cast(optarg); + } catch (const boost::bad_lexical_cast &) { + cerr << "Failed to parse thread count number: [" << optarg + << "], 0-65535 allowed." << endl; + usage(); + } + if (thread_count < 0 || thread_count > 65535) { + cerr << "Failed to parse thread count number: [" << optarg + << "], 0-65535 allowed." << endl; + usage(); + } + break; + default: usage(); } @@ -138,7 +161,6 @@ main(int argc, char* argv[]) { usage(); } - // Configuration file is required. if (config_file.empty()) { cerr << "Configuration file not specified." << endl; @@ -150,7 +172,6 @@ main(int argc, char* argv[]) { if (check_mode) { try { - // We need to initialize logging, in case any error messages are to be printed. // This is just a test, so we don't care about lockfile. setenv("KEA_LOCKFILE_DIR", "none", 0); diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index 0aab657707c7ac20aa79e185c2c01e19cf63da06..d037c77982405ea83613f66bd891f047c1c25dce 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -5,30 +5,36 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include -#include + #include +#include +#include #include #include -#include -#include #include #include #include #include #include +#include +#include +#include #include #include #include -#include +#include + #include + #include using namespace isc::config; -using namespace isc::db; using namespace isc::data; +using namespace isc::db; using namespace isc::dhcp; using namespace isc::hooks; using namespace isc::stats; +using namespace isc::util; using namespace std; namespace { @@ -80,6 +86,38 @@ namespace dhcp { ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL; +void +ControlledDhcpv6Srv::init(const std::string& file_name) { + // Keep the call timestamp. + start_ = boost::posix_time::second_clock::universal_time(); + + // Configure the server using JSON file. + ConstElementPtr result = loadConfigFile(file_name); + + int rcode; + ConstElementPtr comment = isc::config::parseAnswer(rcode, result); + if (rcode != CONTROL_RESULT_SUCCESS) { + string reason = comment ? comment->stringValue() : + "no details available"; + isc_throw(isc::BadValue, reason); + } + + // We don't need to call openActiveSockets() or startD2() as these + // methods are called in processConfig() which is called by + // processCommand("config-set", ...) + + // Set signal handlers. When the SIGHUP is received by the process + // the server reconfiguration will be triggered. When SIGTERM or + // SIGINT will be received, the server will start shutting down. + signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM)); + // Set the pointer to the handler function. + signal_handler_ = signalHandler; +} + +void ControlledDhcpv6Srv::cleanup() { + // Nothing to do here. No need to disconnect from anything. +} + /// @brief Configure DHCPv6 server using the configuration file specified. /// /// This function is used to both configure the DHCP server on its startup @@ -96,16 +134,14 @@ ControlledDhcpv6Srv::loadConfigFile(const std::string& file_name) { // configuration from a JSON file. isc::data::ConstElementPtr json; - isc::data::ConstElementPtr dhcp6; - isc::data::ConstElementPtr logger; isc::data::ConstElementPtr result; // Basic sanity check: file name must not be empty. try { if (file_name.empty()) { // Basic sanity check: file name must not be empty. - isc_throw(isc::BadValue, "JSON configuration file not specified. Please " - "use -c command line option."); + isc_throw(isc::BadValue, "JSON configuration file not specified." + " Please use -c command line option."); } // Read contents of the file and parse it as JSON @@ -136,12 +172,15 @@ ControlledDhcpv6Srv::loadConfigFile(const std::string& file_name) { "processCommand(\"config-set\", json)"); } + // @todo enable multi-threading - disabled for now + MultiThreadingMgr::instance().apply(false, + CfgMgr::instance().getCurrentCfg()->getServerThreadCount()); + // Now check is the returned result is successful (rcode=0) or not // (see @ref isc::config::parseAnswer). int rcode; - isc::data::ConstElementPtr comment = - isc::config::parseAnswer(rcode, result); - if (rcode != 0) { + ConstElementPtr comment = isc::config::parseAnswer(rcode, result); + if (rcode != CONTROL_RESULT_SUCCESS) { string reason = comment ? comment->stringValue() : "no details available"; isc_throw(isc::BadValue, reason); @@ -157,52 +196,26 @@ ControlledDhcpv6Srv::loadConfigFile(const std::string& file_name) { << file_name << "': " << ex.what()); } - return (result); -} - - -void -ControlledDhcpv6Srv::init(const std::string& file_name) { - // Keep the call timestamp. - start_ = boost::posix_time::second_clock::universal_time(); + LOG_INFO(dhcp6_logger, DHCP6_MULTI_THREADING_INFO) + .arg(MultiThreadingMgr::instance().getMode()) + .arg(MultiThreadingMgr::instance().getPktThreadPoolSize()) + .arg(CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize()); - // Configure the server using JSON file. - ConstElementPtr result = loadConfigFile(file_name); - int rcode; - ConstElementPtr comment = isc::config::parseAnswer(rcode, result); - if (rcode != 0) { - string reason = comment ? comment->stringValue() : - "no details available"; - isc_throw(isc::BadValue, reason); - } - - // We don't need to call openActiveSockets() or startD2() as these - // methods are called in processConfig() which is called by - // processCommand("config-set", ...) - - // Set signal handlers. When the SIGHUP is received by the process - // the server reconfiguration will be triggered. When SIGTERM or - // SIGINT will be received, the server will start shutting down. - signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM)); - // Set the pointer to the handler function. - signal_handler_ = signalHandler; -} - -void ControlledDhcpv6Srv::cleanup() { - // Nothing to do here. No need to disconnect from anything. + return (result); } - ConstElementPtr ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) { - if (ControlledDhcpv6Srv::server_) { - ControlledDhcpv6Srv::server_->shutdown(); + if (ControlledDhcpv6Srv::getInstance()) { + ControlledDhcpv6Srv::getInstance()->shutdown(); } else { LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING); - ConstElementPtr answer = isc::config::createAnswer(1, "Shutdown failure."); + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, + "Shutdown failure."); return (answer); } - ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down."); + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, + "Shutting down."); return (answer); } @@ -214,11 +227,11 @@ ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) { bool status = HooksManager::loadLibraries(loaded); if (!status) { LOG_ERROR(dhcp6_logger, DHCP6_HOOKS_LIBS_RELOAD_FAIL); - ConstElementPtr answer = isc::config::createAnswer(1, + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "Failed to reload hooks libraries."); return (answer); } - ConstElementPtr answer = isc::config::createAnswer(0, + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, "Hooks libraries successfully reloaded."); return (answer); } @@ -247,11 +260,12 @@ ControlledDhcpv6Srv::commandConfigGetHandler(const string&, ConstElementPtr /*args*/) { ConstElementPtr config = CfgMgr::instance().getCurrentCfg()->toElement(); - return (createAnswer(0, config)); + return (createAnswer(CONTROL_RESULT_SUCCESS, config)); } ConstElementPtr -ControlledDhcpv6Srv::commandConfigWriteHandler(const string&, ConstElementPtr args) { +ControlledDhcpv6Srv::commandConfigWriteHandler(const string&, + ConstElementPtr args) { string filename; if (args) { @@ -378,6 +392,7 @@ ControlledDhcpv6Srv::commandConfigSetHandler(const string&, isc::config::parseAnswer(rcode, result); if (rcode == CONTROL_RESULT_SUCCESS) { CfgMgr::instance().getStagingCfg()->applyLoggingCfg(); + // Use new configuration. CfgMgr::instance().commit(); } else { @@ -497,23 +512,24 @@ ControlledDhcpv6Srv::commandVersionGetHandler(const string&, ConstElementPtr) { ElementPtr extended = Element::create(Dhcpv6Srv::getVersion(true)); ElementPtr arguments = Element::createMap(); arguments->set("extended", extended); - ConstElementPtr answer = isc::config::createAnswer(0, + ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, Dhcpv6Srv::getVersion(false), arguments); return (answer); } ConstElementPtr -ControlledDhcpv6Srv::commandBuildReportHandler(const string&, ConstElementPtr) { +ControlledDhcpv6Srv::commandBuildReportHandler(const string&, + ConstElementPtr) { ConstElementPtr answer = - isc::config::createAnswer(0, isc::detail::getConfigReport()); + isc::config::createAnswer(CONTROL_RESULT_SUCCESS, isc::detail::getConfigReport()); return (answer); } ConstElementPtr ControlledDhcpv6Srv::commandLeasesReclaimHandler(const string&, ConstElementPtr args) { - int status_code = 1; + int status_code = CONTROL_RESULT_ERROR; string message; // args must be { "remove": } @@ -598,12 +614,12 @@ ControlledDhcpv6Srv::commandStatusGetHandler(const string&, // todo: number of service threads. - return (createAnswer(0, status)); + return (createAnswer(CONTROL_RESULT_SUCCESS, status)); } -isc::data::ConstElementPtr -ControlledDhcpv6Srv::processCommand(const std::string& command, - isc::data::ConstElementPtr args) { +ConstElementPtr +ControlledDhcpv6Srv::processCommand(const string& command, + ConstElementPtr args) { string txt = args ? args->str() : "(none)"; LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED) @@ -612,7 +628,7 @@ ControlledDhcpv6Srv::processCommand(const std::string& command, ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance(); if (!srv) { - ConstElementPtr no_srv = isc::config::createAnswer(1, + ConstElementPtr no_srv = isc::config::createAnswer(CONTROL_RESULT_ERROR, "Server object not initialized, can't process command '" + command + "', arguments: '" + txt + "'."); return (no_srv); @@ -665,12 +681,14 @@ ControlledDhcpv6Srv::processCommand(const std::string& command, return (srv->commandStatusGetHandler(command, args)); } - return (isc::config::createAnswer(1, "Unrecognized command:" + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, + "Unrecognized command: " + command)); - } catch (const Exception& ex) { - return (isc::config::createAnswer(1, "Error while processing command '" - + command + "':" + ex.what())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, + "Error while processing command '" + + command + "':" + ex.what() + + ", params: '" + txt + "'")); } } @@ -682,11 +700,12 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance(); + // Single stream instance used in all error clauses + std::ostringstream err; + if (!srv) { - ConstElementPtr no_srv = isc::config::createAnswer( - CONTROL_RESULT_ERROR, - "Server object not initialized, can't process config."); - return (no_srv); + err << "Server object not initialized, can't process config."; + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } ConstElementPtr answer = configureDhcp6Server(*srv, config); @@ -696,12 +715,12 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { try { int rcode = 0; isc::config::parseAnswer(rcode, answer); - if (rcode != 0) { + if (rcode != CONTROL_RESULT_SUCCESS) { return (answer); } } catch (const std::exception& ex) { - return (isc::config::createAnswer(1, "Failed to process configuration:" - + string(ex.what()))); + err << "Failed to process configuration:" << ex.what(); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Re-open lease and host database with new parameters. @@ -712,8 +731,8 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { cfg_db->setAppendedParameters("universe=6"); cfg_db->createManagers(); } catch (const std::exception& ex) { - return (isc::config::createAnswer(1, "Unable to open database: " - + std::string(ex.what()))); + err << "Unable to open database: " << ex.what(); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Regenerate server identifier if needed. @@ -732,17 +751,16 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { } catch (const std::exception& ex) { std::ostringstream err; err << "unable to configure server identifier: " << ex.what(); - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Server will start DDNS communications if its enabled. try { srv->startD2(); } catch (const std::exception& ex) { - std::ostringstream err; - err << "error starting DHCP_DDNS client " - " after server reconfiguration: " << ex.what(); - return (isc::config::createAnswer(1, err.str())); + err << "Error starting DHCP_DDNS client after server reconfiguration: " + << ex.what(); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Setup DHCPv4-over-DHCPv6 IPC @@ -752,7 +770,7 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { std::ostringstream err; err << "error starting DHCPv4-over-DHCPv6 IPC " " after server reconfiguration: " << ex.what(); - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Configure DHCP packet queueing @@ -765,10 +783,9 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { } } catch (const std::exception& ex) { - std::ostringstream err; err << "Error setting packet queue controls after server reconfiguration: " << ex.what(); - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Configuration may change active interfaces. Therefore, we have to reopen @@ -789,11 +806,10 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { server_); } catch (const std::exception& ex) { - std::ostringstream err; err << "unable to setup timers for periodically running the" " reclamation of the expired leases: " << ex.what() << "."; - return (isc::config::createAnswer(1, err.str())); + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } // Setup config backend polling, if configured for it. @@ -855,14 +871,16 @@ isc::data::ConstElementPtr ControlledDhcpv6Srv::checkConfig(isc::data::ConstElementPtr config) { LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_RECEIVED) - .arg(config->str()); + .arg(config->str()); ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance(); + // Single stream instance used in all error clauses + std::ostringstream err; + if (!srv) { - ConstElementPtr no_srv = isc::config::createAnswer(1, - "Server object not initialized, can't process config."); - return (no_srv); + err << "Server object not initialized, can't process config."; + return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str())); } return (configureDhcp6Server(*srv, config, true)); @@ -872,11 +890,11 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port, uint16_t client_port) : Dhcpv6Srv(server_port, client_port), io_service_(), timer_mgr_(TimerMgr::instance()) { - if (server_) { + if (getInstance()) { isc_throw(InvalidOperation, "There is another Dhcpv6Srv instance already."); } - server_ = this; // remember this instance for use in callback + server_ = this; // remember this instance for later use in handlers // TimerMgr uses IO service to run asynchronous timers. TimerMgr::instance()->setIOService(getIOService()); @@ -898,6 +916,9 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port, CommandMgr::instance().registerCommand("config-reload", boost::bind(&ControlledDhcpv6Srv::commandConfigReloadHandler, this, _1, _2)); + CommandMgr::instance().registerCommand("config-set", + boost::bind(&ControlledDhcpv6Srv::commandConfigSetHandler, this, _1, _2)); + CommandMgr::instance().registerCommand("config-test", boost::bind(&ControlledDhcpv6Srv::commandConfigTestHandler, this, _1, _2)); @@ -913,14 +934,11 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port, CommandMgr::instance().registerCommand("leases-reclaim", boost::bind(&ControlledDhcpv6Srv::commandLeasesReclaimHandler, this, _1, _2)); - CommandMgr::instance().registerCommand("server-tag-get", - boost::bind(&ControlledDhcpv6Srv::commandServerTagGetHandler, this, _1, _2)); - CommandMgr::instance().registerCommand("libreload", boost::bind(&ControlledDhcpv6Srv::commandLibReloadHandler, this, _1, _2)); - CommandMgr::instance().registerCommand("config-set", - boost::bind(&ControlledDhcpv6Srv::commandConfigSetHandler, this, _1, _2)); + CommandMgr::instance().registerCommand("server-tag-get", + boost::bind(&ControlledDhcpv6Srv::commandServerTagGetHandler, this, _1, _2)); CommandMgr::instance().registerCommand("shutdown", boost::bind(&ControlledDhcpv6Srv::commandShutdownHandler, this, _1, _2)); @@ -985,8 +1003,8 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() { CommandMgr::instance().deregisterCommand("build-report"); CommandMgr::instance().deregisterCommand("config-backend-pull"); CommandMgr::instance().deregisterCommand("config-get"); - CommandMgr::instance().deregisterCommand("config-set"); CommandMgr::instance().deregisterCommand("config-reload"); + CommandMgr::instance().deregisterCommand("config-set"); CommandMgr::instance().deregisterCommand("config-test"); CommandMgr::instance().deregisterCommand("config-write"); CommandMgr::instance().deregisterCommand("dhcp-disable"); @@ -1021,8 +1039,8 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() { void ControlledDhcpv6Srv::sessionReader(void) { // Process one asio event. If there are more events, iface_mgr will call // this callback more than once. - if (server_) { - server_->io_service_.run_one(); + if (getInstance()) { + getInstance()->io_service_.run_one(); } } @@ -1061,12 +1079,13 @@ ControlledDhcpv6Srv::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) { if (reopened) { // Cancel the timer. if (TimerMgr::instance()->isTimerRegistered("Dhcp6DbReconnectTimer")) { - TimerMgr::instance()->cancel("Dhcp6DbReconnectTimer"); } + TimerMgr::instance()->cancel("Dhcp6DbReconnectTimer"); + } // Set network state to service enabled network_state_->enableService(); - // Toss the reconnct control, we're done with it + // Toss the reconnect control, we're done with it db_reconnect_ctl.reset(); } else { if (!db_reconnect_ctl->checkRetries()) { @@ -1084,7 +1103,7 @@ ControlledDhcpv6Srv::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) { if (!TimerMgr::instance()->isTimerRegistered("Dhcp6DbReconnectTimer")) { TimerMgr::instance()->registerTimer("Dhcp6DbReconnectTimer", boost::bind(&ControlledDhcpv6Srv::dbReconnect, this, - db_reconnect_ctl), + db_reconnect_ctl), db_reconnect_ctl->retryInterval(), asiolink::IntervalTimer::ONE_SHOT); } @@ -1104,7 +1123,8 @@ ControlledDhcpv6Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) { return (false); } - // If reconnect isn't enabled, log it and return false + // If reconnect isn't enabled or we're out of retries, + // log it, schedule a shutdown, and return false if (!db_reconnect_ctl->retriesLeft() || !db_reconnect_ctl->retryInterval()) { LOG_INFO(dhcp6_logger, DHCP6_DB_RECONNECT_DISABLED) @@ -1150,5 +1170,5 @@ ControlledDhcpv6Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg, } } -}; // end of isc::dhcp namespace -}; // end of isc namespace +} // namespace dhcp +} // namespace isc diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h index f4085fab90ac8547cadcfd9c29ef61a9ce049e18..951c39a40e2bab48036268032a0605457cc8f23c 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.h +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h @@ -65,7 +65,7 @@ public: /// @brief Initiates shutdown procedure for the whole DHCPv6 server. void shutdown(); - /// @brief command processor + /// @brief Command processor /// /// This method is uniform for all config backends. It processes received /// command (as a string + JSON arguments). Internally, it's just a @@ -75,9 +75,9 @@ public: /// Currently supported commands are: /// - config-reload /// - config-test - /// - leases-reclaim - /// - libreload /// - shutdown + /// - libreload + /// - leases-reclaim /// ... /// /// @note It never throws. @@ -89,7 +89,7 @@ public: static isc::data::ConstElementPtr processCommand(const std::string& command, isc::data::ConstElementPtr args); - /// @brief configuration processor + /// @brief Configuration processor /// /// This is a method for handling incoming configuration updates. /// This method should be called by all configuration backends when the @@ -114,7 +114,7 @@ public: isc::data::ConstElementPtr checkConfig(isc::data::ConstElementPtr new_config); - /// @brief returns pointer to the sole instance of Dhcpv6Srv + /// @brief Returns pointer to the sole instance of Dhcpv6Srv /// /// @return server instance (may return NULL, if called before server is spawned) static ControlledDhcpv6Srv* getInstance() { @@ -131,7 +131,7 @@ private: /// (that was sent from some yet unspecified sender). static void sessionReader(void); - /// @brief handler for processing 'shutdown' command + /// @brief Handler for processing 'shutdown' command /// /// This handler processes shutdown command, which initializes shutdown /// procedure. @@ -143,7 +143,7 @@ private: commandShutdownHandler(const std::string& command, isc::data::ConstElementPtr args); - /// @brief handler for processing 'libreload' command + /// @brief Handler for processing 'libreload' command /// /// This handler processes libreload command, which unloads all hook /// libraries and reloads them. @@ -156,7 +156,7 @@ private: commandLibReloadHandler(const std::string& command, isc::data::ConstElementPtr args); - /// @brief handler for processing 'config-reload' command + /// @brief Handler for processing 'config-reload' command /// /// This handler processes config-reload command, which processes /// configuration specified in args parameter. @@ -348,7 +348,6 @@ private: const bool remove_lease, const uint16_t max_unwarned_cycles); - /// @brief Deletes reclaimed leases and reschedules the timer. /// /// This is a wrapper method for @c AllocEngine::deleteExpiredReclaimed6. @@ -373,6 +372,7 @@ private: /// /// If the maximum number of retries has been exhausted an error is logged /// and the server shuts down. + /// /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the /// configured reconnect parameters /// @@ -394,6 +394,8 @@ private: /// /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the /// configured reconnect parameters + /// + /// @return false if reconnect is not configured, true otherwise bool dbLostCallback(db::ReconnectCtlPtr db_reconnect_ctl); /// @brief Callback invoked periodically to fetch configuration updates @@ -426,7 +428,7 @@ private: TimerMgrPtr timer_mgr_; }; -}; // namespace isc::dhcp -}; // namespace isc +} // namespace dhcp +} // namespace isc #endif diff --git a/src/bin/dhcp6/dhcp6_messages.cc b/src/bin/dhcp6/dhcp6_messages.cc index 95d556438f37cee68e35f8a50dc21add3f7ae7de..05283192c72bc5ed111f5c1d95da1bee2325f24e 100644 --- a/src/bin/dhcp6/dhcp6_messages.cc +++ b/src/bin/dhcp6/dhcp6_messages.cc @@ -1,4 +1,4 @@ -// File created from ../../../src/bin/dhcp6/dhcp6_messages.mes on Sun Oct 27 2019 19:41 +// File created from ../../../src/bin/dhcp6/dhcp6_messages.mes on Wed Feb 19 2020 16:59 #include #include @@ -83,6 +83,7 @@ extern const isc::log::MessageID DHCP6_LEASE_DATA = "DHCP6_LEASE_DATA"; extern const isc::log::MessageID DHCP6_LEASE_NA_WITHOUT_DUID = "DHCP6_LEASE_NA_WITHOUT_DUID"; extern const isc::log::MessageID DHCP6_LEASE_PD_WITHOUT_DUID = "DHCP6_LEASE_PD_WITHOUT_DUID"; extern const isc::log::MessageID DHCP6_LEASE_RENEW = "DHCP6_LEASE_RENEW"; +extern const isc::log::MessageID DHCP6_MULTI_THREADING_INFO = "DHCP6_MULTI_THREADING_INFO"; extern const isc::log::MessageID DHCP6_NOT_RUNNING = "DHCP6_NOT_RUNNING"; extern const isc::log::MessageID DHCP6_NO_INTERFACES = "DHCP6_NO_INTERFACES"; extern const isc::log::MessageID DHCP6_NO_SOCKETS_OPEN = "DHCP6_NO_SOCKETS_OPEN"; @@ -227,6 +228,7 @@ const char* values[] = { "DHCP6_LEASE_NA_WITHOUT_DUID", "%1: address lease for address %2 does not have a DUID", "DHCP6_LEASE_PD_WITHOUT_DUID", "%1: lease for prefix %2/%3 does not have a DUID", "DHCP6_LEASE_RENEW", "%1: lease for address %2 and iaid=%3 has been allocated", + "DHCP6_MULTI_THREADING_INFO", "enabled: %1, number of threads: %2, queue size per thread: %3", "DHCP6_NOT_RUNNING", "IPv6 DHCP server is not running", "DHCP6_NO_INTERFACES", "failed to detect any network interfaces", "DHCP6_NO_SOCKETS_OPEN", "no interface configured to listen to DHCP traffic", diff --git a/src/bin/dhcp6/dhcp6_messages.h b/src/bin/dhcp6/dhcp6_messages.h index 0ac9a0444afeab0e09c4872ad4093f25ed4650f5..ae2a4d620df108743836299ce1e1a3a9329062d6 100644 --- a/src/bin/dhcp6/dhcp6_messages.h +++ b/src/bin/dhcp6/dhcp6_messages.h @@ -1,4 +1,4 @@ -// File created from ../../../src/bin/dhcp6/dhcp6_messages.mes on Sun Oct 27 2019 19:41 +// File created from ../../../src/bin/dhcp6/dhcp6_messages.mes on Wed Feb 19 2020 16:59 #ifndef DHCP6_MESSAGES_H #define DHCP6_MESSAGES_H @@ -84,6 +84,7 @@ extern const isc::log::MessageID DHCP6_LEASE_DATA; extern const isc::log::MessageID DHCP6_LEASE_NA_WITHOUT_DUID; extern const isc::log::MessageID DHCP6_LEASE_PD_WITHOUT_DUID; extern const isc::log::MessageID DHCP6_LEASE_RENEW; +extern const isc::log::MessageID DHCP6_MULTI_THREADING_INFO; extern const isc::log::MessageID DHCP6_NOT_RUNNING; extern const isc::log::MessageID DHCP6_NO_INTERFACES; extern const isc::log::MessageID DHCP6_NO_SOCKETS_OPEN; diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes index 9e4d6b25ae68f37323103adf8c7688b8479868c8..517df5c7e9c719b9be995c16a54d6acc389ecdc9 100644 --- a/src/bin/dhcp6/dhcp6_messages.mes +++ b/src/bin/dhcp6/dhcp6_messages.mes @@ -822,6 +822,10 @@ This is a debug message issued during the IPv6 DHCP server startup. It lists some information about the parameters with which the server is running. +% DHCP6_MULTI_THREADING_INFO enabled: %1, number of threads: %2, queue size per thread: %3 +This is a message listing some information about the multi-threading parameters +with which the server is running. + % DHCP6_SUBNET_DATA %1: the selected subnet details: %2 This debug message includes the details of the subnet selected for the client. The first argument includes the client and the diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index ef4c2720298c20a7432ca940f4b651473ca8c058..1f28dd4a12a6e40a1ce54239da5a8c467c503f1e 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -46,7 +47,6 @@ #include #include #include - #include #include #include @@ -472,6 +472,9 @@ bool Dhcpv6Srv::run() { } } + // destroying the thread pool + MultiThreadingMgr::instance().apply(false, 0); + return (true); } @@ -481,11 +484,24 @@ void Dhcpv6Srv::run_one() { Pkt6Ptr rsp; try { - // Set select() timeout to 1s. This value should not be modified - // because it is important that the select() returns control - // frequently so as the IOService can be polled for ready handlers. - uint32_t timeout = 1; - query = receivePacket(timeout); + bool read_pkt = true; + + // Do not read more packets from socket if there are enough + // packets to be processed in the packet thread pool queue + const int max_queue_size = CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize(); + const int thread_count = MultiThreadingMgr::instance().getPktThreadPoolSize(); + size_t pkt_queue_size = MultiThreadingMgr::instance().getPktThreadPool().count(); + if (thread_count && (pkt_queue_size >= thread_count * max_queue_size)) { + read_pkt = false; + } + + if (read_pkt) { + // Set select() timeout to 1s. This value should not be modified + // because it is important that the select() returns control + // frequently so as the IOService can be polled for ready handlers. + uint32_t timeout = 1; + query = receivePacket(timeout); + } // Log if packet has arrived. We can't log the detailed information // about the DHCP message because it hasn't been unpacked/parsed @@ -558,9 +574,33 @@ void Dhcpv6Srv::run_one() { .arg(query->getLabel()); return; } else { - processPacket(query, rsp); + if (MultiThreadingMgr::instance().getMode()) { + typedef function CallBack; + boost::shared_ptr call_back = + boost::make_shared(std::bind(&Dhcpv6Srv::processPacketAndSendResponseNoThrow, + this, query, rsp)); + MultiThreadingMgr::instance().getPktThreadPool().add(call_back); + } else { + processPacketAndSendResponse(query, rsp); + } } +} + +void +Dhcpv6Srv::processPacketAndSendResponseNoThrow(Pkt6Ptr& query, Pkt6Ptr& rsp) { + try { + processPacketAndSendResponse(query, rsp); + } catch (const std::exception& e) { + LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_STD_EXCEPTION) + .arg(e.what()); + } catch (...) { + LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_EXCEPTION); + } +} +void +Dhcpv6Srv::processPacketAndSendResponse(Pkt6Ptr& query, Pkt6Ptr& rsp) { + processPacket(query, rsp); if (!rsp) { return; } @@ -938,8 +978,16 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) { // library unparks the packet. HooksManager::park("leases6_committed", query, [this, callout_handle, query, rsp]() mutable { - processPacketPktSend(callout_handle, query, rsp); - processPacketBufferSend(callout_handle, rsp); + if (MultiThreadingMgr::instance().getMode()) { + typedef function CallBack; + boost::shared_ptr call_back = + boost::make_shared(std::bind(&Dhcpv6Srv::sendResponseNoThrow, + this, callout_handle, query, rsp)); + MultiThreadingMgr::instance().getPktThreadPool().add(call_back); + } else { + processPacketPktSend(callout_handle, query, rsp); + processPacketBufferSend(callout_handle, rsp); + } }); // If we have parked the packet, let's reset the pointer to the @@ -952,6 +1000,20 @@ Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) { } } +void +Dhcpv6Srv::sendResponseNoThrow(hooks::CalloutHandlePtr& callout_handle, + Pkt6Ptr& query, Pkt6Ptr& rsp) { + try { + processPacketPktSend(callout_handle, query, rsp); + processPacketBufferSend(callout_handle, rsp); + } catch (const std::exception& e) { + LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_STD_EXCEPTION) + .arg(e.what()); + } catch (...) { + LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_EXCEPTION); + } +} + void Dhcpv6Srv::processPacketPktSend(hooks::CalloutHandlePtr& callout_handle, Pkt6Ptr& query, Pkt6Ptr& rsp) { diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 80e42a47b11173eb41342934372ad3d789879baa..9421b2b7fa068239c549ba4e82570b6281e613b7 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -8,13 +8,15 @@ #define DHCPV6_SRV_H #include -#include #include #include #include +#include #include #include +#include #include +#include #include #include #include @@ -51,12 +53,16 @@ public: /// @brief DHCPv6 server service. /// -/// This class represents DHCPv6 server. It contains all +/// This singleton class represents DHCPv6 server. It contains all /// top-level methods and routines necessary for server operation. /// In particular, it instantiates IfaceMgr, loads or generates DUID /// that is going to be used as server-identifier, receives incoming /// packets, processes them, manages leases assignment and generates /// appropriate responses. +/// +/// This class does not support any controlling mechanisms directly. +/// See the derived \ref ControlledDhcpv6Srv class for support for +/// command and configuration updates over msgq. class Dhcpv6Srv : public process::Daemon { private: @@ -79,10 +85,13 @@ public: /// Instantiates necessary services, required to run DHCPv6 server. /// In particular, creates IfaceMgr that will be responsible for /// network interaction. Will instantiate lease manager, and load - /// old or create new DUID. + /// old or create new DUID. It is possible to specify alternate + /// port on which DHCPv6 server will listen on and alternate port + /// where DHCPv6 server sends all responses to. Those are mostly useful + /// for testing purposes. /// - /// @param server_port port on which all sockets will listen - /// @param client_port port to which all responses will be sent + /// @param server_port specifies port number to listen on + /// @param client_port specifies port number to send to Dhcpv6Srv(uint16_t server_port = DHCP6_SERVER_PORT, uint16_t client_port = 0); @@ -140,6 +149,32 @@ public: /// a response. void run_one(); + /// @brief Process a single incoming DHCPv6 packet and sends the response. + /// + /// It verifies correctness of the passed packet, call per-type processXXX + /// methods, generates appropriate answer, sends the answer to the client. + /// + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response + void processPacketAndSendResponse(Pkt6Ptr& query, Pkt6Ptr& rsp); + + /// @brief Process a single incoming DHCPv6 packet and sends the response. + /// + /// It verifies correctness of the passed packet, call per-type processXXX + /// methods, generates appropriate answer, sends the answer to the client. + /// + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response + void processPacketAndSendResponseNoThrow(Pkt6Ptr& query, Pkt6Ptr& rsp); + + /// @brief Process an unparked DHCPv6 packet and sends the response. + /// + /// @param callout_handle pointer to the callout handle. + /// @param query A pointer to the packet to be processed. + /// @param rsp A pointer to the response + void sendResponseNoThrow(hooks::CalloutHandlePtr& callout_handle, + Pkt6Ptr& query, Pkt6Ptr& rsp); + /// @brief Process a single incoming DHCPv6 packet. /// /// It verifies correctness of the passed packet, call per-type processXXX @@ -152,15 +187,21 @@ public: /// @brief Instructs the server to shut down. void shutdown(); + /// + /// @name Public accessors returning values required to (re)open sockets. + /// + //@{ + /// /// @brief Get UDP port on which server should listen. /// - /// Typically, server listens on UDP port 547. Other ports are only - /// used for testing purposes. + /// Typically, server listens on UDP port number 547. Other ports are used + /// for testing purposes only. /// /// @return UDP port on which server should listen. uint16_t getServerPort() const { return (server_port_); } + //@} /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled. /// @@ -1059,7 +1100,7 @@ protected: CBControlDHCPv6Ptr cb_control_; }; -}; // namespace isc::dhcp -}; // namespace isc +} // namespace dhcp +} // namespace isc #endif // DHCP6_SRV_H diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc index 42946db9c63e03a5676f7797a38aa3e9665d04bb..15366067839e81f204797e8e758806941e5aceee 100644 --- a/src/bin/dhcp6/main.cc +++ b/src/bin/dhcp6/main.cc @@ -7,15 +7,15 @@ #include #include +#include #include #include #include #include #include +#include #include #include -#include -#include #include #include @@ -37,9 +37,8 @@ using namespace std; /// Dhcpv6Srv and other classes, see \ref dhcpv6Session. namespace { -const char* const DHCP6_NAME = "kea-dhcp6"; -const char* const DHCP6_LOGGER_NAME = "kea-dhcp6"; +const char* const DHCP6_NAME = "kea-dhcp6"; /// @brief Prints Kea Usage and exits /// @@ -60,9 +59,11 @@ usage() { << "(useful for testing only)" << endl; cerr << " -P number: specify non-standard client port number 1-65535 " << "(useful for testing only)" << endl; + cerr << " -N number: specify thread count 0-65535 " + << "(0 means multi-threading disabled)" << endl; exit(EXIT_FAILURE); } -} // end of anonymous namespace +} // namespace int main(int argc, char* argv[]) { @@ -71,13 +72,15 @@ main(int argc, char* argv[]) { int server_port_number = DHCP6_SERVER_PORT; // Not zero values are useful for testing only. int client_port_number = 0; + // Number of threads. 0 means multi-threading disabled + int thread_count = 0; bool verbose_mode = false; // Should server be verbose? bool check_mode = false; // Check syntax // The standard config file std::string config_file(""); - while ((ch = getopt(argc, argv, "dvVWc:p:P:t:")) != -1) { + while ((ch = getopt(argc, argv, "dvVWc:p:P:N:t:")) != -1) { switch (ch) { case 'd': verbose_mode = true; @@ -133,6 +136,21 @@ main(int argc, char* argv[]) { } break; + case 'N': // number of threads + try { + thread_count = boost::lexical_cast(optarg); + } catch (const boost::bad_lexical_cast &) { + cerr << "Failed to parse thread count number: [" << optarg + << "], 0-65535 allowed." << endl; + usage(); + } + if (thread_count < 0 || thread_count > 65535) { + cerr << "Failed to parse thread count number: [" << optarg + << "], 0-65535 allowed." << endl; + usage(); + } + break; + default: usage(); } @@ -193,11 +211,8 @@ main(int argc, char* argv[]) { cerr << "Error encountered: " << answer->stringValue() << endl; return (EXIT_FAILURE); } - - - return (EXIT_SUCCESS); } catch (const std::exception& ex) { - cerr << "Syntax check failed with " << ex.what() << endl; + cerr << "Syntax check failed with: " << ex.what() << endl; } return (EXIT_FAILURE); } @@ -207,11 +222,10 @@ main(int argc, char* argv[]) { // It is important that we set a default logger name because this name // will be used when the user doesn't provide the logging configuration // in the Kea configuration file. - Daemon::setDefaultLoggerName(DHCP6_LOGGER_NAME); + Daemon::setDefaultLoggerName(DHCP6_ROOT_LOGGER_NAME); // Initialize logging. If verbose, we'll use maximum verbosity. - Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode); - + Daemon::loggerInit(DHCP6_ROOT_LOGGER_NAME, verbose_mode); LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO) .arg(getpid()) .arg(server_port_number) @@ -226,16 +240,14 @@ main(int argc, char* argv[]) { // Remember verbose-mode server.setVerbose(verbose_mode); - // Create our PID file + // Create our PID file. server.setProcName(DHCP6_NAME); server.setConfigFile(config_file); server.createPIDFile(); try { - // Initialize the server, e.g. establish control session - // Read a configuration file + // Initialize the server. server.init(config_file); - } catch (const std::exception& ex) { try { @@ -277,7 +289,6 @@ main(int argc, char* argv[]) { } ret = EXIT_FAILURE; } catch (const std::exception& ex) { - // First, we print the error on stderr (that should always work) cerr << DHCP6_NAME << "Fatal error during start up: " << ex.what() << endl; diff --git a/src/lib/dhcpsrv/multi_threading_utils.cc b/src/lib/dhcpsrv/multi_threading_utils.cc index e12c89c22991c60394c9ceb294fd16b50f1a9644..37ccc71cd543a2717c7b8fbe4b4de5e844d6455c 100644 --- a/src/lib/dhcpsrv/multi_threading_utils.cc +++ b/src/lib/dhcpsrv/multi_threading_utils.cc @@ -7,8 +7,9 @@ #include #include -#include #include +#include + using namespace isc::util; @@ -41,5 +42,5 @@ MultiThreadingCriticalSection::~MultiThreadingCriticalSection() { } } -} -} +} // namespace dhcp +} // namespace isc diff --git a/src/lib/dhcpsrv/multi_threading_utils.h b/src/lib/dhcpsrv/multi_threading_utils.h index ef5615736f3dc4733080c22ae6ad50ad71ee3382..3136f2a75a441561cb3c5f99c5a1a8919de2530b 100644 --- a/src/lib/dhcpsrv/multi_threading_utils.h +++ b/src/lib/dhcpsrv/multi_threading_utils.h @@ -46,6 +46,7 @@ public: static void startPktProcessing(); }; -} -} +} // namespace dhcp +} // namespace isc + #endif // MULTI_THREADING_UTIL_H diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index d016bcbecf32cc46738cab8a4b4689eef7b5881f..b40c25a3ae956cefe8bf7d4ad94be7d796bb3826 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -41,6 +41,8 @@ SrvConfig::SrvConfig() cfg_host_operations6_(CfgHostOperations::createConfig6()), class_dictionary_(new ClientClassDictionary()), decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0), + server_threads_(0), + server_max_thread_queue_size_(0), d2_client_config_(new D2ClientConfig()), configured_globals_(Element::createMap()), cfg_consist_(new CfgConsistency()) { @@ -59,6 +61,8 @@ SrvConfig::SrvConfig(const uint32_t sequence) cfg_host_operations6_(CfgHostOperations::createConfig6()), class_dictionary_(new ClientClassDictionary()), decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0), + server_threads_(0), + server_max_thread_queue_size_(0), d2_client_config_(new D2ClientConfig()), configured_globals_(Element::createMap()), cfg_consist_(new CfgConsistency()) { @@ -253,7 +257,6 @@ SrvConfig::mergeGlobals(SrvConfig& other) { void SrvConfig::removeStatistics() { - // Removes statistics for v4 and v6 subnets getCfgSubnets4()->removeStatistics(); diff --git a/src/lib/dhcpsrv/srv_config.h b/src/lib/dhcpsrv/srv_config.h index efbe06aebf77e5c8ace76a19a2a00a0b78a0098f..3107ffeb2eb4f12be5b248f5fd922b80ec187589 100644 --- a/src/lib/dhcpsrv/srv_config.h +++ b/src/lib/dhcpsrv/srv_config.h @@ -705,6 +705,34 @@ public: return (dhcp4o6_port_); } + /// @brief Sets the server thread count. + /// + /// @param threads value of the server thread count + void setServerThreadCount(uint32_t threads) { + server_threads_ = threads; + } + + /// @brief Retrieves the server thread count. + /// + /// @return value of the server thread count + uint32_t getServerThreadCount() const { + return (server_threads_); + } + + /// @brief Sets the server max thread queue size. + /// + /// @param size max thread queue size + void setServerMaxThreadQueueSize(uint32_t size) { + server_max_thread_queue_size_ = size; + } + + /// @brief Retrieves the server max thread queue size. + /// + /// @return value of the max thread queue size + uint32_t getServerMaxThreadQueueSize() const { + return (server_max_thread_queue_size_); + } + /// @brief Returns pointer to the D2 client configuration D2ClientConfigPtr getD2ClientConfig() { return (d2_client_config_); @@ -923,6 +951,12 @@ private: /// this socket is bound and connected to this port and port + 1 uint16_t dhcp4o6_port_; + /// @brief The server thread count. + uint32_t server_threads_; + + /// @brief The server max thread queue size. + uint32_t server_max_thread_queue_size_; + /// @brief Stores D2 client configuration D2ClientConfigPtr d2_client_config_; @@ -943,7 +977,7 @@ typedef boost::shared_ptr SrvConfigPtr; typedef boost::shared_ptr ConstSrvConfigPtr; //@} -} // namespace isc::dhcp -} // namespace isc +} // namespace dhcp +} // namespace isc #endif // DHCPSRV_CONFIG_H diff --git a/src/lib/util/multi_threading_mgr.cc b/src/lib/util/multi_threading_mgr.cc index a572aa0b3bcabcd628ba90b6cd0b1e4f8183c883..401c056cae2b79fc396189131e22fdbd9b9eb4ed 100644 --- a/src/lib/util/multi_threading_mgr.cc +++ b/src/lib/util/multi_threading_mgr.cc @@ -31,5 +31,53 @@ MultiThreadingMgr::setMode(bool enabled) { enabled_ = enabled; } -} // namespace isc::util -} // namespace isc +ThreadPool>& +MultiThreadingMgr::getPktThreadPool() { + return pkt_thread_pool_; +} + +uint32_t +MultiThreadingMgr::getPktThreadPoolSize() const { + return (pkt_thread_pool_size_); +} + +void +MultiThreadingMgr::setPktThreadPoolSize(uint32_t size) { + pkt_thread_pool_size_ = size; +} + +uint32_t +MultiThreadingMgr::supportedThreadCount() { + return (std::thread::hardware_concurrency()); +} + +void +MultiThreadingMgr::apply(bool enabled, uint32_t thread_count) { + // check the enabled flag + if (enabled) { + // check for auto scaling (enabled flag true but thread_count 0) + if (!thread_count) { + // might also return 0 + thread_count = MultiThreadingMgr::supportedThreadCount(); + } + } else { + thread_count = 0; + } + // check enabled flag and explicit number of threads or system supports + // hardware concurrency + if (thread_count) { + if (pkt_thread_pool_.size()) { + pkt_thread_pool_.stop(); + } + setPktThreadPoolSize(thread_count); + setMode(true); + pkt_thread_pool_.start(thread_count); + } else { + pkt_thread_pool_.reset(); + setMode(false); + setPktThreadPoolSize(thread_count); + } +} + +} // namespace util +} // namespace isc diff --git a/src/lib/util/multi_threading_mgr.h b/src/lib/util/multi_threading_mgr.h index abaa7ae66727b66b173d9d2f0bfffdaf7c0744ef..1d5728d90f4d3dad7895766dfdc8d93f3d276706 100644 --- a/src/lib/util/multi_threading_mgr.h +++ b/src/lib/util/multi_threading_mgr.h @@ -7,8 +7,12 @@ #ifndef MULTI_THREADING_MGR_H #define MULTI_THREADING_MGR_H +#include + #include +#include + namespace isc { namespace util { @@ -62,6 +66,36 @@ public: /// @param enabled The new mode. void setMode(bool enabled); + /// @brief Get the packet thread pool. + /// + /// @return The packet thread pool of this binary instance. + ThreadPool>& getPktThreadPool(); + + /// @brief Get the configured packet thread pool size. + /// + /// @return The packet thread pool size of this binary instance. + uint32_t getPktThreadPoolSize() const; + + /// @brief Set the configured packet thread pool size. + /// + /// @param size The packet thread pool size of this binary instance. + void setPktThreadPoolSize(uint32_t size); + + /// @brief The system current supported hardware concurrency thread count. + /// + /// This function will return 0 if the value can not be determined. + /// + /// @return The thread count. + static uint32_t supportedThreadCount(); + + /// @brief Apply the multi-threading related settings + /// + /// @param enabled The enabled flag: true if multi-threading is enabled, + /// false otherwise. + /// @param thread_count The number of desired threads: non 0 if explicitly + /// configured, 0 if auto scaling is desired + void apply(bool enabled, uint32_t thread_count); + protected: /// @brief Constructor. @@ -71,11 +105,18 @@ protected: virtual ~MultiThreadingMgr(); private: - /// @brief the current mode. + + /// @brief The current mode. bool enabled_; + + /// @brief The configured size of the packet thread pool. + uint32_t pkt_thread_pool_size_; + + /// @brief Packet processing thread pool. + ThreadPool> pkt_thread_pool_; }; -} // namespace isc::util -} // namespace isc +} // namespace util +} // namespace isc #endif // MULTI_THREADING_MGR_H diff --git a/src/lib/util/tests/multi_threading_mgr_unittest.cc b/src/lib/util/tests/multi_threading_mgr_unittest.cc index 4fb6b8943a46bf20bd53a34e76098fdcbd2fa8a0..e789e700890f70a54c8ebd7c541d4b2e7626ce92 100644 --- a/src/lib/util/tests/multi_threading_mgr_unittest.cc +++ b/src/lib/util/tests/multi_threading_mgr_unittest.cc @@ -12,15 +12,91 @@ using namespace isc::util; -// Verifies that the default mode is false (MT disabled). +/// @brief Verifies that the default mode is false (MT disabled). TEST(MultiThreadingMgrTest, default) { + // MT should be disabled EXPECT_FALSE(MultiThreadingMgr::instance().getMode()); } -// Verifies that the setter works. +/// @brief Verifies that the mode setter works. TEST(MultiThreadingMgrTest, setMode) { + // enable MT EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(true)); + // MT should be enabled EXPECT_TRUE(MultiThreadingMgr::instance().getMode()); + // disable MT EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(false)); + // MT should be disabled EXPECT_FALSE(MultiThreadingMgr::instance().getMode()); } + +/// @brief Verifies that the thread pool size setter works. +TEST(MultiThreadingMgrTest, pktThreadPoolSize) { + // default thread count is 0 + EXPECT_EQ(MultiThreadingMgr::instance().getPktThreadPoolSize(), 0); + // set thread count to 16 + EXPECT_NO_THROW(MultiThreadingMgr::instance().setPktThreadPoolSize(16)); + // thread count should be 16 + EXPECT_EQ(MultiThreadingMgr::instance().getPktThreadPoolSize(), 16); + // set thread count to 0 + EXPECT_NO_THROW(MultiThreadingMgr::instance().setPktThreadPoolSize(0)); + // thread count should be 0 + EXPECT_EQ(MultiThreadingMgr::instance().getPktThreadPoolSize(), 0); +} + +/// @brief Verifies that determining supported thread count works. +TEST(MultiThreadingMgrTest, supportedThreadCount) { + // determining supported thread count should work + EXPECT_NE(MultiThreadingMgr::supportedThreadCount(), 0); +} + +/// @brief Verifies that accessing the thread pool works. +TEST(MultiThreadingMgrTest, pktThreadPool) { + // get the thread pool + EXPECT_NO_THROW(MultiThreadingMgr::instance().getPktThreadPool()); +} + +/// @brief Verifies that apply settings works. +TEST(MultiThreadingMgrTest, applyConfig) { + // get the thread pool + auto& thread_pool = MultiThreadingMgr::instance().getPktThreadPool(); + // MT should be disabled + EXPECT_FALSE(MultiThreadingMgr::instance().getMode()); + // default thread count is 0 + EXPECT_EQ(MultiThreadingMgr::instance().getPktThreadPoolSize(), 0); + // thread pool should be stopped + EXPECT_EQ(thread_pool.size(), 0); + // enable MT with 16 threads + EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 16)); + // MT should be enabled + EXPECT_TRUE(MultiThreadingMgr::instance().getMode()); + // thread count should be 16 + EXPECT_EQ(MultiThreadingMgr::instance().getPktThreadPoolSize(), 16); + // thread pool should be started + EXPECT_EQ(thread_pool.size(), 16); + // disable MT + EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 16)); + // MT should be disabled + EXPECT_FALSE(MultiThreadingMgr::instance().getMode()); + // thread count should be 0 + EXPECT_EQ(MultiThreadingMgr::instance().getPktThreadPoolSize(), 0); + // thread pool should be stopped + EXPECT_EQ(thread_pool.size(), 0); + // enable MT with auto scaling + EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 0)); + // MT should be enabled + EXPECT_TRUE(MultiThreadingMgr::instance().getMode()); + // thread count should be maximum + EXPECT_EQ(MultiThreadingMgr::instance().getPktThreadPoolSize(), MultiThreadingMgr::supportedThreadCount()); + // thread pool should be started + EXPECT_EQ(thread_pool.size(), MultiThreadingMgr::supportedThreadCount()); + // disable MT + EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 0)); + // MT should be disabled + EXPECT_FALSE(MultiThreadingMgr::instance().getMode()); + // thread count should be 0 + EXPECT_EQ(MultiThreadingMgr::instance().getPktThreadPoolSize(), 0); + // thread pool should be stopped + EXPECT_EQ(thread_pool.size(), 0); +} + diff --git a/src/lib/util/thread_pool.h b/src/lib/util/thread_pool.h index 87727f0efdfceb93fc489c9479b206e6b7b872ad..af97410ad0616a6f7c9149d38c2f54299b45f697 100644 --- a/src/lib/util/thread_pool.h +++ b/src/lib/util/thread_pool.h @@ -230,7 +230,7 @@ private: /// @return the state bool enabled() { return (enabled_); - } + } private: /// @brief underlying queue container