"Fossies" - the Fresh Open Source Software Archive

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