/src/kea/src/bin/dhcp4/json_config_parser.cc
Line | Count | Source |
1 | | // Copyright (C) 2012-2026 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 <asiolink/io_service_mgr.h> |
10 | | #include <cc/command_interpreter.h> |
11 | | #include <config/command_mgr.h> |
12 | | #include <config/http_command_mgr.h> |
13 | | #include <config/unix_command_mgr.h> |
14 | | #include <database/database_connection.h> |
15 | | #include <database/dbaccess_parser.h> |
16 | | #include <database/backend_selector.h> |
17 | | #include <database/server_selector.h> |
18 | | #include <dhcp4/ctrl_dhcp4_srv.h> |
19 | | #include <dhcp4/dhcp4_log.h> |
20 | | #include <dhcp4/dhcp4_srv.h> |
21 | | #include <dhcp4/json_config_parser.h> |
22 | | #include <dhcp/libdhcp++.h> |
23 | | #include <dhcp/option_definition.h> |
24 | | #include <dhcpsrv/legal_log_mgr_factory.h> |
25 | | #include <dhcpsrv/cb_ctl_dhcp4.h> |
26 | | #include <dhcpsrv/cfg_multi_threading.h> |
27 | | #include <dhcpsrv/cfg_option.h> |
28 | | #include <dhcpsrv/cfgmgr.h> |
29 | | #include <dhcpsrv/config_backend_dhcp4_mgr.h> |
30 | | #include <dhcpsrv/db_type.h> |
31 | | #include <dhcpsrv/lease_mgr_factory.h> |
32 | | #include <dhcpsrv/parsers/client_class_def_parser.h> |
33 | | #include <dhcpsrv/parsers/dhcp_parsers.h> |
34 | | #include <dhcpsrv/parsers/expiration_config_parser.h> |
35 | | #include <dhcpsrv/parsers/host_reservation_parser.h> |
36 | | #include <dhcpsrv/parsers/host_reservations_list_parser.h> |
37 | | #include <dhcpsrv/parsers/ifaces_config_parser.h> |
38 | | #include <dhcpsrv/parsers/multi_threading_config_parser.h> |
39 | | #include <dhcpsrv/parsers/option_data_parser.h> |
40 | | #include <dhcpsrv/parsers/dhcp_queue_control_parser.h> |
41 | | #include <dhcpsrv/parsers/simple_parser4.h> |
42 | | #include <dhcpsrv/parsers/shared_networks_list_parser.h> |
43 | | #include <dhcpsrv/parsers/sanity_checks_parser.h> |
44 | | #include <dhcpsrv/host_data_source_factory.h> |
45 | | #include <dhcpsrv/host_mgr.h> |
46 | | #include <dhcpsrv/timer_mgr.h> |
47 | | #include <hooks/hooks_manager.h> |
48 | | #include <hooks/hooks_parser.h> |
49 | | #include <process/config_ctl_parser.h> |
50 | | #include <util/encode/encode.h> |
51 | | #include <util/multi_threading_mgr.h> |
52 | | #include <boost/algorithm/string.hpp> |
53 | | #include <boost/lexical_cast.hpp> |
54 | | |
55 | | #include <iomanip> |
56 | | #include <iostream> |
57 | | #include <limits> |
58 | | #include <map> |
59 | | #include <netinet/in.h> |
60 | | #include <vector> |
61 | | |
62 | | using namespace isc::asiolink; |
63 | | using namespace isc::config; |
64 | | using namespace isc::data; |
65 | | using namespace isc::db; |
66 | | using namespace isc::dhcp; |
67 | | using namespace isc::hooks; |
68 | | using namespace isc::process; |
69 | | using namespace isc::util; |
70 | | using namespace isc; |
71 | | using namespace std; |
72 | | |
73 | | // |
74 | | // Register database backends |
75 | | // |
76 | | namespace { |
77 | | |
78 | | /// @brief Parser that takes care of global DHCPv4 parameters and utility |
79 | | /// functions that work on global level. |
80 | | /// |
81 | | /// This class is a collection of utility method that either handle |
82 | | /// global parameters (see @ref parse), or conducts operations on |
83 | | /// global level (see @ref sanityChecks and @ref copySubnets4). |
84 | | /// |
85 | | /// See @ref parse method for a list of supported parameters. |
86 | | class Dhcp4ConfigParser : public isc::data::SimpleParser { |
87 | | public: |
88 | | |
89 | | /// @brief Sets global parameters in staging configuration |
90 | | /// |
91 | | /// @param global global configuration scope |
92 | | /// @param cfg Server configuration (parsed parameters will be stored here) |
93 | | /// |
94 | | /// Currently this method sets the following global parameters: |
95 | | /// |
96 | | /// - echo-client-id |
97 | | /// - decline-probation-period |
98 | | /// - dhcp4o6-port |
99 | | /// - user-context |
100 | | /// |
101 | | /// @throw DhcpConfigError if parameters are missing or |
102 | | /// or having incorrect values. |
103 | 15.2k | void parse(const SrvConfigPtr& cfg, const ConstElementPtr& global) { |
104 | | |
105 | | // Set whether v4 server is supposed to echo back client-id |
106 | | // (yes = RFC6842 compatible, no = backward compatibility) |
107 | 15.2k | bool echo_client_id = getBoolean(global, "echo-client-id"); |
108 | 15.2k | cfg->setEchoClientId(echo_client_id); |
109 | | |
110 | | // Set the probation period for decline handling. |
111 | 15.2k | uint32_t probation_period = |
112 | 15.2k | getUint32(global, "decline-probation-period"); |
113 | 15.2k | cfg->setDeclinePeriod(probation_period); |
114 | | |
115 | | // Set the DHCPv4-over-DHCPv6 interserver port. |
116 | 15.2k | uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port"); |
117 | 15.2k | cfg->setDhcp4o6Port(dhcp4o6_port); |
118 | | |
119 | | // Set the global user context. |
120 | 15.2k | ConstElementPtr user_context = global->get("user-context"); |
121 | 15.2k | if (user_context) { |
122 | 12 | cfg->setContext(user_context); |
123 | 12 | } |
124 | | |
125 | | // Set the server's logical name |
126 | 15.2k | std::string server_tag = getString(global, "server-tag"); |
127 | 15.2k | cfg->setServerTag(server_tag); |
128 | 15.2k | } |
129 | | |
130 | | /// @brief Sets global parameters before other parameters are parsed. |
131 | | /// |
132 | | /// This method sets selected global parameters before other parameters |
133 | | /// are parsed. This is important when the behavior of the parsers |
134 | | /// run later depends on these global parameters. |
135 | | /// |
136 | | /// Currently this method sets the following global parameters: |
137 | | /// - ip-reservations-unique |
138 | | /// |
139 | | /// @param global global configuration scope |
140 | | /// @param cfg Server configuration (parsed parameters will be stored here) |
141 | 23.7k | void parseEarly(const SrvConfigPtr& cfg, const ConstElementPtr& global) { |
142 | | // Set ip-reservations-unique flag. |
143 | 23.7k | bool ip_reservations_unique = getBoolean(global, "ip-reservations-unique"); |
144 | 23.7k | cfg->setIPReservationsUnique(ip_reservations_unique); |
145 | 23.7k | } |
146 | | |
147 | | /// @brief Copies subnets from shared networks to regular subnets container |
148 | | /// |
149 | | /// @param from pointer to shared networks container (copy from here) |
150 | | /// @param dest pointer to cfg subnets4 (copy to here) |
151 | | /// @throw BadValue if any pointer is missing |
152 | | /// @throw DhcpConfigError if there are duplicates (or other subnet defects) |
153 | | void |
154 | 615 | copySubnets4(const CfgSubnets4Ptr& dest, const CfgSharedNetworks4Ptr& from) { |
155 | | |
156 | 615 | if (!dest || !from) { |
157 | 0 | isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null"); |
158 | 0 | } |
159 | | |
160 | 615 | const SharedNetwork4Collection* networks = from->getAll(); |
161 | 615 | if (!networks) { |
162 | | // Nothing to copy. Technically, it should return a pointer to empty |
163 | | // container, but let's handle null pointer as well. |
164 | 0 | return; |
165 | 0 | } |
166 | | |
167 | | // Let's go through all the networks one by one |
168 | 262k | for (auto const& net : *networks) { |
169 | | |
170 | | // For each network go through all the subnets in it. |
171 | 262k | const Subnet4SimpleCollection* subnets = net->getAllSubnets(); |
172 | 262k | if (!subnets) { |
173 | | // Shared network without subnets it weird, but we decided to |
174 | | // accept such configurations. |
175 | 0 | continue; |
176 | 0 | } |
177 | | |
178 | | // For each subnet, add it to a list of regular subnets. |
179 | 262k | for (auto const& subnet : *subnets) { |
180 | 0 | dest->add(subnet); |
181 | 0 | } |
182 | 262k | } |
183 | 615 | } |
184 | | |
185 | | /// @brief Conducts global sanity checks |
186 | | /// |
187 | | /// This method is very simple now, but more sanity checks are expected |
188 | | /// in the future. |
189 | | /// |
190 | | /// @param cfg - the parsed structure |
191 | | /// @param global global Dhcp4 scope |
192 | | /// @throw DhcpConfigError in case of issues found |
193 | | void |
194 | 15.2k | sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) { |
195 | | |
196 | | /// Global lifetime sanity checks |
197 | 15.2k | cfg->sanityChecksLifetime("valid-lifetime"); |
198 | | |
199 | | /// Sanity check global ddns-ttl parameters |
200 | 15.2k | cfg->sanityChecksDdnsTtlParameters(); |
201 | | |
202 | | /// Shared network sanity checks |
203 | 15.2k | const SharedNetwork4Collection* networks = cfg->getCfgSharedNetworks4()->getAll(); |
204 | 15.2k | if (networks) { |
205 | 12.1k | sharedNetworksSanityChecks(*networks, global->get("shared-networks")); |
206 | 12.1k | } |
207 | | |
208 | | // Sanity check use of SFLQ allocator. |
209 | 15.2k | cfg->sanityChecksSflqAllocator(); |
210 | 15.2k | } |
211 | | |
212 | | /// @brief Sanity checks for shared networks |
213 | | /// |
214 | | /// This method verifies if there are no issues with shared networks. |
215 | | /// @param networks pointer to shared networks being checked |
216 | | /// @param json shared-networks element |
217 | | /// @throw DhcpConfigError if issues are encountered |
218 | | void |
219 | | sharedNetworksSanityChecks(const SharedNetwork4Collection& networks, |
220 | 12.1k | ConstElementPtr json) { |
221 | | |
222 | | /// @todo: in case of errors, use json to extract line numbers. |
223 | 12.1k | if (!json) { |
224 | | // No json? That means that the shared-networks was never specified |
225 | | // in the config. |
226 | 12.1k | return; |
227 | 12.1k | } |
228 | | |
229 | | // Used for names uniqueness checks. |
230 | 0 | std::set<string> names; |
231 | | |
232 | | // Let's go through all the networks one by one |
233 | 0 | for (auto const& net : networks) { |
234 | 0 | string txt; |
235 | | |
236 | | // Let's check if all subnets have either the same interface |
237 | | // or don't have the interface specified at all. |
238 | 0 | bool authoritative = net->getAuthoritative(); |
239 | 0 | string iface = net->getIface(); |
240 | |
|
241 | 0 | const Subnet4SimpleCollection* subnets = net->getAllSubnets(); |
242 | 0 | if (subnets) { |
243 | | // For each subnet, add it to a list of regular subnets. |
244 | 0 | for (auto const& subnet : *subnets) { |
245 | 0 | if (subnet->getAuthoritative() != authoritative) { |
246 | 0 | isc_throw(DhcpConfigError, "Subnet " << boolalpha |
247 | 0 | << subnet->toText() |
248 | 0 | << " has different authoritative setting " |
249 | 0 | << subnet->getAuthoritative() |
250 | 0 | << " than the shared-network itself: " |
251 | 0 | << authoritative); |
252 | 0 | } |
253 | | |
254 | 0 | if (iface.empty()) { |
255 | 0 | iface = subnet->getIface(); |
256 | 0 | continue; |
257 | 0 | } |
258 | | |
259 | 0 | if (subnet->getIface().empty()) { |
260 | 0 | continue; |
261 | 0 | } |
262 | | |
263 | 0 | if (subnet->getIface() != iface) { |
264 | 0 | isc_throw(DhcpConfigError, "Subnet " << subnet->toText() |
265 | 0 | << " has specified interface " << subnet->getIface() |
266 | 0 | << ", but earlier subnet in the same shared-network" |
267 | 0 | << " or the shared-network itself used " << iface); |
268 | 0 | } |
269 | | |
270 | | // Let's collect the subnets in case we later find out the |
271 | | // subnet doesn't have a mandatory name. |
272 | 0 | txt += subnet->toText() + " "; |
273 | 0 | } |
274 | 0 | } |
275 | | |
276 | | // Next, let's check name of the shared network. |
277 | 0 | if (net->getName().empty()) { |
278 | 0 | isc_throw(DhcpConfigError, "Shared-network with subnets " |
279 | 0 | << txt << " is missing mandatory 'name' parameter"); |
280 | 0 | } |
281 | | |
282 | | // Is it unique? |
283 | 0 | if (names.find(net->getName()) != names.end()) { |
284 | 0 | isc_throw(DhcpConfigError, "A shared-network with " |
285 | 0 | "name " << net->getName() << " defined twice."); |
286 | 0 | } |
287 | 0 | names.insert(net->getName()); |
288 | |
|
289 | 0 | } |
290 | 0 | } |
291 | | }; |
292 | | |
293 | | } // anonymous namespace |
294 | | |
295 | | namespace isc { |
296 | | namespace dhcp { |
297 | | |
298 | | /// @brief Initialize the command channel based on the staging configuration |
299 | | /// |
300 | | /// Only close the current channel, if the new channel configuration is |
301 | | /// different. This avoids disconnecting a client and hence not sending them |
302 | | /// a command result, unless they specifically alter the channel configuration. |
303 | | /// In that case the user simply has to accept they'll be disconnected. |
304 | | /// |
305 | 21.3k | void configureCommandChannel() { |
306 | | // Get new UNIX socket configuration. |
307 | 21.3k | ConstElementPtr unix_config = |
308 | 21.3k | CfgMgr::instance().getStagingCfg()->getUnixControlSocketInfo(); |
309 | | |
310 | | // Get current UNIX socket configuration. |
311 | 21.3k | ConstElementPtr current_unix_config = |
312 | 21.3k | CfgMgr::instance().getCurrentCfg()->getUnixControlSocketInfo(); |
313 | | |
314 | | // Determine if the socket configuration has changed. It has if |
315 | | // both old and new configuration is specified but respective |
316 | | // data elements aren't equal. |
317 | 21.3k | bool sock_changed = (unix_config && current_unix_config && |
318 | 0 | !unix_config->equals(*current_unix_config)); |
319 | | |
320 | | // If the previous or new socket configuration doesn't exist or |
321 | | // the new configuration differs from the old configuration we |
322 | | // close the existing socket and open a new socket as appropriate. |
323 | | // Note that closing an existing socket means the client will not |
324 | | // receive the configuration result. |
325 | 21.3k | if (!unix_config || !current_unix_config || sock_changed) { |
326 | 21.3k | if (unix_config) { |
327 | | // This will create a control socket and install the external |
328 | | // socket in IfaceMgr. That socket will be monitored when |
329 | | // Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and |
330 | | // callback in CommandMgr will be called, if necessary. |
331 | 6.76k | UnixCommandMgr::instance().openCommandSockets(unix_config); |
332 | 14.6k | } else if (current_unix_config) { |
333 | 0 | UnixCommandMgr::instance().closeCommandSockets(); |
334 | 0 | } |
335 | 21.3k | } |
336 | | |
337 | | // Get new HTTP/HTTPS socket configuration. |
338 | 21.3k | ConstElementPtr http_config = |
339 | 21.3k | CfgMgr::instance().getStagingCfg()->getHttpControlSocketInfo(); |
340 | | |
341 | | // Get current HTTP/HTTPS socket configuration. |
342 | 21.3k | ConstElementPtr current_http_config = |
343 | 21.3k | CfgMgr::instance().getCurrentCfg()->getHttpControlSocketInfo(); |
344 | | |
345 | 21.3k | if (http_config) { |
346 | 216 | HttpCommandMgr::instance().openCommandSockets(http_config); |
347 | 21.1k | } else if (current_http_config) { |
348 | 0 | HttpCommandMgr::instance().closeCommandSockets(); |
349 | 0 | } |
350 | 21.3k | } |
351 | | |
352 | | /// @brief Process a DHCPv4 configuration and return an answer stating if the |
353 | | /// configuration is valid, or specifying details about the error otherwise. |
354 | | /// |
355 | | /// @param config_set the configuration being processed |
356 | | isc::data::ConstElementPtr |
357 | 26.1k | processDhcp4Config(isc::data::ConstElementPtr config_set) { |
358 | | // Revert any runtime option definitions configured so far and not committed. |
359 | 26.1k | LibDHCP::revertRuntimeOptionDefs(); |
360 | | // Let's set empty container in case a user hasn't specified any configuration |
361 | | // for option definitions. This is equivalent to committing empty container. |
362 | 26.1k | LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer()); |
363 | | |
364 | 26.1k | IfaceMgr::instance().detectIfaces(true); |
365 | | |
366 | | // Answer will hold the result. |
367 | 26.1k | ConstElementPtr answer; |
368 | | |
369 | | // Global parameter name in case of an error. |
370 | 26.1k | string parameter_name; |
371 | 26.1k | ElementPtr mutable_cfg; |
372 | 26.1k | SrvConfigPtr srv_config; |
373 | 26.1k | try { |
374 | | // Get the staging configuration. |
375 | 26.1k | srv_config = CfgMgr::instance().getStagingCfg(); |
376 | | |
377 | | // This is a way to convert ConstElementPtr to ElementPtr. |
378 | | // We need a config that can be edited, because we will insert |
379 | | // default values and will insert derived values as well. |
380 | 26.1k | mutable_cfg = boost::const_pointer_cast<Element>(config_set); |
381 | | |
382 | | // Set all default values if not specified by the user. |
383 | 26.1k | SimpleParser4::setAllDefaults(mutable_cfg); |
384 | | |
385 | | // And now derive (inherit) global parameters to subnets, if not specified. |
386 | 26.1k | 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 | 26.1k | Dhcp4ConfigParser global_parser; |
396 | | |
397 | | // Apply global options in the staging config, e.g. ip-reservations-unique |
398 | 26.1k | global_parser.parseEarly(srv_config, mutable_cfg); |
399 | | |
400 | | // We need definitions first |
401 | 26.1k | ConstElementPtr option_defs = mutable_cfg->get("option-def"); |
402 | 26.1k | if (option_defs) { |
403 | 0 | parameter_name = "option-def"; |
404 | 0 | OptionDefListParser parser(AF_INET); |
405 | 0 | CfgOptionDefPtr cfg_option_def = srv_config->getCfgOptionDef(); |
406 | 0 | parser.parse(cfg_option_def, option_defs); |
407 | 0 | } |
408 | | |
409 | 26.1k | ConstElementPtr option_datas = mutable_cfg->get("option-data"); |
410 | 26.1k | if (option_datas) { |
411 | 921 | parameter_name = "option-data"; |
412 | 921 | OptionDataListParser parser(AF_INET); |
413 | 921 | CfgOptionPtr cfg_option = srv_config->getCfgOption(); |
414 | 921 | parser.parse(cfg_option, option_datas); |
415 | 921 | } |
416 | | |
417 | 26.1k | ConstElementPtr control_socket = mutable_cfg->get("control-socket"); |
418 | 26.1k | if (control_socket) { |
419 | 3.81k | mutable_cfg->remove("control-socket"); |
420 | 3.81k | ElementPtr l = Element::createList(control_socket->getPosition()); |
421 | 3.81k | l->add(UserContext::toElement(control_socket)); |
422 | 3.81k | mutable_cfg->set("control-sockets", l); |
423 | 3.81k | } |
424 | | |
425 | 26.1k | ConstElementPtr control_sockets = mutable_cfg->get("control-sockets"); |
426 | 26.1k | if (control_sockets) { |
427 | 4.96k | parameter_name = "control-sockets"; |
428 | 4.96k | ControlSocketsParser parser; |
429 | 4.96k | parser.parse(*srv_config, control_sockets); |
430 | 4.96k | } |
431 | | |
432 | 26.1k | ConstElementPtr multi_threading = mutable_cfg->get("multi-threading"); |
433 | 26.1k | if (multi_threading) { |
434 | 22.5k | parameter_name = "multi-threading"; |
435 | 22.5k | MultiThreadingConfigParser parser; |
436 | 22.5k | parser.parse(*srv_config, multi_threading); |
437 | 22.5k | } |
438 | | |
439 | 26.1k | bool multi_threading_enabled = true; |
440 | 26.1k | uint32_t thread_count = 0; |
441 | 26.1k | uint32_t queue_size = 0; |
442 | 26.1k | CfgMultiThreading::extract(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading(), |
443 | 26.1k | multi_threading_enabled, thread_count, queue_size); |
444 | | |
445 | | /// depends on "multi-threading" being enabled, so it must come after. |
446 | 26.1k | ConstElementPtr queue_control = mutable_cfg->get("dhcp-queue-control"); |
447 | 26.1k | if (queue_control) { |
448 | 22.5k | parameter_name = "dhcp-queue-control"; |
449 | 22.5k | DHCPQueueControlParser parser; |
450 | 22.5k | srv_config->setDHCPQueueControl(parser.parse(queue_control, multi_threading_enabled)); |
451 | 22.5k | } |
452 | | |
453 | | /// depends on "multi-threading" being enabled, so it must come after. |
454 | 26.1k | ConstElementPtr reservations_lookup_first = mutable_cfg->get("reservations-lookup-first"); |
455 | 26.1k | if (reservations_lookup_first) { |
456 | 22.5k | parameter_name = "reservations-lookup-first"; |
457 | 22.5k | if (multi_threading_enabled) { |
458 | 22.5k | LOG_WARN(dhcp4_logger, DHCP4_RESERVATIONS_LOOKUP_FIRST_ENABLED); |
459 | 22.5k | } |
460 | 22.5k | srv_config->setReservationsLookupFirst(reservations_lookup_first->boolValue()); |
461 | 22.5k | } |
462 | | |
463 | 26.1k | ConstElementPtr hr_identifiers = |
464 | 26.1k | mutable_cfg->get("host-reservation-identifiers"); |
465 | 26.1k | if (hr_identifiers) { |
466 | 6 | parameter_name = "host-reservation-identifiers"; |
467 | 6 | HostReservationIdsParser4 parser(srv_config->getCfgHostOperations4()); |
468 | 6 | parser.parse(hr_identifiers); |
469 | 6 | } |
470 | | |
471 | 26.1k | ConstElementPtr sanity_checks = mutable_cfg->get("sanity-checks"); |
472 | 26.1k | if (sanity_checks) { |
473 | 22.4k | parameter_name = "sanity-checks"; |
474 | 22.4k | SanityChecksParser parser; |
475 | 22.4k | parser.parse(*srv_config, sanity_checks); |
476 | 22.4k | } |
477 | | |
478 | 26.1k | ConstElementPtr expiration_cfg = |
479 | 26.1k | mutable_cfg->get("expired-leases-processing"); |
480 | 26.1k | if (expiration_cfg) { |
481 | 528 | parameter_name = "expired-leases-processing"; |
482 | 528 | ExpirationConfigParser parser; |
483 | 528 | parser.parse(expiration_cfg, CfgMgr::instance().getStagingCfg()->getCfgExpiration()); |
484 | 528 | } |
485 | | |
486 | | // The hooks-libraries configuration must be parsed after parsing |
487 | | // multi-threading configuration so that libraries are checked |
488 | | // for multi-threading compatibility. |
489 | 26.1k | ConstElementPtr hooks_libraries = mutable_cfg->get("hooks-libraries"); |
490 | 26.1k | if (hooks_libraries) { |
491 | 48 | parameter_name = "hooks-libraries"; |
492 | 48 | HooksLibrariesParser hooks_parser; |
493 | 48 | HooksConfig& libraries = srv_config->getHooksConfig(); |
494 | 48 | hooks_parser.parse(libraries, hooks_libraries); |
495 | 48 | libraries.verifyLibraries(hooks_libraries->getPosition(), |
496 | 48 | multi_threading_enabled); |
497 | 48 | } |
498 | | |
499 | | // D2 client configuration. |
500 | 26.1k | D2ClientConfigPtr d2_client_cfg; |
501 | | |
502 | | // Legacy DhcpConfigParser stuff below. |
503 | 26.1k | ConstElementPtr dhcp_ddns = mutable_cfg->get("dhcp-ddns"); |
504 | 26.1k | if (dhcp_ddns) { |
505 | 3 | parameter_name = "dhcp-ddns"; |
506 | | // Apply defaults |
507 | 3 | D2ClientConfigParser::setAllDefaults(dhcp_ddns); |
508 | 3 | D2ClientConfigParser parser; |
509 | 3 | d2_client_cfg = parser.parse(dhcp_ddns); |
510 | 3 | } |
511 | | |
512 | 26.1k | ConstElementPtr client_classes = mutable_cfg->get("client-classes"); |
513 | 26.1k | if (client_classes) { |
514 | 1.24k | parameter_name = "client-classes"; |
515 | 1.24k | ClientClassDefListParser parser; |
516 | 1.24k | ClientClassDictionaryPtr dictionary = |
517 | 1.24k | parser.parse(client_classes, AF_INET); |
518 | 1.24k | srv_config->setClientClassDictionary(dictionary); |
519 | 1.24k | } |
520 | | |
521 | | // Please move at the end when migration will be finished. |
522 | 26.1k | ConstElementPtr lease_database = mutable_cfg->get("lease-database"); |
523 | 26.1k | if (lease_database) { |
524 | 12.8k | parameter_name = "lease-database"; |
525 | 12.8k | db::DbAccessParser parser; |
526 | 12.8k | std::string access_string; |
527 | 12.8k | parser.parse(access_string, lease_database); |
528 | 12.8k | CfgDbAccessPtr cfg_db_access = srv_config->getCfgDbAccess(); |
529 | 12.8k | cfg_db_access->setLeaseDbAccessString(access_string); |
530 | 12.8k | } |
531 | | |
532 | 26.1k | ConstElementPtr hosts_database = mutable_cfg->get("hosts-database"); |
533 | 26.1k | if (hosts_database) { |
534 | 3 | parameter_name = "hosts-database"; |
535 | 3 | db::DbAccessParser parser; |
536 | 3 | std::string access_string; |
537 | 3 | parser.parse(access_string, hosts_database); |
538 | 3 | CfgDbAccessPtr cfg_db_access = srv_config->getCfgDbAccess(); |
539 | 3 | cfg_db_access->setHostDbAccessString(access_string); |
540 | 3 | } |
541 | | |
542 | 26.1k | ConstElementPtr hosts_databases = mutable_cfg->get("hosts-databases"); |
543 | 26.1k | if (hosts_databases) { |
544 | 9 | parameter_name = "hosts-databases"; |
545 | 9 | CfgDbAccessPtr cfg_db_access = srv_config->getCfgDbAccess(); |
546 | 9 | for (auto const& it : hosts_databases->listValue()) { |
547 | 3 | db::DbAccessParser parser; |
548 | 3 | std::string access_string; |
549 | 3 | parser.parse(access_string, it); |
550 | 3 | cfg_db_access->setHostDbAccessString(access_string); |
551 | 3 | } |
552 | 9 | } |
553 | | |
554 | | // Keep relative orders of shared networks and subnets. |
555 | 26.1k | ConstElementPtr shared_networks = mutable_cfg->get("shared-networks"); |
556 | 26.1k | if (shared_networks) { |
557 | 2.31k | parameter_name = "shared-networks"; |
558 | | /// We need to create instance of SharedNetworks4ListParser |
559 | | /// and parse the list of the shared networks into the |
560 | | /// CfgSharedNetworks4 object. One additional step is then to |
561 | | /// add subnets from the CfgSharedNetworks4 into CfgSubnets4 |
562 | | /// as well. |
563 | 2.31k | SharedNetworks4ListParser parser; |
564 | 2.31k | CfgSharedNetworks4Ptr cfg = srv_config->getCfgSharedNetworks4(); |
565 | 2.31k | parser.parse(cfg, shared_networks); |
566 | | |
567 | | // We also need to put the subnets it contains into normal |
568 | | // subnets list. |
569 | 2.31k | global_parser.copySubnets4(srv_config->getCfgSubnets4(), cfg); |
570 | 2.31k | } |
571 | | |
572 | 26.1k | ConstElementPtr subnet4 = mutable_cfg->get("subnet4"); |
573 | 26.1k | if (subnet4) { |
574 | 9.11k | parameter_name = "subnet4"; |
575 | 9.11k | Subnets4ListConfigParser subnets_parser; |
576 | | // parse() returns number of subnets parsed. We may log it one day. |
577 | 9.11k | subnets_parser.parse(srv_config, subnet4); |
578 | 9.11k | } |
579 | | |
580 | 26.1k | ConstElementPtr reservations = mutable_cfg->get("reservations"); |
581 | 26.1k | if (reservations) { |
582 | 2.02k | parameter_name = "reservations"; |
583 | 2.02k | HostCollection hosts; |
584 | 2.02k | HostReservationsListParser<HostReservationParser4> parser; |
585 | 2.02k | parser.parse(SUBNET_ID_GLOBAL, reservations, hosts); |
586 | 2.02k | for (auto const& h : hosts) { |
587 | 1.23k | srv_config->getCfgHosts()->add(h); |
588 | 1.23k | } |
589 | 2.02k | } |
590 | | |
591 | 26.1k | ConstElementPtr config_control = mutable_cfg->get("config-control"); |
592 | 26.1k | if (config_control) { |
593 | 12 | parameter_name = "config-control"; |
594 | 12 | ConfigControlParser parser; |
595 | 12 | ConfigControlInfoPtr config_ctl_info = parser.parse(config_control); |
596 | 12 | CfgMgr::instance().getStagingCfg()->setConfigControlInfo(config_ctl_info); |
597 | 12 | } |
598 | | |
599 | 26.1k | ConstElementPtr compatibility = mutable_cfg->get("compatibility"); |
600 | 26.1k | if (compatibility) { |
601 | 3 | CompatibilityParser parser; |
602 | 3 | parser.parse(compatibility, *CfgMgr::instance().getStagingCfg()); |
603 | 3 | } |
604 | | |
605 | | // Make parsers grouping. |
606 | 26.1k | const std::map<std::string, ConstElementPtr>& values_map = |
607 | 26.1k | mutable_cfg->mapValue(); |
608 | | |
609 | 660k | for (auto const& config_pair : values_map) { |
610 | 660k | parameter_name = config_pair.first; |
611 | | |
612 | | // These are converted to SimpleParser and are handled already above. |
613 | 660k | if ((config_pair.first == "option-def") || |
614 | 660k | (config_pair.first == "option-data") || |
615 | 659k | (config_pair.first == "control-socket") || |
616 | 659k | (config_pair.first == "control-sockets") || |
617 | 655k | (config_pair.first == "multi-threading") || |
618 | 640k | (config_pair.first == "dhcp-queue-control") || |
619 | 624k | (config_pair.first == "host-reservation-identifiers") || |
620 | 624k | (config_pair.first == "interfaces-config") || |
621 | 615k | (config_pair.first == "sanity-checks") || |
622 | 600k | (config_pair.first == "expired-leases-processing") || |
623 | 600k | (config_pair.first == "hooks-libraries") || |
624 | 600k | (config_pair.first == "dhcp-ddns") || |
625 | 600k | (config_pair.first == "client-classes") || |
626 | 599k | (config_pair.first == "lease-database") || |
627 | 587k | (config_pair.first == "hosts-database") || |
628 | 587k | (config_pair.first == "hosts-databases") || |
629 | 587k | (config_pair.first == "subnet4") || |
630 | 578k | (config_pair.first == "shared-networks") || |
631 | 578k | (config_pair.first == "reservations") || |
632 | 578k | (config_pair.first == "config-control") || |
633 | 578k | (config_pair.first == "loggers") || |
634 | 569k | (config_pair.first == "compatibility")) { |
635 | 90.7k | continue; |
636 | 90.7k | } |
637 | | |
638 | | // As of Kea 1.6.0 we have two ways of inheriting the global parameters. |
639 | | // The old method is used in JSON configuration parsers when the global |
640 | | // parameters are derived into the subnets and shared networks and are |
641 | | // being treated as explicitly specified. The new way used by the config |
642 | | // backend is the dynamic inheritance whereby each subnet and shared |
643 | | // network uses a callback function to return global parameter if it |
644 | | // is not specified at lower level. This callback uses configured globals. |
645 | | // We deliberately include both default and explicitly specified globals |
646 | | // so as the callback can access the appropriate global values regardless |
647 | | // whether they are set to a default or other value. |
648 | 569k | if ( (config_pair.first == "renew-timer") || |
649 | 569k | (config_pair.first == "rebind-timer") || |
650 | 569k | (config_pair.first == "valid-lifetime") || |
651 | 554k | (config_pair.first == "min-valid-lifetime") || |
652 | 554k | (config_pair.first == "max-valid-lifetime") || |
653 | 553k | (config_pair.first == "decline-probation-period") || |
654 | 538k | (config_pair.first == "dhcp4o6-port") || |
655 | 522k | (config_pair.first == "echo-client-id") || |
656 | 506k | (config_pair.first == "match-client-id") || |
657 | 490k | (config_pair.first == "authoritative") || |
658 | 474k | (config_pair.first == "next-server") || |
659 | 458k | (config_pair.first == "server-hostname") || |
660 | 443k | (config_pair.first == "boot-file-name") || |
661 | 427k | (config_pair.first == "server-tag") || |
662 | 411k | (config_pair.first == "reservations-global") || |
663 | 395k | (config_pair.first == "reservations-in-subnet") || |
664 | 380k | (config_pair.first == "reservations-out-of-pool") || |
665 | 364k | (config_pair.first == "calculate-tee-times") || |
666 | 348k | (config_pair.first == "t1-percent") || |
667 | 333k | (config_pair.first == "t2-percent") || |
668 | 317k | (config_pair.first == "cache-threshold") || |
669 | 301k | (config_pair.first == "cache-max-age") || |
670 | 301k | (config_pair.first == "adaptive-lease-time-threshold") || |
671 | 301k | (config_pair.first == "hostname-char-set") || |
672 | 285k | (config_pair.first == "hostname-char-replacement") || |
673 | 270k | (config_pair.first == "ddns-send-updates") || |
674 | 254k | (config_pair.first == "ddns-override-no-update") || |
675 | 238k | (config_pair.first == "ddns-override-client-update") || |
676 | 222k | (config_pair.first == "ddns-replace-client-name") || |
677 | 206k | (config_pair.first == "ddns-generated-prefix") || |
678 | 190k | (config_pair.first == "ddns-qualifying-suffix") || |
679 | 174k | (config_pair.first == "ddns-update-on-renew") || |
680 | 158k | (config_pair.first == "ddns-use-conflict-resolution") || |
681 | 158k | (config_pair.first == "ddns-conflict-resolution-mode") || |
682 | 143k | (config_pair.first == "ddns-ttl-percent") || |
683 | 143k | (config_pair.first == "store-extended-info") || |
684 | 127k | (config_pair.first == "statistic-default-sample-count") || |
685 | 111k | (config_pair.first == "statistic-default-sample-age") || |
686 | 96.4k | (config_pair.first == "early-global-reservations-lookup") || |
687 | 80.6k | (config_pair.first == "ip-reservations-unique") || |
688 | 64.8k | (config_pair.first == "reservations-lookup-first") || |
689 | 49.2k | (config_pair.first == "parked-packet-limit") || |
690 | 33.6k | (config_pair.first == "allocator") || |
691 | 17.5k | (config_pair.first == "offer-lifetime") || |
692 | 17.5k | (config_pair.first == "ddns-ttl") || |
693 | 17.4k | (config_pair.first == "ddns-ttl-min") || |
694 | 17.4k | (config_pair.first == "ddns-ttl-max") || |
695 | 567k | (config_pair.first == "stash-agent-options")) { |
696 | 567k | CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first, |
697 | 567k | config_pair.second); |
698 | 567k | continue; |
699 | 567k | } |
700 | | |
701 | | // Nothing to configure for the user-context. |
702 | 1.89k | if (config_pair.first == "user-context") { |
703 | 12 | continue; |
704 | 12 | } |
705 | | |
706 | | // If we got here, no code handled this parameter, so we bail out. |
707 | 1.88k | isc_throw(DhcpConfigError, |
708 | 1.88k | "unsupported global configuration parameter: " << config_pair.first |
709 | 1.88k | << " (" << config_pair.second->getPosition() << ")"); |
710 | 1.88k | } |
711 | | |
712 | | // Reset parameter name. |
713 | 24.2k | parameter_name = "<post parsing>"; |
714 | | |
715 | | // Apply global options in the staging config. |
716 | 24.2k | global_parser.parse(srv_config, mutable_cfg); |
717 | | |
718 | | // This method conducts final sanity checks and tweaks. In particular, |
719 | | // it checks that there is no conflict between plain subnets and those |
720 | | // defined as part of shared networks. |
721 | 24.2k | global_parser.sanityChecks(srv_config, mutable_cfg); |
722 | | |
723 | | // Validate D2 client configuration. |
724 | 24.2k | if (!d2_client_cfg) { |
725 | 12.1k | d2_client_cfg.reset(new D2ClientConfig()); |
726 | 12.1k | } |
727 | 24.2k | d2_client_cfg->validateContents(); |
728 | 24.2k | srv_config->setD2ClientConfig(d2_client_cfg); |
729 | 24.2k | } catch (const isc::Exception& ex) { |
730 | 13.9k | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL) |
731 | 13.9k | .arg(parameter_name).arg(ex.what()); |
732 | 13.9k | answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, ex.what()); |
733 | 13.9k | } catch (...) { |
734 | | // For things like bad_cast in boost::lexical_cast |
735 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(parameter_name); |
736 | 0 | answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, "undefined configuration " |
737 | 0 | "processing error"); |
738 | 0 | } |
739 | | |
740 | 26.1k | if (!answer) { |
741 | 12.1k | answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, "Configuration seems sane. " |
742 | 12.1k | "Control-socket, hook-libraries, and D2 configuration " |
743 | 12.1k | "were sanity checked, but not applied."); |
744 | 12.1k | } |
745 | | |
746 | 26.1k | return (answer); |
747 | 26.1k | } |
748 | | |
749 | | isc::data::ConstElementPtr |
750 | | configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, |
751 | 26.1k | bool check_only, bool extra_checks) { |
752 | 26.1k | if (!config_set) { |
753 | 0 | ConstElementPtr answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, |
754 | 0 | "Can't parse NULL config"); |
755 | 0 | return (answer); |
756 | 0 | } |
757 | | |
758 | 26.1k | LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START) |
759 | 0 | .arg(server.redactConfig(config_set)->str()); |
760 | | |
761 | | // Resource managers usually check for @ref MultiThreadingMgr::isTestMode |
762 | | // value and do not open UNIX or TCP/UDP sockets, lock files, nor do they open |
763 | | // or rotate files, as any of these actions could interfere with a running |
764 | | // process on the same machine. |
765 | 26.1k | std::unique_ptr<MtTestMode> mt_test_mode; |
766 | 26.1k | if (check_only) { |
767 | 4.68k | mt_test_mode.reset(new MtTestMode()); |
768 | 4.68k | } |
769 | | |
770 | 26.1k | auto answer = processDhcp4Config(config_set); |
771 | | |
772 | 26.1k | int status_code = CONTROL_RESULT_SUCCESS; |
773 | 26.1k | isc::config::parseAnswer(status_code, answer); |
774 | | |
775 | 26.1k | SrvConfigPtr srv_config; |
776 | | |
777 | 26.1k | if (status_code == CONTROL_RESULT_SUCCESS) { |
778 | 12.1k | if (check_only) { |
779 | 34 | if (extra_checks) { |
780 | 0 | std::ostringstream err; |
781 | | // Configure DHCP packet queueing |
782 | 0 | try { |
783 | 0 | data::ConstElementPtr qc; |
784 | 0 | qc = CfgMgr::instance().getStagingCfg()->getDHCPQueueControl(); |
785 | 0 | if (IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, qc)) { |
786 | 0 | LOG_INFO(dhcp4_logger, DHCP4_CONFIG_PACKET_QUEUE) |
787 | 0 | .arg(IfaceMgr::instance().getPacketQueue4()->getInfoStr()); |
788 | 0 | } |
789 | |
|
790 | 0 | } catch (const std::exception& ex) { |
791 | 0 | err << "Error setting packet queue controls after server reconfiguration: " |
792 | 0 | << ex.what(); |
793 | 0 | answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, err.str()); |
794 | 0 | status_code = CONTROL_RESULT_ERROR; |
795 | 0 | } |
796 | 0 | } |
797 | 12.1k | } else { |
798 | | |
799 | | // Usually unit tests create managers before calling configureDhcp4Server and |
800 | | // do not call ControlledDhcpv4Srv::processConfig. |
801 | | // Runtime code path creates the managers after calling configureDhcp4Server |
802 | | // and they need to be reset just after successful configuration parsing. |
803 | 12.1k | if (!IfaceMgr::instance().isTestMode()) { |
804 | | // Destroy lease manager before hooks unload. |
805 | 12.1k | LeaseMgrFactory::destroy(); |
806 | | |
807 | | // Destroy host manager before hooks unload. |
808 | 12.1k | HostMgr::create(); |
809 | 12.1k | } |
810 | | |
811 | | // disable multi-threading (it will be applied by new configuration) |
812 | | // this must be done in order to properly handle MT to ST transition |
813 | | // when 'multi-threading' structure is missing from new config and |
814 | | // to properly drop any task items stored in the thread pool which |
815 | | // might reference some handles to loaded hooks, preventing them |
816 | | // from being unloaded. |
817 | 12.1k | MultiThreadingMgr::instance().apply(false, 0, 0); |
818 | | |
819 | | // Close DHCP sockets and remove any existing timers. |
820 | 12.1k | IfaceMgr::instance().closeSockets(); |
821 | 12.1k | TimerMgr::instance()->unregisterTimers(); |
822 | 12.1k | server.discardPackets(); |
823 | 12.1k | server.getCBControl()->reset(); |
824 | 12.1k | } |
825 | 12.1k | } |
826 | | |
827 | | // Parsing stage is complete. The current configuration has not been altered. |
828 | | // From this stage on, every error might have irreversible consequences and the |
829 | | // configuration might not be restored to a working state. |
830 | 26.1k | if (status_code == CONTROL_RESULT_SUCCESS) { |
831 | 12.1k | string parameter_name; |
832 | 12.1k | ElementPtr mutable_cfg; |
833 | 12.1k | try { |
834 | | // Get the staging configuration. |
835 | 12.1k | srv_config = CfgMgr::instance().getStagingCfg(); |
836 | | |
837 | | // This is a way to convert ConstElementPtr to ElementPtr. |
838 | | // We need a config that can be edited, because we will insert |
839 | | // default values and will insert derived values as well. |
840 | 12.1k | mutable_cfg = boost::const_pointer_cast<Element>(config_set); |
841 | | |
842 | 12.1k | ConstElementPtr ifaces_config = mutable_cfg->get("interfaces-config"); |
843 | 12.1k | if (ifaces_config) { |
844 | 8.51k | parameter_name = "interfaces-config"; |
845 | 8.51k | IfacesConfigParser parser(AF_INET, check_only); |
846 | 8.51k | CfgIfacePtr cfg_iface = srv_config->getCfgIface(); |
847 | 8.51k | cfg_iface->reset(); |
848 | 8.51k | parser.parse(cfg_iface, ifaces_config); |
849 | 8.51k | } |
850 | 12.1k | } catch (const isc::Exception& ex) { |
851 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL) |
852 | 0 | .arg(parameter_name).arg(ex.what()); |
853 | 0 | if (!check_only || extra_checks) { |
854 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
855 | 0 | } else { |
856 | 0 | status_code = CONTROL_RESULT_ERROR; |
857 | 0 | } |
858 | 0 | answer = isc::config::createAnswer(status_code, ex.what()); |
859 | 0 | } catch (...) { |
860 | | // For things like bad_cast in boost::lexical_cast |
861 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(parameter_name); |
862 | 0 | if (!check_only || extra_checks) { |
863 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
864 | 0 | } else { |
865 | 0 | status_code = CONTROL_RESULT_ERROR; |
866 | 0 | } |
867 | 0 | answer = isc::config::createAnswer(status_code, "undefined configuration" |
868 | 0 | " processing error"); |
869 | 0 | } |
870 | 12.1k | } |
871 | | |
872 | | // So far so good, there was no parsing error so let's commit the |
873 | | // configuration. This will add created subnets and option values into |
874 | | // the server's configuration. |
875 | | // This operation should be exception safe but let's make sure. |
876 | 26.1k | if (status_code == CONTROL_RESULT_SUCCESS && !check_only) { |
877 | 12.1k | try { |
878 | | |
879 | | // Setup the command channel. |
880 | 12.1k | configureCommandChannel(); |
881 | 12.1k | } catch (const isc::Exception& ex) { |
882 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what()); |
883 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
884 | 0 | answer = isc::config::createAnswer(status_code, ex.what()); |
885 | 0 | } catch (...) { |
886 | | // For things like bad_cast in boost::lexical_cast |
887 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION); |
888 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
889 | 0 | answer = isc::config::createAnswer(status_code, "undefined configuration" |
890 | 0 | " parsing error"); |
891 | 0 | } |
892 | 12.1k | } |
893 | | |
894 | 26.1k | if (status_code == CONTROL_RESULT_SUCCESS && (!check_only || extra_checks)) { |
895 | 12.1k | try { |
896 | | // No need to commit interface names as this is handled by the |
897 | | // CfgMgr::commit() function. |
898 | | |
899 | | // Apply the staged D2ClientConfig, used to be done by parser commit |
900 | 12.1k | D2ClientConfigPtr cfg; |
901 | 12.1k | cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig(); |
902 | 12.1k | CfgMgr::instance().setD2ClientConfig(cfg); |
903 | 12.1k | } catch (const isc::Exception& ex) { |
904 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what()); |
905 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
906 | 0 | answer = isc::config::createAnswer(status_code, ex.what()); |
907 | 0 | } catch (...) { |
908 | | // For things like bad_cast in boost::lexical_cast |
909 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION); |
910 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
911 | 0 | answer = isc::config::createAnswer(status_code, "undefined configuration" |
912 | 0 | " parsing error"); |
913 | 0 | } |
914 | 12.1k | } |
915 | | |
916 | 26.1k | if (status_code == CONTROL_RESULT_SUCCESS && (!check_only || extra_checks)) { |
917 | 12.1k | try { |
918 | | // This occurs last as if it succeeds, there is no easy way to |
919 | | // revert it. As a result, the failure to commit a subsequent |
920 | | // change causes problems when trying to roll back. |
921 | 12.1k | HooksManager::prepareUnloadLibraries(); |
922 | 12.1k | static_cast<void>(HooksManager::unloadLibraries()); |
923 | 12.1k | IOServiceMgr::instance().clearIOServices(); |
924 | 12.1k | const HooksConfig& libraries = |
925 | 12.1k | CfgMgr::instance().getStagingCfg()->getHooksConfig(); |
926 | 12.1k | bool multi_threading_enabled = true; |
927 | 12.1k | uint32_t thread_count = 0; |
928 | 12.1k | uint32_t queue_size = 0; |
929 | 12.1k | CfgMultiThreading::extract(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading(), |
930 | 12.1k | multi_threading_enabled, thread_count, queue_size); |
931 | 12.1k | libraries.loadLibraries(multi_threading_enabled); |
932 | 12.1k | } catch (const isc::Exception& ex) { |
933 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what()); |
934 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
935 | 0 | answer = isc::config::createAnswer(status_code, ex.what()); |
936 | 0 | } catch (...) { |
937 | | // For things like bad_cast in boost::lexical_cast |
938 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION); |
939 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
940 | 0 | answer = isc::config::createAnswer(status_code, "undefined configuration" |
941 | 0 | " parsing error"); |
942 | 0 | } |
943 | | |
944 | 12.1k | if (extra_checks && status_code == CONTROL_RESULT_SUCCESS) { |
945 | | // Re-open lease and host database with new parameters. |
946 | 11 | try { |
947 | | // Get the staging configuration. |
948 | 11 | srv_config = CfgMgr::instance().getStagingCfg(); |
949 | | |
950 | | // Create managers like @ref ControlledDhcpv4Srv::processConfig |
951 | | // does. No need to change "persist" value here because |
952 | | // @ref MultiThreadingMgr::isTestMode is used instead. |
953 | | // This will also make log messages consistent with the checked |
954 | | // config values. |
955 | 11 | CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess(); |
956 | 11 | string params = "universe=4"; |
957 | 11 | cfg_db->setAppendedParameters(params); |
958 | 11 | cfg_db->createManagers(); |
959 | 11 | } catch (const std::exception& ex) { |
960 | 11 | status_code = CONTROL_RESULT_FATAL_ERROR; |
961 | 11 | answer = isc::config::createAnswer(status_code, ex.what()); |
962 | 11 | } |
963 | 11 | } |
964 | 12.1k | } |
965 | | |
966 | | // Log the list of known backends. |
967 | 26.1k | LeaseMgrFactory::logRegistered(); |
968 | | |
969 | | // Log the list of known backends. |
970 | 26.1k | HostDataSourceFactory::logRegistered(); |
971 | | |
972 | | // Log the list of known backends. |
973 | 26.1k | LegalLogMgrFactory::logRegistered(); |
974 | | |
975 | | // Log the list of known backends. |
976 | 26.1k | ConfigBackendDHCPv4Mgr::instance().logRegistered(); |
977 | | |
978 | | // Moved from the commit block to add the config backend indication. |
979 | 26.1k | if (status_code == CONTROL_RESULT_SUCCESS && (!check_only || extra_checks)) { |
980 | 12.1k | try { |
981 | | // If there are config backends, fetch and merge into staging config |
982 | 12.1k | server.getCBControl()->databaseConfigFetch(srv_config, |
983 | 12.1k | CBControlDHCPv4::FetchMode::FETCH_ALL); |
984 | 12.1k | } catch (const isc::Exception& ex) { |
985 | 0 | std::ostringstream err; |
986 | 0 | err << "during update from config backend database: " << ex.what(); |
987 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str()); |
988 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
989 | 0 | answer = isc::config::createAnswer(status_code, err.str()); |
990 | 0 | } catch (...) { |
991 | | // For things like bad_cast in boost::lexical_cast |
992 | 0 | std::ostringstream err; |
993 | 0 | err << "during update from config backend database: " |
994 | 0 | << "undefined configuration parsing error"; |
995 | 0 | LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(err.str()); |
996 | 0 | status_code = CONTROL_RESULT_FATAL_ERROR; |
997 | 0 | answer = isc::config::createAnswer(status_code, err.str()); |
998 | 0 | } |
999 | 12.1k | } |
1000 | | |
1001 | | // Rollback changes as the configuration parsing failed. |
1002 | 26.1k | if (check_only || status_code != CONTROL_RESULT_SUCCESS) { |
1003 | | // Revert to original configuration of runtime option definitions |
1004 | | // in the libdhcp++. |
1005 | 13.9k | LibDHCP::revertRuntimeOptionDefs(); |
1006 | | |
1007 | 13.9k | if (status_code == CONTROL_RESULT_SUCCESS && extra_checks) { |
1008 | 0 | auto notify_libraries = ControlledDhcpv4Srv::finishConfigHookLibraries(config_set); |
1009 | 0 | if (notify_libraries) { |
1010 | 0 | return (notify_libraries); |
1011 | 0 | } |
1012 | | |
1013 | | /// Let postponed hook initializations run. |
1014 | 0 | try { |
1015 | | // Handle events registered by hooks using external IOService objects. |
1016 | 0 | IOServiceMgr::instance().pollIOServices(); |
1017 | 0 | } catch (const std::exception& ex) { |
1018 | 0 | std::ostringstream err; |
1019 | 0 | err << "Error initializing hooks: " |
1020 | 0 | << ex.what(); |
1021 | 0 | return (isc::config::createAnswer(CONTROL_RESULT_FATAL_ERROR, err.str())); |
1022 | 0 | } |
1023 | 0 | } |
1024 | | |
1025 | 13.9k | return (answer); |
1026 | 13.9k | } |
1027 | | |
1028 | 12.1k | LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE) |
1029 | 12.1k | .arg(CfgMgr::instance().getStagingCfg()-> |
1030 | 12.1k | getConfigSummary(SrvConfig::CFGSEL_ALL4)); |
1031 | | |
1032 | | // Also calculate SHA256 hash of the config that was just set and |
1033 | | // append it to the response. |
1034 | 12.1k | ConstElementPtr config = CfgMgr::instance().getStagingCfg()->toElement(); |
1035 | 12.1k | string hash = BaseCommandMgr::getHash(config); |
1036 | 12.1k | ElementPtr hash_map = Element::createMap(); |
1037 | 12.1k | hash_map->set("hash", Element::create(hash)); |
1038 | | |
1039 | | // Everything was fine. Configuration is successful. |
1040 | 12.1k | answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, "Configuration successful.", hash_map); |
1041 | 12.1k | return (answer); |
1042 | 26.1k | } |
1043 | | |
1044 | | } // namespace dhcp |
1045 | | } // namespace isc |