// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
-#include <cc/data.h>
+
#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cfgrpt/config_report.h>
#include <config/command_mgr.h>
+#include <database/dbaccess_parser.h>
#include <dhcp/libdhcp++.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/cfg_db_access.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4to6_ipc.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp4/parser_context.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/db_type.h>
#include <hooks/hooks.h>
#include <hooks/hooks_manager.h>
#include <stats/stats_mgr.h>
-#include <cfgrpt/config_report.h>
+#include <util/threads/lock_guard.h>
+
#include <signal.h>
+
+#include <memory>
#include <sstream>
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::thread;
using namespace std;
namespace {
ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
-void
-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) {
- 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 ControlledDhcpv4Srv::cleanup() {
- // Nothing to do here. No need to disconnect from anything.
-}
-
/// @brief Configure DHCPv4 server using the configuration file specified.
///
/// This function is used to both configure the DHCP server on its startup
// 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.
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
return (result);
}
+void
+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) {
+ 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 ControlledDhcpv4Srv::cleanup() {
+ // Nothing to do here. No need to disconnect from anything.
+}
ConstElementPtr
ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) {
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);
}
ConstElementPtr
ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
ConstElementPtr /*args*/) {
-
// Get configuration file name.
std::string file = ControlledDhcpv4Srv::getInstance()->getConfigFile();
try {
}
ConstElementPtr
-ControlledDhcpv4Srv::commandConfigWriteHandler(const string&,
- ConstElementPtr args) {
+ControlledDhcpv4Srv::commandConfigWriteHandler(const string&, ConstElementPtr args) {
string filename;
if (args) {
if (filename.empty()) {
// filename parameter was not specified, so let's use whatever we remember
+ // from the command-line
filename = getConfigFile();
}
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;
// 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.
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);
}
ConstElementPtr
-ControlledDhcpv4Srv::commandBuildReportHandler(const string&,
- 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);
}
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);
}
+ if (srv->run_multithreaded_) {
+ srv->pkt_thread_pool_.destroy();
+ srv->pkt_thread_pool_.create(Dhcpv4Srv::threadCount());
+ }
+
try {
if (command == "shutdown") {
return (srv->commandShutdownHandler(command, args));
return (srv->commandConfigBackendPullHandler(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 '"
- + command + "':" + ex.what() +
+ return (isc::config::createAnswer(CONTROL_RESULT_ERROR, "Error while processing command '" +
+ command + "': " + ex.what() +
", params: '" + txt + "'"));
}
}
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);
return (answer);
}
} catch (const std::exception& ex) {
- err << "Failed to process configuration:" << ex.what();
- return (isc::config::createAnswer(1, err.str()));
+ 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.
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.
} 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
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
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 << "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<long>(ctl_info->getConfigFetchWaitTime());
ControlledDhcpv4Srv::checkConfig(isc::data::ConstElementPtr config) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
- .arg(config->str());
+ .arg(config->str());
ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
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));
}
ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_PORT*/,
- uint16_t client_port /*= 0*/)
- : Dhcpv4Srv(server_port, client_port), io_service_(),
+ uint16_t client_port /*= 0*/,
+ bool run_multithreaded /*= false*/)
+ : Dhcpv4Srv(server_port, client_port, run_multithreaded), io_service_(),
timer_mgr_(TimerMgr::instance()) {
if (getInstance()) {
isc_throw(InvalidOperation,
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));
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");
;
}
- 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) {
}
}
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+} // namespace dhcp
+} // namespace isc
///
/// @param server_port UDP port to be opened for DHCP traffic
/// @param client_port UDP port where all responses are sent to.
+ /// @param run_multithreaded enables or disables multithreaded mode
ControlledDhcpv4Srv(uint16_t server_port = DHCP4_SERVER_PORT,
- uint16_t client_port = 0);
+ uint16_t client_port = 0,
+ bool run_multithreaded = false);
/// @brief Destructor.
- ~ControlledDhcpv4Srv();
+ virtual ~ControlledDhcpv4Srv();
/// @brief Initializes the server.
///
/// This method may throw if initialization fails.
void init(const std::string& config_file);
- /// @brief Loads specific config file
+ /// @brief Loads specific configuration file
///
/// This utility method is called whenever we know a filename of the config
/// and need to load it. It calls config-set command once the content of
/// the file has been loaded and verified to be a sane JSON configuration.
- /// config-set handler will process the config file (load it as current
+ /// config-set handler will process the config file (apply it as current
/// configuration).
///
/// @param file_name name of the file to be loaded
return (server_);
}
-
private:
/// @brief Callback that will be called from iface_mgr when data
/// is received over control socket.
///
- /// This static callback method is called from IfaceMgr::receive6() method,
+ /// This static callback method is called from IfaceMgr::receive4() method,
/// when there is a new command or configuration sent over control socket
/// (that was sent from some yet unspecified sender).
static void sessionReader(void);
commandDhcpEnableHandler(const std::string& command,
isc::data::ConstElementPtr args);
-
/// @Brief handler for processing 'version-get' command
///
/// This handler processes version-get command, which returns
/// @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.
TimerMgrPtr timer_mgr_;
};
-}; // namespace isc::dhcp
-}; // namespace isc
+} // namespace dhcp
+} // namespace isc
#endif
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_selector.h>
#include <dhcpsrv/utils.h>
-#include <dhcpsrv/utils.h>
#include <eval/evaluate.h>
#include <eval/eval_messages.h>
#include <hooks/callout_handle.h>
#include <hooks/hooks_manager.h>
#include <stats/stats_mgr.h>
#include <util/strutil.h>
-#include <stats/stats_mgr.h>
+#include <util/threads/lock_guard.h>
#include <log/logger.h>
#include <cryptolink/cryptolink.h>
#include <cfgrpt/config_report.h>
#include <iomanip>
#include <set>
+
using namespace isc;
using namespace isc::asiolink;
using namespace isc::cryptolink;
using namespace isc::stats;
using namespace std;
+using isc::util::thread::LockGuard;
+
namespace {
/// Structure that holds registered hook indexes
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");
.arg(query_->getLabel())
.arg(classes.toText());
}
-};
+}
void
Dhcpv4Exchange::initResponse() {
const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
Dhcpv4Srv::Dhcpv4Srv(uint16_t server_port, uint16_t client_port,
+ bool run_multithreaded /* = false */,
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), use_bcast_(use_bcast),
+ shutdown_(true), alloc_engine_(),
network_state_(new NetworkState(NetworkState::DHCPv4)),
- cb_control_(new CBControlDHCPv4()) {
+ cb_control_(new CBControlDHCPv4()),
+ run_multithreaded_(run_multithreaded) {
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
bool
Dhcpv4Srv::run() {
+ if (run_multithreaded_) {
+ // Creating the process packet thread pool
+ // The number of thread pool's threads should be read from configuration
+ // file or it should be determined by the number of hardware threads and
+ // the number of Cassandra DB nodes.
+ pkt_thread_pool_.create(Dhcpv4Srv::threadCount());
+ }
+
while (!shutdown_) {
try {
run_one();
}
}
+ // destroying the thread pool
+ if (run_multithreaded_) {
+ pkt_thread_pool_.destroy();
+ }
+
return (true);
}
Pkt4Ptr rsp;
try {
+
+ // Do not read more packets from socket if there are enough
+ // packets to be processed in the packet thread pool queue
+ const int max_queued_pkt_per_thread = Dhcpv4Srv::maxThreadQueueSize();
+ const auto queue_full_wait = std::chrono::milliseconds(1);
+ size_t pkt_queue_size = pkt_thread_pool_.count();
+ if (pkt_queue_size >= Dhcpv4Srv::threadCount() *
+ max_queued_pkt_per_thread) {
+ std::this_thread::sleep_for(queue_full_wait);
+ return;
+ }
+
// 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.
.arg(query->getLabel());
return;
} else {
- processPacket(query, rsp);
+ if (run_multithreaded_) {
+ ThreadPool::WorkItemCallBack call_back =
+ std::bind(&Dhcpv4Srv::processPacketAndSendResponseNoThrow, this, query, rsp);
+ pkt_thread_pool_.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;
}
return (Hooks.hook_index_lease4_decline_);
}
+uint32_t Dhcpv4Srv::threadCount() {
+ uint32_t sys_threads = CfgMgr::instance().getCurrentCfg()->getServerThreadCount();
+ if (sys_threads) {
+ return sys_threads;
+ }
+ sys_threads = std::thread::hardware_concurrency();
+ return sys_threads * 1;
+}
+
+uint32_t Dhcpv4Srv::maxThreadQueueSize() {
+ uint32_t max_thread_queue_size = CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize();
+ if (max_thread_queue_size) {
+ return max_thread_queue_size;
+ }
+ return 4;
+}
+
void Dhcpv4Srv::discardPackets() {
// Clear any packets held by the callhout handle store and
// all parked packets
HooksManager::clearParkingLots();
}
-} // namespace dhcp
-} // namespace isc
+} // namespace dhcp
+} // namespace isc
#include <dhcp/option4_client_fqdn.h>
#include <dhcp/option_custom.h>
#include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/thread_pool.h>
#include <dhcpsrv/alloc_engine.h>
+#include <dhcpsrv/callout_handle_store.h>
#include <dhcpsrv/cb_ctl_dhcp4.h>
#include <dhcpsrv/cfg_option.h>
-#include <dhcpsrv/callout_handle_store.h>
#include <dhcpsrv/d2_client_mgr.h>
#include <dhcpsrv/network_state.h>
#include <dhcpsrv/subnet.h>
#include <functional>
#include <iostream>
#include <queue>
+#include <boost/scoped_ptr.hpp>
+#include <atomic>
// Undefine the macro OPTIONAL which is defined in some operating
// systems but conflicts with a member of the RequirementLevel enum in
asiolink::IOServicePtr io_service_;
public:
-
/// @brief defines if certain option may, must or must not appear
typedef enum {
FORBIDDEN,
///
/// @param server_port specifies port number to listen on
/// @param client_port specifies port number to send to
+ /// @param run_multithreaded enables or disables multithreaded mode
/// @param use_bcast configure sockets to support broadcast messages.
/// @param direct_response_desired specifies if it is desired to
/// use direct V4 traffic.
Dhcpv4Srv(uint16_t server_port = DHCP4_SERVER_PORT,
uint16_t client_port = 0,
+ bool run_multithreaded = false,
const bool use_bcast = true,
const bool direct_response_desired = true);
/// @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);
/// redeclaration/redefinition. @ref isc::process::Daemon::getVersion()
static std::string getVersion(bool extended);
+ /// @brief returns Kea DHCPv4 server thread count.
+ static uint32_t threadCount();
+
+ /// @brief returns Kea DHCPv4 server max thread queue size.
+ static uint32_t maxThreadQueueSize();
+
/// @brief Main server processing loop.
///
/// Main server processing loop. Call the processing step routine
/// 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 a single incoming DHCPv4 packet.
///
/// It verifies correctness of the passed packet, call per-type processXXX
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:
/// @param lease lease being assigned to the client
/// @param subnet the subnet to which the lease belongs
/// @param resp outbound response for the client to which timers are added.
- void setTeeTimes(const Lease4Ptr& lease, const Subnet4Ptr& subnet, Pkt4Ptr resp);
+ static void setTeeTimes(const Lease4Ptr& lease, const Subnet4Ptr& subnet, Pkt4Ptr resp);
/// @brief Append basic options if they are not present.
///
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
void classifyPacket(const Pkt4Ptr& pkt);
public:
-
/// @brief Evaluate classes.
///
/// @note Second part of the classification.
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<AllocEngine> alloc_engine_;
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_;
+ /// Should broadcast be enabled on sockets (if true).
+ bool use_bcast_;
+
+ /// 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<AllocEngine> alloc_engine_;
/// @brief Holds information about disabled DHCP service and/or
/// disabled subnet/network scopes.
NetworkStatePtr network_state_;
/// @brief Controls access to the configuration backends.
CBControlDHCPv4Ptr cb_control_;
+ /// @brief Packet processing thread pool
+ ThreadPool pkt_thread_pool_;
+
+ // Specifies if the application will use a thread pool or will process
+ // received DHCP packets on the main thread.
+ // It is mandatory to be set on false when running the test cases.
+ std::atomic_bool run_multithreaded_;
+
public:
/// Class methods for DHCPv4-over-DHCPv6 handler
static int getHookIndexLease4Decline();
};
-}; // namespace isc::dhcp
-}; // namespace isc
+} // namespace dhcp
+} // namespace isc
#endif // DHCP4_SRV_H
#include <config.h>
#include <kea_version.h>
+#include <cc/command_interpreter.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/parser_context.h>
#include <dhcp4/json_config_parser.h>
-#include <cc/command_interpreter.h>
#include <dhcpsrv/cfgmgr.h>
#include <log/logger_support.h>
#include <log/logger_manager.h>
+#include <exceptions/exceptions.h>
#include <cfgrpt/config_report.h>
+#include <process/daemon.h>
#include <boost/lexical_cast.hpp>
/// 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 {
<< "(useful for testing only)" << endl;
exit(EXIT_FAILURE);
}
-} // end of anonymous namespace
+} // namespace
int
main(int argc, char* argv[]) {
config_file = optarg;
break;
- case 'p':
+ case 'p': // server port number
try {
server_port_number = boost::lexical_cast<int>(optarg);
} catch (const boost::bad_lexical_cast &) {
}
break;
- case 'P':
+ case 'P': // client port number
try {
client_port_number = boost::lexical_cast<int>(optarg);
} catch (const boost::bad_lexical_cast &) {
usage();
}
-
// Configuration file is required.
if (config_file.empty()) {
cerr << "Configuration file not specified." << endl;
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);
LOG_INFO(dhcp4_logger, DHCP4_STARTING).arg(VERSION);
// Create the server instance.
- ControlledDhcpv4Srv server(server_port_number, client_port_number);
+ ControlledDhcpv4Srv server(server_port_number, client_port_number, true);
// Remember verbose-mode
server.setVerbose(verbose_mode);
#include <hooks/hooks_manager.h>
#include <log/logger_support.h>
#include <stats/stats_mgr.h>
-#include <util/boost_time_utils.h>
#include <testutils/io_utils.h>
#include <testutils/unix_control_client.h>
#include <testutils/sandbox.h>
+#include <util/boost_time_utils.h>
#include "marker_file.h"
#include "test_libraries.h"
EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutting down.\" }",response);
}
-// Tests that the server properly responds to statistics commands. Note this
-// is really only intended to verify that the appropriate Statistics handler
-// is called based on the command. It is not intended to be an exhaustive
-// test of Dhcpv4 statistics.
-TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelStats) {
- createUnixChannelServer();
- std::string response;
-
- // Check statistic-get
- sendUnixCommand("{ \"command\" : \"statistic-get\", "
- " \"arguments\": {"
- " \"name\":\"bogus\" }}", response);
- EXPECT_EQ("{ \"arguments\": { }, \"result\": 0 }", response);
-
- // Check statistic-get-all
- sendUnixCommand("{ \"command\" : \"statistic-get-all\", "
- " \"arguments\": {}}", response);
-
- std::set<std::string> initial_stats = {
- "pkt4-received",
- "pkt4-discover-received",
- "pkt4-offer-received",
- "pkt4-request-received",
- "pkt4-ack-received",
- "pkt4-nak-received",
- "pkt4-release-received",
- "pkt4-decline-received",
- "pkt4-inform-received",
- "pkt4-unknown-received",
- "pkt4-sent",
- "pkt4-offer-sent",
- "pkt4-ack-sent",
- "pkt4-nak-sent",
- "pkt4-parse-failed",
- "pkt4-receive-drop"
- };
-
- // preparing the schema which check if all statistics are set to zero
- std::ostringstream s;
- s << "{ \"arguments\": { ";
- for (auto st = initial_stats.begin(); st != initial_stats.end();) {
- s << "\"" << *st << "\": [ [ 0, \"";
- s << isc::util::ptimeToText(StatsMgr::instance().getObservation(*st)->getInteger().second);
- s << "\" ] ]";
- if (++st != initial_stats.end()) {
- s << ", ";
- }
- }
- s << " }, \"result\": 0 }";
-
- auto stats_get_all = s.str();
-
- EXPECT_EQ(stats_get_all, response);
-
- // Check statistic-reset
- sendUnixCommand("{ \"command\" : \"statistic-reset\", "
- " \"arguments\": {"
- " \"name\":\"bogus\" }}", response);
- EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
- response);
-
- // Check statistic-reset-all
- sendUnixCommand("{ \"command\" : \"statistic-reset-all\", "
- " \"arguments\": {}}", response);
- EXPECT_EQ("{ \"result\": 0, \"text\": "
- "\"All statistics reset to neutral values.\" }", response);
-
- // Check statistic-remove
- sendUnixCommand("{ \"command\" : \"statistic-remove\", "
- " \"arguments\": {"
- " \"name\":\"bogus\" }}", response);
- EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
- response);
-
- // Check statistic-remove-all
- sendUnixCommand("{ \"command\" : \"statistic-remove-all\", "
- " \"arguments\": {}}", response);
- EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics removed.\" }",
- response);
-
- // Check statistic-sample-age-set
- sendUnixCommand("{ \"command\" : \"statistic-sample-age-set\", "
- " \"arguments\": {"
- " \"name\":\"bogus\", \"duration\": 1245 }}", response);
- EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
- response);
-
- // Check statistic-sample-age-set-all
- sendUnixCommand("{ \"command\" : \"statistic-sample-age-set-all\", "
- " \"arguments\": {"
- " \"duration\": 1245 }}", response);
- EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics duration limit are set.\" }",
- response);
-
- // Check statistic-sample-count-set
- sendUnixCommand("{ \"command\" : \"statistic-sample-count-set\", "
- " \"arguments\": {"
- " \"name\":\"bogus\", \"max-samples\": 100 }}", response);
- EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
- response);
-
- // Check statistic-sample-count-set-all
- sendUnixCommand("{ \"command\" : \"statistic-sample-count-set-all\", "
- " \"arguments\": {"
- " \"max-samples\": 100 }}", response);
- EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics count limit are set.\" }",
- response);
-}
-
// Check that the "config-set" command will replace current configuration
TEST_F(CtrlChannelDhcpv4SrvTest, configSet) {
createUnixChannelServer();
ASSERT_FALSE(lease1);
}
+// Tests that the server properly responds to statistics commands. Note this
+// is really only intended to verify that the appropriate Statistics handler
+// is called based on the command. It is not intended to be an exhaustive
+// test of Dhcpv4 statistics.
+TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelStats) {
+ createUnixChannelServer();
+ std::string response;
+
+ // Check statistic-get
+ sendUnixCommand("{ \"command\" : \"statistic-get\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\" }}", response);
+ EXPECT_EQ("{ \"arguments\": { }, \"result\": 0 }", response);
+
+ // Check statistic-get-all
+ sendUnixCommand("{ \"command\" : \"statistic-get-all\", "
+ " \"arguments\": {}}", response);
+
+ std::set<std::string> initial_stats = {
+ "pkt4-received",
+ "pkt4-discover-received",
+ "pkt4-offer-received",
+ "pkt4-request-received",
+ "pkt4-ack-received",
+ "pkt4-nak-received",
+ "pkt4-release-received",
+ "pkt4-decline-received",
+ "pkt4-inform-received",
+ "pkt4-unknown-received",
+ "pkt4-sent",
+ "pkt4-offer-sent",
+ "pkt4-ack-sent",
+ "pkt4-nak-sent",
+ "pkt4-parse-failed",
+ "pkt4-receive-drop"
+ };
+
+ // preparing the schema which check if all statistics are set to zero
+ std::ostringstream s;
+ s << "{ \"arguments\": { ";
+ for (auto st = initial_stats.begin(); st != initial_stats.end();) {
+ s << "\"" << *st << "\": [ [ 0, \"";
+ s << isc::util::ptimeToText(StatsMgr::instance().getObservation(*st)->getInteger().second);
+ s << "\" ] ]";
+ if (++st != initial_stats.end()) {
+ s << ", ";
+ }
+ }
+ s << " }, \"result\": 0 }";
+
+ auto stats_get_all = s.str();
+
+ EXPECT_EQ(stats_get_all, response);
+
+ // Check statistic-reset
+ sendUnixCommand("{ \"command\" : \"statistic-reset\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\" }}", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+ response);
+
+ // Check statistic-reset-all
+ sendUnixCommand("{ \"command\" : \"statistic-reset-all\", "
+ " \"arguments\": {}}", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": "
+ "\"All statistics reset to neutral values.\" }", response);
+
+ // Check statistic-remove
+ sendUnixCommand("{ \"command\" : \"statistic-remove\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\" }}", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+ response);
+
+ // Check statistic-remove-all
+ sendUnixCommand("{ \"command\" : \"statistic-remove-all\", "
+ " \"arguments\": {}}", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics removed.\" }",
+ response);
+
+ // Check statistic-sample-age-set
+ sendUnixCommand("{ \"command\" : \"statistic-sample-age-set\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\", \"duration\": 1245 }}", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+ response);
+
+ // Check statistic-sample-age-set-all
+ sendUnixCommand("{ \"command\" : \"statistic-sample-age-set-all\", "
+ " \"arguments\": {"
+ " \"duration\": 1245 }}", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics duration limit are set.\" }",
+ response);
+
+ // Check statistic-sample-count-set
+ sendUnixCommand("{ \"command\" : \"statistic-sample-count-set\", "
+ " \"arguments\": {"
+ " \"name\":\"bogus\", \"max-samples\": 100 }}", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
+ response);
+
+ // Check statistic-sample-count-set-all
+ sendUnixCommand("{ \"command\" : \"statistic-sample-count-set-all\", "
+ " \"arguments\": {"
+ " \"max-samples\": 100 }}", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics count limit are set.\" }",
+ response);
+}
+
// Tests that the server properly responds to shtudown command sent
// via ControlChannel
TEST_F(CtrlChannelDhcpv4SrvTest, listCommands) {
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
-#include <cc/data.h>
+
#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cfgrpt/config_report.h>
#include <config/command_mgr.h>
+#include <database/dbaccess_parser.h>
#include <dhcp/libdhcp++.h>
-#include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/cfg_db_access.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6to4_ipc.h>
#include <dhcp6/json_config_parser.h>
#include <dhcp6/parser_context.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/db_type.h>
#include <hooks/hooks.h>
#include <hooks/hooks_manager.h>
#include <stats/stats_mgr.h>
-#include <cfgrpt/config_report.h>
+#include <util/threads/lock_guard.h>
+
#include <signal.h>
+
+#include <memory>
#include <sstream>
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::thread;
using namespace std;
namespace {
// 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.
// 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);
+ ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
if (rcode != 0) {
string reason = comment ? comment->stringValue() :
"no details available";
return (result);
}
-
void
ControlledDhcpv6Srv::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) {
// Nothing to do here. No need to disconnect from anything.
}
-
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);
}
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);
}
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) {
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": <bool> }
"On demand configuration update successful."));
}
-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)
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);
}
+ if (srv->run_multithreaded_) {
+ srv->pkt_thread_pool_.destroy();
+ srv->pkt_thread_pool_.create(Dhcpv6Srv::threadCount());
+ }
+
try {
if (command == "shutdown") {
return (srv->commandShutdownHandler(command, args));
return (srv->commandConfigBackendPullHandler(command, args));
}
-
- return (isc::config::createAnswer(1, "Unrecognized command:"
- + 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 + "'"));
}
}
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);
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.
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.
} 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
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
}
} 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()));
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.
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));
}
ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port,
- uint16_t client_port)
- : Dhcpv6Srv(server_port, client_port), io_service_(),
+ uint16_t client_port,
+ bool run_multithreaded /*= false*/)
+ : Dhcpv6Srv(server_port, client_port, run_multithreaded), 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());
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));
CommandMgr::instance().registerCommand("config-write",
boost::bind(&ControlledDhcpv6Srv::commandConfigWriteHandler, this, _1, _2));
+ CommandMgr::instance().registerCommand("dhcp-enable",
+ boost::bind(&ControlledDhcpv6Srv::commandDhcpEnableHandler, this, _1, _2));
+
CommandMgr::instance().registerCommand("dhcp-disable",
boost::bind(&ControlledDhcpv6Srv::commandDhcpDisableHandler, this, _1, _2));
- CommandMgr::instance().registerCommand("dhcp-enable",
- boost::bind(&ControlledDhcpv6Srv::commandDhcpEnableHandler, this, _1, _2));
+ CommandMgr::instance().registerCommand("libreload",
+ boost::bind(&ControlledDhcpv6Srv::commandLibReloadHandler, this, _1, _2));
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("shutdown",
boost::bind(&ControlledDhcpv6Srv::commandShutdownHandler, this, _1, _2));
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();
}
}
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()) {
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);
}
}
}
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+} // namespace dhcp
+} // namespace isc
///
/// @param server_port UDP port to be opened for DHCP traffic
/// @param client_port UDP port where all responses are sent to.
+ /// @param run_multithreaded enables or disables multithreaded mode
ControlledDhcpv6Srv(uint16_t server_port = DHCP6_SERVER_PORT,
- uint16_t client_port = 0);
+ uint16_t client_port = 0,
+ bool run_multithreaded = false);
/// @brief Destructor.
virtual ~ControlledDhcpv6Srv();
/// @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
/// Currently supported commands are:
/// - config-reload
/// - config-test
- /// - leases-reclaim
- /// - libreload
/// - shutdown
+ /// - libreload
+ /// - leases-reclaim
/// ...
///
/// @note It never throws.
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
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() {
}
private:
-
/// @brief Callback that will be called from iface_mgr when data
/// is received over control socket.
///
/// (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.
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.
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.
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.
///
/// 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
///
///
/// @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
TimerMgrPtr timer_mgr_;
};
-}; // namespace isc::dhcp
-}; // namespace isc
+} // namespace dhcp
+} // namespace isc
#endif
#include <hooks/hooks_log.h>
#include <hooks/hooks_manager.h>
#include <stats/stats_mgr.h>
-
#include <util/encode/hex.h>
#include <util/io_utilities.h>
#include <util/pointer_util.h>
#include <util/range_utilities.h>
+#include <util/threads/lock_guard.h>
#include <log/logger.h>
#include <cryptolink/cryptolink.h>
#include <cfgrpt/config_report.h>
using namespace isc::util;
using namespace std;
+using isc::util::thread::LockGuard;
+
namespace {
/// Structure that holds registered hook indexes
"pkt6-receive-drop"
};
-}; // anonymous namespace
+} // namespace
namespace isc {
namespace dhcp {
const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
-Dhcpv6Srv::Dhcpv6Srv(uint16_t server_port, uint16_t client_port)
+Dhcpv6Srv::Dhcpv6Srv(uint16_t server_port, uint16_t client_port,
+ bool run_multithreaded /* = false */)
: io_service_(new IOService()), server_port_(server_port),
- client_port_(client_port), serverid_(), shutdown_(true),
- alloc_engine_(), name_change_reqs_(),
+ client_port_(client_port), serverid_(),
+ shutdown_(true), alloc_engine_(),
+ name_change_reqs_(),
network_state_(new NetworkState(NetworkState::DHCPv6)),
- cb_control_(new CBControlDHCPv6()) {
+ cb_control_(new CBControlDHCPv6()),
+ run_multithreaded_(run_multithreaded) {
+
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET)
.arg(server_port);
}
bool Dhcpv6Srv::run() {
+ if (run_multithreaded_) {
+ // Creating the process packet thread pool
+ // The number of thread pool's threads should be read from configuration
+ // file or it should be determined by the number of hardware threads and
+ // the number of Cassandra DB nodes.
+ pkt_thread_pool_.create(Dhcpv6Srv::threadCount());
+ }
+
while (!shutdown_) {
try {
run_one();
}
}
+ // destroying the thread pool
+ if (run_multithreaded_) {
+ pkt_thread_pool_.destroy();
+ }
+
return (true);
}
Pkt6Ptr rsp;
try {
+
+ // Do not read more packets from socket if there are enough
+ // packets to be processed in the packet thread pool queue
+ const int max_queued_pkt_per_thread = Dhcpv6Srv::maxThreadQueueSize();
+ const auto queue_full_wait = std::chrono::milliseconds(1);
+ size_t pkt_queue_size = pkt_thread_pool_.count();
+ if (pkt_queue_size >= Dhcpv6Srv::threadCount() *
+ max_queued_pkt_per_thread) {
+ std::this_thread::sleep_for(queue_full_wait);
+ return;
+ }
+
// 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.
.arg(query->getLabel());
return;
} else {
- processPacket(query, rsp);
+ if (run_multithreaded_) {
+ ThreadPool::WorkItemCallBack call_back =
+ std::bind(&Dhcpv6Srv::processPacketAndSendResponseNoThrow, this, query, rsp);
+ pkt_thread_pool_.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;
}
switch (pkt->getType()) {
case DHCPV6_SOLICIT:
case DHCPV6_REBIND:
- case DHCPV6_CONFIRM:
+ case DHCPV6_CONFIRM:
sanityCheck(pkt, MANDATORY, FORBIDDEN);
return (true);
if (answer_opt) {
answer->addOption(answer_opt);
}
+ break;
}
default:
break;
return (false);
}
+uint32_t Dhcpv6Srv::threadCount() {
+ uint32_t sys_threads = CfgMgr::instance().getCurrentCfg()->getServerThreadCount();
+ if (sys_threads) {
+ return sys_threads;
+ }
+ sys_threads = std::thread::hardware_concurrency();
+ return sys_threads * 1;
+}
+
+uint32_t Dhcpv6Srv::maxThreadQueueSize() {
+ uint32_t max_thread_queue_size = CfgMgr::instance().getCurrentCfg()->getServerMaxThreadQueueSize();
+ if (max_thread_queue_size) {
+ return max_thread_queue_size;
+ }
+ return 4;
+}
+
void Dhcpv6Srv::discardPackets() {
// Dump all of our current packets, anything that is mid-stream
isc::dhcp::Pkt6Ptr pkt6ptr_empty;
}
}
-};
-};
+} // namespace dhcp
+} // namespace isc
#define DHCPV6_SRV_H
#include <asiolink/io_service.h>
-#include <dhcp_ddns/ncr_msg.h>
#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
+#include <dhcp/option_string.h>
#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
-#include <dhcp/option_definition.h>
-#include <dhcp/pkt6.h>
+#include <dhcp/option_custom.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/thread_pool.h>
#include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/callout_handle_store.h>
#include <dhcpsrv/cb_ctl_dhcp6.h>
#include <hooks/callout_handle.h>
#include <process/daemon.h>
+#include <boost/noncopyable.hpp>
+
#include <functional>
#include <iostream>
#include <queue>
+#include <boost/scoped_ptr.hpp>
+#include <atomic>
// Undefine the macro OPTIONAL which is defined in some operating
// systems but conflicts with a member of the RequirementLevel enum in
/// @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:
/// 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.
- ///
- /// @param server_port port on which all sockets will listen
- /// @param client_port port to which all responses will be sent
+ /// 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 specifies port number to listen on
+ /// @param client_port specifies port number to send to
+ /// @param run_multithreaded enables or disables multithreaded mode
Dhcpv6Srv(uint16_t server_port = DHCP6_SERVER_PORT,
- uint16_t client_port = 0);
+ uint16_t client_port = 0,
+ bool run_multithreaded = false);
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~Dhcpv6Srv();
/// redeclaration/redefinition. @ref isc::process::Daemon::getVersion()
static std::string getVersion(bool extended);
+ /// @brief returns Kea DHCPv6 server thread count.
+ static uint32_t threadCount();
+
+ /// @brief returns Kea DHCPv6 server max thread queue size.
+ static uint32_t maxThreadQueueSize();
+
/// @brief Returns server-identifier option.
///
/// @return server-id option
/// 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 a single incoming DHCPv6 packet.
///
/// It verifies correctness of the passed packet, call per-type processXXX
/// @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 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.
///
/// @param preferred_lft preferred lease time of the lease being assigned to the client
/// @param subnet the subnet to which the lease belongs
/// @param resp outbound IA option in which the timers are set.
- void setTeeTimes(uint32_t preferred_lft, const Subnet6Ptr& subnet, Option6IAPtr& resp);
+ static void setTeeTimes(uint32_t preferred_lft, const Subnet6Ptr& subnet, Option6IAPtr& resp);
/// @brief Attempts to release received addresses
///
/// @param pkt packet to be classified
void classifyPacket(const Pkt6Ptr& pkt);
+public:
/// @brief Evaluate classes.
///
/// @note Second part of the classification.
/// @param pkt packet to be classified.
/// @param depend_on_known if false classes depending on the KNOWN or
/// UNKNOWN classes are skipped, if true only these classes are evaluated.
- void evaluateClasses(const Pkt6Ptr& pkt, bool depend_on_known);
+ static void evaluateClasses(const Pkt6Ptr& pkt, bool depend_on_known);
/// @brief Assigns classes retrieved from host reservation database.
///
/// @brief Controls access to the configuration backends.
CBControlDHCPv6Ptr cb_control_;
+
+ /// @brief Packet processing thread pool
+ ThreadPool pkt_thread_pool_;
+
+ // Specifies if the application will use a thread pool or will process
+ // received DHCP packets on the main thread.
+ // It is mandatory to be set on false when running the test cases.
+ std::atomic_bool run_multithreaded_;
};
-}; // namespace isc::dhcp
-}; // namespace isc
+} // namespace dhcp
+} // namespace isc
#endif // DHCP6_SRV_H
#include <config.h>
#include <kea_version.h>
+#include <cc/command_interpreter.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/parser_context.h>
/// 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
///
<< "(useful for testing only)" << endl;
exit(EXIT_FAILURE);
}
-} // end of anonymous namespace
+} // namespace
int
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);
}
// 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)
LOG_INFO(dhcp6_logger, DHCP6_STARTING).arg(VERSION);
// Create the server instance.
- ControlledDhcpv6Srv server(server_port_number, client_port_number);
+ ControlledDhcpv6Srv server(server_port_number, client_port_number, true);
// 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 {
LOG_ERROR(dhcp6_logger, DHCP6_INIT_FAIL).arg(ex.what());
} catch (...) {
// The exception thrown during the initialization could
- // originate from logger subsystem. Therefore LOG_ERROR() may
- // fail as well.
+ // originate from logger subsystem. Therefore LOG_ERROR()
+ // may fail as well.
cerr << "Failed to initialize server: " << ex.what() << endl;
}
}
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;
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
+#include <cstdlib>
#include <iomanip>
#include <sstream>
+#include <thread>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
-#include <cstdlib>
-
-#include <thread>
using namespace std;
using namespace isc;
"pkt6-receive-drop"
};
+ // preparing the schema which check if all statistics are set to zero
std::ostringstream s;
s << "{ \"arguments\": { ";
for (auto st = initial_stats.begin(); st != initial_stats.end();) {
};
/// Number of option definitions defined.
-const int DOCSIS3_V4_DEFS_SIZE = sizeof(DOCSIS3_V4_DEFS) / sizeof(OptionDefParams);
+const int DOCSIS3_V4_DEFS_SIZE =
+ sizeof(DOCSIS3_V4_DEFS) / sizeof(DOCSIS3_V4_DEFS[0]);
/// @todo define remaining docsis3 v6 codes
#define DOCSIS3_V6_ORO 1
extern const char* DOCSIS3_CLASS_EROUTER;
extern const char* DOCSIS3_CLASS_MODEM;
-}; // isc::dhcp namespace
-}; // isc namespace
+} // namespace dhcp
+} // namespace isc
#endif // DOCSIS3_OPTION_DEFS_H
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_int_array.h>
-#include <dhcp/option_space.h>
#include <dhcp/std_option_defs.h>
#include <dhcp/docsis3_option_defs.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
-#include <dhcp/option_definition.h>
+#include <util/threads/lock_guard.h>
#include <boost/lexical_cast.hpp>
#include <boost/shared_array.hpp>
#include <limits>
#include <list>
+#include <mutex>
using namespace std;
using namespace isc::dhcp;
using namespace isc::util;
+namespace isc {
+namespace dhcp {
+
+namespace {
+
+const OptionDefParamsEncapsulation OPTION_DEF_PARAMS[] = {
+ { STANDARD_V4_OPTION_DEFINITIONS, STANDARD_V4_OPTION_DEFINITIONS_SIZE, DHCP4_OPTION_SPACE },
+ { STANDARD_V6_OPTION_DEFINITIONS, STANDARD_V6_OPTION_DEFINITIONS_SIZE, DHCP6_OPTION_SPACE },
+ { DOCSIS3_V4_DEFS, DOCSIS3_V4_DEFS_SIZE, DOCSIS3_V4_OPTION_SPACE },
+ { DOCSIS3_V6_DEFS, DOCSIS3_V6_DEFS_SIZE, DOCSIS3_V6_OPTION_SPACE },
+ { ISC_V6_OPTION_DEFINITIONS, ISC_V6_OPTION_DEFINITIONS_SIZE, ISC_V6_OPTION_SPACE },
+ { MAPE_V6_OPTION_DEFINITIONS, MAPE_V6_OPTION_DEFINITIONS_SIZE, MAPE_V6_OPTION_SPACE },
+ { MAPT_V6_OPTION_DEFINITIONS, MAPT_V6_OPTION_DEFINITIONS_SIZE, MAPT_V6_OPTION_SPACE },
+ { LW_V6_OPTION_DEFINITIONS, LW_V6_OPTION_DEFINITIONS_SIZE, LW_V6_OPTION_SPACE },
+ { V4V6_RULE_OPTION_DEFINITIONS, V4V6_RULE_OPTION_DEFINITIONS_SIZE, V4V6_RULE_OPTION_SPACE },
+ { V4V6_BIND_OPTION_DEFINITIONS, V4V6_BIND_OPTION_DEFINITIONS_SIZE, V4V6_BIND_OPTION_SPACE },
+ { LAST_RESORT_V4_OPTION_DEFINITIONS, LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE, LAST_RESORT_V4_OPTION_SPACE },
+ { NULL, 0, "" }
+};
+
+} // namespace
+
+} // namespace dhcp
+} // namespace isc
+
// static array with factories for options
std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
// static array with factories for options
std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
-// Static container with DHCPv4 option definitions.
-OptionDefContainerPtr LibDHCP::v4option_defs_(new OptionDefContainer());
-
-// Static container with DHCPv6 option definitions.
-OptionDefContainerPtr LibDHCP::v6option_defs_(new OptionDefContainer());
-
// Static container with option definitions grouped by option space.
OptionDefContainers LibDHCP::option_defs_;
-// Static container with vendor option definitions for DHCPv4.
-VendorOptionDefContainers LibDHCP::vendor4_defs_;
-
-// Static container with vendor option definitions for DHCPv6.
-VendorOptionDefContainers LibDHCP::vendor6_defs_;
-
-// Static container with last resort option definitions for DHCPv4.
-OptionDefContainerPtr LibDHCP::lastresort_defs_(new OptionDefContainer());
-
// Static container with option definitions created in runtime.
StagedValue<OptionDefSpaceContainer> LibDHCP::runtime_option_defs_;
const OptionDefContainerPtr&
LibDHCP::getOptionDefs(const std::string& space) {
+ static mutex local_mutex;
+ isc::util::thread::LockGuard<mutex> lock(&local_mutex);
// If any of the containers is not initialized, it means that we haven't
// initialized option definitions at all.
- if (v4option_defs_->empty()) {
- initStdOptionDefs4();
- initVendorOptsDocsis4();
- initStdOptionDefs6();
- initVendorOptsDocsis6();
- initLastResortOptionDefs();
- }
-
- if (space == DHCP4_OPTION_SPACE) {
- return (v4option_defs_);
-
- } else if (space == DHCP6_OPTION_SPACE) {
- return (v6option_defs_);
+ if (option_defs_.end() == option_defs_.find(space)) {
+ initStdOptionDefs(space);
}
OptionDefContainers::const_iterator container = option_defs_.find(space);
return (null_option_def_container_);
}
-const OptionDefContainerPtr&
-LibDHCP::getVendorOption4Defs(const uint32_t vendor_id) {
-
- if (vendor_id == VENDOR_ID_CABLE_LABS &&
- vendor4_defs_.find(VENDOR_ID_CABLE_LABS) == vendor4_defs_.end()) {
- initVendorOptsDocsis4();
- }
-
- VendorOptionDefContainers::const_iterator def = vendor4_defs_.find(vendor_id);
- if (def == vendor4_defs_.end()) {
- // No such vendor-id space
- return (null_option_def_container_);
- }
- return (def->second);
-}
-
-const OptionDefContainerPtr&
-LibDHCP::getVendorOption6Defs(const uint32_t vendor_id) {
-
- if (vendor_id == VENDOR_ID_CABLE_LABS &&
- vendor6_defs_.find(VENDOR_ID_CABLE_LABS) == vendor6_defs_.end()) {
- initVendorOptsDocsis6();
- }
-
- if (vendor_id == ENTERPRISE_ID_ISC &&
- vendor6_defs_.find(ENTERPRISE_ID_ISC) == vendor6_defs_.end()) {
- initVendorOptsIsc6();
- }
-
- VendorOptionDefContainers::const_iterator def = vendor6_defs_.find(vendor_id);
- if (def == vendor6_defs_.end()) {
- // No such vendor-id space
- return (null_option_def_container_);
+const OptionDefContainerPtr
+LibDHCP::getVendorOptionDefs(const Option::Universe u, const uint32_t vendor_id) {
+ if (Option::V4 == u) {
+ if (VENDOR_ID_CABLE_LABS == vendor_id) {
+ return getOptionDefs(DOCSIS3_V4_OPTION_SPACE);
+ }
+ } else if (Option::V6 == u) {
+ if (VENDOR_ID_CABLE_LABS == vendor_id) {
+ return getOptionDefs(DOCSIS3_V6_OPTION_SPACE);
+ } else if (ENTERPRISE_ID_ISC == vendor_id) {
+ return getOptionDefs(ISC_V6_OPTION_SPACE);
+ }
}
- return (def->second);
+ return (null_option_def_container_);
}
OptionDefinitionPtr
OptionDefinitionPtr
LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
const std::string& name) {
- OptionDefContainerPtr defs = (u == Option::V4 ? getVendorOption4Defs(vendor_id) :
- getVendorOption6Defs(vendor_id));
+ const OptionDefContainerPtr option_defs_ptr = getVendorOptionDefs(u, vendor_id);
- if (!defs) {
+ if (!option_defs_ptr) {
return (OptionDefinitionPtr());
}
- const OptionDefContainerNameIndex& idx = defs->get<2>();
+ const OptionDefContainerNameIndex& idx = option_defs_ptr->get<2>();
const OptionDefContainerNameRange& range = idx.equal_range(name);
if (range.first != range.second) {
return (*range.first);
OptionDefinitionPtr
LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
const uint16_t code) {
- OptionDefContainerPtr defs = (u == Option::V4 ? getVendorOption4Defs(vendor_id) :
- getVendorOption6Defs(vendor_id));
+ const OptionDefContainerPtr option_defs_ptr = getVendorOptionDefs(u, vendor_id);
- if (!defs) {
+ if (!option_defs_ptr) {
// Weird universe or unknown vendor_id. We don't care. No definitions
// one way or another
// What is it anyway?
return (OptionDefinitionPtr());
}
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeIndex& idx = option_defs_ptr->get<1>();
const OptionDefContainerTypeRange& range = idx.equal_range(code);
if (range.first != range.second) {
return (*range.first);
OptionDefContainerPtr
LibDHCP::getLastResortOptionDefs(const std::string& space) {
if (space == DHCP4_OPTION_SPACE) {
- return (lastresort_defs_);
+ return getOptionDefs(LAST_RESORT_V4_OPTION_SPACE);
}
return (null_option_def_container_);
}
size_t length = buf.size();
// Get the list of option definitions for this particular vendor-id
- const OptionDefContainerPtr& option_defs = LibDHCP::getVendorOption6Defs(vendor_id);
+ const OptionDefContainerPtr option_defs_ptr =
+ LibDHCP::getVendorOptionDefs(Option::V6, vendor_id);
// Get the search index #1. It allows to search for option definitions
// using option code. If there's no such vendor-id space, we're out of luck
// anyway.
const OptionDefContainerTypeIndex* idx = NULL;
- if (option_defs) {
- idx = &(option_defs->get<1>());
+ if (option_defs_ptr) {
+ idx = &(option_defs_ptr->get<1>());
}
// The buffer being read comprises a set of options, each starting with
size_t offset = 0;
// Get the list of standard option definitions.
- const OptionDefContainerPtr& option_defs = LibDHCP::getVendorOption4Defs(vendor_id);
+ const OptionDefContainerPtr option_defs_ptr =
+ LibDHCP::getVendorOptionDefs(Option::V4, vendor_id);
// Get the search index #1. It allows to search for option definitions
// using option code.
const OptionDefContainerTypeIndex* idx = NULL;
- if (option_defs) {
- idx = &(option_defs->get<1>());
+ if (option_defs_ptr) {
+ idx = &(option_defs_ptr->get<1>());
}
// The buffer being read comprises a set of options, each starting with
options.insert(std::make_pair(opt_type, opt));
offset += opt_len;
- } // end of data-chunk
+ } // end of data-chunk
break; // end of the vendor block.
}
isc_throw(BadValue, "There is already DHCPv6 factory registered "
<< "for option type " << opt_type);
}
- v6factories_[opt_type]=factory;
+ v6factories_[opt_type] = factory;
return;
}
- case Option::V4:
- {
+ case Option::V4: {
// Option 0 is special (a one octet-long, equal 0) PAD option. It is never
// instantiated as an Option object, but rather consumed during packet parsing.
if (opt_type == 0) {
if (opt_type > 254) {
isc_throw(BadValue, "Too big option type for DHCPv4, only 0-254 allowed.");
}
- if (v4factories_.find(opt_type)!=v4factories_.end()) {
+ if (v4factories_.find(opt_type) != v4factories_.end()) {
isc_throw(BadValue, "There is already DHCPv4 factory registered "
<< "for option type " << opt_type);
}
- v4factories_[opt_type]=factory;
+ v4factories_[opt_type] = factory;
return;
}
default:
return;
}
-void
-LibDHCP::initStdOptionDefs4() {
- initOptionSpace(v4option_defs_, STANDARD_V4_OPTION_DEFINITIONS,
- STANDARD_V4_OPTION_DEFINITIONS_SIZE);
-}
-
-void
-LibDHCP::initStdOptionDefs6() {
- initOptionSpace(v6option_defs_, STANDARD_V6_OPTION_DEFINITIONS,
- STANDARD_V6_OPTION_DEFINITIONS_SIZE);
- initOptionSpace(option_defs_[MAPE_V6_OPTION_SPACE], MAPE_V6_OPTION_DEFINITIONS,
- MAPE_V6_OPTION_DEFINITIONS_SIZE);
- initOptionSpace(option_defs_[MAPT_V6_OPTION_SPACE], MAPT_V6_OPTION_DEFINITIONS,
- MAPT_V6_OPTION_DEFINITIONS_SIZE);
- initOptionSpace(option_defs_[LW_V6_OPTION_SPACE], LW_V6_OPTION_DEFINITIONS,
- LW_V6_OPTION_DEFINITIONS_SIZE);
- initOptionSpace(option_defs_[V4V6_RULE_OPTION_SPACE], V4V6_RULE_OPTION_DEFINITIONS,
- V4V6_RULE_OPTION_DEFINITIONS_SIZE);
- initOptionSpace(option_defs_[V4V6_BIND_OPTION_SPACE], V4V6_BIND_OPTION_DEFINITIONS,
- V4V6_BIND_OPTION_DEFINITIONS_SIZE);
-}
-
-void
-LibDHCP::initLastResortOptionDefs() {
- initOptionSpace(lastresort_defs_, LAST_RESORT_V4_OPTION_DEFINITIONS,
- LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE);
-}
-
-void
-LibDHCP::initVendorOptsDocsis4() {
- initOptionSpace(vendor4_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V4_DEFS,
- DOCSIS3_V4_DEFS_SIZE);
-}
-
-void
-LibDHCP::initVendorOptsDocsis6() {
- initOptionSpace(vendor6_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V6_DEFS,
- DOCSIS3_V6_DEFS_SIZE);
-}
-
-void
-LibDHCP::initVendorOptsIsc6() {
- initOptionSpace(vendor6_defs_[ENTERPRISE_ID_ISC], ISC_V6_OPTION_DEFINITIONS,
- ISC_V6_OPTION_DEFINITIONS_SIZE);
+void LibDHCP::initStdOptionDefs(const std::string &space) {
+ if (option_defs_.end() == option_defs_.find(space)) {
+ option_defs_[space] = OptionDefContainerPtr(new OptionDefContainer);
+ for (int i = 0; OPTION_DEF_PARAMS[i].optionDefParams; i++) {
+ if (space == OPTION_DEF_PARAMS[i].space) {
+ initOptionSpace(option_defs_[space], OPTION_DEF_PARAMS[i].optionDefParams, OPTION_DEF_PARAMS[i].size);
+ break;
+ }
+ }
+ }
}
uint32_t
std::string x = option_space.substr(7);
check = boost::lexical_cast<int64_t>(x);
-
} catch (const boost::bad_lexical_cast &) {
return (0);
}
// case.
if (!defs) {
defs.reset(new OptionDefContainer());
-
} else {
defs->clear();
}
#include <dhcp/option_definition.h>
#include <dhcp/option_space_container.h>
+#include <dhcp/option_space.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
#include <util/buffer.h>
public:
/// Map of factory functions.
- typedef std::map<unsigned short, Option::Factory*> FactoryMap;
+ typedef std::map<unsigned short, Option::Factory*> FactoryMap;
/// @brief Returns collection of option definitions.
///
uint16_t type,
Option::Factory * factory);
- /// @brief Returns v4 option definitions for a given vendor
+ /// @brief Returns option definitions for given universe and vendor
///
+ /// @param u option universe
/// @param vendor_id enterprise-id of a given vendor
- /// @return a container for a given vendor (or NULL if no option
- /// definitions are defined)
- static const OptionDefContainerPtr&
- getVendorOption4Defs(const uint32_t vendor_id);
-
- /// @brief Returns v6 option definitions for a given vendor
///
- /// @param vendor_id enterprise-id of a given vendor
/// @return a container for a given vendor (or NULL if no option
/// definitions are defined)
- static const OptionDefContainerPtr&
- getVendorOption6Defs(const uint32_t vendor_id);
+ static const OptionDefContainerPtr
+ getVendorOptionDefs(Option::Universe u, const uint32_t vendor_id);
/// @brief Parses provided buffer as DHCPv6 vendor options and creates
/// Option objects.
static uint32_t optionSpaceToVendorId(const std::string& option_space);
private:
-
- /// Initialize standard DHCPv4 option definitions.
+ /// Initialize DHCP option definitions.
///
- /// The method creates option definitions for all DHCPv4 options.
- /// Currently this function is not implemented.
+ /// The method creates option definitions for all DHCP options.
///
/// @throw std::bad alloc if system went out of memory.
/// @throw MalformedOptionDefinition if any of the definitions
/// are incorrect. This is programming error.
- static void initStdOptionDefs4();
-
- /// Initialize standard DHCPv6 option definitions.
- ///
- /// The method creates option definitions for all DHCPv6 options.
- ///
- /// @throw std::bad_alloc if system went out of memory.
- /// @throw MalformedOptionDefinition if any of the definitions
- /// is incorrect. This is a programming error.
- static void initStdOptionDefs6();
-
- /// Initialize last resort DHCPv4 option definitions.
- static void initLastResortOptionDefs();
-
- /// Initialize DOCSIS DHCPv4 option definitions.
- static void initVendorOptsDocsis4();
-
- /// Initialize DOCSIS DHCPv6 option definitions.
- static void initVendorOptsDocsis6();
-
- /// Initialize private DHCPv6 option definitions.
- static void initVendorOptsIsc6();
+ static void initStdOptionDefs(const std::string& space);
/// pointers to factories that produce DHCPv6 options
static FactoryMap v4factories_;
/// pointers to factories that produce DHCPv6 options
static FactoryMap v6factories_;
- /// Container with DHCPv4 option definitions.
- static OptionDefContainerPtr v4option_defs_;
-
- /// Container with DHCPv6 option definitions.
- static OptionDefContainerPtr v6option_defs_;
/// Container that holds option definitions for various option spaces.
static OptionDefContainers option_defs_;
- /// Container for v4 vendor option definitions
- static VendorOptionDefContainers vendor4_defs_;
-
- /// Container for v6 vendor option definitions
- static VendorOptionDefContainers vendor6_defs_;
-
- /// Container with DHCPv4 last resort option definitions.
- static OptionDefContainerPtr lastresort_defs_;
-
/// Container for additional option definitions created in runtime.
static util::StagedValue<OptionDefSpaceContainer> runtime_option_defs_;
};
OptionDataTypeUtil::writePrefix(PrefixLen(len), address, buf);
return;
- }
+ }
case OPT_PSID_TYPE:
- {
+ {
std::string txt = value;
// first let's remove any whitespaces
#include <stdint.h>
#include <string>
-#define DHCP4_OPTION_SPACE "dhcp4"
-#define DHCP6_OPTION_SPACE "dhcp6"
-#define MAPE_V6_OPTION_SPACE "s46-cont-mape-options"
-#define MAPT_V6_OPTION_SPACE "s46-cont-mapt-options"
-#define LW_V6_OPTION_SPACE "s46-cont-lw-options"
-#define V4V6_RULE_OPTION_SPACE "s46-rule-options"
-#define V4V6_BIND_OPTION_SPACE "s46-v4v6bind-options"
+#define DHCP4_OPTION_SPACE "dhcp4"
+#define DHCP6_OPTION_SPACE "dhcp6"
+#define DOCSIS3_V4_OPTION_SPACE "docsis3-v4"
+#define DOCSIS3_V6_OPTION_SPACE "docsis3-v6"
+#define ISC_V6_OPTION_SPACE "4o6"
+#define MAPE_V6_OPTION_SPACE "s46-cont-mape-options"
+#define MAPT_V6_OPTION_SPACE "s46-cont-mapt-options"
+#define LW_V6_OPTION_SPACE "s46-cont-lw-options"
+#define V4V6_RULE_OPTION_SPACE "s46-rule-options"
+#define V4V6_BIND_OPTION_SPACE "s46-v4v6bind-options"
+#define LAST_RESORT_V4_OPTION_SPACE "last-resort-v4"
namespace isc {
namespace dhcp {
uint32_t enterprise_number_; ///< IANA assigned enterprise number.
};
-} // namespace isc::dhcp
-} // namespace isc
+} // namespace dhcp
+} // namespace isc
#endif // OPTION_SPACE_H
/// Number of option definitions defined.
const int STANDARD_V4_OPTION_DEFINITIONS_SIZE =
- sizeof(STANDARD_V4_OPTION_DEFINITIONS) / sizeof(STANDARD_V4_OPTION_DEFINITIONS[0]);
+ sizeof(STANDARD_V4_OPTION_DEFINITIONS) /
+ sizeof(STANDARD_V4_OPTION_DEFINITIONS[0]);
/// Last resort definitions (only option 43 for now, these definitions
/// are applied in deferred unpacking when none is found).
OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "vendor-encapsulated-options-space" }
};
-const int LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE = 1;
+const int LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE =
+ sizeof(LAST_RESORT_V4_OPTION_DEFINITIONS) /
+ sizeof(LAST_RESORT_V4_OPTION_DEFINITIONS[0]);
/// Start Definition of DHCPv6 options
{ "v6-access-domain", D6O_V6_ACCESS_DOMAIN, OPT_FQDN_TYPE, false,
NO_RECORD_DEF, "" },
{ "sip-ua-cs-list", D6O_SIP_UA_CS_LIST, OPT_FQDN_TYPE, true,
- NO_RECORD_DEF, "" },
+ NO_RECORD_DEF, "" },
{ "bootfile-url", D6O_BOOTFILE_URL, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "bootfile-param", D6O_BOOTFILE_PARAM, OPT_TUPLE_TYPE, true, NO_RECORD_DEF, "" },
{ "client-arch-type", D6O_CLIENT_ARCH_TYPE, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
sizeof(V4V6_BIND_OPTION_DEFINITIONS) /
sizeof(V4V6_BIND_OPTION_DEFINITIONS[0]);
-} // unnamed namespace
+} // namespace
-} // namespace dhcp
-} // namespace isc
+} // namespace dhcp
+} // namespace isc
#endif // STD_OPTION_DEFS_H
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_opaque_data_tuples.h>
-#include <dhcp/option_space.h>
#include <dhcp/option_string.h>
#include <dhcp/option_vendor.h>
#include <dhcp/option_vendor_class.h>
EXPECT_NO_THROW ({
LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin() + sizeof(v6packed)),
- "dhcp6", options);
+ DHCP6_OPTION_SPACE, options);
});
EXPECT_EQ(options.size(), 6); // there should be 5 options
list<uint16_t> deferred;
ASSERT_NO_THROW(
- LibDHCP::unpackOptions4(v4packed, "dhcp4", options, deferred);
+ LibDHCP::unpackOptions4(v4packed, DHCP4_OPTION_SPACE, options, deferred);
);
isc::dhcp::OptionCollection::const_iterator x = options.find(12);
// This test checks if the definition of the DHCPv6 vendor option can
// be searched by option name.
TEST_F(LibDhcpTest, getVendorOptionDefByName6) {
- const OptionDefContainerPtr& defs =
- LibDHCP::getVendorOption6Defs(VENDOR_ID_CABLE_LABS);
+ const OptionDefContainerPtr defs =
+ LibDHCP::getVendorOptionDefs(Option::V6, VENDOR_ID_CABLE_LABS);
ASSERT_TRUE(defs);
for (OptionDefContainer::const_iterator def = defs->begin();
def != defs->end(); ++def) {
// This test checks if the definition of the DHCPv4 vendor option can
// be searched by option name.
TEST_F(LibDhcpTest, getVendorOptionDefByName4) {
- const OptionDefContainerPtr& defs =
- LibDHCP::getVendorOption4Defs(VENDOR_ID_CABLE_LABS);
+ const OptionDefContainerPtr defs =
+ LibDHCP::getVendorOptionDefs(Option::V4, VENDOR_ID_CABLE_LABS);
ASSERT_TRUE(defs);
for (OptionDefContainer::const_iterator def = defs->begin();
def != defs->end(); ++def) {
isc::util::encode::decodeHex(vendor_class_hex, bin);
ASSERT_NO_THROW ({
- LibDHCP::unpackOptions6(bin, "dhcp6", options);
+ LibDHCP::unpackOptions6(bin, DHCP6_OPTION_SPACE, options);
});
EXPECT_EQ(options.size(), 1); // There should be 1 option.
OptionBuffer buf(mape_bin);
- size_t parsed;
+ size_t parsed = 0;
EXPECT_NO_THROW (parsed = LibDHCP::unpackOptions6(buf, "dhcp6", options));
EXPECT_EQ(mape_bin.size(), parsed);
EXPECT_EQ("type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", portparam->toText());
}
-} // end of anonymous space
+} // end of anonymous space
libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
libkea_dhcpsrv_la_SOURCES += memfile_lease_storage.h
libkea_dhcpsrv_la_SOURCES += multi_threading_utils.h multi_threading_utils.cc
+libkea_dhcpsrv_la_SOURCES += thread_pool.cc thread_pool.h
if HAVE_MYSQL
libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
#include <algorithm>
#include <cstring>
-#include <sstream>
#include <limits>
-#include <vector>
+#include <sstream>
#include <stdint.h>
#include <string.h>
#include <utility>
+#include <vector>
+
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::dhcp_ddns;
using namespace isc::hooks;
using namespace isc::stats;
+using namespace isc::util::thread;
namespace {
-
/// Structure that holds registered hook indexes
struct AllocEngineHooks {
int hook_index_lease4_select_; ///< index for "lease4_receive" hook point
// module is called.
AllocEngineHooks Hooks;
-}; // anonymous namespace
+} // namespace
namespace isc {
namespace dhcp {
-
-AllocEngine::IterativeAllocator::IterativeAllocator(Lease::Type lease_type)
- :Allocator(lease_type) {
+AllocEngine::IterativeAllocator::IterativeAllocator(Lease::Type lease_type) :
+ Allocator(lease_type) {
}
isc::asiolink::IOAddress
}
isc::asiolink::IOAddress
-AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr&,
- const IOAddress&) {
-
+AllocEngine::IterativeAllocator::pickAddressInternal(const SubnetPtr& subnet,
+ const ClientClasses& client_classes,
+ const DuidPtr&,
+ const IOAddress&) {
// Is this prefix allocation?
bool prefix = pool_type_ == Lease::TYPE_PD;
uint8_t prefix_len = 0;
return (last);
}
-AllocEngine::HashedAllocator::HashedAllocator(Lease::Type lease_type)
- :Allocator(lease_type) {
+AllocEngine::HashedAllocator::HashedAllocator(Lease::Type lease_type) :
+ Allocator(lease_type) {
isc_throw(NotImplemented, "Hashed allocator is not implemented");
}
-
isc::asiolink::IOAddress
-AllocEngine::HashedAllocator::pickAddress(const SubnetPtr&,
- const ClientClasses&,
- const DuidPtr&,
- const IOAddress&) {
+AllocEngine::HashedAllocator::pickAddressInternal(const SubnetPtr&,
+ const ClientClasses&,
+ const DuidPtr&,
+ const IOAddress&) {
isc_throw(NotImplemented, "Hashed allocator is not implemented");
}
-AllocEngine::RandomAllocator::RandomAllocator(Lease::Type lease_type)
- :Allocator(lease_type) {
+AllocEngine::RandomAllocator::RandomAllocator(Lease::Type lease_type) :
+ Allocator(lease_type) {
isc_throw(NotImplemented, "Random allocator is not implemented");
}
-
isc::asiolink::IOAddress
-AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&,
- const ClientClasses&,
- const DuidPtr&,
- const IOAddress&) {
+AllocEngine::RandomAllocator::pickAddressInternal(const SubnetPtr&,
+ const ClientClasses&,
+ const DuidPtr&,
+ const IOAddress&) {
isc_throw(NotImplemented, "Random allocator is not implemented");
}
-
-AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts,
- bool ipv6)
- : attempts_(attempts), incomplete_v4_reclamations_(0),
- incomplete_v6_reclamations_(0) {
-
+AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts, bool ipv6) :
+ attempts_(attempts), incomplete_v4_reclamations_(0),
+ incomplete_v6_reclamations_(0) {
// Choose the basic (normal address) lease type
Lease::Type basic_type = ipv6 ? Lease::TYPE_NA : Lease::TYPE_V4;
} // end of namespace isc
namespace {
-
/// @brief Checks if the specified address belongs to one of the subnets
/// within a shared network.
///
// If the subnet belongs to a shared network we will be iterating
// over the subnets that belong to this shared network.
Subnet6Ptr current_subnet = ctx.subnet_;
- while (current_subnet) {
+ while (current_subnet) {
if (current_subnet->clientSupported(ctx.query_->getClasses())) {
if (check_subnet) {
if (current_subnet->inPool(lease_type, address)) {
}
-
// ##########################################################################
// # DHCPv6 lease allocation code starts here.
// ##########################################################################
namespace isc {
namespace dhcp {
-
AllocEngine::ClientContext6::ClientContext6()
: query_(), fake_allocation_(false), subnet_(), host_subnet_(), duid_(),
hwaddr_(), host_identifiers_(), hosts_(), fwd_dns_update_(false),
fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns), hostname_(hostname),
callout_handle_(callout_handle), allocated_resources_(), new_leases_(),
ias_(), ddns_params_() {
-
// Initialize host identifiers.
if (duid) {
addHostIdentifier(Host::IDENT_DUID, duid->getDuid());
// We can only search for the reservation if a subnet has been selected.
while (subnet) {
-
// Only makes sense to get reservations if the client has access
// to the class and host reservations are enabled.
if (subnet->clientSupported(ctx.query_->getClasses()) &&
if (host_map.count(subnet->getID()) > 0) {
ctx.hosts_[subnet->getID()] = host_map[subnet->getID()];
}
-
} else {
// Attempt to find a host using a specified identifier.
ConstHostPtr host = HostMgr::instance().get6(subnet->getID(),
}
}
}
-
}
// We need to get to the next subnet if this is a shared network. If it
return (host);
}
-
Lease6Collection
AllocEngine::allocateLeases6(ClientContext6& ctx) {
-
try {
if (!ctx.subnet_) {
isc_throw(InvalidOperation, "Subnet is required for IPv6 lease allocation");
// Case 1: There are no leases and there's a reservation for this host.
if (leases.empty() && !ctx.hosts_.empty()) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR)
.arg(ctx.query_->getLabel());
// We will return these leases for the client, but we may need to update
// FQDN information.
} else if (!leases.empty() && ctx.hosts_.empty()) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR)
.arg(ctx.query_->getLabel());
// Case 3: There are leases and there are reservations.
} else if (!leases.empty() && !ctx.hosts_.empty()) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_ALLOC_LEASES_HR)
.arg(ctx.query_->getLabel());
}
return (leases);
}
-
-
} catch (const isc::Exception& e) {
-
// Some other error, return an empty lease.
LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_ERROR)
.arg(ctx.query_->getLabel())
Lease6Collection
AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
-
AllocatorPtr allocator = getAllocator(ctx.currentIA().type_);
if (!allocator) {
CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
while (subnet) {
-
if (!subnet->clientSupported(ctx.query_->getClasses())) {
subnet = subnet->getNextSubnet(original_subnet);
continue;
// check if the hint is in pool and is available
// This is equivalent of subnet->inPool(hint), but returns the pool
pool = boost::dynamic_pointer_cast<Pool6>
- (subnet->getPool(ctx.currentIA().type_, ctx.query_->getClasses(),
- hint));
+ (subnet->getPool(ctx.currentIA().type_, ctx.query_->getClasses(), hint));
// check if the pool is allowed
if (pool && !pool->clientSupported(ctx.query_->getClasses())) {
}
if (pool) {
-
// Check which host reservation mode is supported in this subnet.
Network::HRMode hr_mode = subnet->getHostReservationMode();
/// @todo: We support only one hint for now
Lease6Ptr lease =
LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, hint);
- if (!lease) {
+ if (!lease) {
// In-pool reservations: Check if this address is reserved for someone
// else. There is no need to check for whom it is reserved, because if
// it has been reserved for us we would have already allocated a lease.
// no longer usable and we need to continue the regular
// allocation path.
if (lease) {
-
/// @todo: We support only one lease per ia for now
Lease6Collection collection;
collection.push_back(lease);
.arg(ctx.query_->getLabel())
.arg(hint.toText());
}
-
} else {
-
// If the lease is expired, we may likely reuse it, but...
if (lease->expired()) {
-
ConstHostPtr host;
if (hr_mode != Network::HR_DISABLED) {
host = HostMgr::instance().get6(subnet->getID(), hint);
// Let's check if there is a reservation for this address.
if (!host) {
-
// Copy an existing, expired lease so as it can be returned
// to the caller.
Lease6Ptr old_lease(new Lease6(*lease));
/// @todo: We support only one lease per ia for now
leases.push_back(lease);
return (leases);
-
} else {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED)
ctx.subnet_ = subnet = original_subnet;
while (subnet) {
-
if (!subnet->clientSupported(ctx.query_->getClasses())) {
subnet = subnet->getNextSubnet(original_subnet);
continue;
}
for (uint64_t i = 0; i < max_attempts; ++i) {
-
++total_attempts;
IOAddress candidate = allocator->pickAddress(subnet,
/// it has been reserved for us we would have already allocated a lease.
if (hr_mode == Network::HR_ALL &&
HostMgr::instance().get6(subnet->getID(), candidate)) {
-
// Don't allocate.
continue;
}
}
Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
- candidate);
- if (!existing) {
+ candidate);
+ if (!existing) {
// there's no existing lease for selected candidate, so it is
// free. Let's allocate it.
-
ctx.subnet_ = subnet;
Lease6Ptr lease = createLease6(ctx, candidate, prefix_len, callout_status);
if (lease) {
leases.push_back(lease);
return (leases);
-
} else if (ctx.callout_handle_ &&
(callout_status != CalloutHandle::NEXT_STEP_CONTINUE)) {
// Don't retry when the callout status is not continue.
void
AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
Lease6Collection& existing_leases) {
-
// If there are no reservations or the reservation is v4, there's nothing to do.
if (ctx.hosts_.empty()) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
Subnet6Ptr subnet = ctx.subnet_;
while (subnet) {
-
SubnetID subnet_id = subnet->getID();
// No hosts for this subnet or the subnet not supported.
// If there's a lease for this address, let's not create it.
// It doesn't matter whether it is for this client or for someone else.
- if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
- addr)) {
-
+ if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, addr)) {
// Let's remember the subnet from which the reserved address has been
// allocated. We'll use this subnet for allocating other reserved
// resources.
// ... and add it to the existing leases list.
existing_leases.push_back(lease);
-
if (ctx.currentIA().type_ == Lease::TYPE_NA) {
LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_ADDR_GRANTED)
.arg(addr.toText())
// would work for any number of reservations.
return;
}
-
}
subnet = subnet->getNextSubnet(ctx.subnet_);
// If there's a lease for this address, let's not create it.
// It doesn't matter whether it is for this client or for someone else.
if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, addr)) {
-
if (!ghost->getHostname().empty()) {
// If there is a hostname reservation here we should stick
// to this reservation. By updating the hostname in the
bool
AllocEngine::removeLeases(Lease6Collection& container, const asiolink::IOAddress& addr) {
-
bool removed = false;
for (Lease6Collection::iterator lease = container.begin();
lease != container.end(); ++lease) {
// leases for deletion, by setting appropriate pointers to NULL.
for (Lease6Collection::iterator lease = existing_leases.begin();
lease != existing_leases.end(); ++lease) {
-
// If there is reservation for this keep it.
IPv6Resrv resv = makeIPv6Resrv(*(*lease));
if (ctx.hasGlobalReservation(resv) ||
// If there's only one lease left, break the loop.
break;
}
-
}
// Remove all elements that we previously marked for deletion (those that
AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
uint8_t prefix_len,
CalloutHandle::CalloutNextStep& callout_status) {
-
if (!expired->expired()) {
isc_throw(BadValue, "Attempt to recycle lease that is still valid");
}
const IOAddress& addr,
uint8_t prefix_len,
CalloutHandle::CalloutNextStep& callout_status) {
-
if (ctx.currentIA().type_ != Lease::TYPE_PD) {
prefix_len = 128; // non-PD lease types must be always /128
}
*ctx.duid_,
ctx.currentIA().iaid_,
subnet->getID());
+
leases.insert(leases.end(), leases_subnet.begin(), leases_subnet.end());
subnet = subnet->getNextSubnet(ctx.subnet_);
}
-
if (!leases.empty()) {
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_RENEW_REMOVE_RESERVED)
}
if (!ctx.hosts_.empty()) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_RENEW_HR)
.arg(ctx.query_->getLabel());
// new leases during renewals. This is controlled with the
// allow_new_leases_in_renewals_ field.
if (leases.empty()) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED)
.arg(ctx.query_->getLabel());
}
return (leases);
-
} catch (const isc::Exception& e) {
-
// Some other error, return an empty lease.
LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_EXTEND_ERROR)
.arg(ctx.query_->getLabel())
void
AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
-
if (!lease || !ctx.subnet_) {
return;
}
// Now that the lease has been reclaimed, we can go ahead and update it
// in the lease database.
LeaseMgrFactory::instance().updateLease6(lease);
-
} else {
// Copy back the original date to the lease. For MySQL it doesn't make
// much sense, but for memfile, the Lease6Ptr points to the actual lease
ctx.currentIA().changed_leases_.push_back(old_data);
}
-
Lease6Collection
AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases) {
Lease6Collection updated_leases;
lease->fqdn_fwd_ = ctx.fwd_dns_update_;
lease->fqdn_rev_ = ctx.rev_dns_update_;
lease->hostname_ = ctx.hostname_;
- if (!ctx.fake_allocation_) {
+ if (!ctx.fake_allocation_) {
if (lease->state_ == Lease::STATE_EXPIRED_RECLAIMED) {
// Transition lease state to default (aka assigned)
lease->state_ = Lease::STATE_DEFAULT;
AllocEngine::reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeout,
const bool remove_lease,
const uint16_t max_unwarned_cycles) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_LEASES_RECLAMATION_START)
.arg(max_leases)
leases.pop_back();
incomplete_reclamation = true;
}
-
} else {
// If there is no limitation on the number of leases to reclaim,
// we will try to process all. Hence, we don't mark it as incomplete
size_t leases_processed = 0;
BOOST_FOREACH(Lease6Ptr lease, leases) {
-
try {
// Reclaim the lease.
reclaimExpiredLease(lease, remove_lease, callout_handle);
++leases_processed;
-
} catch (const std::exception& ex) {
LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_LEASE_RECLAMATION_FAILED)
.arg(lease->addr_.toText())
// We issued a warning, so let's now reset the counter.
incomplete_v6_reclamations_ = 0;
}
-
} else {
// This was a complete reclamation, so let's reset the counter.
incomplete_v6_reclamations_ = 0;
// Try to delete leases from the lease database.
LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
deleted_leases = lease_mgr.deleteExpiredReclaimedLeases6(secs);
-
} catch (const std::exception& ex) {
LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED)
.arg(ex.what());
.arg(deleted_leases);
}
-
void
AllocEngine::reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeout,
const bool remove_lease,
const uint16_t max_unwarned_cycles) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_LEASES_RECLAMATION_START)
.arg(max_leases)
leases.pop_back();
incomplete_reclamation = true;
}
-
} else {
// If there is no limitation on the number of leases to reclaim,
// we will try to process all. Hence, we don't mark it as incomplete
lease_mgr.getExpiredLeases4(leases, max_leases);
}
-
// Do not initialize the callout handle until we know if there are any
// lease4_expire callouts installed.
CalloutHandlePtr callout_handle;
size_t leases_processed = 0;
BOOST_FOREACH(Lease4Ptr lease, leases) {
-
try {
// Reclaim the lease.
reclaimExpiredLease(lease, remove_lease, callout_handle);
++leases_processed;
-
} catch (const std::exception& ex) {
LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_LEASE_RECLAMATION_FAILED)
.arg(lease->addr_.toText())
// We issued a warning, so let's now reset the counter.
incomplete_v4_reclamations_ = 0;
}
-
} else {
// This was a complete reclamation, so let's reset the counter.
incomplete_v4_reclamations_ = 0;
AllocEngine::reclaimExpiredLease(const Lease6Ptr& lease,
const DbReclaimMode& reclaim_mode,
const CalloutHandlePtr& callout_handle) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V6_LEASE_RECLAIM)
.arg(Pkt6::makeLabel(lease->duid_, lease->hwaddr_))
// it reclaims the lease itself. In this case the reclamation routine
// will not update DNS nor update the database.
bool skipped = false;
- if (callout_handle) {
+ if (callout_handle) {
// Use the RAII wrapper to make sure that the callout handle state is
// reset when this object goes out of scope. All hook points must do
// it to prevent possible circular dependency between the callout
/// Not sure if we need to support every possible status everywhere.
if (!skipped) {
-
// Generate removal name change request for D2, if required.
// This will return immediately if the DNS wasn't updated
// when the lease was created.
lease->subnet_id_,
"assigned-nas"),
int64_t(-1));
-
} else if (lease->type_ == Lease::TYPE_PD) {
// IA_PD
StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
lease->subnet_id_,
"assigned-pds"),
int64_t(-1));
-
}
// Increase total number of reclaimed leases.
AllocEngine::reclaimExpiredLease(const Lease4Ptr& lease,
const DbReclaimMode& reclaim_mode,
const CalloutHandlePtr& callout_handle) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_LEASE_RECLAIM)
.arg(Pkt4::makeLabel(lease->hwaddr_, lease->client_id_))
// it reclaims the lease itself. In this case the reclamation routine
// will not update DNS nor update the database.
bool skipped = false;
- if (callout_handle) {
+ if (callout_handle) {
// Use the RAII wrapper to make sure that the callout handle state is
// reset when this object goes out of scope. All hook points must do
// it to prevent possible circular dependency between the callout
/// Not sure if we need to support every possible status everywhere.
if (!skipped) {
-
// Generate removal name change request for D2, if required.
// This will return immediately if the DNS wasn't updated
// when the lease was created.
// Try to delete leases from the lease database.
LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
deleted_leases = lease_mgr.deleteExpiredReclaimedLeases4(secs);
-
} catch (const std::exception& ex) {
LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED)
.arg(ex.what());
bool
AllocEngine::reclaimDeclined(const Lease4Ptr& lease) {
-
if (!lease || (lease->state_ != Lease::STATE_DECLINED) ) {
return (true);
}
if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease4_recover_)) {
-
// Let's use a static callout handle. It will be initialized the first
// time lease4_recover is called and will keep to that value.
static CalloutHandlePtr callout_handle;
bool
AllocEngine::reclaimDeclined(const Lease6Ptr& lease) {
-
if (!lease || (lease->state_ != Lease::STATE_DECLINED) ) {
return (true);
}
if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease6_recover_)) {
-
// Let's use a static callout handle. It will be initialized the first
// time lease6_recover is called and will keep to that value.
static CalloutHandlePtr callout_handle;
return (true);
}
-
template<typename LeasePtrType>
void AllocEngine::reclaimLeaseInDatabase(const LeasePtrType& lease,
const bool remove_lease,
const boost::function<void (const LeasePtrType&)>&
lease_update_fun) const {
-
LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
// Reclaim the lease - depending on the configuration, set the
// expired-reclaimed state or simply remove it.
if (remove_lease) {
lease_mgr.deleteLease(lease->addr_);
-
} else if (!lease_update_fun.empty()) {
// Clear FQDN information as we have already sent the
// name change request to remove the DNS record.
lease->fqdn_rev_ = false;
lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
lease_update_fun(lease);
-
} else {
return;
}
.arg(lease->addr_.toText());
}
-
-} // end of isc::dhcp namespace
-} // end of isc namespace
+} // namespace dhcp
+} // namespace isc
// ##########################################################################
// # DHCPv4 lease allocation code starts here.
// ##########################################################################
namespace {
-
/// @brief Check if the specific address is reserved for another client.
///
/// This function finds a host reservation for a given address and then
for (Subnet4Ptr subnet = original_subnet; subnet;
subnet = subnet->getNextSubnet(original_subnet,
ctx.query_->getClasses())) {
-
// If client identifier has been supplied and the server wasn't
// explicitly configured to ignore client identifiers for this subnet
// check if there is a lease within this subnet.
// If no lease found using the client identifier, try the lookup using
// the HW address.
if (!client_lease && ctx.hwaddr_) {
-
// Get all leases for this HW address.
Lease4Collection leases_hw_address = lease_mgr.getLease4(*ctx.hwaddr_);
// If the subnet belongs to a shared network we will be iterating
// over the subnets that belong to this shared network.
Subnet4Ptr current_subnet = ctx.subnet_;
- while (current_subnet) {
+ while (current_subnet) {
if (current_subnet->inPool(Lease::TYPE_V4, address,
ctx.query_->getClasses())) {
// We found a subnet that this address belongs to, so it
return (false);
}
-} // end of anonymous namespace
+} // namespace
namespace isc {
namespace dhcp {
-
AllocEngine::ClientContext4::ClientContext4()
: subnet_(), clientid_(), hwaddr_(),
requested_address_(IOAddress::IPV4_ZERO_ADDRESS()),
fake_allocation_(fake_allocation), old_lease_(), new_lease_(),
hosts_(), host_identifiers_(),
ddns_params_(new DdnsParams()) {
-
// Initialize host identifiers.
if (hwaddr) {
addHostIdentifier(Host::IDENT_HWADDR, hwaddr->hwaddr_);
if (ctx.fake_allocation_) {
return (discoverLease4(ctx));
-
} else {
ctx.new_lease_ = requestLease4(ctx);
}
-
} catch (const isc::Exception& e) {
// Some other error, return an empty lease.
LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_ERROR)
// We can only search for the reservation if a subnet has been selected.
while (subnet) {
-
// Only makes sense to get reservations if the client has access
// to the class.
if (subnet->clientSupported(ctx.query_->getClasses()) &&
ctx.hosts_[subnet->getID()] = host_map[subnet->getID()];
break;
}
-
} else {
// Attempt to find a host using a specified identifier.
ConstHostPtr host = HostMgr::instance().get4(subnet->getID(),
return (host);
}
-
Lease4Ptr
AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
// Find an existing lease for this client. This function will return true
// Check if there is a reservation for the client. If there is, we want to
// assign the reserved address, rather than any other one.
if (hasAddressReservation(ctx)) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_DISCOVER_HR)
.arg(ctx.query_->getLabel())
.arg(ctx.conflicting_lease_ ? ctx.conflicting_lease_->toText() :
"(no lease info)");
}
-
} else {
new_lease = renewLease4(client_lease, ctx);
}
// which the reservation has just been removed.
if (!new_lease && client_lease && inAllowedPool(ctx, client_lease->addr_) &&
!addressReserved(client_lease->addr_, ctx)) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE)
.arg(ctx.query_->getLabel());
if (!new_lease && !ctx.requested_address_.isV4Zero() &&
inAllowedPool(ctx, ctx.requested_address_) &&
!addressReserved(ctx.requested_address_, ctx)) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_OFFER_REQUESTED_LEASE)
.arg(ctx.requested_address_.toText())
// addresses. We will now use the allocator to pick the address
// from the dynamic pool.
if (!new_lease) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_OFFER_NEW_LEASE)
.arg(ctx.query_->getLabel());
Lease4Ptr client_lease;
findClientLease(ctx, client_lease);
- // Obtain the sole instance of the LeaseMgr.
- LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
-
// When the client sends the DHCPREQUEST, it should always specify the
// address which it is requesting or renewing. That is, the client should
// either use the requested IP address option or set the ciaddr. However,
// is not reserved for another client. If it is, stop here because
// we can't allocate this address.
if (addressReserved(ctx.requested_address_, ctx)) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED)
.arg(ctx.query_->getLabel())
return (Lease4Ptr());
}
-
} else if (hasAddressReservation(ctx)) {
// The client hasn't specified an address to allocate, so the
// allocation engine needs to find an appropriate address.
if (existing && !existing->expired() &&
!existing->belongsToClient(ctx.hwaddr_, ctx.subnet_->getMatchClientId() ?
ctx.clientid_ : ClientIdPtr())) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_REQUEST_IN_USE)
.arg(ctx.query_->getLabel())
// address, return NULL. The client should go back to the
// DHCPDISCOVER and the reserved address will be offered.
if (!existing || existing->expired()) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_REQUEST_INVALID)
.arg(ctx.query_->getLabel())
if ((!hasAddressReservation(ctx) ||
(ctx.currentHost()->getIPv4Reservation() != ctx.requested_address_)) &&
!inAllowedPool(ctx, ctx.requested_address_)) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL)
.arg(ctx.query_->getLabel())
ctx.requested_address_.isV4Zero()) &&
(hasAddressReservation(ctx) ||
inAllowedPool(ctx, client_lease->addr_))) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_REQUEST_EXTEND_LEASE)
.arg(ctx.query_->getLabel())
// The client doesn't have the lease or it is requesting an address
// which it doesn't have. Let's try to allocate the requested address.
if (!ctx.requested_address_.isV4Zero()) {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_REQUEST_ALLOC_REQUESTED)
.arg(ctx.query_->getLabel())
CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
new_lease = allocateOrReuseLease4(ctx.requested_address_, ctx,
callout_status);
-
} else {
-
LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
ALLOC_ENGINE_V4_REQUEST_PICK_ADDRESS)
.arg(ctx.query_->getLabel());
.arg(ctx.query_->getLabel())
.arg(client_lease->addr_.toText());
- lease_mgr.deleteLease(client_lease->addr_);
+ LeaseMgrFactory::instance().deleteLease(client_lease->addr_);
// Need to decrease statistic for assigned addresses.
StatsMgr::instance().addValue(
// Let's execute all callouts registered for lease4_select
if (ctx.callout_handle_ &&
HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {
-
// Use the RAII wrapper to make sure that the callout handle state is
// reset when this object goes out of scope. All hook points must do
// it to prevent possible circular dependency between the callout
if (!ctx.fake_allocation_) {
// That is a real (REQUEST) allocation
bool status = LeaseMgrFactory::instance().addLease(lease);
- if (status) {
+ if (status) {
// The lease insertion succeeded, let's bump up the statistic.
StatsMgr::instance().addValue(
StatsMgr::generateName("subnet", ctx.subnet_->getID(), "assigned-addresses"),
// involves execution of hooks and DNS update.
if (ctx.old_lease_->expired()) {
reclaimExpiredLease(ctx.old_lease_, ctx.callout_handle_);
-
}
lease->state_ = Lease::STATE_DEFAULT;
// Execute all callouts registered for lease4_renew.
if (HooksManager::getHooksManager().
calloutsPresent(Hooks.hook_index_lease4_renew_)) {
-
// Use the RAII wrapper to make sure that the callout handle state is
// reset when this object goes out of scope. All hook points must do
// it to prevent possible circular dependency between the callout
static_cast<int64_t>(1));
}
}
+
if (skip) {
// Rollback changes (really useful only for memfile)
/// @todo: remove this?
// Let's execute all callouts registered for lease4_select
if (ctx.callout_handle_ && HooksManager::getHooksManager()
.calloutsPresent(hook_index_lease4_select_)) {
-
// Enable copying options from the packet within hook library.
ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_);
if (exist_lease->expired()) {
ctx.old_lease_ = Lease4Ptr(new Lease4(*exist_lease));
return (reuseExpiredLease4(exist_lease, ctx, callout_status));
-
} else {
// If there is a lease and it is not expired, pass this lease back
// to the caller in the context. The caller may need to know
// which lease we're conflicting with.
ctx.conflicting_lease_ = exist_lease;
}
-
} else {
return (createLease4(ctx, candidate, callout_status));
}
Subnet4Ptr original_subnet = subnet;
uint64_t total_attempts = 0;
- while (subnet) {
+ while (subnet) {
ClientIdPtr client_id;
if (subnet->getMatchClientId()) {
client_id = ctx.clientid_;
ctx.requested_address_);
// If address is not reserved for another client, try to allocate it.
if (!addressReserved(candidate, ctx)) {
-
// The call below will return the non-NULL pointer if we
// successfully allocate this lease. This means that the
// address is not in use by another client.
new_lease = allocateOrReuseLease4(candidate, ctx, callout_status);
if (new_lease) {
return (new_lease);
-
} else if (ctx.callout_handle_ &&
(callout_status != CalloutHandle::NEXT_STEP_CONTINUE)) {
// Don't retry when the callout status is not continue.
return (true);
}
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
+} // namespace dhcp
+} // namespace isc
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/lease_mgr.h>
#include <hooks/callout_handle.h>
+#include <util/threads/lock_guard.h>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <list>
#include <map>
+#include <mutex>
#include <set>
#include <utility>
/// @param file name of the file, where exception occurred
/// @param line line of the file, where exception occurred
/// @param what text description of the issue that caused exception
- AllocFailed(const char* file, size_t line, const char* what)
- : isc::Exception(file, line, what) {}
+ AllocFailed(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {
+ }
};
/// @brief DHCPv4 and DHCPv6 allocation engine
/// @todo: Does not handle out of allocation attempts well
class AllocEngine : public boost::noncopyable {
protected:
-
/// @brief base class for all address/prefix allocation algorithms
///
/// This is an abstract class that should not be used directly, but rather
/// specialized implementations should be used instead.
class Allocator {
public:
-
/// @brief picks one address out of available pools in a given subnet
///
/// This method returns one address from the available pools in the
pickAddress(const SubnetPtr& subnet,
const ClientClasses& client_classes,
const DuidPtr& duid,
- const isc::asiolink::IOAddress& hint) = 0;
+ const isc::asiolink::IOAddress& hint) {
+ isc::util::thread::LockGuard<std::mutex> lock(&mutex_);
+ return pickAddressInternal(subnet, client_classes, duid, hint);
+ }
/// @brief Default constructor.
///
/// Specifies which type of leases this allocator will assign
/// @param pool_type specifies pool type (addresses, temp. addr or prefixes)
- Allocator(Lease::Type pool_type)
- :pool_type_(pool_type) {
+ Allocator(Lease::Type pool_type) : pool_type_(pool_type) {
}
/// @brief virtual destructor
virtual ~Allocator() {
}
- protected:
+ private:
+ virtual isc::asiolink::IOAddress
+ pickAddressInternal(const SubnetPtr& subnet,
+ const ClientClasses& client_classes,
+ const DuidPtr& duid,
+ const isc::asiolink::IOAddress& hint) = 0;
+
+ protected:
/// @brief defines pool type allocation
Lease::Type pool_type_;
+
+ private:
+ std::mutex mutex_;
};
/// defines a pointer to allocator
/// over).
class IterativeAllocator : public Allocator {
public:
-
/// @brief default constructor
///
/// Does not do anything
/// @param type - specifies allocation type
IterativeAllocator(Lease::Type type);
+ private:
/// @brief returns the next address from pools in a subnet
///
/// @param subnet next address will be returned from pool of that subnet
/// @param hint client's hint (ignored)
/// @return the next address
virtual isc::asiolink::IOAddress
- pickAddress(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr& duid,
- const isc::asiolink::IOAddress& hint);
- protected:
+ pickAddressInternal(const SubnetPtr& subnet,
+ const ClientClasses& client_classes,
+ const DuidPtr& duid,
+ const isc::asiolink::IOAddress& hint);
+ protected:
/// @brief Returns the next prefix
///
/// This method works for IPv6 addresses only. It increases the
static isc::asiolink::IOAddress
increaseAddress(const isc::asiolink::IOAddress& address,
bool prefix, const uint8_t prefix_len);
-
};
/// @brief Address/prefix allocator that gets an address based on a hash
/// @todo: This is a skeleton class for now and is missing an implementation.
class HashedAllocator : public Allocator {
public:
-
/// @brief default constructor (does nothing)
/// @param type - specifies allocation type
HashedAllocator(Lease::Type type);
+ private:
/// @brief returns an address based on hash calculated from client's DUID.
///
/// @todo: Implement this method
/// @param hint a hint (last address that was picked)
/// @return selected address
virtual isc::asiolink::IOAddress
- pickAddress(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr& duid,
- const isc::asiolink::IOAddress& hint);
+ pickAddressInternal(const SubnetPtr& subnet,
+ const ClientClasses& client_classes,
+ const DuidPtr& duid,
+ const isc::asiolink::IOAddress& hint);
};
/// @brief Random allocator that picks address randomly
/// @todo: This is a skeleton class for now and is missing an implementation.
class RandomAllocator : public Allocator {
public:
-
/// @brief default constructor (does nothing)
/// @param type - specifies allocation type
RandomAllocator(Lease::Type type);
+ private:
/// @brief returns a random address from pool of specified subnet
///
/// @todo: Implement this method
/// @param hint the last address that was picked (ignored)
/// @return a random address from the pool
virtual isc::asiolink::IOAddress
- pickAddress(const SubnetPtr& subnet,
- const ClientClasses& client_classes,
- const DuidPtr& duid,
- const isc::asiolink::IOAddress& hint);
+ pickAddressInternal(const SubnetPtr& subnet,
+ const ClientClasses& client_classes,
+ const DuidPtr& duid,
+ const isc::asiolink::IOAddress& hint);
};
public:
-
/// @brief specifies allocation type
typedef enum {
ALLOC_ITERATIVE, // iterative - one address after another
AllocatorPtr getAllocator(Lease::Type type);
private:
-
/// @brief a pointer to currently used allocator
///
/// For IPv4, there will be only one allocator: TYPE_V4
int hook_index_lease6_select_; ///< index for lease6_select hook
public:
-
/// @brief Defines a single hint
///
/// This is an entry that represents what the client had requested,
/// @note Seems to be used only for DHCPv6.
class Resource {
public:
-
/// @brief Default constructor.
///
/// @param address the address or prefix
}
protected:
-
/// @brief The address or prefix.
isc::asiolink::IOAddress address_;
/// information to the allocation engine methods is that adding
/// new information doesn't modify the API of the allocation engine.
struct ClientContext6 : public boost::noncopyable {
-
/// @name Parameters pertaining to DHCPv6 message
//@{
/// @brief A collection of newly allocated leases.
Lease6Collection new_leases_;
-
//@}
/// @brief Parameters pertaining to individual IAs.
}
private:
-
/// @brief creates a lease and inserts it in LeaseMgr if necessary
///
/// Creates a lease based on specified parameters and tries to insert it
bool reclaimDeclined(const Lease6Ptr& lease);
public:
-
/// @brief Context information for the DHCPv4 lease allocation.
///
/// This structure holds a set of information provided by the DHCPv4
static ConstHostPtr findGlobalReservation(ClientContext4& ctx);
private:
-
/// @brief Offers the lease.
///
/// This method is called by the @c AllocEngine::allocateLease4 when
bool conditionalExtendLifetime(Lease& lease) const;
private:
-
/// @brief Number of consecutive DHCPv4 leases' reclamations after
/// which there are still expired leases in the database.
uint16_t incomplete_v4_reclamations_;
/// @brief A pointer to the @c AllocEngine object.
typedef boost::shared_ptr<AllocEngine> AllocEnginePtr;
-}; // namespace isc::dhcp
-}; // namespace isc
+} // namespace dhcp
+} // namespace isc
#endif // ALLOC_ENGINE_H
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()) {
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()) {
void
SrvConfig::removeStatistics() {
-
// Removes statistics for v4 and v6 subnets
getCfgSubnets4()->removeStatistics();
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_);
/// 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_;
typedef boost::shared_ptr<const SrvConfig> ConstSrvConfigPtr;
//@}
-} // namespace isc::dhcp
-} // namespace isc
+} // namespace dhcp
+} // namespace isc
#endif // DHCPSRV_CONFIG_H
--- /dev/null
+// Copyright (C) 2017-2019 Deutsche Telekom AG.
+//
+// Authors: Andrei Pavel <andrei.pavel@qualitance.com>
+// Cristian Secareanu <cristian.secareanu@qualitance.com>
+// Razvan Becheriu <razvan.becheriu@qualitance.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cassert>
+#include <config.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/thread_pool.h>
+
+#include <functional>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+ThreadPool::ThreadPool() : exit_(true) {
+}
+
+ThreadPool::~ThreadPool() {
+ destroy();
+}
+
+void ThreadPool::create(uint32_t worker_threads) {
+ LOG_INFO(dhcpsrv_logger, "Starting packet thread pool with %1 worker threads")
+ .arg(worker_threads);
+ if (!worker_threads) {
+ return;
+ }
+ destroy();
+ queue_.create();
+ exit_ = false;
+ for (int i = 0; i < worker_threads; ++i) {
+ worker_threads_.push_back(make_shared<thread>(&ThreadPool::threadRun, this));
+ }
+
+ LOG_INFO(dhcpsrv_logger, "Packet thread pool started");
+}
+
+void ThreadPool::destroy() {
+ LOG_INFO(dhcpsrv_logger, "Shutting down packet thread pool");
+ exit_ = true;
+ queue_.destroy();
+ for (auto thread : worker_threads_) {
+ thread->join();
+ }
+ worker_threads_.clear();
+
+ LOG_INFO(dhcpsrv_logger, "Packet thread pool shut down");
+}
+
+void ThreadPool::add(WorkItemCallBack call_back) {
+ queue_.add(call_back);
+}
+
+size_t ThreadPool::count() {
+ return queue_.count();
+}
+
+void ThreadPool::threadRun() {
+ thread::id th_id = this_thread::get_id();
+ LOG_INFO(dhcpsrv_logger, "Packet thread pool new thread started. id: %1").arg(th_id);
+
+ while (!exit_) {
+ WorkItemCallBack work_item;
+ if (queue_.get(work_item)) {
+ work_item();
+ }
+ }
+
+ LOG_INFO(dhcpsrv_logger, "Packet thread pool thread ended. id: %1").arg(th_id);
+}
+
+} // namespace dhcp
+} // namespace isc
--- /dev/null
+// Copyright (C) 2017-2019 Deutsche Telekom AG.
+//
+// Authors: Andrei Pavel <andrei.pavel@qualitance.com>
+// Cristian Secareanu <cristian.secareanu@qualitance.com>
+// Razvan Becheriu <razvan.becheriu@qualitance.com>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef THREAD_POOL_H
+#define THREAD_POOL_H
+
+#include <util/threads/lock_guard.h>
+
+#include <boost/function.hpp>
+
+#include <atomic>
+#include <condition_variable>
+#include <cstdint>
+#include <list>
+#include <mutex>
+#include <queue>
+#include <thread>
+
+namespace isc {
+namespace dhcp {
+
+template <typename WorkItem>
+struct ThreadPoolQueue {
+ ThreadPoolQueue() : exit_(true) {
+ }
+
+ ~ThreadPoolQueue() {
+ }
+
+ void add(WorkItem item) {
+ isc::util::thread::LockGuard<std::mutex> lock(&mutex_);
+ if (exit_) {
+ return;
+ }
+ queue_.push(item);
+ // Notify get() so that it can effectively get a work item.
+ cv_.notify_all();
+ }
+
+ bool get(WorkItem& item) {
+ std::unique_lock<std::mutex> lock(mutex_);
+
+ while (!exit_) {
+ if (queue_.empty()) {
+ // Wait for add() or destroy().
+ cv_.wait(lock);
+ continue;
+ }
+
+ item = queue_.front();
+ queue_.pop();
+ return true;
+ }
+
+ return false;
+ }
+
+ size_t count() {
+ isc::util::thread::LockGuard<std::mutex> lock(&mutex_);
+ return queue_.size();
+ }
+
+ void removeAll() {
+ isc::util::thread::LockGuard<std::mutex> lock(&mutex_);
+ removeAllUnsafe();
+ }
+
+ void create() {
+ isc::util::thread::LockGuard<std::mutex> lock(&mutex_);
+ exit_ = false;
+ }
+
+ void destroy() {
+ isc::util::thread::LockGuard<std::mutex> lock(&mutex_);
+ exit_ = true;
+ // Notify get() so that it can exit.
+ cv_.notify_all();
+ removeAllUnsafe();
+ }
+
+private:
+ /// @brief Has to be called in a mutex_-locked environment.
+ void removeAllUnsafe() {
+ while (queue_.size()) {
+ queue_.pop();
+ }
+ }
+
+ std::queue<WorkItem> queue_;
+ std::mutex mutex_;
+ std::condition_variable cv_;
+ std::atomic_bool exit_;
+};
+
+struct ThreadPool {
+ using WorkItemCallBack = std::function<void()>;
+
+ ThreadPool();
+ ~ThreadPool();
+
+ void create(uint32_t worker_threads);
+
+ void destroy();
+
+ void add(WorkItemCallBack call_back);
+
+ size_t count();
+
+private:
+ void threadRun();
+
+ std::list<std::shared_ptr<std::thread>> worker_threads_;
+ ThreadPoolQueue<WorkItemCallBack> queue_;
+ std::atomic_bool exit_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // THREAD_POOL_H
--- /dev/null
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef THREAD_RESOURCE_MGR_H
+#define THREAD_RESOURCE_MGR_H
+
+#include <util/threads/lock_guard.h>
+
+#include <boost/shared_ptr.hpp>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+
+namespace isc {
+namespace dhcp {
+
+template <typename Resource>
+class ThreadResourceMgr {
+ typedef boost::shared_ptr<Resource> ResourcePtr;
+public:
+ ResourcePtr resource() {
+ isc::util::thread::LockGuard<std::mutex> lock(&mutex_);
+ auto id = std::this_thread::get_id();
+ if (map_.find(id) != map_.end()) {
+ return map_[id];
+ }
+ ResourcePtr result(new Resource());
+ map_[id] = result;
+ return result;
+ }
+private:
+ std::mutex mutex_;
+ std::unordered_map<std::thread::id, ResourcePtr> map_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // THREAD_RESOURCE_MGR_H
#include <database/database_connection.h>
#include <database/db_exceptions.h>
#include <database/db_log.h>
+#include <dhcpsrv/thread_resource_mgr.h>
#include <exceptions/exceptions.h>
#include <mysql/mysql_binding.h>
#include <mysql/mysql_constants.h>
#include <stats/stats_mgr.h>
#include <cc/data.h>
#include <cc/command_interpreter.h>
+#include <util/threads/lock_guard.h>
#include <util/boost_time_utils.h>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace std;
using namespace isc::data;
using namespace isc::config;
+using namespace isc::util::thread;
namespace isc {
namespace stats {
StatsMgr::StatsMgr() :
global_(new StatContext()) {
-
+ mutex_.reset(new std::mutex());
}
-void StatsMgr::setValue(const std::string& name, const int64_t value) {
+void StatsMgr::setValue(const std::string& name, const int64_t value, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
setValueInternal(name, value);
}
-void StatsMgr::setValue(const std::string& name, const double value) {
+void StatsMgr::setValue(const std::string& name, const double value, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
setValueInternal(name, value);
}
-void StatsMgr::setValue(const std::string& name, const StatsDuration& value) {
+void StatsMgr::setValue(const std::string& name, const StatsDuration& value, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
setValueInternal(name, value);
}
-void StatsMgr::setValue(const std::string& name, const std::string& value) {
+
+void StatsMgr::setValue(const std::string& name, const std::string& value, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
setValueInternal(name, value);
}
-void StatsMgr::addValue(const std::string& name, const int64_t value) {
+void StatsMgr::addValue(const std::string& name, const int64_t value, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
addValueInternal(name, value);
}
-void StatsMgr::addValue(const std::string& name, const double value) {
+void StatsMgr::addValue(const std::string& name, const double value, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
addValueInternal(name, value);
}
-void StatsMgr::addValue(const std::string& name, const StatsDuration& value) {
+void StatsMgr::addValue(const std::string& name, const StatsDuration& value, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
addValueInternal(name, value);
}
-void StatsMgr::addValue(const std::string& name, const std::string& value) {
+void StatsMgr::addValue(const std::string& name, const std::string& value, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
addValueInternal(name, value);
}
-ObservationPtr StatsMgr::getObservation(const std::string& name) const {
+ObservationPtr StatsMgr::getObservation(const std::string& name, bool lock) const {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
/// @todo: Implement contexts.
// Currently we keep everything in a global context.
return (global_->get(name));
}
-void StatsMgr::addObservation(const ObservationPtr& stat) {
+void StatsMgr::addObservation(const ObservationPtr& stat, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
/// @todo: Implement contexts.
// Currently we keep everything in a global context.
return (global_->add(stat));
}
-bool StatsMgr::deleteObservation(const std::string& name) {
+bool StatsMgr::deleteObservation(const std::string& name, bool lock) {
+ std::mutex* mlock = nullptr;
+ if (lock) {
+ mlock = mutex_.get();
+ }
+ LockGuard<std::mutex> lockGuard(mlock);
/// @todo: Implement contexts.
// Currently we keep everything in a global context.
return (global_->del(name));
bool StatsMgr::setMaxSampleAge(const std::string& name,
const StatsDuration& duration) {
- ObservationPtr obs = getObservation(name);
+ LockGuard<std::mutex> lock(mutex_.get());
+ ObservationPtr obs = getObservation(name, false);
if (obs) {
obs->setMaxSampleAge(duration);
return (true);
bool StatsMgr::setMaxSampleCount(const std::string& name,
uint32_t max_samples) {
- ObservationPtr obs = getObservation(name);
+ LockGuard<std::mutex> lock(mutex_.get());
+ ObservationPtr obs = getObservation(name, false);
if (obs) {
obs->setMaxSampleCount(max_samples);
return (true);
}
void StatsMgr::setMaxSampleAgeAll(const StatsDuration& duration) {
+ LockGuard<std::mutex> lock(mutex_.get());
// Let's iterate over all stored statistics...
for (std::map<std::string, ObservationPtr>::iterator s = global_->stats_.begin();
s != global_->stats_.end(); ++s) {
}
void StatsMgr::setMaxSampleCountAll(uint32_t max_samples) {
+ LockGuard<std::mutex> lock(mutex_.get());
// Let's iterate over all stored statistics...
for (std::map<std::string, ObservationPtr>::iterator s = global_->stats_.begin();
s != global_->stats_.end(); ++s) {
}
bool StatsMgr::reset(const std::string& name) {
- ObservationPtr obs = getObservation(name);
+ LockGuard<std::mutex> lock(mutex_.get());
+ ObservationPtr obs = getObservation(name, false);
if (obs) {
obs->reset();
return (true);
}
bool StatsMgr::del(const std::string& name) {
+ LockGuard<std::mutex> lock(mutex_.get());
return (global_->del(name));
}
void StatsMgr::removeAll() {
+ LockGuard<std::mutex> lock(mutex_.get());
global_->stats_.clear();
}
isc::data::ConstElementPtr StatsMgr::get(const std::string& name) const {
+ LockGuard<std::mutex> lock(mutex_.get());
isc::data::ElementPtr response = isc::data::Element::createMap(); // a map
- ObservationPtr obs = getObservation(name);
+ ObservationPtr obs = getObservation(name, false);
if (obs) {
response->set(name, obs->getJSON()); // that contains observations
}
}
isc::data::ConstElementPtr StatsMgr::getAll() const {
+ LockGuard<std::mutex> lock(mutex_.get());
isc::data::ElementPtr map = isc::data::Element::createMap(); // a map
// Let's iterate over all stored statistics...
}
void StatsMgr::resetAll() {
+ LockGuard<std::mutex> lock(mutex_.get());
// Let's iterate over all stored statistics...
for (std::map<std::string, ObservationPtr>::iterator s = global_->stats_.begin();
s != global_->stats_.end(); ++s) {
}
size_t StatsMgr::getSize(const std::string& name) const {
- ObservationPtr obs = getObservation(name);
+ LockGuard<std::mutex> lock(mutex_.get());
+ ObservationPtr obs = getObservation(name, false);
size_t size = 0;
if (obs) {
size = obs->getSize();
}
size_t StatsMgr::count() const {
+ LockGuard<std::mutex> lock(mutex_.get());
return (global_->stats_.size());
}
#include <stats/observation.h>
#include <stats/context.h>
#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
#include <map>
+#include <mutex>
#include <string>
#include <vector>
#include <sstream>
/// @param name name of the observation
/// @param value integer value observed
/// @throw InvalidStatType if statistic is not integer
- void setValue(const std::string& name, const int64_t value);
+ void setValue(const std::string& name, const int64_t value, bool lock = true);
/// @brief Records absolute floating point observation.
///
/// @param name name of the observation
/// @param value floating point value observed
/// @throw InvalidStatType if statistic is not fp
- void setValue(const std::string& name, const double value);
+ void setValue(const std::string& name, const double value, bool lock = true);
/// @brief Records absolute duration observation.
///
/// @param name name of the observation
/// @param value duration value observed
/// @throw InvalidStatType if statistic is not time duration
- void setValue(const std::string& name, const StatsDuration& value);
+ void setValue(const std::string& name, const StatsDuration& value, bool lock = true);
/// @brief Records absolute string observation.
///
/// @param name name of the observation
/// @param value string value observed
/// @throw InvalidStatType if statistic is not a string
- void setValue(const std::string& name, const std::string& value);
+ void setValue(const std::string& name, const std::string& value, bool lock = true);
/// @brief Records incremental integer observation.
///
/// @param name name of the observation
/// @param value integer value observed
/// @throw InvalidStatType if statistic is not integer
- void addValue(const std::string& name, const int64_t value);
+ void addValue(const std::string& name, const int64_t value, bool lock = true);
/// @brief Records incremental floating point observation.
///
/// @param name name of the observation
/// @param value floating point value observed
/// @throw InvalidStatType if statistic is not fp
- void addValue(const std::string& name, const double value);
+ void addValue(const std::string& name, const double value, bool lock = true);
/// @brief Records incremental duration observation.
///
/// @param name name of the observation
/// @param value duration value observed
/// @throw InvalidStatType if statistic is not time duration
- void addValue(const std::string& name, const StatsDuration& value);
+ void addValue(const std::string& name, const StatsDuration& value, bool lock = true);
/// @brief Records incremental string observation.
///
/// @param name name of the observation
/// @param value string value observed
/// @throw InvalidStatType if statistic is not a string
- void addValue(const std::string& name, const std::string& value);
+ void addValue(const std::string& name, const std::string& value, bool lock = true);
/// @brief Determines maximum age of samples.
///
/// Used in testing only. Production code should use @ref get() method.
/// @param name name of the statistic
/// @return Pointer to the Observation object
- ObservationPtr getObservation(const std::string& name) const;
+ ObservationPtr getObservation(const std::string& name, bool lock = true) const;
/// @brief Generates statistic name in a given context
///
/// @throw InvalidStatType is statistic exists and has a different type.
template<typename DataType>
void setValueInternal(const std::string& name, DataType value) {
-
// If we want to log each observation, here would be the best place for it.
- ObservationPtr stat = getObservation(name);
+ ObservationPtr stat = getObservation(name, false);
if (stat) {
stat->setValue(value);
} else {
stat.reset(new Observation(name, value));
- addObservation(stat);
+ addObservation(stat, false);
}
}
/// @throw InvalidStatType is statistic exists and has a different type.
template<typename DataType>
void addValueInternal(const std::string& name, DataType value) {
-
// If we want to log each observation, here would be the best place for it.
- ObservationPtr existing = getObservation(name);
+ ObservationPtr existing = getObservation(name, false);
if (!existing) {
// We tried to add to a non-existing statistic. We can recover from
// that. Simply add the new incremental value as a new statistic and
// we're done.
- setValue(name, value);
+ setValue(name, value, false);
return;
} else {
// Let's hope it is of correct type. If not, the underlying
/// That's an utility method used by public @ref setValue() and
/// @ref addValue() methods.
/// @param stat observation
- void addObservation(const ObservationPtr& stat);
+ void addObservation(const ObservationPtr& stat, bool lock = true);
/// @private
///
/// @param name of the statistic to be deleted
/// @return true if deleted, false if not found
- bool deleteObservation(const std::string& name);
+ bool deleteObservation(const std::string& name, bool lock = true);
/// @brief Utility method that attempts to extract statistic name
///
// This is a global context. All statistics will initially be stored here.
StatContextPtr global_;
+
+ boost::scoped_ptr<std::mutex> mutex_;
};
};
--- /dev/null
+#ifndef LOCK_GUARD_H
+#define LOCK_GUARD_H
+
+#include <memory>
+
+namespace isc {
+namespace util {
+namespace thread {
+
+template <typename Lock>
+class LockGuard {
+public:
+ LockGuard(Lock* lock) : lk_(lock) {
+ if (lk_) {
+ lk_->lock();
+ }
+ }
+
+ ~LockGuard() {
+ if (lk_) {
+ lk_->unlock();
+ }
+ }
+
+ LockGuard(const LockGuard&) = delete;
+ LockGuard& operator=(const LockGuard&) = delete;
+
+ LockGuard(LockGuard&&) = delete;
+ LockGuard& operator=(LockGuard&&) = delete;
+
+private:
+ Lock* lk_;
+};
+
+} // namespace thread
+} // namespace util
+} // namespace isc
+
+#endif // LOCK_GUARD_H