2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-23 18:37:35 +00:00
kea/src/bin/dhcp4/json_config_parser.cc

560 lines
22 KiB
C++
Raw Normal View History

// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
2012-10-11 19:08:23 +02:00
//
// 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/.
2012-10-11 19:08:23 +02:00
#include <config.h>
#include <cc/command_interpreter.h>
2012-10-11 19:08:23 +02:00
#include <dhcp4/dhcp4_log.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/cfgmgr.h>
2014-05-21 16:16:18 +02:00
#include <dhcp4/json_config_parser.h>
#include <dhcpsrv/parsers/client_class_def_parser.h>
#include <dhcpsrv/parsers/dbaccess_parser.h>
#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <dhcpsrv/parsers/expiration_config_parser.h>
#include <dhcpsrv/parsers/host_reservation_parser.h>
#include <dhcpsrv/parsers/host_reservations_list_parser.h>
#include <dhcpsrv/parsers/ifaces_config_parser.h>
#include <dhcpsrv/parsers/option_data_parser.h>
#include <dhcpsrv/parsers/simple_parser4.h>
#include <dhcpsrv/parsers/shared_networks_list_parser.h>
#include <dhcpsrv/timer_mgr.h>
#include <hooks/hooks_parser.h>
#include <config/command_mgr.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
2012-12-12 13:02:40 +01:00
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
2012-12-11 18:07:37 +01:00
#include <limits>
#include <iostream>
#include <netinet/in.h>
#include <vector>
#include <map>
2012-10-11 19:08:23 +02:00
using namespace std;
using namespace isc;
using namespace isc::dhcp;
2012-10-11 19:08:23 +02:00
using namespace isc::data;
using namespace isc::asiolink;
using namespace isc::hooks;
2012-10-11 19:08:23 +02:00
namespace {
/// @brief Parser that takes care of global DHCPv4 parameters and utility
/// functions that work on global level.
///
/// This class is a collection of utility method that either handle
/// global parameters (see @ref parse), or conducts operations on
/// global level (see @ref sanityChecks and @ref copySubnets4).
///
/// See @ref parse method for a list of supported parameters.
class Dhcp4ConfigParser : public isc::data::SimpleParser {
public:
/// @brief Sets global parameters in staging configuration
///
/// @param global global configuration scope
/// @param cfg Server configuration (parsed parameters will be stored here)
///
/// 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.
void parse(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
// 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");
cfg->setEchoClientId(echo_client_id);
// 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.
uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
cfg->setDhcp4o6Port(dhcp4o6_port);
}
/// @brief Copies subnets from shared networks to regular subnets container
///
/// @param from pointer to shared networks container (copy from here)
/// @param dest pointer to cfg subnets4 (copy to here)
/// @throw BadValue if any pointer is missing
/// @throw DhcpConfigError if there are duplicates (or other subnet defects)
void
copySubnets4(const CfgSubnets4Ptr& dest, const CfgSharedNetworks4Ptr& from) {
if (!dest || !from) {
isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null");
}
const SharedNetwork4Collection* networks = from->getAll();
if (!networks) {
// Nothing to copy. Technically, it should return a pointer to empty
// container, but let's handle null pointer as well.
return;
}
// Let's go through all the networks one by one
for (auto net = networks->begin(); net != networks->end(); ++net) {
// For each network go through all the subnets in it.
const Subnet4Collection* subnets = (*net)->getAllSubnets();
if (!subnets) {
// Shared network without subnets it weird, but we decided to
// accept such configurations.
continue;
}
// For each subnet, add it to a list of regular subnets.
for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
dest->add(*subnet);
}
}
}
/// @brief Conducts global sanity checks
///
/// This method is very simple now, but more sanity checks are expected
/// in the future.
///
/// @param cfg - the parsed structure
/// @param global global Dhcp4 scope
/// @throw DhcpConfigError in case of issues found
void
sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
/// Shared network sanity checks
const SharedNetwork4Collection* networks = cfg->getCfgSharedNetworks4()->getAll();
if (networks) {
sharedNetworksSanityChecks(*networks, global->get("shared-networks"));
}
}
/// @brief Sanity checks for shared networks
///
/// This method verifies if there are no issues with shared networks.
/// @param networks pointer to shared networks being checked
/// @param json shared-networks element
/// @throw DhcpConfigError if issues are encountered
void
sharedNetworksSanityChecks(const SharedNetwork4Collection& networks,
ConstElementPtr json) {
/// @todo: in case of errors, use json to extract line numbers.
if (!json) {
// No json? That means that the shared-networks was never specified
// in the config.
return;
}
// Used for names uniqueness checks.
std::set<string> names;
// Let's go through all the networks one by one
for (auto net = networks.begin(); net != networks.end(); ++net) {
string txt;
// Let's check if all subnets have either the same interface
// or don't have the interface specified at all.
string iface = (*net)->getIface();
const Subnet4Collection* subnets = (*net)->getAllSubnets();
if (subnets) {
// For each subnet, add it to a list of regular subnets.
for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
if (iface.empty()) {
iface = (*subnet)->getIface();
continue;
}
if ((*subnet)->getIface().empty()) {
continue;
}
if (iface != (*subnet)->getIface()) {
isc_throw(DhcpConfigError, "Subnet " << (*subnet)->toText()
<< " has specified interface " << (*subnet)->getIface()
<< ", but earlier subnet in the same shared-network"
<< " or the shared-network itself used " << iface);
}
// Let's collect the subnets in case we later find out the
// subnet doesn't have a mandatory name.
txt += (*subnet)->toText() + " ";
}
}
// Next, let's check name of the shared network.
if ((*net)->getName().empty()) {
isc_throw(DhcpConfigError, "Shared-network with subnets "
<< txt << " is missing mandatory 'name' parameter");
}
// Is it unique?
if (names.find((*net)->getName()) != names.end()) {
isc_throw(DhcpConfigError, "A shared-network with "
"name " << (*net)->getName() << " defined twice.");
}
names.insert((*net)->getName());
}
}
};
} // anonymous namespace
namespace isc {
namespace dhcp {
/// @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
2017-07-23 11:23:40 -04:00
// data elements aren't equal.
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
2017-07-23 12:54:55 -04:00
// close the existing socket and open a new socket as appropriate.
// 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);
}
}
}
2012-10-11 19:08:23 +02:00
isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
bool check_only) {
2012-10-11 19:08:23 +02:00
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
return (answer);
2012-10-11 19:08:23 +02:00
}
2013-07-11 10:34:43 +02:00
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND,
DHCP4_CONFIG_START).arg(config_set->str());
2012-10-11 19:08:23 +02:00
// 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();
// Remove any existing timers.
if (!check_only) {
TimerMgr::instance()->unregisterTimers();
}
// Revert any runtime option definitions configured so far and not committed.
LibDHCP::revertRuntimeOptionDefs();
// Let's set empty container in case a user hasn't specified any configuration
2017-07-23 11:56:50 -04:00
// for option definitions. This is equivalent to committing empty container.
LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer());
// Answer will hold the result.
ConstElementPtr answer;
2015-01-22 11:28:13 +01:00
// Rollback informs whether error occurred and original data
// have to be restored to global storages.
bool rollback = false;
// config_pair holds the details of the current parser when iterating over
// 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.
ConfigPair config_pair;
2012-10-11 19:08:23 +02:00
try {
SrvConfigPtr srv_cfg = CfgMgr::instance().getStagingCfg();
// 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.
ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(config_set);
// Set all default values if not specified by the user.
SimpleParser4::setAllDefaults(mutable_cfg);
// And now derive (inherit) global parameters to subnets, if not specified.
SimpleParser4::deriveParameters(mutable_cfg);
// We need definitions first
ConstElementPtr option_defs = mutable_cfg->get("option-def");
if (option_defs) {
OptionDefListParser parser;
CfgOptionDefPtr cfg_option_def = srv_cfg->getCfgOptionDef();
parser.parse(cfg_option_def, option_defs);
}
// This parser is used in several places, so it should be available
// early.
Dhcp4ConfigParser global_parser;
// Make parsers grouping.
const std::map<std::string, ConstElementPtr>& values_map =
mutable_cfg->mapValue();
BOOST_FOREACH(config_pair, values_map) {
// 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
2017-01-03 17:12:38 +01:00
// with the parser code debugability, so I decided to keep it as a
// series of independent ifs.
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);
CfgOptionPtr cfg_option = srv_cfg->getCfgOption();
parser.parse(cfg_option, config_pair.second);
continue;
}
if (config_pair.first == "control-socket") {
ControlSocketParser parser;
parser.parse(*srv_cfg, config_pair.second);
continue;
}
if (config_pair.first == "host-reservation-identifiers") {
HostReservationIdsParser4 parser;
parser.parse(config_pair.second);
continue;
}
if (config_pair.first == "interfaces-config") {
2017-03-16 23:38:52 +01:00
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));
}
IfacesConfigParser parser(AF_INET);
CfgIfacePtr cfg_iface = srv_cfg->getCfgIface();
2017-03-16 23:38:52 +01:00
parser.parse(cfg_iface, ifaces_cfg);
continue;
}
2017-01-07 07:18:40 +01:00
if (config_pair.first == "expired-leases-processing") {
ExpirationConfigParser parser;
parser.parse(config_pair.second);
continue;
}
if (config_pair.first == "hooks-libraries") {
HooksLibrariesParser hooks_parser;
HooksConfig& libraries = srv_cfg->getHooksConfig();
hooks_parser.parse(libraries, config_pair.second);
libraries.verifyLibraries(config_pair.second->getPosition());
continue;
}
// Legacy DhcpConfigParser stuff below
if (config_pair.first == "dhcp-ddns") {
// Apply defaults
D2ClientConfigParser::setAllDefaults(config_pair.second);
D2ClientConfigParser parser;
D2ClientConfigPtr cfg = parser.parse(config_pair.second);
srv_cfg->setD2ClientConfig(cfg);
continue;
}
if (config_pair.first == "client-classes") {
ClientClassDefListParser parser;
2017-01-18 02:27:14 +01:00
ClientClassDictionaryPtr dictionary =
parser.parse(config_pair.second, AF_INET);
srv_cfg->setClientClassDictionary(dictionary);
continue;
}
// 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;
}
if (config_pair.first == "hosts-database") {
DbAccessParser parser(DbAccessParser::HOSTS_DB);
CfgDbAccessPtr cfg_db_access = srv_cfg->getCfgDbAccess();
parser.parse(cfg_db_access, config_pair.second);
continue;
}
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;
}
if (config_pair.first == "shared-networks") {
/// We need to create instance of SharedNetworks4ListParser
/// and parse the list of the shared networks into the
/// CfgSharedNetworks4 object. One additional step is then to
/// add subnets from the CfgSharedNetworks4 into CfgSubnets4
/// as well.
SharedNetworks4ListParser parser;
CfgSharedNetworks4Ptr cfg = srv_cfg->getCfgSharedNetworks4();
parser.parse(cfg, config_pair.second);
// We also need to put the subnets it contains into normal
// subnets list.
global_parser.copySubnets4(srv_cfg->getCfgSubnets4(), cfg);
continue;
}
// 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
// handled in global_parser.parse() which sets global parameters.
// 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;
}
// 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() << ")");
2012-10-11 19:08:23 +02:00
}
// Apply global options in the staging config.
global_parser.parse(srv_cfg, mutable_cfg);
// This method conducts final sanity checks and tweaks. In particular,
// it checks that there is no conflict between plain subnets and those
// defined as part of shared networks.
global_parser.sanityChecks(srv_cfg, mutable_cfg);
2012-10-11 19:08:23 +02:00
} catch (const isc::Exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
.arg(config_pair.first).arg(ex.what());
answer = isc::config::createAnswer(1, ex.what());
2015-01-22 11:28:13 +01:00
// An error occurred, so make sure that we restore original data.
rollback = true;
2012-10-11 19:08:23 +02:00
} catch (...) {
// For things like bad_cast in boost::lexical_cast
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(config_pair.first);
answer = isc::config::createAnswer(1, "undefined configuration"
" processing error");
2015-01-22 11:28:13 +01:00
// An error occurred, so make sure that we restore original data.
rollback = true;
2012-10-11 19:08:23 +02:00
}
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.");
}
}
// 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 {
// Setup the command channel.
configureCommandChannel();
// No need to commit interface names as this is handled by the
// CfgMgr::commit() function.
2013-08-13 13:14:38 +01:00
// Apply the staged D2ClientConfig, used to be done by parser commit
D2ClientConfigPtr cfg;
cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
CfgMgr::instance().setD2ClientConfig(cfg);
// 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.
const HooksConfig& libraries =
CfgMgr::instance().getStagingCfg()->getHooksConfig();
libraries.loadLibraries();
}
catch (const isc::Exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
answer = isc::config::createAnswer(2, ex.what());
rollback = true;
} catch (...) {
// For things like bad_cast in boost::lexical_cast
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
answer = isc::config::createAnswer(2, "undefined configuration"
" parsing error");
rollback = true;
2012-10-11 19:08:23 +02:00
}
}
// Rollback changes as the configuration parsing failed.
if (rollback) {
// Revert to original configuration of runtime option definitions
// in the libdhcp++.
LibDHCP::revertRuntimeOptionDefs();
2012-10-11 19:08:23 +02:00
return (answer);
}
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE)
.arg(CfgMgr::instance().getStagingCfg()->
getConfigSummary(SrvConfig::CFGSEL_ALL4));
2012-10-11 19:08:23 +02:00
// Everything was fine. Configuration is successful.
answer = isc::config::createAnswer(0, "Configuration successful.");
2012-10-11 19:08:23 +02:00
return (answer);
}
}; // end of isc::dhcp namespace
}; // end of isc namespace