Kea  1.5.0
ctrl_dhcp4_srv.cc
Go to the documentation of this file.
1 // Copyright (C) 2014-2018 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>
10 #include <config/command_mgr.h>
11 #include <dhcp4/ctrl_dhcp4_srv.h>
12 #include <dhcp4/dhcp4_log.h>
13 #include <dhcp4/dhcp4to6_ipc.h>
14 #include <dhcp4/parser_context.h>
16 #include <dhcpsrv/cfgmgr.h>
17 #include <dhcpsrv/cfg_db_access.h>
18 #include <hooks/hooks.h>
19 #include <hooks/hooks_manager.h>
20 #include <stats/stats_mgr.h>
21 #include <cfgrpt/config_report.h>
22 #include <signal.h>
23 #include <sstream>
24 
25 using namespace isc::data;
26 using namespace isc::db;
27 using namespace isc::dhcp;
28 using namespace isc::hooks;
29 using namespace isc::config;
30 using namespace isc::stats;
31 using namespace std;
32 
33 namespace {
34 
36 struct CtrlDhcp4Hooks {
37  int hooks_index_dhcp4_srv_configured_;
38 
40  CtrlDhcp4Hooks() {
41  hooks_index_dhcp4_srv_configured_ = HooksManager::registerHook("dhcp4_srv_configured");
42  }
43 
44 };
45 
46 // Declare a Hooks object. As this is outside any function or method, it
47 // will be instantiated (and the constructor run) when the module is loaded.
48 // As a result, the hook indexes will be defined before any method in this
49 // module is called.
50 CtrlDhcp4Hooks Hooks;
51 
61 void signalHandler(int signo) {
62  // SIGHUP signals a request to reconfigure the server.
63  if (signo == SIGHUP) {
64  ControlledDhcpv4Srv::processCommand("config-reload",
65  ConstElementPtr());
66  } else if ((signo == SIGTERM) || (signo == SIGINT)) {
67  ControlledDhcpv4Srv::processCommand("shutdown",
68  ConstElementPtr());
69  }
70 }
71 
72 }
73 
74 namespace isc {
75 namespace dhcp {
76 
77 ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
78 
79 void
80 ControlledDhcpv4Srv::init(const std::string& file_name) {
81  // Configure the server using JSON file.
82  ConstElementPtr result = loadConfigFile(file_name);
83  int rcode;
84  ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
85  if (rcode != 0) {
86  string reason = comment ? comment->stringValue() :
87  "no details available";
88  isc_throw(isc::BadValue, reason);
89  }
90 
91  // We don't need to call openActiveSockets() or startD2() as these
92  // methods are called in processConfig() which is called by
93  // processCommand("config-set", ...)
94 
95  // Set signal handlers. When the SIGHUP is received by the process
96  // the server reconfiguration will be triggered. When SIGTERM or
97  // SIGINT will be received, the server will start shutting down.
98  signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM));
99  // Set the pointer to the handler function.
100  signal_handler_ = signalHandler;
101 }
102 
103 void ControlledDhcpv4Srv::cleanup() {
104  // Nothing to do here. No need to disconnect from anything.
105 }
106 
118 ControlledDhcpv4Srv::loadConfigFile(const std::string& file_name) {
119  // This is a configuration backend implementation that reads the
120  // configuration from a JSON file.
121 
126 
127  // Basic sanity check: file name must not be empty.
128  try {
129  if (file_name.empty()) {
130  // Basic sanity check: file name must not be empty.
131  isc_throw(isc::BadValue, "JSON configuration file not specified."
132  " Please use -c command line option.");
133  }
134 
135  // Read contents of the file and parse it as JSON
136  Parser4Context parser;
137  json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
138  if (!json) {
139  isc_throw(isc::BadValue, "no configuration found");
140  }
141 
142  // Let's do sanity check before we call json->get() which
143  // works only for map.
144  if (json->getType() != isc::data::Element::map) {
145  isc_throw(isc::BadValue, "Configuration file is expected to be "
146  "a map, i.e., start with { and end with } and contain "
147  "at least an entry called 'Dhcp4' that itself is a map. "
148  << file_name
149  << " is a valid JSON, but its top element is not a map."
150  " Did you forget to add { } around your configuration?");
151  }
152 
153  // Use parsed JSON structures to configure the server
154  result = ControlledDhcpv4Srv::processCommand("config-set", json);
155  if (!result) {
156  // Undetermined status of the configuration. This should never
157  // happen, but as the configureDhcp4Server returns a pointer, it is
158  // theoretically possible that it will return NULL.
159  isc_throw(isc::BadValue, "undefined result of "
160  "processCommand(\"config-set\", json)");
161  }
162 
163  // Now check is the returned result is successful (rcode=0) or not
164  // (see @ref isc::config::parseAnswer).
165  int rcode;
166  ConstElementPtr comment = isc::config::parseAnswer(rcode, result);
167  if (rcode != 0) {
168  string reason = comment ? comment->stringValue() :
169  "no details available";
170  isc_throw(isc::BadValue, reason);
171  }
172  } catch (const std::exception& ex) {
173  // If configuration failed at any stage, we drop the staging
174  // configuration and continue to use the previous one.
175  CfgMgr::instance().rollback();
176 
177  LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
178  .arg(file_name).arg(ex.what());
179  isc_throw(isc::BadValue, "configuration error using file '"
180  << file_name << "': " << ex.what());
181  }
182 
183  return (result);
184 }
185 
186 
188 ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) {
189  if (ControlledDhcpv4Srv::getInstance()) {
190  ControlledDhcpv4Srv::getInstance()->shutdown();
191  } else {
192  LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
194  "Shutdown failure.");
195  return (answer);
196  }
197  ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
198  return (answer);
199 }
200 
202 ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
203 
206  HookLibsCollection loaded = HooksManager::getLibraryInfo();
207  bool status = HooksManager::loadLibraries(loaded);
208  if (!status) {
209  LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
211  "Failed to reload hooks libraries.");
212  return (answer);
213  }
215  "Hooks libraries successfully reloaded.");
216  return (answer);
217 }
218 
220 ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
221  ConstElementPtr /*args*/) {
222 
223  // Get configuration file name.
224  std::string file = ControlledDhcpv4Srv::getInstance()->getConfigFile();
225  try {
226  LOG_INFO(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION).arg(file);
227  return (loadConfigFile(file));
228  } catch (const std::exception& ex) {
229  // Log the unsuccessful reconfiguration. The reason for failure
230  // should be already logged. Don't rethrow an exception so as
231  // the server keeps working.
232  LOG_ERROR(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION_FAIL)
233  .arg(file);
235  "Config reload failed:" + string(ex.what())));
236  }
237 }
238 
240 ControlledDhcpv4Srv::commandConfigGetHandler(const string&,
241  ConstElementPtr /*args*/) {
242  ConstElementPtr config = CfgMgr::instance().getCurrentCfg()->toElement();
243 
244  return (createAnswer(0, config));
245 }
246 
248 ControlledDhcpv4Srv::commandConfigWriteHandler(const string&,
249  ConstElementPtr args) {
250  string filename;
251 
252  if (args) {
253  if (args->getType() != Element::map) {
254  return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
255  }
256  ConstElementPtr filename_param = args->get("filename");
257  if (filename_param) {
258  if (filename_param->getType() != Element::string) {
260  "passed parameter 'filename' is not a string"));
261  }
262  filename = filename_param->stringValue();
263  }
264  }
265 
266  if (filename.empty()) {
267  // filename parameter was not specified, so let's use whatever we remember
268  filename = getConfigFile();
269  }
270 
271  if (filename.empty()) {
272  return (createAnswer(CONTROL_RESULT_ERROR, "Unable to determine filename."
273  "Please specify filename explicitly."));
274  }
275 
276  // Ok, it's time to write the file.
277  size_t size = 0;
278  try {
279  ConstElementPtr cfg = CfgMgr::instance().getCurrentCfg()->toElement();
280  size = writeConfigFile(filename, cfg);
281  } catch (const isc::Exception& ex) {
282  return (createAnswer(CONTROL_RESULT_ERROR, string("Error during write-config:")
283  + ex.what()));
284  }
285  if (size == 0) {
286  return (createAnswer(CONTROL_RESULT_ERROR, "Error writing configuration to "
287  + filename));
288  }
289 
290  // Ok, it's time to return the successful response.
291  ElementPtr params = Element::createMap();
292  params->set("size", Element::create(static_cast<long long>(size)));
293  params->set("filename", Element::create(filename));
294 
295  return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
296  + filename + " successful", params));
297 }
298 
300 ControlledDhcpv4Srv::commandConfigSetHandler(const string&,
301  ConstElementPtr args) {
302  const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
303  ConstElementPtr dhcp4;
304  string message;
305 
306  // Command arguments are expected to be:
307  // { "Dhcp4": { ... }, "Logging": { ... } }
308  // The Logging component is technically optional. If it's not supplied
309  // logging will revert to default logging.
310  if (!args) {
311  message = "Missing mandatory 'arguments' parameter.";
312  } else {
313  dhcp4 = args->get("Dhcp4");
314  if (!dhcp4) {
315  message = "Missing mandatory 'Dhcp4' parameter.";
316  } else if (dhcp4->getType() != Element::map) {
317  message = "'Dhcp4' parameter expected to be a map.";
318  }
319  }
320 
321  if (!message.empty()) {
322  // Something is amiss with arguments, return a failure response.
323  ConstElementPtr result = isc::config::createAnswer(status_code,
324  message);
325  return (result);
326  }
327 
328  // We are starting the configuration process so we should remove any
329  // staging configuration that has been created during previous
330  // configuration attempts.
331  CfgMgr::instance().rollback();
332 
333  // Logging is a sibling element and must be parsed explicitly.
334  // The call to configureLogger parses the given Logging element if
335  // not null, into the staging config. Note this does not alter the
336  // current loggers, they remain in effect until we apply the
337  // logging config below. If no logging is supplied logging will
338  // revert to default logging.
339  Daemon::configureLogger(args->get("Logging"),
340  CfgMgr::instance().getStagingCfg());
341 
342  // Let's apply the new logging. We do it early, so we'll be able to print
343  // out what exactly is wrong with the new socnfig in case of problems.
344  CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
345 
346  // Now we configure the server proper.
347  ConstElementPtr result = processConfig(dhcp4);
348 
349  // If the configuration parsed successfully, apply the new logger
350  // configuration and the commit the new configuration. We apply
351  // the logging first in case there's a configuration failure.
352  int rcode = 0;
353  isc::config::parseAnswer(rcode, result);
354  if (rcode == 0) {
355  CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
356 
357  // Use new configuration.
358  CfgMgr::instance().commit();
359  } else {
360  // Ok, we applied the logging from the upcoming configuration, but
361  // there were problems with the config. As such, we need to back off
362  // and revert to the previous logging configuration.
363  CfgMgr::instance().getCurrentCfg()->applyLoggingCfg();
364  }
365 
366  return (result);
367 }
368 
370 ControlledDhcpv4Srv::commandConfigTestHandler(const string&,
371  ConstElementPtr args) {
372  const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
373  ConstElementPtr dhcp4;
374  string message;
375 
376  // Command arguments are expected to be:
377  // { "Dhcp4": { ... }, "Logging": { ... } }
378  // The Logging component is technically optional. If it's not supplied
379  // logging will revert to default logging.
380  if (!args) {
381  message = "Missing mandatory 'arguments' parameter.";
382  } else {
383  dhcp4 = args->get("Dhcp4");
384  if (!dhcp4) {
385  message = "Missing mandatory 'Dhcp4' parameter.";
386  } else if (dhcp4->getType() != Element::map) {
387  message = "'Dhcp4' parameter expected to be a map.";
388  }
389  }
390 
391  if (!message.empty()) {
392  // Something is amiss with arguments, return a failure response.
393  ConstElementPtr result = isc::config::createAnswer(status_code,
394  message);
395  return (result);
396  }
397 
398  // We are starting the configuration process so we should remove any
399  // staging configuration that has been created during previous
400  // configuration attempts.
401  CfgMgr::instance().rollback();
402 
403  // Now we check the server proper.
404  return (checkConfig(dhcp4));
405 }
406 
408 ControlledDhcpv4Srv::commandDhcpDisableHandler(const std::string&,
409  ConstElementPtr args) {
410  std::ostringstream message;
411  int64_t max_period = 0;
412 
413  // Parse arguments to see if the 'max-period' parameter has been specified.
414  if (args) {
415  // Arguments must be a map.
416  if (args->getType() != Element::map) {
417  message << "arguments for the 'dhcp-disable' command must be a map";
418 
419  } else {
420  ConstElementPtr max_period_element = args->get("max-period");
421  // max-period is optional.
422  if (max_period_element) {
423  // It must be an integer, if specified.
424  if (max_period_element->getType() != Element::integer) {
425  message << "'max-period' argument must be a number";
426 
427  } else {
428  // It must be positive integer.
429  max_period = max_period_element->intValue();
430  if (max_period <= 0) {
431  message << "'max-period' must be positive integer";
432  }
433 
434  // The user specified that the DHCP service should resume not
435  // later than in max-period seconds. If the 'dhcp-enable' command
436  // is not sent, the DHCP service will resume automatically.
437  network_state_->delayedEnableAll(static_cast<unsigned>(max_period));
438  }
439  }
440  }
441  }
442 
443  // No error occurred, so let's disable the service.
444  if (message.tellp() == 0) {
445  network_state_->disableService();
446 
447  message << "DHCPv4 service disabled";
448  if (max_period > 0) {
449  message << " for " << max_period << " seconds";
450  }
451  // Success.
452  return (config::createAnswer(CONTROL_RESULT_SUCCESS, message.str()));
453  }
454 
455  // Failure.
456  return (config::createAnswer(CONTROL_RESULT_ERROR, message.str()));
457 }
458 
460 ControlledDhcpv4Srv::commandDhcpEnableHandler(const std::string&, ConstElementPtr) {
461  network_state_->enableService();
462  return (config::createAnswer(CONTROL_RESULT_SUCCESS, "DHCP service successfully enabled"));
463 }
464 
466 ControlledDhcpv4Srv::commandVersionGetHandler(const string&, ConstElementPtr) {
467  ElementPtr extended = Element::create(Dhcpv4Srv::getVersion(true));
468  ElementPtr arguments = Element::createMap();
469  arguments->set("extended", extended);
471  Dhcpv4Srv::getVersion(false),
472  arguments);
473  return (answer);
474 }
475 
477 ControlledDhcpv4Srv::commandBuildReportHandler(const string&,
478  ConstElementPtr) {
479  ConstElementPtr answer =
481  return (answer);
482 }
483 
485 ControlledDhcpv4Srv::commandLeasesReclaimHandler(const string&,
486  ConstElementPtr args) {
487  int status_code = CONTROL_RESULT_ERROR;
488  string message;
489 
490  // args must be { "remove": <bool> }
491  if (!args) {
492  message = "Missing mandatory 'remove' parameter.";
493  } else {
494  ConstElementPtr remove_name = args->get("remove");
495  if (!remove_name) {
496  message = "Missing mandatory 'remove' parameter.";
497  } else if (remove_name->getType() != Element::boolean) {
498  message = "'remove' parameter expected to be a boolean.";
499  } else {
500  bool remove_lease = remove_name->boolValue();
501  server_->alloc_engine_->reclaimExpiredLeases4(0, 0, remove_lease);
502  status_code = 0;
503  message = "Reclamation of expired leases is complete.";
504  }
505  }
506  ConstElementPtr answer = isc::config::createAnswer(status_code, message);
507  return (answer);
508 }
509 
511 ControlledDhcpv4Srv::processCommand(const string& command,
512  ConstElementPtr args) {
513  string txt = args ? args->str() : "(none)";
514 
515  LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
516  .arg(command).arg(txt);
517 
518  ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
519 
520  if (!srv) {
522  "Server object not initialized, so can't process command '" +
523  command + "', arguments: '" + txt + "'.");
524  return (no_srv);
525  }
526 
527  try {
528  if (command == "shutdown") {
529  return (srv->commandShutdownHandler(command, args));
530 
531  } else if (command == "libreload") {
532  return (srv->commandLibReloadHandler(command, args));
533 
534  } else if (command == "config-reload") {
535  return (srv->commandConfigReloadHandler(command, args));
536 
537  } else if (command == "config-set") {
538  return (srv->commandConfigSetHandler(command, args));
539 
540  } else if (command == "config-get") {
541  return (srv->commandConfigGetHandler(command, args));
542 
543  } else if (command == "config-test") {
544  return (srv->commandConfigTestHandler(command, args));
545 
546  } else if (command == "dhcp-disable") {
547  return (srv->commandDhcpDisableHandler(command, args));
548 
549  } else if (command == "dhcp-enable") {
550  return (srv->commandDhcpEnableHandler(command, args));
551 
552  } else if (command == "version-get") {
553  return (srv->commandVersionGetHandler(command, args));
554 
555  } else if (command == "build-report") {
556  return (srv->commandBuildReportHandler(command, args));
557 
558  } else if (command == "leases-reclaim") {
559  return (srv->commandLeasesReclaimHandler(command, args));
560 
561  } else if (command == "config-write") {
562  return (srv->commandConfigWriteHandler(command, args));
563 
564  }
566  "Unrecognized command:" + command);
567  return (answer);
568  } catch (const Exception& ex) {
569  return (isc::config::createAnswer(1, "Error while processing command '"
570  + command + "':" + ex.what() +
571  ", params: '" + txt + "'"));
572  }
573 }
574 
576 ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
577 
578  LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
579  .arg(config->str());
580 
581  ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
582 
583  // Single stream instance used in all error clauses
584  std::ostringstream err;
585 
586  if (!srv) {
587  err << "Server object not initialized, can't process config.";
588  return (isc::config::createAnswer(1, err.str()));
589  }
590 
591  ConstElementPtr answer = configureDhcp4Server(*srv, config);
592 
593  // Check that configuration was successful. If not, do not reopen sockets
594  // and don't bother with DDNS stuff.
595  try {
596  int rcode = 0;
597  isc::config::parseAnswer(rcode, answer);
598  if (rcode != 0) {
599  return (answer);
600  }
601  } catch (const std::exception& ex) {
602  err << "Failed to process configuration:" << ex.what();
603  return (isc::config::createAnswer(1, err.str()));
604  }
605 
606  // Re-open lease and host database with new parameters.
607  try {
608  DatabaseConnection::db_lost_callback =
609  boost::bind(&ControlledDhcpv4Srv::dbLostCallback, srv, _1);
610  CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess();
611  cfg_db->setAppendedParameters("universe=4");
612  cfg_db->createManagers();
613  } catch (const std::exception& ex) {
614  err << "Unable to open database: " << ex.what();
615  return (isc::config::createAnswer(1, err.str()));
616  }
617 
618  // Server will start DDNS communications if its enabled.
619  try {
620  srv->startD2();
621  } catch (const std::exception& ex) {
622  err << "Error starting DHCP_DDNS client after server reconfiguration: "
623  << ex.what();
624  return (isc::config::createAnswer(1, err.str()));
625  }
626 
627  // Setup DHCPv4-over-DHCPv6 IPC
628  try {
629  Dhcp4to6Ipc::instance().open();
630  } catch (const std::exception& ex) {
631  std::ostringstream err;
632  err << "error starting DHCPv4-over-DHCPv6 IPC "
633  " after server reconfiguration: " << ex.what();
634  return (isc::config::createAnswer(1, err.str()));
635  }
636 
637  // Configure DHCP packet queueing
638  try {
640  qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl();
641  if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, qc)) {
642  LOG_INFO(dhcp4_logger, DHCP4_CONFIG_PACKET_QUEUE)
643  .arg(IfaceMgr::instance().getPacketQueue4()->getInfoStr());
644  }
645 
646  } catch (const std::exception& ex) {
647  err << "Error setting packet queue controls after server reconfiguration: "
648  << ex.what();
649  return (isc::config::createAnswer(1, err.str()));
650  }
651 
652  // Configuration may change active interfaces. Therefore, we have to reopen
653  // sockets according to new configuration. It is possible that this
654  // operation will fail for some interfaces but the openSockets function
655  // guards against exceptions and invokes a callback function to
656  // log warnings. Since we allow that this fails for some interfaces there
657  // is no need to rollback configuration if socket fails to open on any
658  // of the interfaces.
659  CfgMgr::instance().getStagingCfg()->getCfgIface()->
660  openSockets(AF_INET, srv->getPort(), getInstance()->useBroadcast());
661 
662  // Install the timers for handling leases reclamation.
663  try {
664  CfgMgr::instance().getStagingCfg()->getCfgExpiration()->
665  setupTimers(&ControlledDhcpv4Srv::reclaimExpiredLeases,
666  &ControlledDhcpv4Srv::deleteExpiredReclaimedLeases,
667  server_);
668 
669  } catch (const std::exception& ex) {
670  err << "unable to setup timers for periodically running the"
671  " reclamation of the expired leases: "
672  << ex.what() << ".";
673  return (isc::config::createAnswer(1, err.str()));
674  }
675 
676  // This hook point notifies hooks libraries that the configuration of the
677  // DHCPv4 server has completed. It provides the hook library with the pointer
678  // to the common IO service object, new server configuration in the JSON
679  // format and with the pointer to the configuration storage where the
680  // parsed configuration is stored.
681  if (HooksManager::calloutsPresent(Hooks.hooks_index_dhcp4_srv_configured_)) {
682  CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
683 
684  callout_handle->setArgument("io_context", srv->getIOService());
685  callout_handle->setArgument("network_state", srv->getNetworkState());
686  callout_handle->setArgument("json_config", config);
687  callout_handle->setArgument("server_config", CfgMgr::instance().getStagingCfg());
688 
689  HooksManager::callCallouts(Hooks.hooks_index_dhcp4_srv_configured_,
690  *callout_handle);
691 
692  // Ignore status code as none of them would have an effect on further
693  // operation.
694  }
695 
696  return (answer);
697 }
698 
700 ControlledDhcpv4Srv::checkConfig(isc::data::ConstElementPtr config) {
701 
702  LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
703  .arg(config->str());
704 
705  ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
706 
707  // Single stream instance used in all error clauses
708  std::ostringstream err;
709 
710  if (!srv) {
711  err << "Server object not initialized, can't process config.";
712  return (isc::config::createAnswer(1, err.str()));
713  }
714 
715  return (configureDhcp4Server(*srv, config, true));
716 }
717 
718 ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
719  : Dhcpv4Srv(port), io_service_(), timer_mgr_(TimerMgr::instance()) {
720  if (getInstance()) {
722  "There is another Dhcpv4Srv instance already.");
723  }
724  server_ = this; // remember this instance for later use in handlers
725 
726  // TimerMgr uses IO service to run asynchronous timers.
727  TimerMgr::instance()->setIOService(getIOService());
728 
729  // CommandMgr uses IO service to run asynchronous socket operations.
730  CommandMgr::instance().setIOService(getIOService());
731 
732  // These are the commands always supported by the DHCPv4 server.
733  // Please keep the list in alphabetic order.
734  CommandMgr::instance().registerCommand("build-report",
735  boost::bind(&ControlledDhcpv4Srv::commandBuildReportHandler, this, _1, _2));
736 
737  CommandMgr::instance().registerCommand("config-get",
738  boost::bind(&ControlledDhcpv4Srv::commandConfigGetHandler, this, _1, _2));
739 
740  CommandMgr::instance().registerCommand("config-reload",
741  boost::bind(&ControlledDhcpv4Srv::commandConfigReloadHandler, this, _1, _2));
742 
743  CommandMgr::instance().registerCommand("config-set",
744  boost::bind(&ControlledDhcpv4Srv::commandConfigSetHandler, this, _1, _2));
745 
746  CommandMgr::instance().registerCommand("config-test",
747  boost::bind(&ControlledDhcpv4Srv::commandConfigTestHandler, this, _1, _2));
748 
749  CommandMgr::instance().registerCommand("config-write",
750  boost::bind(&ControlledDhcpv4Srv::commandConfigWriteHandler, this, _1, _2));
751 
752  CommandMgr::instance().registerCommand("dhcp-enable",
753  boost::bind(&ControlledDhcpv4Srv::commandDhcpEnableHandler, this, _1, _2));
754 
755  CommandMgr::instance().registerCommand("dhcp-disable",
756  boost::bind(&ControlledDhcpv4Srv::commandDhcpDisableHandler, this, _1, _2));
757 
758  CommandMgr::instance().registerCommand("libreload",
759  boost::bind(&ControlledDhcpv4Srv::commandLibReloadHandler, this, _1, _2));
760 
761  CommandMgr::instance().registerCommand("leases-reclaim",
762  boost::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, _1, _2));
763 
764  CommandMgr::instance().registerCommand("shutdown",
765  boost::bind(&ControlledDhcpv4Srv::commandShutdownHandler, this, _1, _2));
766 
767  CommandMgr::instance().registerCommand("version-get",
768  boost::bind(&ControlledDhcpv4Srv::commandVersionGetHandler, this, _1, _2));
769 
770  // Register statistic related commands
771  CommandMgr::instance().registerCommand("statistic-get",
772  boost::bind(&StatsMgr::statisticGetHandler, _1, _2));
773 
774  CommandMgr::instance().registerCommand("statistic-reset",
775  boost::bind(&StatsMgr::statisticResetHandler, _1, _2));
776 
777  CommandMgr::instance().registerCommand("statistic-remove",
778  boost::bind(&StatsMgr::statisticRemoveHandler, _1, _2));
779 
780  CommandMgr::instance().registerCommand("statistic-get-all",
781  boost::bind(&StatsMgr::statisticGetAllHandler, _1, _2));
782 
783  CommandMgr::instance().registerCommand("statistic-reset-all",
784  boost::bind(&StatsMgr::statisticResetAllHandler, _1, _2));
785 
786  CommandMgr::instance().registerCommand("statistic-remove-all",
787  boost::bind(&StatsMgr::statisticRemoveAllHandler, _1, _2));
788 
789 }
790 
792  io_service_.stop(); // Stop ASIO transmissions
793  Dhcpv4Srv::shutdown(); // Initiate DHCPv4 shutdown procedure.
794 }
795 
797  try {
798  cleanup();
799 
800  // The closure captures either a shared pointer (memory leak)
801  // or a raw pointer (pointing to a deleted object).
802  DatabaseConnection::db_lost_callback = 0;
803 
804  timer_mgr_->unregisterTimers();
805 
806  // Close the command socket (if it exists).
807  CommandMgr::instance().closeCommandSocket();
808 
809  // Deregister any registered commands (please keep in alphabetic order)
810  CommandMgr::instance().deregisterCommand("build-report");
811  CommandMgr::instance().deregisterCommand("config-get");
812  CommandMgr::instance().deregisterCommand("config-reload");
813  CommandMgr::instance().deregisterCommand("config-test");
814  CommandMgr::instance().deregisterCommand("config-write");
815  CommandMgr::instance().deregisterCommand("leases-reclaim");
816  CommandMgr::instance().deregisterCommand("libreload");
817  CommandMgr::instance().deregisterCommand("config-set");
818  CommandMgr::instance().deregisterCommand("dhcp-disable");
819  CommandMgr::instance().deregisterCommand("dhcp-enable");
820  CommandMgr::instance().deregisterCommand("shutdown");
821  CommandMgr::instance().deregisterCommand("statistic-get");
822  CommandMgr::instance().deregisterCommand("statistic-get-all");
823  CommandMgr::instance().deregisterCommand("statistic-remove");
824  CommandMgr::instance().deregisterCommand("statistic-remove-all");
825  CommandMgr::instance().deregisterCommand("statistic-reset");
826  CommandMgr::instance().deregisterCommand("statistic-reset-all");
827  CommandMgr::instance().deregisterCommand("version-get");
828 
829  } catch (...) {
830  // Don't want to throw exceptions from the destructor. The server
831  // is shutting down anyway.
832  ;
833  }
834 
835  server_ = NULL; // forget this instance. Noone should call any handlers at
836  // this stage.
837 }
838 
839 void ControlledDhcpv4Srv::sessionReader(void) {
840  // Process one asio event. If there are more events, iface_mgr will call
841  // this callback more than once.
842  if (getInstance()) {
843  getInstance()->io_service_.run_one();
844  }
845 }
846 
847 void
848 ControlledDhcpv4Srv::reclaimExpiredLeases(const size_t max_leases,
849  const uint16_t timeout,
850  const bool remove_lease,
851  const uint16_t max_unwarned_cycles) {
852  server_->alloc_engine_->reclaimExpiredLeases4(max_leases, timeout,
853  remove_lease,
854  max_unwarned_cycles);
855  // We're using the ONE_SHOT timer so there is a need to re-schedule it.
857 }
858 
859 void
860 ControlledDhcpv4Srv::deleteExpiredReclaimedLeases(const uint32_t secs) {
861  server_->alloc_engine_->deleteExpiredReclaimedLeases4(secs);
862  // We're using the ONE_SHOT timer so there is a need to re-schedule it.
864 }
865 
866 void
867 ControlledDhcpv4Srv::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
868  bool reopened = false;
869 
870  // Re-open lease and host database with new parameters.
871  try {
872  CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
873  cfg_db->createManagers();
874  reopened = true;
875  } catch (const std::exception& ex) {
876  LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_ATTEMPT_FAILED).arg(ex.what());
877  }
878 
879  if (reopened) {
880  // Cancel the timer.
881  if (TimerMgr::instance()->isTimerRegistered("Dhcp4DbReconnectTimer")) {
882  TimerMgr::instance()->cancel("Dhcp4DbReconnectTimer");
883  }
884 
885  // Set network state to service enabled
886  network_state_->enableService();
887 
888  // Toss the reconnect control, we're done with it
889  db_reconnect_ctl.reset();
890  } else {
891  if (!db_reconnect_ctl->checkRetries()) {
892  LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_RETRIES_EXHAUSTED)
893  .arg(db_reconnect_ctl->maxRetries());
894  shutdown();
895  return;
896  }
897 
898  LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_ATTEMPT_SCHEDULE)
899  .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
900  .arg(db_reconnect_ctl->maxRetries())
901  .arg(db_reconnect_ctl->retryInterval());
902 
903  if (!TimerMgr::instance()->isTimerRegistered("Dhcp4DbReconnectTimer")) {
904  TimerMgr::instance()->registerTimer("Dhcp4DbReconnectTimer",
905  boost::bind(&ControlledDhcpv4Srv::dbReconnect, this,
906  db_reconnect_ctl),
907  db_reconnect_ctl->retryInterval(),
909  }
910 
911  TimerMgr::instance()->setup("Dhcp4DbReconnectTimer");
912  }
913 }
914 
915 bool
916 ControlledDhcpv4Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
917  // Disable service until we recover
918  network_state_->disableService();
919 
920  if (!db_reconnect_ctl) {
921  // This shouldn't never happen
922  LOG_ERROR(dhcp4_logger, DHCP4_DB_RECONNECT_NO_DB_CTL);
923  return (false);
924  }
925 
926  // If reconnect isn't enabled, log it and return false
927  if (!db_reconnect_ctl->retriesLeft() ||
928  !db_reconnect_ctl->retryInterval()) {
929  LOG_INFO(dhcp4_logger, DHCP4_DB_RECONNECT_DISABLED)
930  .arg(db_reconnect_ctl->retriesLeft())
931  .arg(db_reconnect_ctl->retryInterval());
932  return(false);
933  }
934 
935  // Invoke reconnect method
936  dbReconnect(db_reconnect_ctl);
937 
938  return(true);
939 }
940 
941 }; // end of isc::dhcp namespace
942 }; // end of isc namespace
DHCPv4 server service.
Definition: dhcp4_srv.h:194
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition: macros.h:26
uint16_t getPort() const
Get UDP port on which server should listen.
Definition: dhcp4_srv.h:288
isc::data::ElementPtr parseFile(const std::string &filename, ParserType parser_type)
Run the parser on the file specified.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
ConstElementPtr createAnswer(const int status_code, const std::string &text, const ConstElementPtr &arg)
const int CONTROL_RESULT_SUCCESS
Status code indicating a successful operation.
Evaluation context, an interface to the expression evaluation.
void shutdown()
Instructs the server to shut down.
Definition: dhcp4_srv.cc:511
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition: cfgmgr.cc:25
Manages a pool of asynchronous interval timers.
Definition: timer_mgr.h:54
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
boost::shared_ptr< Element > ElementPtr
Definition: data.h:20
SrvConfigPtr getCurrentCfg()
Returns a pointer to the current configuration.
Definition: cfgmgr.cc:154
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition: data.cc:268
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
isc::data::ConstElementPtr configureDhcp4Server(Dhcpv4Srv &server, isc::data::ConstElementPtr config_set, bool check_only)
Configure DHCPv4 server (Dhcpv4Srv) with a set of configuration values.
std::vector< HookLibInfo > HookLibsCollection
A storage for information about hook libraries.
Definition: libinfo.h:31
isc::log::Logger dhcp4_logger(DHCP4_APP_LOGGER_NAME)
Base logger for DHCPv4 server.
Definition: dhcp4_log.h:90
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
static const std::string FLUSH_RECLAIMED_TIMER_NAME
Name of the timer for flushing reclaimed leases.
asiolink::IOServicePtr & getIOService()
Returns pointer to the IO service used by the server.
Definition: dhcp4_srv.h:234
boost::shared_ptr< CfgDbAccess > CfgDbAccessPtr
A pointer to the CfgDbAccess.
void shutdown()
Initiates shutdown procedure for the whole DHCPv4 server.
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
void cleanup()
Performs cleanup, immediately before termination.
Represents a collection of signals handled in a customized way.
Definition: signal_set.h:87
boost::shared_ptr< AllocEngine > alloc_engine_
Allocation Engine.
Definition: dhcp4_srv.h:911
This is a base class for exceptions thrown from the DNS library module.
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
Defines the logger used by the top-level component of kea-dhcp-ddns.
NetworkStatePtr network_state_
Holds information about disabled DHCP service and/or disabled subnet/network scopes.
Definition: dhcp4_srv.h:937
isc::log::Logger logger("asiodns")
Use the ASIO logger.
void startD2()
Starts DHCP_DDNS client IO if DDNS updates are enabled.
Definition: dhcp4_srv.cc:3465
boost::shared_ptr< CalloutHandle > CalloutHandlePtr
A shared pointer to a CalloutHandle object.
Dhcp4Hooks Hooks
Definition: dhcp4_srv.cc:117
static const std::string RECLAIM_EXPIRED_TIMER_NAME
Name of the timer for reclaiming expired leases.
This file contains several functions and constants that are used for handling commands and responses ...
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition: data.cc:223
A generic exception that is thrown if a function is called in a prohibited way.
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
static ControlledDhcpv4Srv * getInstance()
Returns pointer to the sole instance of Dhcpv4Srv.
Controlled version of the DHCPv4 server.
std::string getConfigReport()
Definition: cfgrpt.cc:20
Defines the Dhcp4o6Ipc class.
static const TimerMgrPtr & instance()
Returns pointer to the sole instance of the TimerMgr.
Definition: timer_mgr.cc:319
const int DBG_DHCP4_COMMAND
Debug level used to log receiving commands.
Definition: dhcp4_log.h:30
boost::shared_ptr< ReconnectCtl > ReconnectCtlPtr
Pointer to an instance of ReconnectCtl.
NetworkStatePtr & getNetworkState()
Returns pointer to the network state used by the server.
Definition: dhcp4_srv.h:239
Contains declarations for loggers used by the DHCPv4 server component.