Coverage Report

Created: 2026-05-16 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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