1 // Copyright (C) 2014-2021 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 
9 #include <cc/command_interpreter.h>
10 #include <cc/data.h>
11 #include <cfgrpt/config_report.h>
12 #include <config/command_mgr.h>
13 #include <dhcp/libdhcp++.h>
14 #include <dhcp4/ctrl_dhcp4_srv.h>
15 #include <dhcp4/dhcp4_log.h>
16 #include <dhcp4/dhcp4to6_ipc.h>
17 #include <dhcp4/json_config_parser.h>
18 #include <dhcp4/parser_context.h>
19 #include <dhcpsrv/cfg_db_access.h>
20 #include <dhcpsrv/cfg_multi_threading.h>
21 #include <dhcpsrv/cfgmgr.h>
22 #include <dhcpsrv/db_type.h>
23 #include <dhcpsrv/host_mgr.h>
24 #include <dhcpsrv/lease_mgr_factory.h>
25 #include <hooks/hooks.h>
26 #include <hooks/hooks_manager.h>
27 #include <stats/stats_mgr.h>
28 #include <util/multi_threading_mgr.h>
29 
30 #include <signal.h>
31 
32 #include <sstream>
33 
34 using namespace isc::asiolink;
35 using namespace isc::config;
36 using namespace isc::data;
37 using namespace isc::db;
38 using namespace isc::dhcp;
39 using namespace isc::hooks;
40 using namespace isc::stats;
41 using namespace isc::util;
42 using namespace std;
43 namespace ph = std::placeholders;
44 
45 namespace {
46 
47 /// Structure that holds registered hook indexes.
48 struct CtrlDhcp4Hooks {
49     int hooks_index_dhcp4_srv_configured_;
50 
51     /// Constructor that registers hook points for the DHCPv4 server.
CtrlDhcp4Hooks__anon71fdb6d20111::CtrlDhcp4Hooks52     CtrlDhcp4Hooks() {
53         hooks_index_dhcp4_srv_configured_ = HooksManager::registerHook("dhcp4_srv_configured");
54     }
55 
56 };
57 
58 // Declare a Hooks object. As this is outside any function or method, it
59 // will be instantiated (and the constructor run) when the module is loaded.
60 // As a result, the hook indexes will be defined before any method in this
61 // module is called.
62 CtrlDhcp4Hooks Hooks;
63 
64 /// @brief Signals handler for DHCPv4 server.
65 ///
66 /// This signal handler handles the following signals received by the DHCPv4
67 /// server process:
68 /// - SIGHUP - triggers server's dynamic reconfiguration.
69 /// - SIGTERM - triggers server's shut down.
70 /// - SIGINT - triggers server's shut down.
71 ///
72 /// @param signo Signal number received.
signalHandler(int signo)73 void signalHandler(int signo) {
74     // SIGHUP signals a request to reconfigure the server.
75     if (signo == SIGHUP) {
76         ControlledDhcpv4Srv::processCommand("config-reload",
77                                             ConstElementPtr());
78     } else if ((signo == SIGTERM) || (signo == SIGINT)) {
79         ControlledDhcpv4Srv::processCommand("shutdown",
80                                             ConstElementPtr());
81     }
82 }
83 
84 }
85 
86 namespace isc {
87 namespace dhcp {
88 
89 ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
90 
91 void
init(const std::string & file_name)92 ControlledDhcpv4Srv::init(const std::string& file_name) {
93     // Keep the call timestamp.
94     start_ = boost::posix_time::second_clock::universal_time();
95 
96     // Configure the server using JSON file.
97     ConstElementPtr result = loadConfigFile(file_name);
98 
99     int rcode;
100     ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
101     if (rcode != CONTROL_RESULT_SUCCESS) {
102         string reason = comment ? comment->stringValue() :
103             "no details available";
104         isc_throw(isc::BadValue, reason);
105     }
106 
107     // We don't need to call openActiveSockets() or startD2() as these
108     // methods are called in processConfig() which is called by
109     // processCommand("config-set", ...)
110 
111     // Set signal handlers. When the SIGHUP is received by the process
112     // the server reconfiguration will be triggered. When SIGTERM or
113     // SIGINT will be received, the server will start shutting down.
114     signal_set_.reset(new IOSignalSet(getIOService(), signalHandler));
115 
116     signal_set_->add(SIGINT);
117     signal_set_->add(SIGHUP);
118     signal_set_->add(SIGTERM);
119 }
120 
cleanup()121 void ControlledDhcpv4Srv::cleanup() {
122     // Nothing to do here. No need to disconnect from anything.
123 }
124 
125 ConstElementPtr
loadConfigFile(const std::string & file_name)126 ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) {
127     // This is a configuration backend implementation that reads the
128     // configuration from a JSON file.
129 
130     isc::data::ConstElementPtr json;
131     isc::data::ConstElementPtr result;
132 
133     // Basic sanity check: file name must not be empty.
134     try {
135         if (file_name.empty()) {
136             // Basic sanity check: file name must not be empty.
137             isc_throw(isc::BadValue, "JSON configuration file not specified."
138                       " Please use -c command line option.");
139         }
140 
141         // Read contents of the file and parse it as JSON
142         Parser4Context parser;
143         json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
144         if (!json) {
145             isc_throw(isc::BadValue, "no configuration found");
146         }
147 
148         // Let's do sanity check before we call json->get() which
149         // works only for map.
150         if (json->getType() != isc::data::Element::map) {
151             isc_throw(isc::BadValue, "Configuration file is expected to be "
152                       "a map, i.e., start with { and end with } and contain "
153                       "at least an entry called 'Dhcp4' that itself is a map. "
154                       << file_name
155                       << " is a valid JSON, but its top element is not a map."
156                       " Did you forget to add { } around your configuration?");
157         }
158 
159         // Use parsed JSON structures to configure the server
160         result = ControlledDhcpv4Srv::processCommand("config-set", json);
161         if (!result) {
162             // Undetermined status of the configuration. This should never
163             // happen, but as the configureDhcp4Server returns a pointer, it is
164             // theoretically possible that it will return NULL.
165             isc_throw(isc::BadValue, "undefined result of "
166                       "processCommand(\"config-set\", json)");
167         }
168 
169         // Now check is the returned result is successful (rcode=0) or not
170         // (see @ref isc::config::parseAnswer).
171         int rcode;
172         ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
173         if (rcode != CONTROL_RESULT_SUCCESS) {
174             string reason = comment ? comment->stringValue() :
175                 "no details available";
176             isc_throw(isc::BadValue, reason);
177         }
178     }  catch (const std::exception& ex) {
179         // If configuration failed at any stage, we drop the staging
180         // configuration and continue to use the previous one.
181         CfgMgr::instance().rollback();
182 
183         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
184             .arg(file_name).arg(ex.what());
185         isc_throw(isc::BadValue, "configuration error using file '"
186                   << file_name << "': " << ex.what());
187     }
188 
189     LOG_WARN(dhcp4_logger, DHCP4_MULTI_THREADING_INFO)
190         .arg(MultiThreadingMgr::instance().getMode() ? "yes" : "no")
191         .arg(MultiThreadingMgr::instance().getThreadPoolSize())
192         .arg(MultiThreadingMgr::instance().getPacketQueueSize());
193 
194     return (result);
195 }
196 
197 ConstElementPtr
commandShutdownHandler(const string &,ConstElementPtr args)198 ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr args) {
199     if (!ControlledDhcpv4Srv::getInstance()) {
200         LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
201         return(createAnswer(CONTROL_RESULT_ERROR, "Shutdown failure."));
202     }
203 
204     int exit_value = 0;
205     if (args) {
206         // @todo Should we go ahead and shutdown even if the args are invalid?
207         if (args->getType() != Element::map) {
208             return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
209         }
210 
211         ConstElementPtr param = args->get("exit-value");
212         if (param)  {
213             if (param->getType() != Element::integer) {
214                 return (createAnswer(CONTROL_RESULT_ERROR,
215                                      "parameter 'exit-value' is not an integer"));
216             }
217 
218             exit_value = param->intValue();
219         }
220     }
221 
222     ControlledDhcpv4Srv::getInstance()->shutdownServer(exit_value);
223     return (createAnswer(CONTROL_RESULT_SUCCESS, "Shutting down."));
224 }
225 
226 ConstElementPtr
commandLibReloadHandler(const string &,ConstElementPtr)227 ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
228     // stop thread pool (if running)
229     MultiThreadingCriticalSection cs;
230 
231     // Clear the packet queue.
232     MultiThreadingMgr::instance().getThreadPool().reset();
233 
234     try {
235         /// Get list of currently loaded libraries and reload them.
236         HookLibsCollection loaded = HooksManager::getLibraryInfo();
237         HooksManager::prepareUnloadLibraries();
238         static_cast<void>(HooksManager::unloadLibraries());
239         bool status = HooksManager::loadLibraries(loaded);
240         if (!status) {
241             isc_throw(Unexpected, "Failed to reload hooks libraries.");
242         }
243     } catch (const std::exception& ex) {
244         LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
245         ConstElementPtr answer = isc::config::createAnswer(1, ex.what());
246         return (answer);
247     }
248     ConstElementPtr answer = isc::config::createAnswer(0,
249                              "Hooks libraries successfully reloaded.");
250     return (answer);
251 }
252 
253 ConstElementPtr
commandConfigReloadHandler(const string &,ConstElementPtr)254 ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
255                                                 ConstElementPtr /*args*/) {
256     // Get configuration file name.
257     std::string file = ControlledDhcpv4Srv::getInstance()->getConfigFile();
258     try {
259         LOG_INFO(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION).arg(file);
260         auto result = loadConfigFile(file);
261         LOG_INFO(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION_SUCCESS).arg(file);
262         return (result);
263     } catch (const std::exception& ex) {
264         // Log the unsuccessful reconfiguration. The reason for failure
265         // should be already logged. Don't rethrow an exception so as
266         // the server keeps working.
267         LOG_FATAL(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION_FAIL)
268             .arg(file);
269         return (createAnswer(CONTROL_RESULT_ERROR,
270                              "Config reload failed: " + string(ex.what())));
271     }
272 }
273 
274 ConstElementPtr
commandConfigGetHandler(const string &,ConstElementPtr)275 ControlledDhcpv4Srv::commandConfigGetHandler(const string&,
276                                              ConstElementPtr /*args*/) {
277     ConstElementPtr config = CfgMgr::instance().getCurrentCfg()->toElement();
278 
279     return (createAnswer(0, config));
280 }
281 
282 ConstElementPtr
commandConfigWriteHandler(const string &,ConstElementPtr args)283 ControlledDhcpv4Srv::commandConfigWriteHandler(const string&,
284                                                ConstElementPtr args) {
285     string filename;
286 
287     if (args) {
288         if (args->getType() != Element::map) {
289             return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
290         }
291         ConstElementPtr filename_param = args->get("filename");
292         if (filename_param) {
293             if (filename_param->getType() != Element::string) {
294                 return (createAnswer(CONTROL_RESULT_ERROR,
295                                      "passed parameter 'filename' is not a string"));
296             }
297             filename = filename_param->stringValue();
298         }
299     }
300 
301     if (filename.empty()) {
302         // filename parameter was not specified, so let's use whatever we remember
303         // from the command-line
304         filename = getConfigFile();
305     }
306 
307     if (filename.empty()) {
308         return (createAnswer(CONTROL_RESULT_ERROR, "Unable to determine filename."
309                              "Please specify filename explicitly."));
310     }
311 
312     // Ok, it's time to write the file.
313     size_t size = 0;
314     try {
315         ConstElementPtr cfg = CfgMgr::instance().getCurrentCfg()->toElement();
316         size = writeConfigFile(filename, cfg);
317     } catch (const isc::Exception& ex) {
318         return (createAnswer(CONTROL_RESULT_ERROR, string("Error during write-config:")
319                              + ex.what()));
320     }
321     if (size == 0) {
322         return (createAnswer(CONTROL_RESULT_ERROR, "Error writing configuration to "
323                              + filename));
324     }
325 
326     // Ok, it's time to return the successful response.
327     ElementPtr params = Element::createMap();
328     params->set("size", Element::create(static_cast<long long>(size)));
329     params->set("filename", Element::create(filename));
330 
331     return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
332                          + filename + " successful", params));
333 }
334 
335 ConstElementPtr
commandConfigSetHandler(const string &,ConstElementPtr args)336 ControlledDhcpv4Srv::commandConfigSetHandler(const string&,
337                                              ConstElementPtr args) {
338     const int status_code = CONTROL_RESULT_ERROR;
339     ConstElementPtr dhcp4;
340     string message;
341 
342     // Command arguments are expected to be:
343     // { "Dhcp4": { ... } }
344     if (!args) {
345         message = "Missing mandatory 'arguments' parameter.";
346     } else {
347         dhcp4 = args->get("Dhcp4");
348         if (!dhcp4) {
349             message = "Missing mandatory 'Dhcp4' parameter.";
350         } else if (dhcp4->getType() != Element::map) {
351             message = "'Dhcp4' parameter expected to be a map.";
352         }
353     }
354 
355     // Check unsupported objects.
356     if (message.empty()) {
357         for (auto obj : args->mapValue()) {
358             const string& obj_name = obj.first;
359             if (obj_name != "Dhcp4") {
360                 LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_UNSUPPORTED_OBJECT)
361                     .arg(obj_name);
362                 if (message.empty()) {
363                     message = "Unsupported '" + obj_name + "' parameter";
364                 } else {
365                     message += " (and '" + obj_name + "')";
366                 }
367             }
368         }
369         if (!message.empty()) {
370             message += ".";
371         }
372     }
373 
374     if (!message.empty()) {
375         // Something is amiss with arguments, return a failure response.
376         ConstElementPtr result = isc::config::createAnswer(status_code,
377                                                            message);
378         return (result);
379     }
380 
381     // stop thread pool (if running)
382     MultiThreadingCriticalSection cs;
383 
384     // disable multi-threading (it will be applied by new configuration)
385     // this must be done in order to properly handle MT to ST transition
386     // when 'multi-threading' structure is missing from new config
387     MultiThreadingMgr::instance().apply(false, 0, 0);
388 
389     // We are starting the configuration process so we should remove any
390     // staging configuration that has been created during previous
391     // configuration attempts.
392     CfgMgr::instance().rollback();
393 
394     // Parse the logger configuration explicitly into the staging config.
395     // Note this does not alter the current loggers, they remain in
396     // effect until we apply the logging config below.  If no logging
397     // is supplied logging will revert to default logging.
398     Daemon::configureLogger(dhcp4, CfgMgr::instance().getStagingCfg());
399 
400     // Let's apply the new logging. We do it early, so we'll be able to print
401     // out what exactly is wrong with the new config in case of problems.
402     CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
403 
404     // Now we configure the server proper.
405     ConstElementPtr result = processConfig(dhcp4);
406 
407     // If the configuration parsed successfully, apply the new logger
408     // configuration and the commit the new configuration.  We apply
409     // the logging first in case there's a configuration failure.
410     int rcode = 0;
411     isc::config::parseAnswer(rcode, result);
412     if (rcode == CONTROL_RESULT_SUCCESS) {
413         CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
414 
415         // Use new configuration.
416         CfgMgr::instance().commit();
417     } else {
418         // Ok, we applied the logging from the upcoming configuration, but
419         // there were problems with the config. As such, we need to back off
420         // and revert to the previous logging configuration.
421         CfgMgr::instance().getCurrentCfg()->applyLoggingCfg();
422 
423         if (CfgMgr::instance().getCurrentCfg()->getSequence() != 0) {
424             // Not initial configuration so someone can believe we reverted
425             // to the previous configuration. It is not the case so be clear
426             // about this.
427             LOG_FATAL(dhcp4_logger, DHCP4_CONFIG_UNRECOVERABLE_ERROR);
428         }
429     }
430 
431     return (result);
432 }
433 
434 ConstElementPtr
commandConfigTestHandler(const string &,ConstElementPtr args)435 ControlledDhcpv4Srv::commandConfigTestHandler(const string&,
436                                               ConstElementPtr args) {
437     const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
438     ConstElementPtr dhcp4;
439     string message;
440 
441     // Command arguments are expected to be:
442     // { "Dhcp4": { ... } }
443     if (!args) {
444         message = "Missing mandatory 'arguments' parameter.";
445     } else {
446         dhcp4 = args->get("Dhcp4");
447         if (!dhcp4) {
448             message = "Missing mandatory 'Dhcp4' parameter.";
449         } else if (dhcp4->getType() != Element::map) {
450             message = "'Dhcp4' parameter expected to be a map.";
451         }
452     }
453 
454     // Check unsupported objects.
455     if (message.empty()) {
456         for (auto obj : args->mapValue()) {
457             const string& obj_name = obj.first;
458             if (obj_name != "Dhcp4") {
459                 LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_UNSUPPORTED_OBJECT)
460                     .arg(obj_name);
461                 if (message.empty()) {
462                     message = "Unsupported '" + obj_name + "' parameter";
463                 } else {
464                     message += " (and '" + obj_name + "')";
465                 }
466             }
467         }
468         if (!message.empty()) {
469             message += ".";
470         }
471     }
472 
473     if (!message.empty()) {
474         // Something is amiss with arguments, return a failure response.
475         ConstElementPtr result = isc::config::createAnswer(status_code,
476                                                            message);
477         return (result);
478     }
479 
480     // stop thread pool (if running)
481     MultiThreadingCriticalSection cs;
482 
483     // We are starting the configuration process so we should remove any
484     // staging configuration that has been created during previous
485     // configuration attempts.
486     CfgMgr::instance().rollback();
487 
488     // Now we check the server proper.
489     return (checkConfig(dhcp4));
490 }
491 
492 ConstElementPtr
commandDhcpDisableHandler(const std::string &,ConstElementPtr args)493 ControlledDhcpv4Srv::commandDhcpDisableHandler(const std::string&,
494                                                ConstElementPtr args) {
495     std::ostringstream message;
496     int64_t max_period = 0;
497     std::string origin;
498 
499     // If the args map does not contain 'origin' parameter, the default type
500     // will be used (user command).
501     NetworkState::Origin type = NetworkState::Origin::USER_COMMAND;
502 
503     // Parse arguments to see if the 'max-period' or 'origin' parameters have
504     // been specified.
505     if (args) {
506         // Arguments must be a map.
507         if (args->getType() != Element::map) {
508             message << "arguments for the 'dhcp-disable' command must be a map";
509 
510         } else {
511             ConstElementPtr max_period_element = args->get("max-period");
512             // max-period is optional.
513             if (max_period_element) {
514                 // It must be an integer, if specified.
515                 if (max_period_element->getType() != Element::integer) {
516                     message << "'max-period' argument must be a number";
517 
518                 } else {
519                     // It must be positive integer.
520                     max_period = max_period_element->intValue();
521                     if (max_period <= 0) {
522                         message << "'max-period' must be positive integer";
523                     }
524                 }
525             }
526             ConstElementPtr origin_element = args->get("origin");
527             // The 'origin' parameter is optional.
528             if (origin_element) {
529                 // It must be a string, if specified.
530                 if (origin_element->getType() != Element::string) {
531                     message << "'origin' argument must be a string";
532 
533                 } else {
534                     origin = origin_element->stringValue();
535                     if (origin == "ha-partner") {
536                         type = NetworkState::Origin::HA_COMMAND;
537                     } else if (origin != "user") {
538                         if (origin.empty()) {
539                             origin = "(empty string)";
540                         }
541                         message << "invalid value used for 'origin' parameter: "
542                                 << origin;
543                     }
544                 }
545             }
546         }
547     }
548 
549     // No error occurred, so let's disable the service.
550     if (message.tellp() == 0) {
551         message << "DHCPv4 service disabled";
552         if (max_period > 0) {
553             message << " for " << max_period << " seconds";
554 
555             // The user specified that the DHCP service should resume not
556             // later than in max-period seconds. If the 'dhcp-enable' command
557             // is not sent, the DHCP service will resume automatically.
558             network_state_->delayedEnableAll(static_cast<unsigned>(max_period),
559                                              type);
560         }
561         network_state_->disableService(type);
562 
563         // Success.
564         return (config::createAnswer(CONTROL_RESULT_SUCCESS, message.str()));
565     }
566 
567     // Failure.
568     return (config::createAnswer(CONTROL_RESULT_ERROR, message.str()));
569 }
570 
571 ConstElementPtr
commandDhcpEnableHandler(const std::string &,ConstElementPtr args)572 ControlledDhcpv4Srv::commandDhcpEnableHandler(const std::string&,
573                                               ConstElementPtr args) {
574     std::ostringstream message;
575     std::string origin;
576 
577     // If the args map does not contain 'origin' parameter, the default type
578     // will be used (user command).
579     NetworkState::Origin type = NetworkState::Origin::USER_COMMAND;
580 
581     // Parse arguments to see if the 'origin' parameter has been specified.
582     if (args) {
583         // Arguments must be a map.
584         if (args->getType() != Element::map) {
585             message << "arguments for the 'dhcp-enable' command must be a map";
586 
587         } else {
588             ConstElementPtr origin_element = args->get("origin");
589             // The 'origin' parameter is optional.
590             if (origin_element) {
591                 // It must be a string, if specified.
592                 if (origin_element->getType() != Element::string) {
593                     message << "'origin' argument must be a string";
594 
595                 } else {
596                     origin = origin_element->stringValue();
597                     if (origin == "ha-partner") {
598                         type = NetworkState::Origin::HA_COMMAND;
599                     } else if (origin != "user") {
600                         if (origin.empty()) {
601                             origin = "(empty string)";
602                         }
603                         message << "invalid value used for 'origin' parameter: "
604                                 << origin;
605                     }
606                 }
607             }
608         }
609     }
610 
611     // No error occurred, so let's enable the service.
612     if (message.tellp() == 0) {
613         network_state_->enableService(type);
614 
615         // Success.
616         return (config::createAnswer(CONTROL_RESULT_SUCCESS,
617                                      "DHCP service successfully enabled"));
618     }
619 
620     // Failure.
621     return (config::createAnswer(CONTROL_RESULT_ERROR, message.str()));
622 }
623 
624 ConstElementPtr
commandVersionGetHandler(const string &,ConstElementPtr)625 ControlledDhcpv4Srv::commandVersionGetHandler(const string&, ConstElementPtr) {
626     ElementPtr extended = Element::create(Dhcpv4Srv::getVersion(true));
627     ElementPtr arguments = Element::createMap();
628     arguments->set("extended", extended);
629     ConstElementPtr answer = isc::config::createAnswer(0,
630                                 Dhcpv4Srv::getVersion(false),
631                                 arguments);
632     return (answer);
633 }
634 
635 ConstElementPtr
commandBuildReportHandler(const string &,ConstElementPtr)636 ControlledDhcpv4Srv::commandBuildReportHandler(const string&,
637                                                ConstElementPtr) {
638     ConstElementPtr answer =
639         isc::config::createAnswer(0, isc::detail::getConfigReport());
640     return (answer);
641 }
642 
643 ConstElementPtr
commandLeasesReclaimHandler(const string &,ConstElementPtr args)644 ControlledDhcpv4Srv::commandLeasesReclaimHandler(const string&,
645                                                  ConstElementPtr args) {
646     int status_code = CONTROL_RESULT_ERROR;
647     string message;
648 
649     // args must be { "remove": <bool> }
650     if (!args) {
651         message = "Missing mandatory 'remove' parameter.";
652     } else {
653         ConstElementPtr remove_name = args->get("remove");
654         if (!remove_name) {
655             message = "Missing mandatory 'remove' parameter.";
656         } else if (remove_name->getType() != Element::boolean) {
657             message = "'remove' parameter expected to be a boolean.";
658         } else {
659             bool remove_lease = remove_name->boolValue();
660             server_->alloc_engine_->reclaimExpiredLeases4(0, 0, remove_lease);
661             status_code = 0;
662             message = "Reclamation of expired leases is complete.";
663         }
664     }
665     ConstElementPtr answer = isc::config::createAnswer(status_code, message);
666     return (answer);
667 }
668 
669 ConstElementPtr
commandServerTagGetHandler(const std::string &,ConstElementPtr)670 ControlledDhcpv4Srv::commandServerTagGetHandler(const std::string&,
671                                                 ConstElementPtr) {
672     const std::string& tag =
673         CfgMgr::instance().getCurrentCfg()->getServerTag();
674     ElementPtr response = Element::createMap();
675     response->set("server-tag", Element::create(tag));
676 
677     return (createAnswer(CONTROL_RESULT_SUCCESS, response));
678 }
679 
680 ConstElementPtr
commandConfigBackendPullHandler(const std::string &,ConstElementPtr)681 ControlledDhcpv4Srv::commandConfigBackendPullHandler(const std::string&,
682                                                      ConstElementPtr) {
683     auto ctl_info = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo();
684     if (!ctl_info) {
685         return (createAnswer(CONTROL_RESULT_EMPTY, "No config backend."));
686     }
687 
688     // stop thread pool (if running)
689     MultiThreadingCriticalSection cs;
690 
691     // Reschedule the periodic CB fetch.
692     if (TimerMgr::instance()->isTimerRegistered("Dhcp4CBFetchTimer")) {
693         TimerMgr::instance()->cancel("Dhcp4CBFetchTimer");
694         TimerMgr::instance()->setup("Dhcp4CBFetchTimer");
695     }
696 
697     // Code from cbFetchUpdates.
698     // The configuration to use is the current one because this is called
699     // after the configuration manager commit.
700     try {
701         auto srv_cfg = CfgMgr::instance().getCurrentCfg();
702         auto mode = CBControlDHCPv4::FetchMode::FETCH_UPDATE;
703         server_->getCBControl()->databaseConfigFetch(srv_cfg, mode);
704     } catch (const std::exception& ex) {
705         LOG_ERROR(dhcp4_logger, DHCP4_CB_ON_DEMAND_FETCH_UPDATES_FAIL)
706             .arg(ex.what());
707         return (createAnswer(CONTROL_RESULT_ERROR,
708                              "On demand configuration update failed: " +
709                              string(ex.what())));
710     }
711     return (createAnswer(CONTROL_RESULT_SUCCESS,
712                          "On demand configuration update successful."));
713 }
714 
715 ConstElementPtr
commandStatusGetHandler(const string &,ConstElementPtr)716 ControlledDhcpv4Srv::commandStatusGetHandler(const string&,
717                                              ConstElementPtr /*args*/) {
718     ElementPtr status = Element::createMap();
719     status->set("pid", Element::create(static_cast<int>(getpid())));
720 
721     auto now = boost::posix_time::second_clock::universal_time();
722     // Sanity check: start_ is always initialized.
723     if (!start_.is_not_a_date_time()) {
724         auto uptime = now - start_;
725         status->set("uptime", Element::create(uptime.total_seconds()));
726     }
727 
728     auto last_commit = CfgMgr::instance().getCurrentCfg()->getLastCommitTime();
729     if (!last_commit.is_not_a_date_time()) {
730         auto reload = now - last_commit;
731         status->set("reload", Element::create(reload.total_seconds()));
732     }
733 
734     auto& mt_mgr = MultiThreadingMgr::instance();
735     if (mt_mgr.getMode()) {
736         status->set("multi-threading-enabled", Element::create(true));
737         status->set("thread-pool-size", Element::create(static_cast<int32_t>(
738                         MultiThreadingMgr::instance().getThreadPoolSize())));
739         status->set("packet-queue-size", Element::create(static_cast<int32_t>(
740                         MultiThreadingMgr::instance().getPacketQueueSize())));
741         ElementPtr queue_stats = Element::createList();
742         queue_stats->add(Element::create(mt_mgr.getThreadPool().getQueueStat(10)));
743         queue_stats->add(Element::create(mt_mgr.getThreadPool().getQueueStat(100)));
744         queue_stats->add(Element::create(mt_mgr.getThreadPool().getQueueStat(1000)));
745         status->set("packet-queue-statistics", queue_stats);
746 
747     } else {
748         status->set("multi-threading-enabled", Element::create(false));
749     }
750 
751     return (createAnswer(0, status));
752 }
753 
754 ConstElementPtr
commandStatisticSetMaxSampleCountAllHandler(const string &,ConstElementPtr args)755 ControlledDhcpv4Srv::commandStatisticSetMaxSampleCountAllHandler(const string&,
756                                                                  ConstElementPtr args) {
757     StatsMgr& stats_mgr = StatsMgr::instance();
758     ConstElementPtr answer = stats_mgr.statisticSetMaxSampleCountAllHandler(args);
759     // Update the default parameter.
760     long max_samples = stats_mgr.getMaxSampleCountDefault();
761     CfgMgr::instance().getCurrentCfg()->addConfiguredGlobal(
762         "statistic-default-sample-count", Element::create(max_samples));
763     return (answer);
764 }
765 
766 ConstElementPtr
commandStatisticSetMaxSampleAgeAllHandler(const string &,ConstElementPtr args)767 ControlledDhcpv4Srv::commandStatisticSetMaxSampleAgeAllHandler(const string&,
768                                                                ConstElementPtr args) {
769     StatsMgr& stats_mgr = StatsMgr::instance();
770     ConstElementPtr answer = stats_mgr.statisticSetMaxSampleAgeAllHandler(args);
771     // Update the default parameter.
772     auto duration = stats_mgr.getMaxSampleAgeDefault();
773     long max_age = toSeconds(duration);
774     CfgMgr::instance().getCurrentCfg()->addConfiguredGlobal(
775         "statistic-default-sample-age", Element::create(max_age));
776     return (answer);
777 }
778 
779 ConstElementPtr
processCommand(const string & command,ConstElementPtr args)780 ControlledDhcpv4Srv::processCommand(const string& command,
781                                     ConstElementPtr args) {
782     string txt = args ? args->str() : "(none)";
783 
784     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
785               .arg(command).arg(txt);
786 
787     ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
788 
789     if (!srv) {
790         ConstElementPtr no_srv = isc::config::createAnswer(1,
791             "Server object not initialized, so can't process command '" +
792             command + "', arguments: '" + txt + "'.");
793         return (no_srv);
794     }
795 
796     try {
797         if (command == "shutdown") {
798             return (srv->commandShutdownHandler(command, args));
799 
800         } else if (command == "libreload") {
801             return (srv->commandLibReloadHandler(command, args));
802 
803         } else if (command == "config-reload") {
804             return (srv->commandConfigReloadHandler(command, args));
805 
806         } else if (command == "config-set") {
807             return (srv->commandConfigSetHandler(command, args));
808 
809         } else if (command == "config-get") {
810             return (srv->commandConfigGetHandler(command, args));
811 
812         } else if (command == "config-test") {
813             return (srv->commandConfigTestHandler(command, args));
814 
815         } else if (command == "dhcp-disable") {
816             return (srv->commandDhcpDisableHandler(command, args));
817 
818         } else if (command == "dhcp-enable") {
819             return (srv->commandDhcpEnableHandler(command, args));
820 
821         } else if (command == "version-get") {
822             return (srv->commandVersionGetHandler(command, args));
823 
824         } else if (command == "build-report") {
825             return (srv->commandBuildReportHandler(command, args));
826 
827         } else if (command == "leases-reclaim") {
828             return (srv->commandLeasesReclaimHandler(command, args));
829 
830         } else if (command == "config-write") {
831             return (srv->commandConfigWriteHandler(command, args));
832 
833         } else if (command == "server-tag-get") {
834             return (srv->commandServerTagGetHandler(command, args));
835 
836         } else if (command == "config-backend-pull") {
837             return (srv->commandConfigBackendPullHandler(command, args));
838 
839         } else if (command == "status-get") {
840             return (srv->commandStatusGetHandler(command, args));
841         }
842 
843         return (isc::config::createAnswer(1, "Unrecognized command:"
844                                           + command));
845 
846     } catch (const isc::Exception& ex) {
847         return (isc::config::createAnswer(1, "Error while processing command '"
848                                           + command + "':" + ex.what() +
849                                           ", params: '" + txt + "'"));
850     }
851 }
852 
853 isc::data::ConstElementPtr
processConfig(isc::data::ConstElementPtr config)854 ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
855     ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
856 
857     // Single stream instance used in all error clauses
858     std::ostringstream err;
859 
860     if (!srv) {
861         err << "Server object not initialized, can't process config.";
862         return (isc::config::createAnswer(1, err.str()));
863     }
864 
865     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
866         .arg(srv->redactConfig(config)->str());
867 
868     ConstElementPtr answer = configureDhcp4Server(*srv, config);
869 
870     // Check that configuration was successful. If not, do not reopen sockets
871     // and don't bother with DDNS stuff.
872     try {
873         int rcode = 0;
874         isc::config::parseAnswer(rcode, answer);
875         if (rcode != 0) {
876             return (answer);
877         }
878     } catch (const std::exception& ex) {
879         err << "Failed to process configuration:" << ex.what();
880         return (isc::config::createAnswer(1, err.str()));
881     }
882 
883     // Re-open lease and host database with new parameters.
884     try {
885         DatabaseConnection::db_lost_callback_ =
886             std::bind(&ControlledDhcpv4Srv::dbLostCallback, srv, ph::_1);
887 
888         DatabaseConnection::db_recovered_callback_ =
889             std::bind(&ControlledDhcpv4Srv::dbRecoveredCallback, srv, ph::_1);
890 
891         DatabaseConnection::db_failed_callback_ =
892             std::bind(&ControlledDhcpv4Srv::dbFailedCallback, srv, ph::_1);
893 
894         CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
895         cfg_db->setAppendedParameters("universe=4");
896         cfg_db->createManagers();
897         // Reset counters related to connections as all managers have been recreated.
898         srv->getNetworkState()->reset(NetworkState::Origin::DB_CONNECTION);
899     } catch (const std::exception& ex) {
900         err << "Unable to open database: " << ex.what();
901         return (isc::config::createAnswer(1, err.str()));
902     }
903 
904     // Server will start DDNS communications if its enabled.
905     try {
906         srv->startD2();
907     } catch (const std::exception& ex) {
908         err << "Error starting DHCP_DDNS client after server reconfiguration: "
909             << ex.what();
910         return (isc::config::createAnswer(1, err.str()));
911     }
912 
913     // Setup DHCPv4-over-DHCPv6 IPC
914     try {
915         Dhcp4to6Ipc::instance().open();
916     } catch (const std::exception& ex) {
917         std::ostringstream err;
918         err << "error starting DHCPv4-over-DHCPv6 IPC "
919                " after server reconfiguration: " << ex.what();
920         return (isc::config::createAnswer(1, err.str()));
921     }
922 
923     // Configure DHCP packet queueing
924     try {
925         data::ConstElementPtr qc;
926         qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
927         if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, qc)) {
928             LOG_INFO(dhcp4_logger, DHCP4_CONFIG_PACKET_QUEUE)
929                      .arg(IfaceMgr::instance().getPacketQueue4()->getInfoStr());
930         }
931 
932     } catch (const std::exception& ex) {
933         err << "Error setting packet queue controls after server reconfiguration: "
934             << ex.what();
935         return (isc::config::createAnswer(1, err.str()));
936     }
937 
938     // Configuration may change active interfaces. Therefore, we have to reopen
939     // sockets according to new configuration. It is possible that this
940     // operation will fail for some interfaces but the openSockets function
941     // guards against exceptions and invokes a callback function to
942     // log warnings. Since we allow that this fails for some interfaces there
943     // is no need to rollback configuration if socket fails to open on any
944     // of the interfaces.
945     CfgMgr::instance().getStagingCfg()->getCfgIface()->
946         openSockets(AF_INET, srv->getServerPort(),
947                     getInstance()->useBroadcast());
948 
949     // Install the timers for handling leases reclamation.
950     try {
951         CfgMgr::instance().getStagingCfg()->getCfgExpiration()->
952             setupTimers(&ControlledDhcpv4Srv::reclaimExpiredLeases,
953                         &ControlledDhcpv4Srv::deleteExpiredReclaimedLeases,
954                         server_);
955 
956     } catch (const std::exception& ex) {
957         err << "unable to setup timers for periodically running the"
958             " reclamation of the expired leases: "
959             << ex.what() << ".";
960         return (isc::config::createAnswer(1, err.str()));
961     }
962 
963     // Setup config backend polling, if configured for it.
964     auto ctl_info = CfgMgr::instance().getStagingCfg()->getConfigControlInfo();
965     if (ctl_info) {
966         long fetch_time = static_cast<long>(ctl_info->getConfigFetchWaitTime());
967         // Only schedule the CB fetch timer if the fetch wait time is greater
968         // than 0.
969         if (fetch_time > 0) {
970             // When we run unit tests, we want to use milliseconds unit for the
971             // specified interval. Otherwise, we use seconds. Note that using
972             // milliseconds as a unit in unit tests prevents us from waiting 1
973             // second on more before the timer goes off. Instead, we wait one
974             // millisecond which significantly reduces the test time.
975             if (!server_->inTestMode()) {
976                 fetch_time = 1000 * fetch_time;
977             }
978 
979             boost::shared_ptr<unsigned> failure_count(new unsigned(0));
980             TimerMgr::instance()->
981                 registerTimer("Dhcp4CBFetchTimer",
982                               std::bind(&ControlledDhcpv4Srv::cbFetchUpdates,
983                                         server_, CfgMgr::instance().getStagingCfg(),
984                                         failure_count),
985                               fetch_time,
986                               asiolink::IntervalTimer::ONE_SHOT);
987             TimerMgr::instance()->setup("Dhcp4CBFetchTimer");
988         }
989     }
990 
991     // Finally, we can commit runtime option definitions in libdhcp++. This is
992     // exception free.
993     LibDHCP::commitRuntimeOptionDefs();
994 
995     // This hook point notifies hooks libraries that the configuration of the
996     // DHCPv4 server has completed. It provides the hook library with the pointer
997     // to the common IO service object, new server configuration in the JSON
998     // format and with the pointer to the configuration storage where the
999     // parsed configuration is stored.
1000     if (HooksManager::calloutsPresent(Hooks.hooks_index_dhcp4_srv_configured_)) {
1001         CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
1002 
1003         callout_handle->setArgument("io_context", srv->getIOService());
1004         callout_handle->setArgument("network_state", srv->getNetworkState());
1005         callout_handle->setArgument("json_config", config);
1006         callout_handle->setArgument("server_config", CfgMgr::instance().getStagingCfg());
1007 
1008         HooksManager::callCallouts(Hooks.hooks_index_dhcp4_srv_configured_,
1009                                    *callout_handle);
1010 
1011         // Ignore status code as none of them would have an effect on further
1012         // operation.
1013     }
1014 
1015     // Apply multi threading settings.
1016     // @note These settings are applied/updated only if no errors occur while
1017     // applying the new configuration.
1018     // @todo This should be fixed.
1019     try {
1020         CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading());
1021     } catch (const std::exception& ex) {
1022         err << "Error applying multi threading settings: "
1023             << ex.what();
1024         return (isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()));
1025     }
1026 
1027     return (answer);
1028 }
1029 
1030 isc::data::ConstElementPtr
checkConfig(isc::data::ConstElementPtr config)1031 ControlledDhcpv4Srv::checkConfig(isc::data::ConstElementPtr config) {
1032 
1033     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
1034         .arg(redactConfig(config)->str());
1035 
1036     ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
1037 
1038     // Single stream instance used in all error clauses
1039     std::ostringstream err;
1040 
1041     if (!srv) {
1042         err << "Server object not initialized, can't process config.";
1043         return (isc::config::createAnswer(1, err.str()));
1044     }
1045 
1046     return (configureDhcp4Server(*srv, config, true));
1047 }
1048 
ControlledDhcpv4Srv(uint16_t server_port,uint16_t client_port)1049 ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_PORT*/,
1050                                          uint16_t client_port /*= 0*/)
1051     : Dhcpv4Srv(server_port, client_port), timer_mgr_(TimerMgr::instance()) {
1052     if (getInstance()) {
1053         isc_throw(InvalidOperation,
1054                   "There is another Dhcpv4Srv instance already.");
1055     }
1056     server_ = this; // remember this instance for later use in handlers
1057 
1058     // TimerMgr uses IO service to run asynchronous timers.
1059     TimerMgr::instance()->setIOService(getIOService());
1060 
1061     // CommandMgr uses IO service to run asynchronous socket operations.
1062     CommandMgr::instance().setIOService(getIOService());
1063 
1064     // LeaseMgr uses IO service to run asynchronous timers.
1065     LeaseMgr::setIOService(getIOService());
1066 
1067     // HostMgr uses IO service to run asynchronous timers.
1068     HostMgr::setIOService(getIOService());
1069 
1070     // These are the commands always supported by the DHCPv4 server.
1071     // Please keep the list in alphabetic order.
1072     CommandMgr::instance().registerCommand("build-report",
1073         std::bind(&ControlledDhcpv4Srv::commandBuildReportHandler, this, ph::_1, ph::_2));
1074 
1075     CommandMgr::instance().registerCommand("config-backend-pull",
1076         std::bind(&ControlledDhcpv4Srv::commandConfigBackendPullHandler, this, ph::_1, ph::_2));
1077 
1078     CommandMgr::instance().registerCommand("config-get",
1079         std::bind(&ControlledDhcpv4Srv::commandConfigGetHandler, this, ph::_1, ph::_2));
1080 
1081     CommandMgr::instance().registerCommand("config-reload",
1082         std::bind(&ControlledDhcpv4Srv::commandConfigReloadHandler, this, ph::_1, ph::_2));
1083 
1084     CommandMgr::instance().registerCommand("config-set",
1085         std::bind(&ControlledDhcpv4Srv::commandConfigSetHandler, this, ph::_1, ph::_2));
1086 
1087     CommandMgr::instance().registerCommand("config-test",
1088         std::bind(&ControlledDhcpv4Srv::commandConfigTestHandler, this, ph::_1, ph::_2));
1089 
1090     CommandMgr::instance().registerCommand("config-write",
1091         std::bind(&ControlledDhcpv4Srv::commandConfigWriteHandler, this, ph::_1, ph::_2));
1092 
1093     CommandMgr::instance().registerCommand("dhcp-enable",
1094         std::bind(&ControlledDhcpv4Srv::commandDhcpEnableHandler, this, ph::_1, ph::_2));
1095 
1096     CommandMgr::instance().registerCommand("dhcp-disable",
1097         std::bind(&ControlledDhcpv4Srv::commandDhcpDisableHandler, this, ph::_1, ph::_2));
1098 
1099     CommandMgr::instance().registerCommand("libreload",
1100         std::bind(&ControlledDhcpv4Srv::commandLibReloadHandler, this, ph::_1, ph::_2));
1101 
1102     CommandMgr::instance().registerCommand("leases-reclaim",
1103         std::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, ph::_1, ph::_2));
1104 
1105     CommandMgr::instance().registerCommand("server-tag-get",
1106         std::bind(&ControlledDhcpv4Srv::commandServerTagGetHandler, this, ph::_1, ph::_2));
1107 
1108     CommandMgr::instance().registerCommand("shutdown",
1109         std::bind(&ControlledDhcpv4Srv::commandShutdownHandler, this, ph::_1, ph::_2));
1110 
1111     CommandMgr::instance().registerCommand("status-get",
1112         std::bind(&ControlledDhcpv4Srv::commandStatusGetHandler, this, ph::_1, ph::_2));
1113 
1114     CommandMgr::instance().registerCommand("version-get",
1115         std::bind(&ControlledDhcpv4Srv::commandVersionGetHandler, this, ph::_1, ph::_2));
1116 
1117     // Register statistic related commands
1118     CommandMgr::instance().registerCommand("statistic-get",
1119         std::bind(&StatsMgr::statisticGetHandler, ph::_1, ph::_2));
1120 
1121     CommandMgr::instance().registerCommand("statistic-reset",
1122         std::bind(&StatsMgr::statisticResetHandler, ph::_1, ph::_2));
1123 
1124     CommandMgr::instance().registerCommand("statistic-remove",
1125         std::bind(&StatsMgr::statisticRemoveHandler, ph::_1, ph::_2));
1126 
1127     CommandMgr::instance().registerCommand("statistic-get-all",
1128         std::bind(&StatsMgr::statisticGetAllHandler, ph::_1, ph::_2));
1129 
1130     CommandMgr::instance().registerCommand("statistic-reset-all",
1131         std::bind(&StatsMgr::statisticResetAllHandler, ph::_1, ph::_2));
1132 
1133     CommandMgr::instance().registerCommand("statistic-remove-all",
1134         std::bind(&StatsMgr::statisticRemoveAllHandler, ph::_1, ph::_2));
1135 
1136     CommandMgr::instance().registerCommand("statistic-sample-age-set",
1137         std::bind(&StatsMgr::statisticSetMaxSampleAgeHandler, ph::_1, ph::_2));
1138 
1139     CommandMgr::instance().registerCommand("statistic-sample-age-set-all",
1140         std::bind(&ControlledDhcpv4Srv::commandStatisticSetMaxSampleAgeAllHandler, this, ph::_1, ph::_2));
1141 
1142     CommandMgr::instance().registerCommand("statistic-sample-count-set",
1143         std::bind(&StatsMgr::statisticSetMaxSampleCountHandler, ph::_1, ph::_2));
1144 
1145     CommandMgr::instance().registerCommand("statistic-sample-count-set-all",
1146         std::bind(&ControlledDhcpv4Srv::commandStatisticSetMaxSampleCountAllHandler, this, ph::_1, ph::_2));
1147 }
1148 
shutdownServer(int exit_value)1149 void ControlledDhcpv4Srv::shutdownServer(int exit_value) {
1150     setExitValue(exit_value);
1151     getIOService()->stop();   // Stop ASIO transmissions
1152     shutdown();               // Initiate DHCPv4 shutdown procedure.
1153 }
1154 
~ControlledDhcpv4Srv()1155 ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
1156     try {
1157         LeaseMgrFactory::destroy();
1158         HostMgr::create();
1159         cleanup();
1160 
1161         // The closure captures either a shared pointer (memory leak)
1162         // or a raw pointer (pointing to a deleted object).
1163         DatabaseConnection::db_lost_callback_ = 0;
1164         DatabaseConnection::db_recovered_callback_ = 0;
1165         DatabaseConnection::db_failed_callback_ = 0;
1166 
1167         timer_mgr_->unregisterTimers();
1168 
1169         // Close the command socket (if it exists).
1170         CommandMgr::instance().closeCommandSocket();
1171 
1172         // Deregister any registered commands (please keep in alphabetic order)
1173         CommandMgr::instance().deregisterCommand("build-report");
1174         CommandMgr::instance().deregisterCommand("config-backend-pull");
1175         CommandMgr::instance().deregisterCommand("config-get");
1176         CommandMgr::instance().deregisterCommand("config-reload");
1177         CommandMgr::instance().deregisterCommand("config-set");
1178         CommandMgr::instance().deregisterCommand("config-test");
1179         CommandMgr::instance().deregisterCommand("config-write");
1180         CommandMgr::instance().deregisterCommand("dhcp-disable");
1181         CommandMgr::instance().deregisterCommand("dhcp-enable");
1182         CommandMgr::instance().deregisterCommand("leases-reclaim");
1183         CommandMgr::instance().deregisterCommand("libreload");
1184         CommandMgr::instance().deregisterCommand("server-tag-get");
1185         CommandMgr::instance().deregisterCommand("shutdown");
1186         CommandMgr::instance().deregisterCommand("statistic-get");
1187         CommandMgr::instance().deregisterCommand("statistic-get-all");
1188         CommandMgr::instance().deregisterCommand("statistic-remove");
1189         CommandMgr::instance().deregisterCommand("statistic-remove-all");
1190         CommandMgr::instance().deregisterCommand("statistic-reset");
1191         CommandMgr::instance().deregisterCommand("statistic-reset-all");
1192         CommandMgr::instance().deregisterCommand("statistic-sample-age-set");
1193         CommandMgr::instance().deregisterCommand("statistic-sample-age-set-all");
1194         CommandMgr::instance().deregisterCommand("statistic-sample-count-set");
1195         CommandMgr::instance().deregisterCommand("statistic-sample-count-set-all");
1196         CommandMgr::instance().deregisterCommand("status-get");
1197         CommandMgr::instance().deregisterCommand("version-get");
1198 
1199         // LeaseMgr uses IO service to run asynchronous timers.
1200         LeaseMgr::setIOService(IOServicePtr());
1201 
1202         // HostMgr uses IO service to run asynchronous timers.
1203         HostMgr::setIOService(IOServicePtr());
1204     } catch (...) {
1205         // Don't want to throw exceptions from the destructor. The server
1206         // is shutting down anyway.
1207         ;
1208     }
1209 
1210     server_ = NULL; // forget this instance. There should be no callback anymore
1211                     // at this stage anyway.
1212 }
1213 
1214 void
reclaimExpiredLeases(const size_t max_leases,const uint16_t timeout,const bool remove_lease,const uint16_t max_unwarned_cycles)1215 ControlledDhcpv4Srv::reclaimExpiredLeases(const size_t max_leases,
1216                                           const uint16_t timeout,
1217                                           const bool remove_lease,
1218                                           const uint16_t max_unwarned_cycles) {
1219     try {
1220         server_->alloc_engine_->reclaimExpiredLeases4(max_leases, timeout,
1221                                                       remove_lease,
1222                                                       max_unwarned_cycles);
1223     } catch (const std::exception& ex) {
1224         LOG_ERROR(dhcp4_logger, DHCP4_RECLAIM_EXPIRED_LEASES_FAIL)
1225             .arg(ex.what());
1226     }
1227     // We're using the ONE_SHOT timer so there is a need to re-schedule it.
1228     TimerMgr::instance()->setup(CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME);
1229 }
1230 
1231 void
deleteExpiredReclaimedLeases(const uint32_t secs)1232 ControlledDhcpv4Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
1233     server_->alloc_engine_->deleteExpiredReclaimedLeases4(secs);
1234     // We're using the ONE_SHOT timer so there is a need to re-schedule it.
1235     TimerMgr::instance()->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
1236 }
1237 
1238 bool
dbLostCallback(ReconnectCtlPtr db_reconnect_ctl)1239 ControlledDhcpv4Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
1240     if (!db_reconnect_ctl) {
1241         // This should never happen
1242         LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
1243         return (false);
1244     }
1245 
1246     // Disable service until the connection is recovered.
1247     if (db_reconnect_ctl->retriesLeft() == db_reconnect_ctl->maxRetries() &&
1248         db_reconnect_ctl->alterServiceState()) {
1249         network_state_->disableService(NetworkState::Origin::DB_CONNECTION);
1250     }
1251 
1252     LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_LOST_CONNECTION);
1253 
1254     // If reconnect isn't enabled log it, initiate a shutdown if needed and
1255     // return false.
1256     if (!db_reconnect_ctl->retriesLeft() ||
1257         !db_reconnect_ctl->retryInterval()) {
1258         LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_DISABLED)
1259             .arg(db_reconnect_ctl->retriesLeft())
1260             .arg(db_reconnect_ctl->retryInterval());
1261         if (db_reconnect_ctl->exitOnFailure()) {
1262             shutdownServer(EXIT_FAILURE);
1263         }
1264         return (false);
1265     }
1266 
1267     return (true);
1268 }
1269 
1270 bool
dbRecoveredCallback(ReconnectCtlPtr db_reconnect_ctl)1271 ControlledDhcpv4Srv::dbRecoveredCallback(ReconnectCtlPtr db_reconnect_ctl) {
1272     if (!db_reconnect_ctl) {
1273         // This should never happen
1274         LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
1275         return (false);
1276     }
1277 
1278     // Enable service after the connection is recovered.
1279     if (db_reconnect_ctl->alterServiceState()) {
1280         network_state_->enableService(NetworkState::Origin::DB_CONNECTION);
1281     }
1282 
1283     LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_SUCCEEDED);
1284 
1285     db_reconnect_ctl->resetRetries();
1286 
1287     return (true);
1288 }
1289 
1290 bool
dbFailedCallback(ReconnectCtlPtr db_reconnect_ctl)1291 ControlledDhcpv4Srv::dbFailedCallback(ReconnectCtlPtr db_reconnect_ctl) {
1292     if (!db_reconnect_ctl) {
1293         // This should never happen
1294         LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
1295         return (false);
1296     }
1297 
1298     LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_FAILED)
1299             .arg(db_reconnect_ctl->maxRetries());
1300 
1301     if (db_reconnect_ctl->exitOnFailure()) {
1302         shutdownServer(EXIT_FAILURE);
1303     }
1304 
1305     return (true);
1306 }
1307 
1308 void
cbFetchUpdates(const SrvConfigPtr & srv_cfg,boost::shared_ptr<unsigned> failure_count)1309 ControlledDhcpv4Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg,
1310                                     boost::shared_ptr<unsigned> failure_count) {
1311     // stop thread pool (if running)
1312     MultiThreadingCriticalSection cs;
1313 
1314     try {
1315         // Fetch any configuration backend updates since our last fetch.
1316         server_->getCBControl()->databaseConfigFetch(srv_cfg,
1317                                                      CBControlDHCPv4::FetchMode::FETCH_UPDATE);
1318         (*failure_count) = 0;
1319 
1320     } catch (const std::exception& ex) {
1321         LOG_ERROR(dhcp4_logger, DHCP4_CB_PERIODIC_FETCH_UPDATES_FAIL)
1322             .arg(ex.what());
1323 
1324         // We allow at most 10 consecutive failures after which we stop
1325         // making further attempts to fetch the configuration updates.
1326         // Let's return without re-scheduling the timer.
1327         if (++(*failure_count) > 10) {
1328             LOG_ERROR(dhcp4_logger,
1329                       DHCP4_CB_PERIODIC_FETCH_UPDATES_RETRIES_EXHAUSTED);
1330             return;
1331         }
1332     }
1333 
1334     // Reschedule the timer to fetch new updates or re-try if
1335     // the previous attempt resulted in an error.
1336     if (TimerMgr::instance()->isTimerRegistered("Dhcp4CBFetchTimer")) {
1337         TimerMgr::instance()->setup("Dhcp4CBFetchTimer");
1338     }
1339 }
1340 
1341 }  // namespace dhcp
1342 }  // namespace isc
1343