"Fossies" - the Fresh Open Source Software Archive

Member "kea-1.6.2/src/bin/dhcp4/ctrl_dhcp4_srv.cc" (21 Feb 2020, 42039 Bytes) of package /linux/misc/kea-1.6.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ctrl_dhcp4_srv.cc" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.6.1_vs_1.6.2.

    1 // Copyright (C) 2014-2020 Internet Systems Consortium, Inc. ("ISC")
    2 //
    3 // This Source Code Form is subject to the terms of the Mozilla Public
    4 // License, v. 2.0. If a copy of the MPL was not distributed with this
    5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
    6 
    7 #include <config.h>
    8 #include <cc/data.h>
    9 #include <cc/command_interpreter.h>
   10 #include <config/command_mgr.h>
   11 #include <dhcp4/ctrl_dhcp4_srv.h>
   12 #include <dhcp4/dhcp4_log.h>
   13 #include <dhcp4/dhcp4to6_ipc.h>
   14 #include <dhcp4/parser_context.h>
   15 #include <dhcp4/json_config_parser.h>
   16 #include <dhcpsrv/cfgmgr.h>
   17 #include <dhcpsrv/cfg_db_access.h>
   18 #include <hooks/hooks.h>
   19 #include <hooks/hooks_manager.h>
   20 #include <stats/stats_mgr.h>
   21 #include <cfgrpt/config_report.h>
   22 #include <signal.h>
   23 #include <sstream>
   24 
   25 using namespace isc::data;
   26 using namespace isc::db;
   27 using namespace isc::dhcp;
   28 using namespace isc::hooks;
   29 using namespace isc::config;
   30 using namespace isc::stats;
   31 using namespace std;
   32 
   33 namespace {
   34 
   35 /// Structure that holds registered hook indexes.
   36 struct CtrlDhcp4Hooks {
   37     int hooks_index_dhcp4_srv_configured_;
   38 
   39     /// Constructor that registers hook points for the DHCPv4 server.
   40     CtrlDhcp4Hooks() {
   41         hooks_index_dhcp4_srv_configured_ = HooksManager::registerHook("dhcp4_srv_configured");
   42     }
   43 
   44 };
   45 
   46 // Declare a Hooks object. As this is outside any function or method, it
   47 // will be instantiated (and the constructor run) when the module is loaded.
   48 // As a result, the hook indexes will be defined before any method in this
   49 // module is called.
   50 CtrlDhcp4Hooks Hooks;
   51 
   52 /// @brief Signals handler for DHCPv4 server.
   53 ///
   54 /// This signal handler handles the following signals received by the DHCPv4
   55 /// server process:
   56 /// - SIGHUP - triggers server's dynamic reconfiguration.
   57 /// - SIGTERM - triggers server's shut down.
   58 /// - SIGINT - triggers server's shut down.
   59 ///
   60 /// @param signo Signal number received.
   61 void signalHandler(int signo) {
   62     // SIGHUP signals a request to reconfigure the server.
   63     if (signo == SIGHUP) {
   64         ControlledDhcpv4Srv::processCommand("config-reload",
   65                                             ConstElementPtr());
   66     } else if ((signo == SIGTERM) || (signo == SIGINT)) {
   67         ControlledDhcpv4Srv::processCommand("shutdown",
   68                                             ConstElementPtr());
   69     }
   70 }
   71 
   72 }
   73 
   74 namespace isc {
   75 namespace dhcp {
   76 
   77 ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
   78 
   79 void
   80 ControlledDhcpv4Srv::init(const std::string& file_name) {
   81     // Configure the server using JSON file.
   82     ConstElementPtr result = loadConfigFile(file_name);
   83     int rcode;
   84     ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
   85     if (rcode != 0) {
   86         string reason = comment ? comment->stringValue() :
   87             "no details available";
   88         isc_throw(isc::BadValue, reason);
   89     }
   90 
   91     // We don't need to call openActiveSockets() or startD2() as these
   92     // methods are called in processConfig() which is called by
   93     // processCommand("config-set", ...)
   94 
   95     // Set signal handlers. When the SIGHUP is received by the process
   96     // the server reconfiguration will be triggered. When SIGTERM or
   97     // SIGINT will be received, the server will start shutting down.
   98     signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM));
   99     // Set the pointer to the handler function.
  100     signal_handler_ = signalHandler;
  101 }
  102 
  103 void ControlledDhcpv4Srv::cleanup() {
  104     // Nothing to do here. No need to disconnect from anything.
  105 }
  106 
  107 /// @brief Configure DHCPv4 server using the configuration file specified.
  108 ///
  109 /// This function is used to both configure the DHCP server on its startup
  110 /// and dynamically reconfigure the server when SIGHUP signal is received.
  111 ///
  112 /// It fetches DHCPv4 server's configuration from the 'Dhcp4' section of
  113 /// the JSON configuration file.
  114 ///
  115 /// @param file_name Configuration file location.
  116 /// @return status of the command
  117 ConstElementPtr
  118 ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) {
  119     // This is a configuration backend implementation that reads the
  120     // configuration from a JSON file.
  121 
  122     isc::data::ConstElementPtr json;
  123     isc::data::ConstElementPtr dhcp4;
  124     isc::data::ConstElementPtr logger;
  125     isc::data::ConstElementPtr result;
  126 
  127     // Basic sanity check: file name must not be empty.
  128     try {
  129         if (file_name.empty()) {
  130             // Basic sanity check: file name must not be empty.
  131             isc_throw(isc::BadValue, "JSON configuration file not specified."
  132                       " Please use -c command line option.");
  133         }
  134 
  135         // Read contents of the file and parse it as JSON
  136         Parser4Context parser;
  137         json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
  138         if (!json) {
  139             isc_throw(isc::BadValue, "no configuration found");
  140         }
  141 
  142         // Let's do sanity check before we call json->get() which
  143         // works only for map.
  144         if (json->getType() != isc::data::Element::map) {
  145             isc_throw(isc::BadValue, "Configuration file is expected to be "
  146                       "a map, i.e., start with { and end with } and contain "
  147                       "at least an entry called 'Dhcp4' that itself is a map. "
  148                       << file_name
  149                       << " is a valid JSON, but its top element is not a map."
  150                       " Did you forget to add { } around your configuration?");
  151         }
  152 
  153         // Use parsed JSON structures to configure the server
  154         result = ControlledDhcpv4Srv::processCommand("config-set", json);
  155         if (!result) {
  156             // Undetermined status of the configuration. This should never
  157             // happen, but as the configureDhcp4Server returns a pointer, it is
  158             // theoretically possible that it will return NULL.
  159             isc_throw(isc::BadValue, "undefined result of "
  160                       "processCommand(\"config-set\", json)");
  161         }
  162 
  163         // Now check is the returned result is successful (rcode=0) or not
  164         // (see @ref isc::config::parseAnswer).
  165         int rcode;
  166         ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
  167         if (rcode != 0) {
  168             string reason = comment ? comment->stringValue() :
  169                 "no details available";
  170             isc_throw(isc::BadValue, reason);
  171         }
  172     }  catch (const std::exception& ex) {
  173         // If configuration failed at any stage, we drop the staging
  174         // configuration and continue to use the previous one.
  175         CfgMgr::instance().rollback();
  176 
  177         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
  178             .arg(file_name).arg(ex.what());
  179         isc_throw(isc::BadValue, "configuration error using file '"
  180                   << file_name << "': " << ex.what());
  181     }
  182 
  183     return (result);
  184 }
  185 
  186 
  187 ConstElementPtr
  188 ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) {
  189     if (ControlledDhcpv4Srv::getInstance()) {
  190         ControlledDhcpv4Srv::getInstance()->shutdown();
  191     } else {
  192         LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
  193         ConstElementPtr answer = isc::config::createAnswer(1,
  194                                               "Shutdown failure.");
  195         return (answer);
  196     }
  197     ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
  198     return (answer);
  199 }
  200 
  201 ConstElementPtr
  202 ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
  203 
  204     /// @todo delete any stored CalloutHandles referring to the old libraries
  205     /// Get list of currently loaded libraries and reload them.
  206     HookLibsCollection loaded = HooksManager::getLibraryInfo();
  207     bool status = HooksManager::loadLibraries(loaded);
  208     if (!status) {
  209         LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
  210         ConstElementPtr answer = isc::config::createAnswer(1,
  211                                  "Failed to reload hooks libraries.");
  212         return (answer);
  213     }
  214     ConstElementPtr answer = isc::config::createAnswer(0,
  215                              "Hooks libraries successfully reloaded.");
  216     return (answer);
  217 }
  218 
  219 ConstElementPtr
  220 ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
  221                                                 ConstElementPtr /*args*/) {
  222 
  223     // Get configuration file name.
  224     std::string file = ControlledDhcpv4Srv::getInstance()->getConfigFile();
  225     try {
  226         LOG_INFO(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION).arg(file);
  227         return (loadConfigFile(file));
  228     } catch (const std::exception& ex) {
  229         // Log the unsuccessful reconfiguration. The reason for failure
  230         // should be already logged. Don't rethrow an exception so as
  231         // the server keeps working.
  232         LOG_ERROR(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION_FAIL)
  233             .arg(file);
  234         return (createAnswer(CONTROL_RESULT_ERROR,
  235                              "Config reload failed: " + string(ex.what())));
  236     }
  237 }
  238 
  239 ConstElementPtr
  240 ControlledDhcpv4Srv::commandConfigGetHandler(const string&,
  241                                              ConstElementPtr /*args*/) {
  242     ConstElementPtr config = CfgMgr::instance().getCurrentCfg()->toElement();
  243 
  244     return (createAnswer(0, config));
  245 }
  246 
  247 ConstElementPtr
  248 ControlledDhcpv4Srv::commandConfigWriteHandler(const string&,
  249                                                ConstElementPtr args) {
  250     string filename;
  251 
  252     if (args) {
  253         if (args->getType() != Element::map) {
  254             return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
  255         }
  256         ConstElementPtr filename_param = args->get("filename");
  257         if (filename_param) {
  258             if (filename_param->getType() != Element::string) {
  259                 return (createAnswer(CONTROL_RESULT_ERROR,
  260                                      "passed parameter 'filename' is not a string"));
  261             }
  262             filename = filename_param->stringValue();
  263         }
  264     }
  265 
  266     if (filename.empty()) {
  267         // filename parameter was not specified, so let's use whatever we remember
  268         filename = getConfigFile();
  269     }
  270 
  271     if (filename.empty()) {
  272         return (createAnswer(CONTROL_RESULT_ERROR, "Unable to determine filename."
  273                              "Please specify filename explicitly."));
  274     }
  275 
  276     // Ok, it's time to write the file.
  277     size_t size = 0;
  278     try {
  279         ConstElementPtr cfg = CfgMgr::instance().getCurrentCfg()->toElement();
  280         size = writeConfigFile(filename, cfg);
  281     } catch (const isc::Exception& ex) {
  282         return (createAnswer(CONTROL_RESULT_ERROR, string("Error during write-config:")
  283                              + ex.what()));
  284     }
  285     if (size == 0) {
  286         return (createAnswer(CONTROL_RESULT_ERROR, "Error writing configuration to "
  287                              + filename));
  288     }
  289 
  290     // Ok, it's time to return the successful response.
  291     ElementPtr params = Element::createMap();
  292     params->set("size", Element::create(static_cast<long long>(size)));
  293     params->set("filename", Element::create(filename));
  294 
  295     return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
  296                          + filename + " successful", params));
  297 }
  298 
  299 ConstElementPtr
  300 ControlledDhcpv4Srv::commandConfigSetHandler(const string&,
  301                                              ConstElementPtr args) {
  302     const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
  303     ConstElementPtr dhcp4;
  304     string message;
  305 
  306     // Command arguments are expected to be:
  307     // { "Dhcp4": { ... } }
  308     // The Logging component is supported by backward compatiblity.
  309     if (!args) {
  310         message = "Missing mandatory 'arguments' parameter.";
  311     } else {
  312         dhcp4 = args->get("Dhcp4");
  313         if (!dhcp4) {
  314             message = "Missing mandatory 'Dhcp4' parameter.";
  315         } else if (dhcp4->getType() != Element::map) {
  316             message = "'Dhcp4' parameter expected to be a map.";
  317         }
  318     }
  319 
  320     if (!message.empty()) {
  321         // Something is amiss with arguments, return a failure response.
  322         ConstElementPtr result = isc::config::createAnswer(status_code,
  323                                                            message);
  324         return (result);
  325     }
  326 
  327     // We are starting the configuration process so we should remove any
  328     // staging configuration that has been created during previous
  329     // configuration attempts.
  330     CfgMgr::instance().rollback();
  331 
  332     // Check deprecated, obsolete or unknown (aka unsupported) objects.
  333     list<string> unsupported;
  334     for (auto obj : args->mapValue()) {
  335         const string& obj_name = obj.first;
  336         if ((obj_name == "Dhcp4") || (obj_name == "Logging")) {
  337             continue;
  338         }
  339         unsupported.push_back(obj_name);
  340     }
  341 
  342     // Relocate Logging: if there is a global Logging object takes its
  343     // loggers entry, move the entry to Dhcp4 and remove now empty Logging.
  344     Daemon::relocateLogging(args, "Dhcp4");
  345 
  346     // Parse the logger configuration explicitly into the staging config.
  347     // Note this does not alter the current loggers, they remain in
  348     // effect until we apply the logging config below.  If no logging
  349     // is supplied logging will revert to default logging.
  350     Daemon::configureLogger(dhcp4, CfgMgr::instance().getStagingCfg());
  351 
  352     // Let's apply the new logging. We do it early, so we'll be able to print
  353     // out what exactly is wrong with the new config in case of problems.
  354     CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
  355 
  356     // Log unsupported objects.
  357     if (!unsupported.empty()) {
  358         for (auto name : unsupported) {
  359             LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_UNSUPPORTED_OBJECT).arg(name);
  360         }
  361 
  362         // Will return an error in a future version.
  363     }
  364 
  365     // Now we configure the server proper.
  366     ConstElementPtr result = processConfig(dhcp4);
  367 
  368     // If the configuration parsed successfully, apply the new logger
  369     // configuration and the commit the new configuration.  We apply
  370     // the logging first in case there's a configuration failure.
  371     int rcode = 0;
  372     isc::config::parseAnswer(rcode, result);
  373     if (rcode == 0) {
  374         CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
  375 
  376         // Update the fetch globals callback.
  377         auto cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
  378         cfg->setFetchGlobalsFn([]() -> ConstElementPtr {
  379             return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
  380         });
  381 
  382         // Use new configuration.
  383         CfgMgr::instance().commit();
  384     } else {
  385         // Ok, we applied the logging from the upcoming configuration, but
  386         // there were problems with the config. As such, we need to back off
  387         // and revert to the previous logging configuration.
  388         CfgMgr::instance().getCurrentCfg()->applyLoggingCfg();
  389     }
  390 
  391     return (result);
  392 }
  393 
  394 ConstElementPtr
  395 ControlledDhcpv4Srv::commandConfigTestHandler(const string&,
  396                                               ConstElementPtr args) {
  397     const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
  398     ConstElementPtr dhcp4;
  399     string message;
  400 
  401     // Command arguments are expected to be:
  402     // { "Dhcp4": { ... } }
  403     // The Logging component is supported by backward compatiblity.
  404     if (!args) {
  405         message = "Missing mandatory 'arguments' parameter.";
  406     } else {
  407         dhcp4 = args->get("Dhcp4");
  408         if (!dhcp4) {
  409             message = "Missing mandatory 'Dhcp4' parameter.";
  410         } else if (dhcp4->getType() != Element::map) {
  411             message = "'Dhcp4' parameter expected to be a map.";
  412         }
  413     }
  414 
  415     if (!message.empty()) {
  416         // Something is amiss with arguments, return a failure response.
  417         ConstElementPtr result = isc::config::createAnswer(status_code,
  418                                                            message);
  419         return (result);
  420     }
  421 
  422     // We are starting the configuration process so we should remove any
  423     // staging configuration that has been created during previous
  424     // configuration attempts.
  425     CfgMgr::instance().rollback();
  426 
  427     // Check obsolete objects.
  428 
  429     // Relocate Logging. Note this allows to check the loggers configuration.
  430     Daemon::relocateLogging(args, "Dhcp4");
  431 
  432     // Log obsolete objects and return an error.
  433 
  434     // Now we check the server proper.
  435     return (checkConfig(dhcp4));
  436 }
  437 
  438 ConstElementPtr
  439 ControlledDhcpv4Srv::commandDhcpDisableHandler(const std::string&,
  440                                                ConstElementPtr args) {
  441     std::ostringstream message;
  442     int64_t max_period = 0;
  443 
  444     // Parse arguments to see if the 'max-period' parameter has been specified.
  445     if (args) {
  446         // Arguments must be a map.
  447         if (args->getType() != Element::map) {
  448             message << "arguments for the 'dhcp-disable' command must be a map";
  449 
  450         } else {
  451             ConstElementPtr max_period_element = args->get("max-period");
  452             // max-period is optional.
  453             if (max_period_element) {
  454                 // It must be an integer, if specified.
  455                 if (max_period_element->getType() != Element::integer) {
  456                     message << "'max-period' argument must be a number";
  457 
  458                 } else {
  459                     // It must be positive integer.
  460                     max_period = max_period_element->intValue();
  461                     if (max_period <= 0) {
  462                         message << "'max-period' must be positive integer";
  463                     }
  464 
  465                     // The user specified that the DHCP service should resume not
  466                     // later than in max-period seconds. If the 'dhcp-enable' command
  467                     // is not sent, the DHCP service will resume automatically.
  468                     network_state_->delayedEnableAll(static_cast<unsigned>(max_period));
  469                 }
  470             }
  471         }
  472     }
  473 
  474     // No error occurred, so let's disable the service.
  475     if (message.tellp() == 0) {
  476         network_state_->disableService();
  477 
  478         message << "DHCPv4 service disabled";
  479         if (max_period > 0) {
  480             message << " for " << max_period << " seconds";
  481         }
  482         // Success.
  483         return (config::createAnswer(CONTROL_RESULT_SUCCESS, message.str()));
  484     }
  485 
  486     // Failure.
  487     return (config::createAnswer(CONTROL_RESULT_ERROR, message.str()));
  488 }
  489 
  490 ConstElementPtr
  491 ControlledDhcpv4Srv::commandDhcpEnableHandler(const std::string&, ConstElementPtr) {
  492     network_state_->enableService();
  493     return (config::createAnswer(CONTROL_RESULT_SUCCESS, "DHCP service successfully enabled"));
  494 }
  495 
  496 ConstElementPtr
  497 ControlledDhcpv4Srv::commandVersionGetHandler(const string&, ConstElementPtr) {
  498     ElementPtr extended = Element::create(Dhcpv4Srv::getVersion(true));
  499     ElementPtr arguments = Element::createMap();
  500     arguments->set("extended", extended);
  501     ConstElementPtr answer = isc::config::createAnswer(0,
  502                                 Dhcpv4Srv::getVersion(false),
  503                                 arguments);
  504     return (answer);
  505 }
  506 
  507 ConstElementPtr
  508 ControlledDhcpv4Srv::commandBuildReportHandler(const string&,
  509                                                ConstElementPtr) {
  510     ConstElementPtr answer =
  511         isc::config::createAnswer(0, isc::detail::getConfigReport());
  512     return (answer);
  513 }
  514 
  515 ConstElementPtr
  516 ControlledDhcpv4Srv::commandLeasesReclaimHandler(const string&,
  517                                                  ConstElementPtr args) {
  518     int status_code = CONTROL_RESULT_ERROR;
  519     string message;
  520 
  521     // args must be { "remove": <bool> }
  522     if (!args) {
  523         message = "Missing mandatory 'remove' parameter.";
  524     } else {
  525         ConstElementPtr remove_name = args->get("remove");
  526         if (!remove_name) {
  527             message = "Missing mandatory 'remove' parameter.";
  528         } else if (remove_name->getType() != Element::boolean) {
  529             message = "'remove' parameter expected to be a boolean.";
  530         } else {
  531             bool remove_lease = remove_name->boolValue();
  532             server_->alloc_engine_->reclaimExpiredLeases4(0, 0, remove_lease);
  533             status_code = 0;
  534             message = "Reclamation of expired leases is complete.";
  535         }
  536     }
  537     ConstElementPtr answer = isc::config::createAnswer(status_code, message);
  538     return (answer);
  539 }
  540 
  541 ConstElementPtr
  542 ControlledDhcpv4Srv::commandServerTagGetHandler(const std::string&,
  543                                                 ConstElementPtr) {
  544     const std::string& tag =
  545         CfgMgr::instance().getCurrentCfg()->getServerTag();
  546     ElementPtr response = Element::createMap();
  547     response->set("server-tag", Element::create(tag));
  548 
  549     return (createAnswer(CONTROL_RESULT_SUCCESS, response));
  550 }
  551 
  552 ConstElementPtr
  553 ControlledDhcpv4Srv::processCommand(const string& command,
  554                                     ConstElementPtr args) {
  555     string txt = args ? args->str() : "(none)";
  556 
  557     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
  558               .arg(command).arg(txt);
  559 
  560     ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
  561 
  562     if (!srv) {
  563         ConstElementPtr no_srv = isc::config::createAnswer(1,
  564           "Server object not initialized, so can't process command '" +
  565           command + "', arguments: '" + txt + "'.");
  566         return (no_srv);
  567     }
  568 
  569     try {
  570         if (command == "shutdown") {
  571             return (srv->commandShutdownHandler(command, args));
  572 
  573         } else if (command == "libreload") {
  574             return (srv->commandLibReloadHandler(command, args));
  575 
  576         } else if (command == "config-reload") {
  577             return (srv->commandConfigReloadHandler(command, args));
  578 
  579         } else if (command == "config-set") {
  580             return (srv->commandConfigSetHandler(command, args));
  581 
  582         } else if (command == "config-get") {
  583             return (srv->commandConfigGetHandler(command, args));
  584 
  585         } else if (command == "config-test") {
  586             return (srv->commandConfigTestHandler(command, args));
  587 
  588         } else if (command == "dhcp-disable") {
  589             return (srv->commandDhcpDisableHandler(command, args));
  590 
  591         } else if (command == "dhcp-enable") {
  592             return (srv->commandDhcpEnableHandler(command, args));
  593 
  594         } else if (command == "version-get") {
  595             return (srv->commandVersionGetHandler(command, args));
  596 
  597         } else if (command == "build-report") {
  598             return (srv->commandBuildReportHandler(command, args));
  599 
  600         } else if (command == "leases-reclaim") {
  601             return (srv->commandLeasesReclaimHandler(command, args));
  602 
  603         } else if (command == "config-write") {
  604             return (srv->commandConfigWriteHandler(command, args));
  605 
  606         } else if (command == "server-tag-get") {
  607             return (srv->commandServerTagGetHandler(command, args));
  608 
  609         }
  610         ConstElementPtr answer = isc::config::createAnswer(1,
  611                                  "Unrecognized command:" + command);
  612         return (answer);
  613     } catch (const Exception& ex) {
  614         return (isc::config::createAnswer(1, "Error while processing command '"
  615                                           + command + "':" + ex.what() +
  616                                           ", params: '" + txt + "'"));
  617     }
  618 }
  619 
  620 isc::data::ConstElementPtr
  621 ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
  622 
  623     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
  624               .arg(config->str());
  625 
  626     ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
  627 
  628     // Single stream instance used in all error clauses
  629     std::ostringstream err;
  630 
  631     if (!srv) {
  632         err << "Server object not initialized, can't process config.";
  633         return (isc::config::createAnswer(1, err.str()));
  634     }
  635 
  636     ConstElementPtr answer = configureDhcp4Server(*srv, config);
  637 
  638     // Check that configuration was successful. If not, do not reopen sockets
  639     // and don't bother with DDNS stuff.
  640     try {
  641         int rcode = 0;
  642         isc::config::parseAnswer(rcode, answer);
  643         if (rcode != 0) {
  644             return (answer);
  645         }
  646     } catch (const std::exception& ex) {
  647         err << "Failed to process configuration:" << ex.what();
  648         return (isc::config::createAnswer(1, err.str()));
  649     }
  650 
  651     // Re-open lease and host database with new parameters.
  652     try {
  653         DatabaseConnection::db_lost_callback =
  654             boost::bind(&ControlledDhcpv4Srv::dbLostCallback, srv, _1);
  655         CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
  656         cfg_db->setAppendedParameters("universe=4");
  657         cfg_db->createManagers();
  658     } catch (const std::exception& ex) {
  659         err << "Unable to open database: " << ex.what();
  660         return (isc::config::createAnswer(1, err.str()));
  661     }
  662 
  663     // Server will start DDNS communications if its enabled.
  664     try {
  665         srv->startD2();
  666     } catch (const std::exception& ex) {
  667         err << "Error starting DHCP_DDNS client after server reconfiguration: "
  668             << ex.what();
  669         return (isc::config::createAnswer(1, err.str()));
  670     }
  671 
  672     // Setup DHCPv4-over-DHCPv6 IPC
  673     try {
  674         Dhcp4to6Ipc::instance().open();
  675     } catch (const std::exception& ex) {
  676         std::ostringstream err;
  677         err << "error starting DHCPv4-over-DHCPv6 IPC "
  678                " after server reconfiguration: " << ex.what();
  679         return (isc::config::createAnswer(1, err.str()));
  680     }
  681 
  682     // Configure DHCP packet queueing
  683     try {
  684         data::ConstElementPtr qc;
  685         qc  = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
  686         if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, qc)) {
  687             LOG_INFO(dhcp4_logger, DHCP4_CONFIG_PACKET_QUEUE)
  688                       .arg(IfaceMgr::instance().getPacketQueue4()->getInfoStr());
  689         }
  690 
  691     } catch (const std::exception& ex) {
  692         err << "Error setting packet queue controls after server reconfiguration: "
  693             << ex.what();
  694         return (isc::config::createAnswer(1, err.str()));
  695     }
  696 
  697     // Configuration may change active interfaces. Therefore, we have to reopen
  698     // sockets according to new configuration. It is possible that this
  699     // operation will fail for some interfaces but the openSockets function
  700     // guards against exceptions and invokes a callback function to
  701     // log warnings. Since we allow that this fails for some interfaces there
  702     // is no need to rollback configuration if socket fails to open on any
  703     // of the interfaces.
  704     CfgMgr::instance().getStagingCfg()->getCfgIface()->
  705         openSockets(AF_INET, srv->getServerPort(),
  706                     getInstance()->useBroadcast());
  707 
  708     // Install the timers for handling leases reclamation.
  709     try {
  710         CfgMgr::instance().getStagingCfg()->getCfgExpiration()->
  711             setupTimers(&ControlledDhcpv4Srv::reclaimExpiredLeases,
  712                         &ControlledDhcpv4Srv::deleteExpiredReclaimedLeases,
  713                         server_);
  714 
  715     } catch (const std::exception& ex) {
  716         err << "unable to setup timers for periodically running the"
  717             " reclamation of the expired leases: "
  718             << ex.what() << ".";
  719         return (isc::config::createAnswer(1, err.str()));
  720     }
  721 
  722     auto ctl_info = CfgMgr::instance().getStagingCfg()->getConfigControlInfo();
  723     if (ctl_info) {
  724         long fetch_time = static_cast<long>(ctl_info->getConfigFetchWaitTime());
  725         // Only schedule the CB fetch timer if the fetch wait time is greater
  726         // than 0.
  727         if (fetch_time > 0) {
  728             // When we run unit tests, we want to use milliseconds unit for the
  729             // specified interval. Otherwise, we use seconds. Note that using
  730             // milliseconds as a unit in unit tests prevents us from waiting 1
  731             // second on more before the timer goes off. Instead, we wait one
  732             // millisecond which significantly reduces the test time.
  733             if (!server_->inTestMode()) {
  734                 fetch_time = 1000 * fetch_time;
  735             }
  736 
  737             boost::shared_ptr<unsigned> failure_count(new unsigned(0));
  738             TimerMgr::instance()->
  739                 registerTimer("Dhcp4CBFetchTimer",
  740                               boost::bind(&ControlledDhcpv4Srv::cbFetchUpdates,
  741                                           server_, CfgMgr::instance().getStagingCfg(),
  742                                           failure_count),
  743                               fetch_time,
  744                               asiolink::IntervalTimer::ONE_SHOT);
  745             TimerMgr::instance()->setup("Dhcp4CBFetchTimer");
  746         }
  747     }
  748 
  749     // This hook point notifies hooks libraries that the configuration of the
  750     // DHCPv4 server has completed. It provides the hook library with the pointer
  751     // to the common IO service object, new server configuration in the JSON
  752     // format and with the pointer to the configuration storage where the
  753     // parsed configuration is stored.
  754     if (HooksManager::calloutsPresent(Hooks.hooks_index_dhcp4_srv_configured_)) {
  755         CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
  756 
  757         callout_handle->setArgument("io_context", srv->getIOService());
  758         callout_handle->setArgument("network_state", srv->getNetworkState());
  759         callout_handle->setArgument("json_config", config);
  760         callout_handle->setArgument("server_config", CfgMgr::instance().getStagingCfg());
  761 
  762         HooksManager::callCallouts(Hooks.hooks_index_dhcp4_srv_configured_,
  763                                    *callout_handle);
  764 
  765         // Ignore status code as none of them would have an effect on further
  766         // operation.
  767     }
  768 
  769     return (answer);
  770 }
  771 
  772 isc::data::ConstElementPtr
  773 ControlledDhcpv4Srv::checkConfig(isc::data::ConstElementPtr config) {
  774 
  775     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
  776               .arg(config->str());
  777 
  778     ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
  779 
  780     // Single stream instance used in all error clauses
  781     std::ostringstream err;
  782 
  783     if (!srv) {
  784         err << "Server object not initialized, can't process config.";
  785         return (isc::config::createAnswer(1, err.str()));
  786     }
  787 
  788     return (configureDhcp4Server(*srv, config, true));
  789 }
  790 
  791 ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_PORT*/,
  792                                          uint16_t client_port /*= 0*/)
  793     : Dhcpv4Srv(server_port, client_port), io_service_(),
  794       timer_mgr_(TimerMgr::instance()) {
  795     if (getInstance()) {
  796         isc_throw(InvalidOperation,
  797                   "There is another Dhcpv4Srv instance already.");
  798     }
  799     server_ = this; // remember this instance for later use in handlers
  800 
  801     // TimerMgr uses IO service to run asynchronous timers.
  802     TimerMgr::instance()->setIOService(getIOService());
  803 
  804     // CommandMgr uses IO service to run asynchronous socket operations.
  805     CommandMgr::instance().setIOService(getIOService());
  806 
  807     // These are the commands always supported by the DHCPv4 server.
  808     // Please keep the list in alphabetic order.
  809     CommandMgr::instance().registerCommand("build-report",
  810         boost::bind(&ControlledDhcpv4Srv::commandBuildReportHandler, this, _1, _2));
  811 
  812     CommandMgr::instance().registerCommand("config-get",
  813         boost::bind(&ControlledDhcpv4Srv::commandConfigGetHandler, this, _1, _2));
  814 
  815     CommandMgr::instance().registerCommand("config-reload",
  816         boost::bind(&ControlledDhcpv4Srv::commandConfigReloadHandler, this, _1, _2));
  817 
  818     CommandMgr::instance().registerCommand("config-set",
  819         boost::bind(&ControlledDhcpv4Srv::commandConfigSetHandler, this, _1, _2));
  820 
  821     CommandMgr::instance().registerCommand("config-test",
  822         boost::bind(&ControlledDhcpv4Srv::commandConfigTestHandler, this, _1, _2));
  823 
  824     CommandMgr::instance().registerCommand("config-write",
  825         boost::bind(&ControlledDhcpv4Srv::commandConfigWriteHandler, this, _1, _2));
  826 
  827     CommandMgr::instance().registerCommand("dhcp-enable",
  828         boost::bind(&ControlledDhcpv4Srv::commandDhcpEnableHandler, this, _1, _2));
  829 
  830     CommandMgr::instance().registerCommand("dhcp-disable",
  831         boost::bind(&ControlledDhcpv4Srv::commandDhcpDisableHandler, this, _1, _2));
  832 
  833     CommandMgr::instance().registerCommand("libreload",
  834         boost::bind(&ControlledDhcpv4Srv::commandLibReloadHandler, this, _1, _2));
  835 
  836     CommandMgr::instance().registerCommand("leases-reclaim",
  837         boost::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, _1, _2));
  838 
  839     CommandMgr::instance().registerCommand("server-tag-get",
  840         boost::bind(&ControlledDhcpv4Srv::commandServerTagGetHandler, this, _1, _2));
  841 
  842     CommandMgr::instance().registerCommand("shutdown",
  843         boost::bind(&ControlledDhcpv4Srv::commandShutdownHandler, this, _1, _2));
  844 
  845     CommandMgr::instance().registerCommand("version-get",
  846         boost::bind(&ControlledDhcpv4Srv::commandVersionGetHandler, this, _1, _2));
  847 
  848     // Register statistic related commands
  849     CommandMgr::instance().registerCommand("statistic-get",
  850         boost::bind(&StatsMgr::statisticGetHandler, _1, _2));
  851 
  852     CommandMgr::instance().registerCommand("statistic-reset",
  853         boost::bind(&StatsMgr::statisticResetHandler, _1, _2));
  854 
  855     CommandMgr::instance().registerCommand("statistic-remove",
  856         boost::bind(&StatsMgr::statisticRemoveHandler, _1, _2));
  857 
  858     CommandMgr::instance().registerCommand("statistic-get-all",
  859         boost::bind(&StatsMgr::statisticGetAllHandler, _1, _2));
  860 
  861     CommandMgr::instance().registerCommand("statistic-reset-all",
  862         boost::bind(&StatsMgr::statisticResetAllHandler, _1, _2));
  863 
  864     CommandMgr::instance().registerCommand("statistic-remove-all",
  865         boost::bind(&StatsMgr::statisticRemoveAllHandler, _1, _2));
  866 
  867     CommandMgr::instance().registerCommand("statistic-sample-age-set",
  868         boost::bind(&StatsMgr::statisticSetMaxSampleAgeHandler, _1, _2));
  869 
  870     CommandMgr::instance().registerCommand("statistic-sample-age-set-all",
  871         boost::bind(&StatsMgr::statisticSetMaxSampleAgeAllHandler, _1, _2));
  872 
  873     CommandMgr::instance().registerCommand("statistic-sample-count-set",
  874         boost::bind(&StatsMgr::statisticSetMaxSampleCountHandler, _1, _2));
  875 
  876     CommandMgr::instance().registerCommand("statistic-sample-count-set-all",
  877         boost::bind(&StatsMgr::statisticSetMaxSampleCountAllHandler, _1, _2));
  878 }
  879 
  880 void ControlledDhcpv4Srv::shutdown() {
  881     io_service_.stop(); // Stop ASIO transmissions
  882     Dhcpv4Srv::shutdown(); // Initiate DHCPv4 shutdown procedure.
  883 }
  884 
  885 ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
  886     try {
  887         cleanup();
  888 
  889         // The closure captures either a shared pointer (memory leak)
  890         // or a raw pointer (pointing to a deleted object).
  891         DatabaseConnection::db_lost_callback = 0;
  892 
  893         timer_mgr_->unregisterTimers();
  894 
  895         // Close the command socket (if it exists).
  896         CommandMgr::instance().closeCommandSocket();
  897 
  898         // Deregister any registered commands (please keep in alphabetic order)
  899         CommandMgr::instance().deregisterCommand("build-report");
  900         CommandMgr::instance().deregisterCommand("config-get");
  901         CommandMgr::instance().deregisterCommand("config-reload");
  902         CommandMgr::instance().deregisterCommand("config-test");
  903         CommandMgr::instance().deregisterCommand("config-write");
  904         CommandMgr::instance().deregisterCommand("leases-reclaim");
  905         CommandMgr::instance().deregisterCommand("libreload");
  906         CommandMgr::instance().deregisterCommand("config-set");
  907         CommandMgr::instance().deregisterCommand("dhcp-disable");
  908         CommandMgr::instance().deregisterCommand("dhcp-enable");
  909         CommandMgr::instance().deregisterCommand("server-tag-get");
  910         CommandMgr::instance().deregisterCommand("shutdown");
  911         CommandMgr::instance().deregisterCommand("statistic-get");
  912         CommandMgr::instance().deregisterCommand("statistic-get-all");
  913         CommandMgr::instance().deregisterCommand("statistic-remove");
  914         CommandMgr::instance().deregisterCommand("statistic-remove-all");
  915         CommandMgr::instance().deregisterCommand("statistic-reset");
  916         CommandMgr::instance().deregisterCommand("statistic-reset-all");
  917         CommandMgr::instance().deregisterCommand("statistic-sample-age-set");
  918         CommandMgr::instance().deregisterCommand("statistic-sample-age-set-all");
  919         CommandMgr::instance().deregisterCommand("statistic-sample-count-set");
  920         CommandMgr::instance().deregisterCommand("statistic-sample-count-set-all");
  921         CommandMgr::instance().deregisterCommand("version-get");
  922 
  923     } catch (...) {
  924         // Don't want to throw exceptions from the destructor. The server
  925         // is shutting down anyway.
  926         ;
  927     }
  928 
  929     server_ = NULL; // forget this instance. Noone should call any handlers at
  930                     // this stage.
  931 }
  932 
  933 void ControlledDhcpv4Srv::sessionReader(void) {
  934     // Process one asio event. If there are more events, iface_mgr will call
  935     // this callback more than once.
  936     if (getInstance()) {
  937         getInstance()->io_service_.run_one();
  938     }
  939 }
  940 
  941 void
  942 ControlledDhcpv4Srv::reclaimExpiredLeases(const size_t max_leases,
  943                                           const uint16_t timeout,
  944                                           const bool remove_lease,
  945                                           const uint16_t max_unwarned_cycles) {
  946     server_->alloc_engine_->reclaimExpiredLeases4(max_leases, timeout,
  947                                                   remove_lease,
  948                                                   max_unwarned_cycles);
  949     // We're using the ONE_SHOT timer so there is a need to re-schedule it.
  950     TimerMgr::instance()->setup(CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME);
  951 }
  952 
  953 void
  954 ControlledDhcpv4Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
  955     server_->alloc_engine_->deleteExpiredReclaimedLeases4(secs);
  956     // We're using the ONE_SHOT timer so there is a need to re-schedule it.
  957     TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
  958 }
  959 
  960 void
  961 ControlledDhcpv4Srv::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
  962     bool reopened = false;
  963 
  964     // Re-open lease and host database with new parameters.
  965     try {
  966         CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
  967         cfg_db->createManagers();
  968         reopened = true;
  969     } catch (const std::exception& ex) {
  970         LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_ATTEMPT_FAILED).arg(ex.what());
  971     }
  972 
  973     if (reopened) {
  974         // Cancel the timer.
  975         if (TimerMgr::instance()->isTimerRegistered("Dhcp4DbReconnectTimer")) {
  976             TimerMgr::instance()->cancel("Dhcp4DbReconnectTimer");
  977         }
  978 
  979         // Set network state to service enabled
  980         network_state_->enableService();
  981 
  982         // Toss the reconnect control, we're done with it
  983         db_reconnect_ctl.reset();
  984     } else {
  985         if (!db_reconnect_ctl->checkRetries()) {
  986             LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_RETRIES_EXHAUSTED)
  987             .arg(db_reconnect_ctl->maxRetries());
  988             shutdown();
  989             return;
  990         }
  991 
  992         LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_ATTEMPT_SCHEDULE)
  993                 .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
  994                 .arg(db_reconnect_ctl->maxRetries())
  995                 .arg(db_reconnect_ctl->retryInterval());
  996 
  997         if (!TimerMgr::instance()->isTimerRegistered("Dhcp4DbReconnectTimer")) {
  998             TimerMgr::instance()->registerTimer("Dhcp4DbReconnectTimer",
  999                             boost::bind(&ControlledDhcpv4Srv::dbReconnect, this,
 1000                                         db_reconnect_ctl),
 1001                             db_reconnect_ctl->retryInterval(),
 1002                             asiolink::IntervalTimer::ONE_SHOT);
 1003         }
 1004 
 1005         TimerMgr::instance()->setup("Dhcp4DbReconnectTimer");
 1006     }
 1007 }
 1008 
 1009 bool
 1010 ControlledDhcpv4Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
 1011     // Disable service until we recover
 1012     network_state_->disableService();
 1013 
 1014     if (!db_reconnect_ctl) {
 1015         // This shouldn't never happen
 1016         LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
 1017         return (false);
 1018     }
 1019 
 1020     // If reconnect isn't enabled or we're out of retries,
 1021     // log it, schedule a shutdown,  and return false
 1022     if (!db_reconnect_ctl->retriesLeft() ||
 1023         !db_reconnect_ctl->retryInterval()) {
 1024         LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_DISABLED)
 1025             .arg(db_reconnect_ctl->retriesLeft())
 1026             .arg(db_reconnect_ctl->retryInterval());
 1027         ControlledDhcpv4Srv::processCommand("shutdown", ConstElementPtr());
 1028         return(false);
 1029     }
 1030 
 1031     // Invoke reconnect method
 1032     dbReconnect(db_reconnect_ctl);
 1033 
 1034     return(true);
 1035 }
 1036 
 1037 void
 1038 ControlledDhcpv4Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg,
 1039                                     boost::shared_ptr<unsigned> failure_count) {
 1040     try {
 1041         // Fetch any configuration backend updates since our last fetch.
 1042         server_->getCBControl()->databaseConfigFetch(srv_cfg,
 1043                                                      CBControlDHCPv4::FetchMode::FETCH_UPDATE);
 1044         (*failure_count) = 0;
 1045 
 1046     } catch (const std::exception& ex) {
 1047         LOG_ERROR(dhcp4_logger, DHCP4_CB_FETCH_UPDATES_FAIL)
 1048             .arg(ex.what());
 1049 
 1050         // We allow at most 10 consecutive failures after which we stop
 1051         // making further attempts to fetch the configuration updates.
 1052         // Let's return without re-scheduling the timer.
 1053         if (++(*failure_count) > 10) {
 1054             LOG_ERROR(dhcp4_logger, DHCP4_CB_FETCH_UPDATES_RETRIES_EXHAUSTED);
 1055             return;
 1056         }
 1057     }
 1058 
 1059     // Reschedule the timer to fetch new updates or re-try if
 1060     // the previous attempt resulted in an error.
 1061     if (TimerMgr::instance()->isTimerRegistered("Dhcp4CBFetchTimer")) {
 1062         TimerMgr::instance()->setup("Dhcp4CBFetchTimer");
 1063     }
 1064 }
 1065 
 1066 }; // end of isc::dhcp namespace
 1067 }; // end of isc namespace