1 // Copyright (C) 2012-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 <database/dbaccess_parser.h>
11 #include <database/backend_selector.h>
12 #include <database/server_selector.h>
13 #include <dhcp4/dhcp4_log.h>
14 #include <dhcp4/dhcp4_srv.h>
15 #include <dhcp4/json_config_parser.h>
16 #include <dhcp/libdhcp++.h>
17 #include <dhcp/option_definition.h>
18 #include <dhcpsrv/cb_ctl_dhcp4.h>
19 #include <dhcpsrv/cfg_option.h>
20 #include <dhcpsrv/cfgmgr.h>
21 #include <dhcpsrv/config_backend_dhcp4_mgr.h>
22 #include <dhcpsrv/db_type.h>
23 #include <dhcpsrv/parsers/client_class_def_parser.h>
24 #include <dhcpsrv/parsers/dhcp_parsers.h>
25 #include <dhcpsrv/parsers/expiration_config_parser.h>
26 #include <dhcpsrv/parsers/host_reservation_parser.h>
27 #include <dhcpsrv/parsers/host_reservations_list_parser.h>
28 #include <dhcpsrv/parsers/ifaces_config_parser.h>
29 #include <dhcpsrv/parsers/multi_threading_config_parser.h>
30 #include <dhcpsrv/parsers/option_data_parser.h>
31 #include <dhcpsrv/parsers/dhcp_queue_control_parser.h>
32 #include <dhcpsrv/parsers/simple_parser4.h>
33 #include <dhcpsrv/parsers/shared_networks_list_parser.h>
34 #include <dhcpsrv/parsers/sanity_checks_parser.h>
35 #include <dhcpsrv/host_data_source_factory.h>
36 #include <dhcpsrv/timer_mgr.h>
37 #include <process/config_ctl_parser.h>
38 #include <hooks/hooks_manager.h>
39 #include <hooks/hooks_parser.h>
40 #include <config/command_mgr.h>
41 #include <util/encode/hex.h>
42 #include <util/strutil.h>
43
44 #include <boost/foreach.hpp>
45 #include <boost/lexical_cast.hpp>
46 #include <boost/algorithm/string.hpp>
47
48 #include <limits>
49 #include <iostream>
50 #include <iomanip>
51 #include <netinet/in.h>
52 #include <vector>
53 #include <map>
54
55 using namespace std;
56 using namespace isc;
57 using namespace isc::dhcp;
58 using namespace isc::data;
59 using namespace isc::asiolink;
60 using namespace isc::hooks;
61 using namespace isc::process;
62 using namespace isc::config;
63
64 namespace {
65
66 /// @brief Parser that takes care of global DHCPv4 parameters and utility
67 /// functions that work on global level.
68 ///
69 /// This class is a collection of utility method that either handle
70 /// global parameters (see @ref parse), or conducts operations on
71 /// global level (see @ref sanityChecks and @ref copySubnets4).
72 ///
73 /// See @ref parse method for a list of supported parameters.
74 class Dhcp4ConfigParser : public isc::data::SimpleParser {
75 public:
76
77 /// @brief Sets global parameters in staging configuration
78 ///
79 /// @param global global configuration scope
80 /// @param cfg Server configuration (parsed parameters will be stored here)
81 ///
82 /// Currently this method sets the following global parameters:
83 ///
84 /// - echo-client-id
85 /// - decline-probation-period
86 /// - dhcp4o6-port
87 /// - user-context
88 ///
89 /// @throw DhcpConfigError if parameters are missing or
90 /// or having incorrect values.
parse(const SrvConfigPtr & cfg,const ConstElementPtr & global)91 void parse(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
92
93 // Set whether v4 server is supposed to echo back client-id
94 // (yes = RFC6842 compatible, no = backward compatibility)
95 bool echo_client_id = getBoolean(global, "echo-client-id");
96 cfg->setEchoClientId(echo_client_id);
97
98 // Set the probation period for decline handling.
99 uint32_t probation_period =
100 getUint32(global, "decline-probation-period");
101 cfg->setDeclinePeriod(probation_period);
102
103 // Set the DHCPv4-over-DHCPv6 interserver port.
104 uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
105 cfg->setDhcp4o6Port(dhcp4o6_port);
106
107 // Set the global user context.
108 ConstElementPtr user_context = global->get("user-context");
109 if (user_context) {
110 cfg->setContext(user_context);
111 }
112
113 // Set the server's logical name
114 std::string server_tag = getString(global, "server-tag");
115 cfg->setServerTag(server_tag);
116 }
117
118 /// @brief Sets global parameters before other parameters are parsed.
119 ///
120 /// This method sets selected global parameters before other parameters
121 /// are parsed. This is important when the behavior of the parsers
122 /// run later depends on these global parameters.
123 ///
124 /// Currently this method sets the following global parameters:
125 /// - ip-reservations-unique
126 ///
127 /// @param global global configuration scope
128 /// @param cfg Server configuration (parsed parameters will be stored here)
parseEarly(const SrvConfigPtr & cfg,const ConstElementPtr & global)129 void parseEarly(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
130 // Set ip-reservations-unique flag.
131 bool ip_reservations_unique = getBoolean(global, "ip-reservations-unique");
132 cfg->setIPReservationsUnique(ip_reservations_unique);
133 }
134
135 /// @brief Copies subnets from shared networks to regular subnets container
136 ///
137 /// @param from pointer to shared networks container (copy from here)
138 /// @param dest pointer to cfg subnets4 (copy to here)
139 /// @throw BadValue if any pointer is missing
140 /// @throw DhcpConfigError if there are duplicates (or other subnet defects)
141 void
copySubnets4(const CfgSubnets4Ptr & dest,const CfgSharedNetworks4Ptr & from)142 copySubnets4(const CfgSubnets4Ptr& dest, const CfgSharedNetworks4Ptr& from) {
143
144 if (!dest || !from) {
145 isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null");
146 }
147
148 const SharedNetwork4Collection* networks = from->getAll();
149 if (!networks) {
150 // Nothing to copy. Technically, it should return a pointer to empty
151 // container, but let's handle null pointer as well.
152 return;
153 }
154
155 // Let's go through all the networks one by one
156 for (auto net = networks->begin(); net != networks->end(); ++net) {
157
158 // For each network go through all the subnets in it.
159 const Subnet4Collection* subnets = (*net)->getAllSubnets();
160 if (!subnets) {
161 // Shared network without subnets it weird, but we decided to
162 // accept such configurations.
163 continue;
164 }
165
166 // For each subnet, add it to a list of regular subnets.
167 for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
168 dest->add(*subnet);
169 }
170 }
171 }
172
173 /// @brief Conducts global sanity checks
174 ///
175 /// This method is very simple now, but more sanity checks are expected
176 /// in the future.
177 ///
178 /// @param cfg - the parsed structure
179 /// @param global global Dhcp4 scope
180 /// @throw DhcpConfigError in case of issues found
181 void
sanityChecks(const SrvConfigPtr & cfg,const ConstElementPtr & global)182 sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
183
184 /// Global lifetime sanity checks
185 cfg->sanityChecksLifetime("valid-lifetime");
186
187 /// Shared network sanity checks
188 const SharedNetwork4Collection* networks = cfg->getCfgSharedNetworks4()->getAll();
189 if (networks) {
190 sharedNetworksSanityChecks(*networks, global->get("shared-networks"));
191 }
192 }
193
194 /// @brief Sanity checks for shared networks
195 ///
196 /// This method verifies if there are no issues with shared networks.
197 /// @param networks pointer to shared networks being checked
198 /// @param json shared-networks element
199 /// @throw DhcpConfigError if issues are encountered
200 void
sharedNetworksSanityChecks(const SharedNetwork4Collection & networks,ConstElementPtr json)201 sharedNetworksSanityChecks(const SharedNetwork4Collection& networks,
202 ConstElementPtr json) {
203
204 /// @todo: in case of errors, use json to extract line numbers.
205 if (!json) {
206 // No json? That means that the shared-networks was never specified
207 // in the config.
208 return;
209 }
210
211 // Used for names uniqueness checks.
212 std::set<string> names;
213
214 // Let's go through all the networks one by one
215 for (auto net = networks.begin(); net != networks.end(); ++net) {
216 string txt;
217
218 // Let's check if all subnets have either the same interface
219 // or don't have the interface specified at all.
220 bool authoritative = (*net)->getAuthoritative();
221 string iface = (*net)->getIface();
222
223 const Subnet4Collection* subnets = (*net)->getAllSubnets();
224 if (subnets) {
225 // For each subnet, add it to a list of regular subnets.
226 for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
227 if ((*subnet)->getAuthoritative() != authoritative) {
228 isc_throw(DhcpConfigError, "Subnet " << boolalpha
229 << (*subnet)->toText()
230 << " has different authoritative setting "
231 << (*subnet)->getAuthoritative()
232 << " than the shared-network itself: "
233 << authoritative);
234 }
235
236 if (iface.empty()) {
237 iface = (*subnet)->getIface();
238 continue;
239 }
240
241 if ((*subnet)->getIface().empty()) {
242 continue;
243 }
244
245 if ((*subnet)->getIface() != iface) {
246 isc_throw(DhcpConfigError, "Subnet " << (*subnet)->toText()
247 << " has specified interface " << (*subnet)->getIface()
248 << ", but earlier subnet in the same shared-network"
249 << " or the shared-network itself used " << iface);
250 }
251
252 // Let's collect the subnets in case we later find out the
253 // subnet doesn't have a mandatory name.
254 txt += (*subnet)->toText() + " ";
255 }
256 }
257
258 // Next, let's check name of the shared network.
259 if ((*net)->getName().empty()) {
260 isc_throw(DhcpConfigError, "Shared-network with subnets "
261 << txt << " is missing mandatory 'name' parameter");
262 }
263
264 // Is it unique?
265 if (names.find((*net)->getName()) != names.end()) {
266 isc_throw(DhcpConfigError, "A shared-network with "
267 "name " << (*net)->getName() << " defined twice.");
268 }
269 names.insert((*net)->getName());
270
271 }
272 }
273 };
274
275 } // anonymous namespace
276
277 namespace isc {
278 namespace dhcp {
279
280 /// @brief Initialize the command channel based on the staging configuration
281 ///
282 /// Only close the current channel, if the new channel configuration is
283 /// different. This avoids disconnecting a client and hence not sending them
284 /// a command result, unless they specifically alter the channel configuration.
285 /// In that case the user simply has to accept they'll be disconnected.
286 ///
configureCommandChannel()287 void configureCommandChannel() {
288 // Get new socket configuration.
289 ConstElementPtr sock_cfg =
290 CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
291
292 // Get current socket configuration.
293 ConstElementPtr current_sock_cfg =
294 CfgMgr::instance().getCurrentCfg()->getControlSocketInfo();
295
296 // Determine if the socket configuration has changed. It has if
297 // both old and new configuration is specified but respective
298 // data elements aren't equal.
299 bool sock_changed = (sock_cfg && current_sock_cfg &&
300 !sock_cfg->equals(*current_sock_cfg));
301
302 // If the previous or new socket configuration doesn't exist or
303 // the new configuration differs from the old configuration we
304 // close the existing socket and open a new socket as appropriate.
305 // Note that closing an existing socket means the client will not
306 // receive the configuration result.
307 if (!sock_cfg || !current_sock_cfg || sock_changed) {
308 // Close the existing socket (if any).
309 isc::config::CommandMgr::instance().closeCommandSocket();
310
311 if (sock_cfg) {
312 // This will create a control socket and install the external
313 // socket in IfaceMgr. That socket will be monitored when
314 // Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and
315 // callback in CommandMgr will be called, if necessary.
316 isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
317 }
318 }
319 }
320
321 isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv & server,isc::data::ConstElementPtr config_set,bool check_only)322 configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
323 bool check_only) {
324 if (!config_set) {
325 ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_ERROR,
326 string("Can't parse NULL config"));
327 return (answer);
328 }
329
330 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START)
331 .arg(server.redactConfig(config_set)->str());
332
333 // Before starting any subnet operations, let's reset the subnet-id counter,
334 // so newly recreated configuration starts with first subnet-id equal 1.
335 Subnet::resetSubnetID();
336
337 // Close DHCP sockets and remove any existing timers.
338 if (!check_only) {
339 IfaceMgr::instance().closeSockets();
340 TimerMgr::instance()->unregisterTimers();
341 server.discardPackets();
342 server.getCBControl()->reset();
343 }
344
345 // Revert any runtime option definitions configured so far and not committed.
346 LibDHCP::revertRuntimeOptionDefs();
347 // Let's set empty container in case a user hasn't specified any configuration
348 // for option definitions. This is equivalent to committing empty container.
349 LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer());
350
351 // Print the list of known backends.
352 HostDataSourceFactory::printRegistered();
353
354 // Answer will hold the result.
355 ConstElementPtr answer;
356 // Rollback informs whether error occurred and original data
357 // have to be restored to global storages.
358 bool rollback = false;
359 // Global parameter name in case of an error.
360 string parameter_name;
361 ElementPtr mutable_cfg;
362 SrvConfigPtr srv_cfg;
363 try {
364 // Get the staging configuration
365 srv_cfg = CfgMgr::instance().getStagingCfg();
366
367 // This is a way to convert ConstElementPtr to ElementPtr.
368 // We need a config that can be edited, because we will insert
369 // default values and will insert derived values as well.
370 mutable_cfg = boost::const_pointer_cast<Element>(config_set);
371
372 // Relocate dhcp-ddns parameters that have moved to global scope.
373 // Rule is that a global value overrides the dhcp-ddns value, so
374 // we need to do this before we apply global defaults.
375 // Note this is done for backward compatibility.
376 srv_cfg->moveDdnsParams(mutable_cfg);
377
378 // Move from reservation mode to new reservations flags.
379 // @todo add warning
380 BaseNetworkParser::moveReservationMode(mutable_cfg);
381
382 // Set all default values if not specified by the user.
383 SimpleParser4::setAllDefaults(mutable_cfg);
384
385 // And now derive (inherit) global parameters to subnets, if not specified.
386 SimpleParser4::deriveParameters(mutable_cfg);
387
388 // In principle we could have the following code structured as a series
389 // of long if else if clauses. That would give a marginal performance
390 // boost, but would make the code less readable. We had serious issues
391 // with the parser code debugability, so I decided to keep it as a
392 // series of independent ifs.
393
394 // This parser is used in several places.
395 Dhcp4ConfigParser global_parser;
396
397 // Apply global options in the staging config, e.g. ip-reservations-unique
398 global_parser.parseEarly(srv_cfg, mutable_cfg);
399
400 // We need definitions first
401 ConstElementPtr option_defs = mutable_cfg->get("option-def");
402 if (option_defs) {
403 parameter_name = "option-def";
404 OptionDefListParser parser(AF_INET);
405 CfgOptionDefPtr cfg_option_def = srv_cfg->getCfgOptionDef();
406 parser.parse(cfg_option_def, option_defs);
407 }
408
409 ConstElementPtr option_datas = mutable_cfg->get("option-data");
410 if (option_datas) {
411 parameter_name = "option-data";
412 OptionDataListParser parser(AF_INET);
413 CfgOptionPtr cfg_option = srv_cfg->getCfgOption();
414 parser.parse(cfg_option, option_datas);
415 }
416
417 ConstElementPtr control_socket = mutable_cfg->get("control-socket");
418 if (control_socket) {
419 parameter_name = "control-socket";
420 ControlSocketParser parser;
421 parser.parse(*srv_cfg, control_socket);
422 }
423
424 ConstElementPtr multi_threading = mutable_cfg->get("multi-threading");
425 if (multi_threading) {
426 parameter_name = "multi-threading";
427 MultiThreadingConfigParser parser;
428 parser.parse(*srv_cfg, multi_threading);
429 }
430
431 ConstElementPtr queue_control = mutable_cfg->get("dhcp-queue-control");
432 if (queue_control) {
433 parameter_name = "dhcp-queue-control";
434 DHCPQueueControlParser parser;
435 srv_cfg->setDHCPQueueControl(parser.parse(queue_control));
436 }
437
438 ConstElementPtr hr_identifiers =
439 mutable_cfg->get("host-reservation-identifiers");
440 if (hr_identifiers) {
441 parameter_name = "host-reservation-identifiers";
442 HostReservationIdsParser4 parser;
443 parser.parse(hr_identifiers);
444 }
445
446 ConstElementPtr ifaces_config = mutable_cfg->get("interfaces-config");
447 if (ifaces_config) {
448 parameter_name = "interfaces-config";
449 IfacesConfigParser parser(AF_INET, check_only);
450 CfgIfacePtr cfg_iface = srv_cfg->getCfgIface();
451 parser.parse(cfg_iface, ifaces_config);
452 }
453
454 ConstElementPtr sanity_checks = mutable_cfg->get("sanity-checks");
455 if (sanity_checks) {
456 parameter_name = "sanity-checks";
457 SanityChecksParser parser;
458 parser.parse(*srv_cfg, sanity_checks);
459 }
460
461 ConstElementPtr expiration_cfg =
462 mutable_cfg->get("expired-leases-processing");
463 if (expiration_cfg) {
464 parameter_name = "expired-leases-processing";
465 ExpirationConfigParser parser;
466 parser.parse(expiration_cfg);
467 }
468
469 // The hooks-libraries configuration must be parsed after parsing
470 // multi-threading configuration so that libraries are checked
471 // for multi-threading compatibility.
472 ConstElementPtr hooks_libraries = mutable_cfg->get("hooks-libraries");
473 if (hooks_libraries) {
474 parameter_name = "hooks-libraries";
475 HooksLibrariesParser hooks_parser;
476 HooksConfig& libraries = srv_cfg->getHooksConfig();
477 hooks_parser.parse(libraries, hooks_libraries);
478 libraries.verifyLibraries(hooks_libraries->getPosition());
479 }
480
481 // D2 client configuration.
482 D2ClientConfigPtr d2_client_cfg;
483
484 // Legacy DhcpConfigParser stuff below.
485 ConstElementPtr dhcp_ddns = mutable_cfg->get("dhcp-ddns");
486 if (dhcp_ddns) {
487 parameter_name = "dhcp-ddns";
488 // Apply defaults
489 D2ClientConfigParser::setAllDefaults(dhcp_ddns);
490 D2ClientConfigParser parser;
491 d2_client_cfg = parser.parse(dhcp_ddns);
492 }
493
494 ConstElementPtr client_classes = mutable_cfg->get("client-classes");
495 if (client_classes) {
496 parameter_name = "client-classes";
497 ClientClassDefListParser parser;
498 ClientClassDictionaryPtr dictionary =
499 parser.parse(client_classes, AF_INET);
500 srv_cfg->setClientClassDictionary(dictionary);
501 }
502
503 // Please move at the end when migration will be finished.
504 ConstElementPtr lease_database = mutable_cfg->get("lease-database");
505 if (lease_database) {
506 parameter_name = "lease-database";
507 db::DbAccessParser parser;
508 std::string access_string;
509 parser.parse(access_string, lease_database);
510 CfgDbAccessPtr cfg_db_access = srv_cfg->getCfgDbAccess();
511 cfg_db_access->setLeaseDbAccessString(access_string);
512 }
513
514 ConstElementPtr hosts_database = mutable_cfg->get("hosts-database");
515 if (hosts_database) {
516 parameter_name = "hosts-database";
517 db::DbAccessParser parser;
518 std::string access_string;
519 parser.parse(access_string, hosts_database);
520 CfgDbAccessPtr cfg_db_access = srv_cfg->getCfgDbAccess();
521 cfg_db_access->setHostDbAccessString(access_string);
522 }
523
524 ConstElementPtr hosts_databases = mutable_cfg->get("hosts-databases");
525 if (hosts_databases) {
526 parameter_name = "hosts-databases";
527 CfgDbAccessPtr cfg_db_access = srv_cfg->getCfgDbAccess();
528 db::DbAccessParser parser;
529 for (auto it : hosts_databases->listValue()) {
530 std::string access_string;
531 parser.parse(access_string, it);
532 cfg_db_access->setHostDbAccessString(access_string);
533 }
534 }
535
536 // Keep relative orders of shared networks and subnets.
537 ConstElementPtr shared_networks = mutable_cfg->get("shared-networks");
538 if (shared_networks) {
539 parameter_name = "shared-networks";
540 /// We need to create instance of SharedNetworks4ListParser
541 /// and parse the list of the shared networks into the
542 /// CfgSharedNetworks4 object. One additional step is then to
543 /// add subnets from the CfgSharedNetworks4 into CfgSubnets4
544 /// as well.
545 SharedNetworks4ListParser parser;
546 CfgSharedNetworks4Ptr cfg = srv_cfg->getCfgSharedNetworks4();
547 parser.parse(cfg, shared_networks);
548
549 // We also need to put the subnets it contains into normal
550 // subnets list.
551 global_parser.copySubnets4(srv_cfg->getCfgSubnets4(), cfg);
552 }
553
554 ConstElementPtr subnet4 = mutable_cfg->get("subnet4");
555 if (subnet4) {
556 parameter_name = "subnet4";
557 Subnets4ListConfigParser subnets_parser;
558 // parse() returns number of subnets parsed. We may log it one day.
559 subnets_parser.parse(srv_cfg, subnet4);
560 }
561
562 ConstElementPtr reservations = mutable_cfg->get("reservations");
563 if (reservations) {
564 parameter_name = "reservations";
565 HostCollection hosts;
566 HostReservationsListParser<HostReservationParser4> parser;
567 parser.parse(SUBNET_ID_GLOBAL, reservations, hosts);
568 for (auto h = hosts.begin(); h != hosts.end(); ++h) {
569 srv_cfg->getCfgHosts()->add(*h);
570 }
571 }
572
573 ConstElementPtr config_control = mutable_cfg->get("config-control");
574 if (config_control) {
575 parameter_name = "config-control";
576 ConfigControlParser parser;
577 ConfigControlInfoPtr config_ctl_info = parser.parse(config_control);
578 CfgMgr::instance().getStagingCfg()->setConfigControlInfo(config_ctl_info);
579 }
580
581 ConstElementPtr compatibility = mutable_cfg->get("compatibility");
582 if (compatibility) {
583 for (auto kv : compatibility->mapValue()) {
584 if (kv.first == "lenient-option-parsing") {
585 CfgMgr::instance().getStagingCfg()->setLenientOptionParsing(
586 kv.second->boolValue());
587 }
588 }
589 }
590
591 // Make parsers grouping.
592 ConfigPair config_pair;
593 const std::map<std::string, ConstElementPtr>& values_map =
594 mutable_cfg->mapValue();
595
596 BOOST_FOREACH(config_pair, values_map) {
597
598 parameter_name = config_pair.first;
599
600 // These are converted to SimpleParser and are handled already above.
601 if ((config_pair.first == "option-def") ||
602 (config_pair.first == "option-data") ||
603 (config_pair.first == "control-socket") ||
604 (config_pair.first == "multi-threading") ||
605 (config_pair.first == "dhcp-queue-control") ||
606 (config_pair.first == "host-reservation-identifiers") ||
607 (config_pair.first == "interfaces-config") ||
608 (config_pair.first == "sanity-checks") ||
609 (config_pair.first == "expired-leases-processing") ||
610 (config_pair.first == "hooks-libraries") ||
611 (config_pair.first == "dhcp-ddns") ||
612 (config_pair.first == "client-classes") ||
613 (config_pair.first == "lease-database") ||
614 (config_pair.first == "hosts-database") ||
615 (config_pair.first == "hosts-databases") ||
616 (config_pair.first == "subnet4") ||
617 (config_pair.first == "shared-networks") ||
618 (config_pair.first == "reservations") ||
619 (config_pair.first == "config-control") ||
620 (config_pair.first == "compatibility")) {
621 continue;
622 }
623
624 // As of Kea 1.6.0 we have two ways of inheriting the global parameters.
625 // The old method is used in JSON configuration parsers when the global
626 // parameters are derived into the subnets and shared networks and are
627 // being treated as explicitly specified. The new way used by the config
628 // backend is the dynamic inheritance whereby each subnet and shared
629 // network uses a callback function to return global parameter if it
630 // is not specified at lower level. This callback uses configured globals.
631 // We deliberately include both default and explicitly specified globals
632 // so as the callback can access the appropriate global values regardless
633 // whether they are set to a default or other value.
634 if ( (config_pair.first == "renew-timer") ||
635 (config_pair.first == "rebind-timer") ||
636 (config_pair.first == "valid-lifetime") ||
637 (config_pair.first == "min-valid-lifetime") ||
638 (config_pair.first == "max-valid-lifetime") ||
639 (config_pair.first == "decline-probation-period") ||
640 (config_pair.first == "dhcp4o6-port") ||
641 (config_pair.first == "echo-client-id") ||
642 (config_pair.first == "match-client-id") ||
643 (config_pair.first == "authoritative") ||
644 (config_pair.first == "next-server") ||
645 (config_pair.first == "server-hostname") ||
646 (config_pair.first == "boot-file-name") ||
647 (config_pair.first == "server-tag") ||
648 (config_pair.first == "reservation-mode") ||
649 (config_pair.first == "reservations-global") ||
650 (config_pair.first == "reservations-in-subnet") ||
651 (config_pair.first == "reservations-out-of-pool") ||
652 (config_pair.first == "calculate-tee-times") ||
653 (config_pair.first == "t1-percent") ||
654 (config_pair.first == "t2-percent") ||
655 (config_pair.first == "cache-threshold") ||
656 (config_pair.first == "cache-max-age") ||
657 (config_pair.first == "loggers") ||
658 (config_pair.first == "hostname-char-set") ||
659 (config_pair.first == "hostname-char-replacement") ||
660 (config_pair.first == "ddns-send-updates") ||
661 (config_pair.first == "ddns-override-no-update") ||
662 (config_pair.first == "ddns-override-client-update") ||
663 (config_pair.first == "ddns-replace-client-name") ||
664 (config_pair.first == "ddns-generated-prefix") ||
665 (config_pair.first == "ddns-qualifying-suffix") ||
666 (config_pair.first == "ddns-update-on-renew") ||
667 (config_pair.first == "ddns-use-conflict-resolution") ||
668 (config_pair.first == "store-extended-info") ||
669 (config_pair.first == "statistic-default-sample-count") ||
670 (config_pair.first == "statistic-default-sample-age") ||
671 (config_pair.first == "ip-reservations-unique") ||
672 (config_pair.first == "parked-packet-limit")) {
673 CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first,
674 config_pair.second);
675 continue;
676 }
677
678 // Nothing to configure for the user-context.
679 if (config_pair.first == "user-context") {
680 continue;
681 }
682
683 // If we got here, no code handled this parameter, so we bail out.
684 isc_throw(DhcpConfigError,
685 "unsupported global configuration parameter: " << config_pair.first
686 << " (" << config_pair.second->getPosition() << ")");
687 }
688
689 // Reset parameter name.
690 parameter_name = "<post parsing>";
691
692 // Apply global options in the staging config.
693 global_parser.parse(srv_cfg, mutable_cfg);
694
695 // This method conducts final sanity checks and tweaks. In particular,
696 // it checks that there is no conflict between plain subnets and those
697 // defined as part of shared networks.
698 global_parser.sanityChecks(srv_cfg, mutable_cfg);
699
700 // Validate D2 client configuration.
701 if (!d2_client_cfg) {
702 d2_client_cfg.reset(new D2ClientConfig());
703 }
704 d2_client_cfg->validateContents();
705 srv_cfg->setD2ClientConfig(d2_client_cfg);
706 } catch (const isc::Exception& ex) {
707 LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
708 .arg(parameter_name).arg(ex.what());
709 answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, ex.what());
710
711 // An error occurred, so make sure that we restore original data.
712 rollback = true;
713 } catch (...) {
714 // For things like bad_cast in boost::lexical_cast
715 LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(parameter_name);
716 answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "undefined configuration"
717 " processing error");
718
719 // An error occurred, so make sure that we restore original data.
720 rollback = true;
721 }
722
723 if (check_only) {
724 rollback = true;
725 if (!answer) {
726 answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS,
727 "Configuration seems sane. Control-socket, hook-libraries, and D2 "
728 "configuration were sanity checked, but not applied.");
729 }
730 }
731
732 // So far so good, there was no parsing error so let's commit the
733 // configuration. This will add created subnets and option values into
734 // the server's configuration.
735 // This operation should be exception safe but let's make sure.
736 if (!rollback) {
737 try {
738
739 // Setup the command channel.
740 configureCommandChannel();
741
742 // No need to commit interface names as this is handled by the
743 // CfgMgr::commit() function.
744
745 // Apply the staged D2ClientConfig, used to be done by parser commit
746 D2ClientConfigPtr cfg;
747 cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
748 CfgMgr::instance().setD2ClientConfig(cfg);
749
750 // This occurs last as if it succeeds, there is no easy way to
751 // revert it. As a result, the failure to commit a subsequent
752 // change causes problems when trying to roll back.
753 HooksManager::prepareUnloadLibraries();
754 static_cast<void>(HooksManager::unloadLibraries());
755 const HooksConfig& libraries =
756 CfgMgr::instance().getStagingCfg()->getHooksConfig();
757 libraries.loadLibraries();
758 }
759 catch (const isc::Exception& ex) {
760 LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
761 answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, ex.what());
762
763 // An error occurred, so make sure to restore the original data.
764 rollback = true;
765 } catch (...) {
766 // For things like bad_cast in boost::lexical_cast
767 LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
768 answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "undefined configuration"
769 " parsing error");
770
771 // An error occurred, so make sure to restore the original data.
772 rollback = true;
773 }
774 }
775
776 // Moved from the commit block to add the config backend indication.
777 if (!rollback) {
778 try {
779
780 // If there are config backends, fetch and merge into staging config
781 server.getCBControl()->databaseConfigFetch(srv_cfg,
782 CBControlDHCPv4::FetchMode::FETCH_ALL);
783 }
784 catch (const isc::Exception& ex) {
785 std::ostringstream err;
786 err << "during update from config backend database: " << ex.what();
787 LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str());
788 answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str());
789
790 // An error occurred, so make sure to restore the original data.
791 rollback = true;
792 } catch (...) {
793 // For things like bad_cast in boost::lexical_cast
794 std::ostringstream err;
795 err << "during update from config backend database: "
796 << "undefined configuration parsing error";
797 LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str());
798 answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str());
799
800 // An error occurred, so make sure to restore the original data.
801 rollback = true;
802 }
803 }
804
805 // Rollback changes as the configuration parsing failed.
806 if (rollback) {
807 // Revert to original configuration of runtime option definitions
808 // in the libdhcp++.
809 LibDHCP::revertRuntimeOptionDefs();
810 return (answer);
811 }
812
813 LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE)
814 .arg(CfgMgr::instance().getStagingCfg()->
815 getConfigSummary(SrvConfig::CFGSEL_ALL4));
816
817 // Everything was fine. Configuration is successful.
818 answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, "Configuration successful.");
819 return (answer);
820 }
821
822 } // namespace dhcp
823 } // namespace isc
824