1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
// Copyright (C) 2019-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>
#include <dhcpsrv/cb_ctl_dhcp4.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/client_class_def.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/parsers/simple_parser4.h>
#include <hooks/callout_handle.h>
#include <hooks/hooks_manager.h>
#include <boost/foreach.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc::db;
using namespace isc::data;
using namespace isc::process;
using namespace isc::hooks;

namespace {

/// Structure that holds registered hook indexes.
struct CbCtlHooks {
    int hook_index_cb4_updated_; ///< index for "cb4_updated" hook point.

    /// Constructor that registers hook points for CBControlDHCPv4.
    CbCtlHooks() {
        hook_index_cb4_updated_ = HooksManager::registerHook("cb4_updated");
    }
};

// Declare a Hooks object. As this is outside any function or method, it
// will be instantiated (and the constructor run) when the module is loaded.
// As a result, the hook indexes will be defined before any method in this
// module is called.
CbCtlHooks hooks_;

}; // anonymous namespace

namespace isc {
namespace dhcp {

void
CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector,
                                     const ServerSelector& server_selector,
                                     const boost::posix_time::ptime& lb_modification_time,
                                     const AuditEntryCollection& audit_entries) {

    auto globals_fetched = false;
    auto reconfig = audit_entries.empty();
    auto cb_update = !reconfig;
    auto current_cfg = CfgMgr::instance().getCurrentCfg();
    auto staging_cfg = CfgMgr::instance().getStagingCfg();

    // Let's first delete all the configuration elements for which DELETE audit
    // entries are found. Although, this may break chronology of the audit in
    // some cases it should not affect the end result of the data fetch. If the
    // object was created and then subsequently deleted, we will first try to
    // delete this object from the local configuration (which will fail because
    // the object does not exist) and then we will try to fetch it from the
    // database which will return no result.
    if (cb_update) {

        auto external_cfg = CfgMgr::instance().createExternalCfg();

        // Get audit entries for deleted global parameters.
        auto const& index = audit_entries.get<AuditEntryObjectTypeTag>();
        auto range = index.equal_range(boost::make_tuple("dhcp4_global_parameter",
                                                         AuditEntry::ModificationType::DELETE));
        if (range.first != range.second) {
            // Some globals have been deleted. Since we currently don't track database
            // identifiers of the global parameters we have to fetch all global
            // parameters for this server. Next, we simply replace existing
            // global parameters with the new parameters. This is slightly
            // inefficient but only slightly. Note that this is a single
            // database query and the number of global parameters is small.
            data::StampedValueCollection globals;
            globals = getMgr().getPool()->getAllGlobalParameters4(backend_selector, server_selector);
            translateAndAddGlobalsToConfig(external_cfg, globals);

            // Add defaults.
            external_cfg->applyDefaultsConfiguredGlobals(SimpleParser4::GLOBAL4_DEFAULTS);

            // Sanity check it.
            external_cfg->sanityChecksLifetime("valid-lifetime");

            // Now that we successfully fetched the new global parameters, let's
            // remove existing ones and merge them into the current configuration.
            current_cfg->clearConfiguredGlobals();
            CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence());
            globals_fetched = true;
        }

        try {
            // Get audit entries for deleted option definitions and delete each
            // option definition from the current configuration for which the
            // audit entry is found.
            range = index.equal_range(boost::make_tuple("dhcp4_option_def",
                                                        AuditEntry::ModificationType::DELETE));
            BOOST_FOREACH(auto const& entry, range) {
                current_cfg->getCfgOptionDef()->del(entry->getObjectId());
            }

            // Repeat the same for other configuration elements.

            range = index.equal_range(boost::make_tuple("dhcp4_options",
                                                        AuditEntry::ModificationType::DELETE));
            BOOST_FOREACH(auto const& entry, range) {
                current_cfg->getCfgOption()->del(entry->getObjectId());
            }

            range = index.equal_range(boost::make_tuple("dhcp4_client_class",
                                                        AuditEntry::ModificationType::DELETE));
            BOOST_FOREACH(auto const& entry, range) {
                current_cfg->getClientClassDictionary()->removeClass(entry->getObjectId());
            }

            range = index.equal_range(boost::make_tuple("dhcp4_shared_network",
                                                        AuditEntry::ModificationType::DELETE));
            BOOST_FOREACH(auto const& entry, range) {
                current_cfg->getCfgSharedNetworks4()->del(entry->getObjectId());
            }

            range = index.equal_range(boost::make_tuple("dhcp4_subnet",
                                                        AuditEntry::ModificationType::DELETE));
            BOOST_FOREACH(auto const& entry, range) {
                // If the deleted subnet belongs to a shared network and the
                // shared network is not being removed, we need to detach the
                // subnet from the shared network.
                auto subnet = current_cfg->getCfgSubnets4()->getBySubnetId(entry->getObjectId());
                if (subnet) {
                    // Check if the subnet belongs to a shared network.
                    SharedNetwork4Ptr network;
                    subnet->getSharedNetwork(network);
                    if (network) {
                        // Detach the subnet from the shared network.
                        network->del(subnet->getID());
                    }
                    // Actually delete the subnet from the configuration.
                    current_cfg->getCfgSubnets4()->del(entry->getObjectId());
                }
            }

        } catch (...) {
            // Ignore errors thrown when attempting to delete a non-existing
            // configuration entry. There is no guarantee that the deleted
            // entry is actually there as we're not processing the audit
            // chronologically.
        }
    }

    // Create the external config into which we'll fetch backend config data.
    auto external_cfg = CfgMgr::instance().createExternalCfg();

    // First let's fetch the globals and add them to external config.
    AuditEntryCollection updated_entries;
    if (!globals_fetched) {
        if (cb_update) {
            updated_entries = fetchConfigElement(audit_entries, "dhcp4_global_parameter");
        }
        if (reconfig || !updated_entries.empty()) {
            data::StampedValueCollection globals;
            globals = getMgr().getPool()->getModifiedGlobalParameters4(backend_selector, server_selector,
                                                                       lb_modification_time);
            translateAndAddGlobalsToConfig(external_cfg, globals);
            globals_fetched = true;
        }
    }

    // Now we fetch the option definitions and add them.
    if (cb_update) {
        updated_entries = fetchConfigElement(audit_entries, "dhcp4_option_def");
    }
    if (reconfig || !updated_entries.empty()) {
        OptionDefContainer option_defs =
            getMgr().getPool()->getModifiedOptionDefs4(backend_selector, server_selector,
                                                       lb_modification_time);
        for (auto const& option_def : option_defs) {
            if (!audit_entries.empty() && !hasObjectId(updated_entries, option_def->getId())) {
                continue;
            }
            external_cfg->getCfgOptionDef()->add(option_def);
        }
    }

    // Next fetch the options. They are returned as a container of OptionDescriptors.
    if (cb_update) {
        updated_entries = fetchConfigElement(audit_entries, "dhcp4_options");
    }
    if (reconfig || !updated_entries.empty()) {
        OptionContainer options = getMgr().getPool()->getModifiedOptions4(backend_selector,
                                                                          server_selector,
                                                                          lb_modification_time);
        for (auto const& option : options) {
            if (!audit_entries.empty() && !hasObjectId(updated_entries, option.getId())) {
                continue;
            }
            external_cfg->getCfgOption()->add(option, option.space_name_);
        }
    }

    // Fetch client classes. They are returned in a ClientClassDictionary.
    if (cb_update) {
        updated_entries = fetchConfigElement(audit_entries, "dhcp4_client_class");
    }
    if (reconfig || !updated_entries.empty()) {
        ClientClassDictionary client_classes = getMgr().getPool()->getAllClientClasses4(backend_selector,
                                                                                        server_selector);
        // Match expressions are not initialized for classes returned from the config backend.
        // We have to ensure to initialize them before they can be used by the server.
        client_classes.initMatchExpr(AF_INET);

        // Class options also need to be created when returned from the config backend.
        client_classes.createOptions(external_cfg->getCfgOptionDef());

        external_cfg->setClientClassDictionary(boost::make_shared<ClientClassDictionary>(client_classes));
    }

    // Allocator selection at the global level can affect subnets and shared networks
    // for which the allocator hasn't been specified explicitly. Let's see if the
    // allocator has been specified at the global level.
    std::string global_allocator;
    auto allocator = external_cfg->getConfiguredGlobal(CfgGlobals::ALLOCATOR);<--- Shadowed declaration
    if (allocator && (allocator->getType() == Element::string)) {
       global_allocator = allocator->stringValue();
    }

    // If we're fetching the changes from the config backend we also want
    // to see if the global allocator has changed. Let's get the currently
    // used allocator too.
    auto allocator_changed = false;
    // We're only affected by the allocator change if this is the update from
    // the configuration backend.
    if (cb_update) {<--- First condition
        auto allocator = CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::ALLOCATOR);<--- Shadow variable
        if (allocator && (allocator->getType() == Element::string)) {
            allocator_changed = (global_allocator != allocator->stringValue());
        }
    }

    // Now fetch the shared networks.
    if (cb_update) {<--- Second condition
        updated_entries = fetchConfigElement(audit_entries, "dhcp4_shared_network");
    }
    SharedNetwork4Collection networks;
    if (allocator_changed || reconfig) {
        // A change of the allocator or the server reconfiguration can affect all
        // shared networks. Get all shared networks.
        networks = getMgr().getPool()->getAllSharedNetworks4(backend_selector, server_selector);

    } else if (!updated_entries.empty()) {
        // An update from the config backend when the global allocator hasn't changed
        // means that we only need to handle the modified subnets.
        networks = getMgr().getPool()->getModifiedSharedNetworks4(backend_selector, server_selector,
                                                                  lb_modification_time);
    }
    // Iterate over all shared networks that may require reconfiguration.
    for (auto const& network : networks) {
        if (!allocator_changed && cb_update && !hasObjectId(updated_entries, network->getId())) {
            continue;
        }
        // In order to take advantage of the dynamic inheritance of global
        // parameters to a shared network we need to set a callback function
        // for each network to allow for fetching global parameters.
        network->setFetchGlobalsFn([] () -> ConstCfgGlobalsPtr {
             return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
        });
        network->setDefaultAllocatorType(global_allocator);
        external_cfg->getCfgSharedNetworks4()->add(network);
    }

    // Next, fetch the subnets.
    if (cb_update) {
        updated_entries = fetchConfigElement(audit_entries, "dhcp4_subnet");
    }
    Subnet4Collection subnets;
    if (allocator_changed || reconfig) {
        // A change of the allocator or the server reconfiguration can affect all
        // subnets. Get all subnets.
        subnets = getMgr().getPool()->getAllSubnets4(backend_selector, server_selector);

    }  else if (!updated_entries.empty()) {
        // An update from the config backend when the global allocator hasn't changed
        // means that we only need to handle the modified subnets.
        subnets = getMgr().getPool()->getModifiedSubnets4(backend_selector,
                                                          server_selector,
                                                          lb_modification_time);
    }
    // Iterate over all subnets that may require reconfiguration.
    for (auto const& subnet : subnets) {
        if (!allocator_changed && cb_update && !hasObjectId(updated_entries, subnet->getID())) {
            continue;
        }
        // In order to take advantage of the dynamic inheritance of global
        // parameters to a subnet we need to set a callback function for each
        // subnet to allow for fetching global parameters.
        subnet->setFetchGlobalsFn([] () -> ConstCfgGlobalsPtr {
            return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
        });
        subnet->setDefaultAllocatorType(global_allocator);
        external_cfg->getCfgSubnets4()->add(subnet);
    }

    if (reconfig) {
        // If we're configuring the server after startup, we do not apply the
        // ip-reservations-unique setting here. It will be applied when the
        // configuration is committed.
        external_cfg->sanityChecksLifetime(*staging_cfg, "valid-lifetime");
        CfgMgr::instance().mergeIntoStagingCfg(external_cfg->getSequence());

    } else {
        if (globals_fetched) {
            // ip-reservations-unique parameter requires special handling because
            // setting it to false may be unsupported by some host backends.
            bool ip_unique = true;
            auto ip_unique_param = external_cfg->getConfiguredGlobal("ip-reservations-unique");
            if (ip_unique_param && (ip_unique_param->getType() == Element::boolean)) {
                ip_unique = ip_unique_param->boolValue();
            }
            // First try to use the new setting to configure the HostMgr because it
            // may fail if the backend does not support it.
            if (!HostMgr::instance().setIPReservationsUnique(ip_unique)) {
                // The new setting is unsupported by the backend, so do not apply this
                // setting at all.
                LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_IPV4_RESERVATIONS_NON_UNIQUE_IGNORED);
                external_cfg->addConfiguredGlobal("ip-reservations-unique", Element::create(true));
            }
        }
        external_cfg->sanityChecksLifetime(*current_cfg, "valid-lifetime");
        CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence());
        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->initAllocatorsAfterConfigure();
    }

    LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIG4_MERGED);

    if (cb_update &&
        HooksManager::calloutsPresent(hooks_.hook_index_cb4_updated_)) {
        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();

        // Use the RAII wrapper to make sure that the callout handle state is
        // reset when this object goes out of scope. All hook points must do
        // it to prevent possible circular dependency between the callout
        // handle and its arguments.
        ScopedCalloutHandleState callout_handle_state(callout_handle);

        // Pass a shared pointer to audit entries.
        AuditEntryCollectionPtr ptr(new AuditEntryCollection(audit_entries));
        callout_handle->setArgument("audit_entries", ptr);

        // Call the callouts
        HooksManager::callCallouts(hooks_.hook_index_cb4_updated_, *callout_handle);

        // Ignore the result.
    }
}

} // end of namespace isc::dhcp
} // end of namespace isc