json_config_parser.cc 15.9 KB
Newer Older
1
// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// 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/.
6

7 8
#include <config.h>

9
#include <cc/command_interpreter.h>
10
#include <dhcp4/dhcp4_log.h>
11
#include <dhcp4/simple_parser4.h>
12 13
#include <dhcp/libdhcp++.h>
#include <dhcp/option_definition.h>
14
#include <dhcpsrv/cfg_option.h>
15
#include <dhcpsrv/cfgmgr.h>
16
#include <dhcpsrv/parsers/client_class_def_parser.h>
17
#include <dhcp4/json_config_parser.h>
18 19
#include <dhcpsrv/parsers/dbaccess_parser.h>
#include <dhcpsrv/parsers/dhcp_parsers.h>
20
#include <dhcpsrv/parsers/expiration_config_parser.h>
21 22
#include <dhcpsrv/parsers/host_reservation_parser.h>
#include <dhcpsrv/parsers/host_reservations_list_parser.h>
23
#include <dhcpsrv/parsers/ifaces_config_parser.h>
24
#include <dhcpsrv/parsers/option_data_parser.h>
25
#include <dhcpsrv/timer_mgr.h>
26
#include <hooks/hooks_parser.h>
27
#include <config/command_mgr.h>
28
#include <util/encode/hex.h>
29
#include <util/strutil.h>
30

Tomek Mrugalski's avatar
Tomek Mrugalski committed
31 32 33
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
34

Tomek Mrugalski's avatar
Tomek Mrugalski committed
35
#include <limits>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
36
#include <iostream>
37
#include <netinet/in.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
38 39 40
#include <vector>
#include <map>

41
using namespace std;
42 43
using namespace isc;
using namespace isc::dhcp;
44 45
using namespace isc::data;
using namespace isc::asiolink;
46
using namespace isc::hooks;
47

48 49
namespace {

50
/// @brief Parser that takes care of global DHCPv4 parameters.
51 52
///
/// See @ref parse method for a list of supported parameters.
53 54 55 56 57 58
class Dhcp4ConfigParser : public isc::data::SimpleParser {
public:

    /// @brief Sets global parameters in staging configuration
    ///
    /// @param global global configuration scope
59
    /// @param cfg Server configuration (parsed parameters will be stored here)
60 61 62 63 64 65 66 67 68
    ///
    /// Currently this method sets the following global parameters:
    ///
    /// - echo-client-id
    /// - decline-probation-period
    /// - dhcp4o6-port
    ///
    /// @throw DhcpConfigError if parameters are missing or
    /// or having incorrect values.
69
    void parse(SrvConfigPtr cfg, ConstElementPtr global) {
70 71 72 73

        // Set whether v4 server is supposed to echo back client-id
        // (yes = RFC6842 compatible, no = backward compatibility)
        bool echo_client_id = getBoolean(global, "echo-client-id");
74
        cfg->setEchoClientId(echo_client_id);
75

76 77 78 79 80 81
        // Set the probation period for decline handling.
        uint32_t probation_period =
            getUint32(global, "decline-probation-period");
        cfg->setDeclinePeriod(probation_period);

        // Set the DHCPv4-over-DHCPv6 interserver port.
82
        uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
83
        cfg->setDhcp4o6Port(dhcp4o6_port);
84 85 86
    }
};

87 88 89 90 91
} // anonymous namespace

namespace isc {
namespace dhcp {

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
/// @brief Initialize the command channel based on the staging configuration
///
/// Only close the current channel, if the new channel configuration is
/// different.  This avoids disconnecting a client and hence not sending them
/// a command result, unless they specifically alter the channel configuration.
/// In that case the user simply has to accept they'll be disconnected.
///
void configureCommandChannel() {
    // Get new socket configuration.
    ConstElementPtr sock_cfg =
        CfgMgr::instance().getStagingCfg()->getControlSocketInfo();

    // Get current socket configuration.
    ConstElementPtr current_sock_cfg =
            CfgMgr::instance().getCurrentCfg()->getControlSocketInfo();

    // Determine if the socket configuration has changed. It has if
    // both old and new configuration is specified but respective
Josh Soref's avatar
Josh Soref committed
110
    // data elements aren't equal.
111 112 113 114 115
    bool sock_changed = (sock_cfg && current_sock_cfg &&
                         !sock_cfg->equals(*current_sock_cfg));

    // If the previous or new socket configuration doesn't exist or
    // the new configuration differs from the old configuration we
Josh Soref's avatar
Josh Soref committed
116
    // close the existing socket and open a new socket as appropriate.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
    // Note that closing an existing socket means the clien will not
    // receive the configuration result.
    if (!sock_cfg || !current_sock_cfg || sock_changed) {
        // Close the existing socket (if any).
        isc::config::CommandMgr::instance().closeCommandSocket();

        if (sock_cfg) {
            // This will create a control socket and install the external
            // socket in IfaceMgr. That socket will be monitored when
            // Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and
            // callback in CommandMgr will be called, if necessary.
            isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
        }
    }
}

133
isc::data::ConstElementPtr
134 135
configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
                     bool check_only) {
136
    if (!config_set) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
137 138 139
        ConstElementPtr answer = isc::config::createAnswer(1,
                                 string("Can't parse NULL config"));
        return (answer);
140 141
    }

142
    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND,
143
              DHCP4_CONFIG_START).arg(config_set->str());
144

145 146 147 148
    // Before starting any subnet operations, let's reset the subnet-id counter,
    // so newly recreated configuration starts with first subnet-id equal 1.
    Subnet::resetSubnetID();

149
    // Remove any existing timers.
150 151 152
    if (!check_only) {
        TimerMgr::instance()->unregisterTimers();
    }
153

154 155
    // Revert any runtime option definitions configured so far and not committed.
    LibDHCP::revertRuntimeOptionDefs();
156
    // Let's set empty container in case a user hasn't specified any configuration
Josh Soref's avatar
Josh Soref committed
157
    // for option definitions. This is equivalent to committing empty container.
158
    LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer());
159

160
    // Answer will hold the result.
161
    ConstElementPtr answer;
Francis Dupont's avatar
Francis Dupont committed
162
    // Rollback informs whether error occurred and original data
163 164
    // have to be restored to global storages.
    bool rollback = false;
165
    // config_pair holds the details of the current parser when iterating over
166 167
    // the parsers.  It is declared outside the loops so in case of an error,
    // the name of the failing parser can be retrieved in the "catch" clause.
168
    ConfigPair config_pair;
169
    try {
170

171 172
        SrvConfigPtr srv_cfg = CfgMgr::instance().getStagingCfg();

173 174 175
        // This is a way to convert ConstElementPtr to ElementPtr.
        // We need a config that can be edited, because we will insert
        // default values and will insert derived values as well.
176
        ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(config_set);
177 178

        // Set all default values if not specified by the user.
179
        SimpleParser4::setAllDefaults(mutable_cfg);
180

181 182 183
        // And now derive (inherit) global parameters to subnets, if not specified.
        SimpleParser4::deriveParameters(mutable_cfg);

184 185 186
        // We need definitions first
        ConstElementPtr option_defs = mutable_cfg->get("option-def");
        if (option_defs) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
187
            OptionDefListParser parser;
188
            CfgOptionDefPtr cfg_option_def = srv_cfg->getCfgOptionDef();
189 190 191
            parser.parse(cfg_option_def, option_defs);
        }

192
        // Make parsers grouping.
193
        const std::map<std::string, ConstElementPtr>& values_map =
194
                                                        mutable_cfg->mapValue();
195
        BOOST_FOREACH(config_pair, values_map) {
196 197 198
            // In principle we could have the following code structured as a series
            // of long if else if clauses. That would give a marginal performance
            // boost, but would make the code less readable. We had serious issues
Tomek Mrugalski's avatar
Tomek Mrugalski committed
199
            // with the parser code debugability, so I decided to keep it as a
200
            // series of independent ifs.
201 202 203 204 205 206 207
            if (config_pair.first == "option-def") {
                // This is converted to SimpleParser and is handled already above.
                continue;
            }

            if (config_pair.first == "option-data") {
                OptionDataListParser parser(AF_INET);
208
                CfgOptionPtr cfg_option = srv_cfg->getCfgOption();
209 210 211 212
                parser.parse(cfg_option, config_pair.second);
                continue;
            }

213 214 215 216 217 218
            if (config_pair.first == "control-socket") {
                ControlSocketParser parser;
                parser.parse(*srv_cfg, config_pair.second);
                continue;
            }

219 220 221 222 223 224
            if (config_pair.first == "host-reservation-identifiers") {
                HostReservationIdsParser4 parser;
                parser.parse(config_pair.second);
                continue;
            }

225
            if (config_pair.first == "interfaces-config") {
Francis Dupont's avatar
Francis Dupont committed
226 227 228 229 230 231
                ElementPtr ifaces_cfg =
                    boost::const_pointer_cast<Element>(config_pair.second);
                if (check_only) {
                    // No re-detection in check only mode
                    ifaces_cfg->set("re-detect", Element::create(false));
                }
232
                IfacesConfigParser parser(AF_INET);
233
                CfgIfacePtr cfg_iface = srv_cfg->getCfgIface();
Francis Dupont's avatar
Francis Dupont committed
234
                parser.parse(cfg_iface, ifaces_cfg);
235 236 237
                continue;
            }

Francis Dupont's avatar
Francis Dupont committed
238 239 240 241 242 243
            if (config_pair.first == "expired-leases-processing") {
                ExpirationConfigParser parser;
                parser.parse(config_pair.second);
                continue;
            }

244
            if (config_pair.first == "hooks-libraries") {
245 246 247 248
                HooksLibrariesParser hooks_parser;
                HooksConfig& libraries = srv_cfg->getHooksConfig();
                hooks_parser.parse(libraries, config_pair.second);
                libraries.verifyLibraries(config_pair.second->getPosition());
249 250
                continue;
            }
251

252
            // Legacy DhcpConfigParser stuff below
253
            if (config_pair.first == "dhcp-ddns") {
254 255
                // Apply defaults
                D2ClientConfigParser::setAllDefaults(config_pair.second);
256 257
                D2ClientConfigParser parser;
                D2ClientConfigPtr cfg = parser.parse(config_pair.second);
258
                srv_cfg->setD2ClientConfig(cfg);
259 260 261
                continue;
            }

262 263
            if (config_pair.first == "client-classes") {
                ClientClassDefListParser parser;
264 265
                ClientClassDictionaryPtr dictionary =
                    parser.parse(config_pair.second, AF_INET);
266 267 268 269
                srv_cfg->setClientClassDictionary(dictionary);
                continue;
            }

270 271 272 273 274 275 276 277
            // Please move at the end when migration will be finished.
            if (config_pair.first == "lease-database") {
                DbAccessParser parser(DbAccessParser::LEASE_DB);
                CfgDbAccessPtr cfg_db_access = srv_cfg->getCfgDbAccess();
                parser.parse(cfg_db_access, config_pair.second);
                continue;
            }

278
            if (config_pair.first == "hosts-database") {
279 280 281
                DbAccessParser parser(DbAccessParser::HOSTS_DB);
                CfgDbAccessPtr cfg_db_access = srv_cfg->getCfgDbAccess();
                parser.parse(cfg_db_access, config_pair.second);
282 283 284
                continue;
            }

285 286 287 288 289 290 291 292
            if (config_pair.first == "subnet4") {
                SrvConfigPtr srv_cfg = CfgMgr::instance().getStagingCfg();
                Subnets4ListConfigParser subnets_parser;
                // parse() returns number of subnets parsed. We may log it one day.
                subnets_parser.parse(srv_cfg, config_pair.second);
                continue;
            }

293 294 295
            // Timers are not used in the global scope. Their values are derived
            // to specific subnets (see SimpleParser6::deriveParameters).
            // decline-probation-period, dhcp4o6-port, echo-client-id are
296
            // handled in global_parser.parse() which sets global parameters.
297 298 299 300 301 302 303 304 305 306 307 308
            // match-client-id is derived to subnet scope level.
            if ( (config_pair.first == "renew-timer") ||
                 (config_pair.first == "rebind-timer") ||
                 (config_pair.first == "valid-lifetime") ||
                 (config_pair.first == "decline-probation-period") ||
                 (config_pair.first == "dhcp4o6-port") ||
                 (config_pair.first == "echo-client-id") ||
                 (config_pair.first == "match-client-id") ||
                 (config_pair.first == "next-server")) {
                continue;
            }

309 310 311 312
            // If we got here, no code handled this parameter, so we bail out.
            isc_throw(DhcpConfigError,
                      "unsupported global configuration parameter: " << config_pair.first
                      << " (" << config_pair.second->getPosition() << ")");
313
        }
314

315 316
        // Apply global options in the staging config.
        Dhcp4ConfigParser global_parser;
317
        global_parser.parse(srv_cfg, mutable_cfg);
318

319
    } catch (const isc::Exception& ex) {
320
        LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
321
                  .arg(config_pair.first).arg(ex.what());
322
        answer = isc::config::createAnswer(1, ex.what());
323

Francis Dupont's avatar
Francis Dupont committed
324
        // An error occurred, so make sure that we restore original data.
325 326
        rollback = true;

327
    } catch (...) {
328
        // For things like bad_cast in boost::lexical_cast
329
        LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(config_pair.first);
330 331
        answer = isc::config::createAnswer(1, "undefined configuration"
                                           " processing error");
332

Francis Dupont's avatar
Francis Dupont committed
333
        // An error occurred, so make sure that we restore original data.
334
        rollback = true;
335 336
    }

337 338 339 340 341 342 343 344 345
    if (check_only) {
        rollback = true;
        if (!answer) {
            answer = isc::config::createAnswer(0,
            "Configuration seems sane. Control-socket, hook-libraries, and D2 "
            "configuration were sanity checked, but not applied.");
        }
    }

346 347 348 349 350 351
    // So far so good, there was no parsing error so let's commit the
    // configuration. This will add created subnets and option values into
    // the server's configuration.
    // This operation should be exception safe but let's make sure.
    if (!rollback) {
        try {
352 353 354 355

            // Setup the command channel.
            configureCommandChannel();

356 357
            // No need to commit interface names as this is handled by the
            // CfgMgr::commit() function.
358

359 360 361 362
            // Apply the staged D2ClientConfig, used to be done by parser commit
            D2ClientConfigPtr cfg;
            cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
            CfgMgr::instance().setD2ClientConfig(cfg);
363 364 365 366

            // This occurs last as if it succeeds, there is no easy way
            // revert it.  As a result, the failure to commit a subsequent
            // change causes problems when trying to roll back.
367 368 369
            const HooksConfig& libraries =
                CfgMgr::instance().getStagingCfg()->getHooksConfig();
            libraries.loadLibraries();
370 371
        }
        catch (const isc::Exception& ex) {
372
            LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
373
            answer = isc::config::createAnswer(2, ex.what());
374 375
            rollback = true;
        } catch (...) {
376
            // For things like bad_cast in boost::lexical_cast
377
            LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
378 379
            answer = isc::config::createAnswer(2, "undefined configuration"
                                               " parsing error");
380
            rollback = true;
381 382
        }
    }
383 384 385

    // Rollback changes as the configuration parsing failed.
    if (rollback) {
386 387 388
        // Revert to original configuration of runtime option definitions
        // in the libdhcp++.
        LibDHCP::revertRuntimeOptionDefs();
389 390 391
        return (answer);
    }

392
    LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE)
393
        .arg(CfgMgr::instance().getStagingCfg()->
394
             getConfigSummary(SrvConfig::CFGSEL_ALL4));
395

396
    // Everything was fine. Configuration is successful.
397
    answer = isc::config::createAnswer(0, "Configuration successful.");
398 399 400 401 402
    return (answer);
}

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