summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/dhcpsrv/parsers/dhcp_parsers.cc
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcpsrv/parsers/dhcp_parsers.cc')
-rw-r--r--src/lib/dhcpsrv/parsers/dhcp_parsers.cc1676
1 files changed, 1676 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
new file mode 100644
index 0000000..384369c
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
@@ -0,0 +1,1676 @@
+// Copyright (C) 2013-2023 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 <dhcp/iface_mgr.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservations_list_parser.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcpsrv/cfg_mac_source.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <limits>
+#include <map>
+#include <string>
+#include <vector>
+#include <iomanip>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+// ******************** MACSourcesListConfigParser *************************
+
+void
+MACSourcesListConfigParser::parse(CfgMACSource& mac_sources, ConstElementPtr value) {
+ uint32_t source = 0;
+ size_t cnt = 0;
+
+ // By default, there's only one source defined: ANY.
+ // If user specified anything, we need to get rid of that default.
+ mac_sources.clear();
+
+ BOOST_FOREACH(ConstElementPtr source_elem, value->listValue()) {
+ std::string source_str = source_elem->stringValue();
+ try {
+ source = CfgMACSource::MACSourceFromText(source_str);
+ mac_sources.add(source);
+ ++cnt;
+ } catch (const InvalidParameter& ex) {
+ isc_throw(DhcpConfigError, "The mac-sources value '" << source_str
+ << "' was specified twice (" << value->getPosition() << ")");
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "Failed to convert '"
+ << source_str << "' to any recognized MAC source:"
+ << ex.what() << " (" << value->getPosition() << ")");
+ }
+ }
+
+ if (!cnt) {
+ isc_throw(DhcpConfigError, "If specified, MAC Sources cannot be empty");
+ }
+}
+
+// ******************** ControlSocketParser *************************
+void ControlSocketParser::parse(SrvConfig& srv_cfg, isc::data::ConstElementPtr value) {
+ if (!value) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError, "Logic error: specified control-socket is null");
+ }
+
+ if (value->getType() != Element::map) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError, "Specified control-socket is expected to be a map"
+ ", i.e. a structure defined within { }");
+ }
+ srv_cfg.setControlSocketInfo(value);
+}
+
+// ******************************** OptionDefParser ****************************
+
+OptionDefParser::OptionDefParser(const uint16_t address_family)
+ : address_family_(address_family) {
+}
+
+OptionDefinitionPtr
+OptionDefParser::parse(ConstElementPtr option_def) {
+
+ // Check parameters.
+ if (address_family_ == AF_INET) {
+ checkKeywords(SimpleParser4::OPTION4_DEF_PARAMETERS, option_def);
+ } else {
+ checkKeywords(SimpleParser6::OPTION6_DEF_PARAMETERS, option_def);
+ }
+
+ // Get mandatory parameters.
+ std::string name = getString(option_def, "name");
+ int64_t code64 = getInteger(option_def, "code");
+ std::string type = getString(option_def, "type");
+
+ // Get optional parameters. Whoever called this parser, should have
+ // called SimpleParser::setDefaults first.
+ bool array_type = getBoolean(option_def, "array");
+ std::string record_types = getString(option_def, "record-types");
+ std::string space = getString(option_def, "space");
+ std::string encapsulates = getString(option_def, "encapsulate");
+ ConstElementPtr user_context = option_def->get("user-context");
+
+ // Check code value.
+ if (code64 < 0) {
+ isc_throw(DhcpConfigError, "option code must not be negative "
+ "(" << getPosition("code", option_def) << ")");
+ } else if (address_family_ == AF_INET &&
+ code64 > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << code64
+ << "', it must not be greater than '"
+ << static_cast<int>(std::numeric_limits<uint8_t>::max())
+ << "' (" << getPosition("code", option_def) << ")");
+ } else if (address_family_ == AF_INET6 &&
+ code64 > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << code64
+ << "', it must not be greater than '"
+ << std::numeric_limits<uint16_t>::max()
+ << "' (" << getPosition("code", option_def) << ")");
+ }
+ uint32_t code = static_cast<uint32_t>(code64);
+
+ // Validate space name.
+ if (!OptionSpace::validateName(space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "' ("
+ << getPosition("space", option_def) << ")");
+ }
+
+ // Protect against definition of options 0 (PAD) or 255 (END)
+ // in (and only in) the dhcp4 space.
+ if (space == DHCP4_OPTION_SPACE) {
+ if (code == DHO_PAD) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved for PAD ("
+ << getPosition("code", option_def) << ")");
+ } else if (code == DHO_END) {
+ isc_throw(DhcpConfigError, "invalid option code '255': "
+ << "reserved for END ("
+ << getPosition("code", option_def) << ")");
+ }
+ }
+
+ // For dhcp6 space the value 0 is reserved.
+ if (space == DHCP6_OPTION_SPACE) {
+ if (code == 0) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved value ("
+ << getPosition("code", option_def) << ")");
+ }
+ }
+
+ // Create option definition.
+ OptionDefinitionPtr def;
+ // We need to check if user has set encapsulated option space
+ // name. If so, different constructor will be used.
+ if (!encapsulates.empty()) {
+ // Arrays can't be used together with sub-options.
+ if (array_type) {
+ isc_throw(DhcpConfigError, "option '" << space << "."
+ << name << "', comprising an array of data"
+ << " fields may not encapsulate any option space ("
+ << option_def->getPosition() << ")");
+
+ } else if (encapsulates == space) {
+ isc_throw(DhcpConfigError, "option must not encapsulate"
+ << " an option space it belongs to: '"
+ << space << "." << name << "' is set to"
+ << " encapsulate '" << space << "' ("
+ << option_def->getPosition() << ")");
+
+ } else {
+ def.reset(new OptionDefinition(name, code, space, type,
+ encapsulates.c_str()));
+ }
+
+ } else {
+ def.reset(new OptionDefinition(name, code, space, type, array_type));
+
+ }
+
+ if (user_context) {
+ def->setContext(user_context);
+ }
+
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what() << " ("
+ << getPosition("record-types", option_def) << ")");
+ }
+ }
+
+ // Validate the definition.
+ try {
+ def->validate();
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what()
+ << " (" << option_def->getPosition() << ")");
+ }
+
+ // Option definition has been created successfully.
+ return (def);
+}
+
+// ******************************** OptionDefListParser ************************
+
+OptionDefListParser::OptionDefListParser(const uint16_t address_family)
+ : address_family_(address_family) {
+}
+
+void
+OptionDefListParser::parse(CfgOptionDefPtr storage, ConstElementPtr option_def_list) {
+ if (!option_def_list) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL ("
+ << option_def_list->getPosition() << ")");
+ }
+
+ OptionDefParser parser(address_family_);
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ OptionDefinitionPtr def = parser.parse(option_def);
+ try {
+ storage->add(def);
+ } catch (const std::exception& ex) {
+ // Append position if there is a failure.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << option_def->getPosition() << ")");
+ }
+ }
+
+ // All definitions have been prepared. Put them as runtime options into
+ // the libdhcp++.
+ LibDHCP::setRuntimeOptionDefs(storage->getContainer());
+}
+
+//****************************** RelayInfoParser ********************************
+RelayInfoParser::RelayInfoParser(const Option::Universe& family)
+ : family_(family) {
+};
+
+void
+RelayInfoParser::parse(const isc::dhcp::Network::RelayInfoPtr& relay_info,
+ ConstElementPtr relay_elem) {
+
+ if (relay_elem->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "relay must be a map");
+ }
+
+ ConstElementPtr address = relay_elem->get("ip-address");
+ ConstElementPtr addresses = relay_elem->get("ip-addresses");
+
+ if (address && addresses) {
+ isc_throw(DhcpConfigError,
+ "specify either ip-address or ip-addresses, not both");
+ }
+
+ if (!address && !addresses) {
+ isc_throw(DhcpConfigError, "ip-addresses is required");
+ }
+
+ // Create our resultant RelayInfo structure
+ *relay_info = isc::dhcp::Network::RelayInfo();
+
+ if (address) {
+ addAddress("ip-address", getString(relay_elem, "ip-address"),
+ relay_elem, relay_info);
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_CFGMGR_RELAY_IP_ADDRESS_DEPRECATED)
+ .arg(getPosition("ip-address", relay_elem));
+ return;
+ }
+
+ if (addresses->getType() != Element::list) {
+ isc_throw(DhcpConfigError, "ip-addresses must be a list "
+ "(" << getPosition("ip-addresses", relay_elem) << ")");
+ }
+
+ BOOST_FOREACH(ConstElementPtr address_element, addresses->listValue()) {
+ addAddress("ip-addresses", address_element->stringValue(),
+ relay_elem, relay_info);
+ }
+}
+
+void
+RelayInfoParser::addAddress(const std::string& name,
+ const std::string& address_str,
+ ConstElementPtr relay_elem,
+ const isc::dhcp::Network::RelayInfoPtr& relay_info) {
+ boost::scoped_ptr<isc::asiolink::IOAddress> ip;
+ try {
+ ip.reset(new isc::asiolink::IOAddress(address_str));
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "address " << address_str
+ << " is not a valid: "
+ << (family_ == Option::V4 ? "IPv4" : "IPv6")
+ << "address"
+ << " (" << getPosition(name, relay_elem) << ")");
+ }
+
+ // Check if the address family matches.
+ if ((ip->isV4() && family_ != Option::V4) ||
+ (ip->isV6() && family_ != Option::V6) ) {
+ isc_throw(DhcpConfigError, "address " << address_str
+ << " is not a: "
+ << (family_ == Option::V4 ? "IPv4" : "IPv6")
+ << "address"
+ << " (" << getPosition(name, relay_elem) << ")");
+ }
+
+ try {
+ relay_info->addAddress(*ip);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "cannot add address: " << address_str
+ << " to relay info: " << ex.what()
+ << " (" << getPosition(name, relay_elem) << ")");
+ }
+}
+
+//****************************** PoolParser ********************************
+
+void
+PoolParser::parse(PoolStoragePtr pools,
+ ConstElementPtr pool_structure,
+ const uint16_t address_family,
+ bool encapsulate_options) {
+
+ if (address_family == AF_INET) {
+ checkKeywords(SimpleParser4::POOL4_PARAMETERS, pool_structure);
+ } else {
+ checkKeywords(SimpleParser6::POOL6_PARAMETERS, pool_structure);
+ }
+
+ ConstElementPtr text_pool = pool_structure->get("pool");
+
+ if (!text_pool) {
+ isc_throw(DhcpConfigError, "Mandatory 'pool' entry missing in "
+ "definition: (" << pool_structure->getPosition() << ")");
+ }
+
+ // That should be a single pool representation. It should contain
+ // text is form prefix/len or first - last. Note that spaces
+ // are allowed
+ string txt = text_pool->stringValue();
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ PoolPtr pool;
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+ if (pos != string::npos) {
+ isc::asiolink::IOAddress addr("::");
+ uint8_t len = 0;
+ try {
+ addr = isc::asiolink::IOAddress(txt.substr(0, pos));
+
+ // start with the first character after /
+ string prefix_len = txt.substr(pos + 1);
+
+ // It is lexical cast to int and then downcast to uint8_t.
+ // Direct cast to uint8_t (which is really an unsigned char)
+ // will result in interpreting the first digit as output
+ // value and throwing exception if length is written on two
+ // digits (because there are extra characters left over).
+
+ // No checks for values over 128. Range correctness will
+ // be checked in Pool4 constructor, here we only check
+ // the representation fits in an uint8_t as this can't
+ // be done by a direct lexical cast as explained...
+ int val_len = boost::lexical_cast<int>(prefix_len);
+ if ((val_len < std::numeric_limits<uint8_t>::min()) ||
+ (val_len > std::numeric_limits<uint8_t>::max())) {
+ // This exception will be handled 4 line later!
+ isc_throw(OutOfRange, "");
+ }
+ len = static_cast<uint8_t>(val_len);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Failed to parse pool "
+ "definition: " << txt << " ("
+ << text_pool->getPosition() << ")");
+ }
+
+ try {
+ pool = poolMaker(addr, len);
+ pools->push_back(pool);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "Failed to create pool defined by: "
+ << txt << " (" << text_pool->getPosition() << ")");
+ }
+
+ } else {
+ isc::asiolink::IOAddress min("::");
+ isc::asiolink::IOAddress max("::");
+
+ // Is this min-max notation?
+ pos = txt.find("-");
+ if (pos != string::npos) {
+ // using min-max notation
+ try {
+ min = isc::asiolink::IOAddress(txt.substr(0, pos));
+ max = isc::asiolink::IOAddress(txt.substr(pos + 1));
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Failed to parse pool "
+ "definition: " << txt << " ("
+ << text_pool->getPosition() << ")");
+ }
+
+ try {
+ pool = poolMaker(min, max);
+ pools->push_back(pool);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "Failed to create pool defined by: "
+ << txt << " (" << text_pool->getPosition() << ")");
+ }
+ }
+ }
+
+ if (!pool) {
+ isc_throw(DhcpConfigError, "invalid pool definition: "
+ << text_pool->stringValue() <<
+ ". There are two acceptable formats <min address-max address>"
+ " or <prefix/len> ("
+ << text_pool->getPosition() << ")");
+ }
+
+ // If there is a pool-id, store it.
+ ConstElementPtr pool_id = pool_structure->get("pool-id");
+ if (pool_id) {
+ if (pool_id->intValue() <= 0) {
+ isc_throw(BadValue, "pool-id " << pool_id->intValue() << " is not"
+ << " a positive integer greater than 0");
+ } else if (pool_id->intValue() > numeric_limits<uint32_t>::max()) {
+ isc_throw(BadValue, "pool-id " << pool_id->intValue() << " is not"
+ << " a 32 bit unsigned integer");
+ }
+
+ pool->setID(pool_id->intValue());
+ }
+
+ // If there's user-context specified, store it.
+ ConstElementPtr user_context = pool_structure->get("user-context");
+ if (user_context) {
+ // The grammar accepts only maps but still check it.
+ if (user_context->getType() != Element::map) {
+ isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
+ << user_context->getPosition() << ")");
+ }
+ pool->setContext(user_context);
+ }
+
+ // Parser pool specific options.
+ ConstElementPtr option_data = pool_structure->get("option-data");
+ if (option_data) {
+ try {
+ CfgOptionPtr cfg = pool->getCfgOption();
+ auto option_parser = createOptionDataListParser(address_family);
+ option_parser->parse(cfg, option_data, encapsulate_options);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::dhcp::DhcpConfigError, ex.what()
+ << " (" << option_data->getPosition() << ")");
+ }
+ }
+
+ // Client-class.
+ ConstElementPtr client_class = pool_structure->get("client-class");
+ if (client_class) {
+ string cclass = client_class->stringValue();
+ if (!cclass.empty()) {
+ pool->allowClientClass(cclass);
+ }
+ }
+
+ // Try setting up required client classes.
+ ConstElementPtr class_list = pool_structure->get("require-client-classes");
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ pool->requireClientClass((*cclass)->stringValue());
+ }
+ }
+}
+
+boost::shared_ptr<OptionDataListParser>
+PoolParser::createOptionDataListParser(const uint16_t address_family) const {
+ auto parser = boost::make_shared<OptionDataListParser>(address_family);
+ return (parser);
+}
+
+//****************************** Pool4Parser *************************
+
+PoolPtr
+Pool4Parser::poolMaker (IOAddress &addr, uint32_t len, int32_t) {
+ return (PoolPtr(new Pool4(addr, len)));
+}
+
+PoolPtr
+Pool4Parser::poolMaker (IOAddress &min, IOAddress &max, int32_t) {
+ return (PoolPtr(new Pool4(min, max)));
+}
+
+//****************************** Pools4ListParser *************************
+
+void
+Pools4ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list,
+ bool encapsulate_options) {
+ BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) {
+ auto parser = createPoolConfigParser();
+ parser->parse(pools, pool, AF_INET, encapsulate_options);
+ }
+}
+
+boost::shared_ptr<PoolParser>
+Pools4ListParser::createPoolConfigParser() const {
+ auto parser = boost::make_shared<Pool4Parser>();
+ return (parser);
+}
+
+//****************************** SubnetConfigParser *************************
+
+SubnetConfigParser::SubnetConfigParser(uint16_t family, bool check_iface)
+ : pools_(new PoolStorage()),
+ address_family_(family),
+ check_iface_(check_iface) {
+ relay_info_.reset(new isc::dhcp::Network::RelayInfo());
+}
+
+SubnetPtr
+SubnetConfigParser::parse(ConstElementPtr subnet, bool encapsulate_options) {
+
+ ConstElementPtr relay_params = subnet->get("relay");
+ if (relay_params) {
+ Option::Universe u = (address_family_ == AF_INET) ? Option::V4 : Option::V6;
+ RelayInfoParser parser(u);
+ parser.parse(relay_info_, relay_params);
+ }
+
+ // Create a subnet.
+ try {
+ createSubnet(subnet);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError,
+ "subnet configuration failed: " << ex.what());
+ }
+
+ // We create subnet first and then parse the options straight into the subnet's
+ // CfgOption structure. Previously, we first parsed the options and then copied
+ // them into the CfgOption after creating the subnet but it had two issues. First,
+ // it cost performance. Second, copying options reset the isEncapsulated() flag.
+ // If the options have been encapsulated we want to preserve the flag to ensure
+ // they are not encapsulated several times.
+ ConstElementPtr options_params = subnet->get("option-data");
+ if (options_params) {
+ auto opt_parser = createOptionDataListParser();
+ opt_parser->parse(subnet_->getCfgOption(), options_params, encapsulate_options);
+ }
+
+ return (subnet_);
+}
+
+void
+SubnetConfigParser::createSubnet(ConstElementPtr params) {
+ std::string subnet_txt;
+ try {
+ subnet_txt = getString(params, "subnet");
+ } catch (const DhcpConfigError &) {
+ // rethrow with precise error
+ isc_throw(DhcpConfigError,
+ "mandatory 'subnet' parameter is missing for a subnet being"
+ " configured (" << params->getPosition() << ")");
+ }
+
+ // Remove any spaces or tabs.
+ boost::erase_all(subnet_txt, " ");
+ boost::erase_all(subnet_txt, "\t");
+
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
+ size_t pos = subnet_txt.find("/");
+ if (pos == string::npos) {
+ ConstElementPtr elem = params->get("subnet");
+ isc_throw(DhcpConfigError,
+ "Invalid subnet syntax (prefix/len expected):" << subnet_txt
+ << " (" << elem->getPosition() << ")");
+ }
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
+ isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
+
+ // Now parse out the prefix length.
+ unsigned int len;
+ try {
+ len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ } catch (const boost::bad_lexical_cast&) {
+ ConstElementPtr elem = params->get("subnet");
+ isc_throw(DhcpConfigError, "prefix length: '" <<
+ subnet_txt.substr(pos+1) << "' is not an integer ("
+ << elem->getPosition() << ")");
+ }
+
+ // Sanity check the prefix length
+ if ((addr.isV6() && len > 128) ||
+ (addr.isV4() && len > 32)) {
+ ConstElementPtr elem = params->get("subnet");
+ isc_throw(BadValue,
+ "Invalid prefix length specified for subnet: " << len
+ << " (" << elem->getPosition() << ")");
+ }
+
+ // Call the subclass's method to instantiate the subnet
+ initSubnet(params, addr, len);
+
+ // Add pools to it.
+ for (const auto& pool : *pools_) {
+ try {
+ subnet_->addPool(pool);
+ } catch (const BadValue& ex) {
+ // addPool() can throw BadValue if the pool is overlapping or
+ // is out of bounds for the subnet.
+ isc_throw(DhcpConfigError,
+ ex.what() << " (" << params->getPosition() << ")");
+ }
+ }
+ // If there's user-context specified, store it.
+ ConstElementPtr user_context = params->get("user-context");
+ if (user_context) {
+ // The grammar accepts only maps but still check it.
+ if (user_context->getType() != Element::map) {
+ isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
+ << user_context->getPosition() << ")");
+ }
+ subnet_->setContext(user_context);
+ }
+
+ // 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());
+ });
+}
+
+boost::shared_ptr<OptionDataListParser>
+SubnetConfigParser::createOptionDataListParser() const {
+ auto parser = boost::make_shared<OptionDataListParser>(address_family_);
+ return (parser);
+}
+
+//****************************** Subnet4ConfigParser *************************
+
+Subnet4ConfigParser::Subnet4ConfigParser(bool check_iface)
+ : SubnetConfigParser(AF_INET, check_iface) {
+}
+
+Subnet4Ptr
+Subnet4ConfigParser::parse(ConstElementPtr subnet, bool encapsulate_options) {
+ // Check parameters.
+ checkKeywords(SimpleParser4::SUBNET4_PARAMETERS, subnet);
+
+ /// Parse Pools first.
+ ConstElementPtr pools = subnet->get("pools");
+ if (pools) {
+ auto parser = createPoolsListParser();
+ parser->parse(pools_, pools, encapsulate_options);
+ }
+
+ SubnetPtr generic = SubnetConfigParser::parse(subnet, encapsulate_options);
+
+ if (!generic) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError,
+ "Failed to create an IPv4 subnet (" <<
+ subnet->getPosition() << ")");
+ }
+
+ Subnet4Ptr sn4ptr = boost::dynamic_pointer_cast<Subnet4>(subnet_);
+ if (!sn4ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid Subnet4 cast in Subnet4ConfigParser::parse");
+ }
+
+ // Set relay information if it was parsed
+ if (relay_info_) {
+ sn4ptr->setRelayInfo(*relay_info_);
+ }
+
+ // Parse Host Reservations for this subnet if any.
+ ConstElementPtr reservations = subnet->get("reservations");
+ if (reservations) {
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser4> parser;
+ parser.parse(subnet_->getID(), reservations, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ validateResv(sn4ptr, *h);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }
+
+ // Parse allocator specification.
+ auto network4 = boost::dynamic_pointer_cast<Network>(sn4ptr);
+ parseAllocatorParams(subnet, network4);
+
+ // Instantiate the allocator.
+ sn4ptr->createAllocators();
+
+ return (sn4ptr);
+}
+
+void
+Subnet4ConfigParser::initSubnet(data::ConstElementPtr params,
+ asiolink::IOAddress addr, uint8_t len) {
+ // Subnet ID is optional. If it is not supplied the value of 0 is used,
+ // which means autogenerate. The value was inserted earlier by calling
+ // SimpleParser4::setAllDefaults.
+ int64_t subnet_id_max = static_cast<int64_t>(SUBNET_ID_MAX);
+ SubnetID subnet_id = static_cast<SubnetID>(getInteger(params, "id", 0,
+ subnet_id_max));
+
+ auto subnet4 = Subnet4::create(addr, len, Triplet<uint32_t>(),
+ Triplet<uint32_t>(), Triplet<uint32_t>(),
+ subnet_id);
+ subnet_ = subnet4;
+
+ // Move from reservation mode to new reservations flags.
+ ElementPtr mutable_params;
+ mutable_params = boost::const_pointer_cast<Element>(params);
+ // @todo add warning
+ BaseNetworkParser::moveReservationMode(mutable_params);
+
+ // Parse parameters common to all Network derivations.
+ NetworkPtr network = boost::dynamic_pointer_cast<Network>(subnet4);
+ parseCommon(mutable_params, network);
+
+ std::ostringstream output;
+ output << addr << "/" << static_cast<int>(len) << " with params: ";
+
+ bool has_renew = !subnet4->getT1().unspecified();
+ bool has_rebind = !subnet4->getT2().unspecified();
+ int64_t renew = -1;
+ int64_t rebind = -1;
+
+ // t1 and t2 are optional may be not specified.
+ if (has_renew) {
+ renew = subnet4->getT1().get();
+ output << "t1=" << renew << ", ";
+ }
+ if (has_rebind) {
+ rebind = subnet4->getT2().get();
+ output << "t2=" << rebind << ", ";
+ }
+
+ if (!subnet4->getValid().unspecified()) {
+ output << "valid-lifetime=" << subnet4->getValid().get();
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_NEW_SUBNET4).arg(output.str());
+
+ // Set the match-client-id value for the subnet.
+ if (params->contains("match-client-id")) {
+ bool match_client_id = getBoolean(params, "match-client-id");
+ subnet4->setMatchClientId(match_client_id);
+ }
+
+ // Set the authoritative value for the subnet.
+ if (params->contains("authoritative")) {
+ bool authoritative = getBoolean(params, "authoritative");
+ subnet4->setAuthoritative(authoritative);
+ }
+
+ // Set next-server. The default value is 0.0.0.0. Nevertheless, the
+ // user could have messed that up by specifying incorrect value.
+ // To avoid using 0.0.0.0, user can specify "".
+ if (params->contains("next-server")) {
+ string next_server;
+ try {
+ next_server = getString(params, "next-server");
+ if (!next_server.empty()) {
+ subnet4->setSiaddr(IOAddress(next_server));
+ }
+ } catch (...) {
+ ConstElementPtr next = params->get("next-server");
+ string pos;
+ if (next) {
+ pos = next->getPosition().str();
+ } else {
+ pos = params->getPosition().str();
+ }
+ isc_throw(DhcpConfigError, "invalid parameter next-server : "
+ << next_server << "(" << pos << ")");
+ }
+ }
+
+ // Set server-hostname.
+ if (params->contains("server-hostname")) {
+ std::string sname = getString(params, "server-hostname");
+ if (!sname.empty()) {
+ if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
+ ConstElementPtr error = params->get("server-hostname");
+ isc_throw(DhcpConfigError, "server-hostname must be at most "
+ << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
+ << sname.length() << " ("
+ << error->getPosition() << ")");
+ }
+ subnet4->setSname(sname);
+ }
+ }
+
+ // Set boot-file-name.
+ if (params->contains("boot-file-name")) {
+ std::string filename =getString(params, "boot-file-name");
+ if (!filename.empty()) {
+ if (filename.length() > Pkt4::MAX_FILE_LEN) {
+ ConstElementPtr error = params->get("boot-file-name");
+ isc_throw(DhcpConfigError, "boot-file-name must be at most "
+ << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
+ << filename.length() << " ("
+ << error->getPosition() << ")");
+ }
+ subnet4->setFilename(filename);
+ }
+ }
+
+ // Get interface name. If it is defined, then the subnet is available
+ // directly over specified network interface.
+ if (params->contains("interface")) {
+ std::string iface = getString(params, "interface");
+ if (!iface.empty()) {
+ if (check_iface_ && !IfaceMgr::instance().getIface(iface)) {
+ ConstElementPtr error = params->get("interface");
+ isc_throw(DhcpConfigError, "Specified network interface name " << iface
+ << " for subnet " << subnet4->toText()
+ << " is not present in the system ("
+ << error->getPosition() << ")");
+ }
+
+ subnet4->setIface(iface);
+ }
+ }
+
+ // Try setting up client class.
+ if (params->contains("client-class")) {
+ string client_class = getString(params, "client-class");
+ if (!client_class.empty()) {
+ subnet4->allowClientClass(client_class);
+ }
+ }
+
+ // Try setting up required client classes.
+ ConstElementPtr class_list = params->get("require-client-classes");
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ subnet4->requireClientClass((*cclass)->stringValue());
+ }
+ }
+
+ // 4o6 specific parameter: 4o6-interface.
+ if (params->contains("4o6-interface")) {
+ string iface4o6 = getString(params, "4o6-interface");
+ if (!iface4o6.empty()) {
+ subnet4->get4o6().setIface4o6(iface4o6);
+ subnet4->get4o6().enabled(true);
+ }
+ }
+
+ // 4o6 specific parameter: 4o6-subnet.
+ if (params->contains("4o6-subnet")) {
+ string subnet4o6 = getString(params, "4o6-subnet");
+ if (!subnet4o6.empty()) {
+ size_t slash = subnet4o6.find("/");
+ if (slash == std::string::npos) {
+ isc_throw(DhcpConfigError, "Missing / in the 4o6-subnet parameter:"
+ << subnet4o6 << ", expected format: prefix6/length");
+ }
+ string prefix = subnet4o6.substr(0, slash);
+ string lenstr = subnet4o6.substr(slash + 1);
+
+ uint8_t len = 128;
+ try {
+ len = boost::lexical_cast<unsigned int>(lenstr.c_str());
+ } catch (const boost::bad_lexical_cast &) {
+ isc_throw(DhcpConfigError, "Invalid prefix length specified in "
+ "4o6-subnet parameter: " << subnet4o6 << ", expected 0..128 value");
+ }
+ subnet4->get4o6().setSubnet4o6(IOAddress(prefix), len);
+ subnet4->get4o6().enabled(true);
+ }
+ }
+
+ // Try 4o6 specific parameter: 4o6-interface-id
+ if (params->contains("4o6-interface-id")) {
+ std::string ifaceid = getString(params, "4o6-interface-id");
+ if (!ifaceid.empty()) {
+ OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+ OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet4->get4o6().setInterfaceId(opt);
+ subnet4->get4o6().enabled(true);
+ }
+ }
+
+ /// client-class processing is now generic and handled in the common
+ /// code (see isc::data::SubnetConfigParser::createSubnet)
+
+ // Here globally defined options were merged to the subnet specific
+ // options but this is no longer the case (they have a different
+ // and not consecutive priority).
+
+ // Parse t1-percent and t2-percent
+ parseTeePercents(params, network);
+
+ // Parse DDNS parameters
+ parseDdnsParams(params, network);
+
+ // Parse lease cache parameters
+ parseCacheParams(params, network);
+
+ // Set the offer_lft value for the subnet.
+ if (params->contains("offer-lifetime")) {
+ uint32_t offer_lft = getInteger(params, "offer-lifetime");
+ subnet4->setOfferLft(offer_lft);
+ }
+
+ // Parse offer-lifetime parameter.
+ Network4Ptr network4 = boost::dynamic_pointer_cast<Network4>(subnet4);
+ parseOfferLft(params, network4);
+
+}
+
+void
+Subnet4ConfigParser::validateResv(const Subnet4Ptr& subnet, ConstHostPtr host) {
+ const IOAddress& address = host->getIPv4Reservation();
+ if (!address.isV4Zero() && !subnet->inRange(address)) {
+ isc_throw(DhcpConfigError, "specified reservation '" << address
+ << "' is not within the IPv4 subnet '"
+ << subnet->toText() << "'");
+ }
+}
+
+boost::shared_ptr<PoolsListParser>
+Subnet4ConfigParser::createPoolsListParser() const {
+ auto parser = boost::make_shared<Pools4ListParser>();
+ return (parser);
+}
+
+//**************************** Subnets4ListConfigParser **********************
+
+Subnets4ListConfigParser::Subnets4ListConfigParser(bool check_iface)
+ : check_iface_(check_iface) {
+}
+
+size_t
+Subnets4ListConfigParser::parse(SrvConfigPtr cfg,
+ ConstElementPtr subnets_list,
+ bool encapsulate_options) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ auto parser = createSubnetConfigParser();
+ Subnet4Ptr subnet = parser->parse(subnet_json, encapsulate_options);
+ if (subnet) {
+
+ // Adding a subnet to the Configuration Manager may fail if the
+ // subnet id is invalid (duplicate). Thus, we catch exceptions
+ // here to append a position in the configuration string.
+ try {
+ cfg->getCfgSubnets4()->add(subnet);
+ cnt++;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << subnet_json->getPosition() << ")");
+ }
+ }
+ }
+ return (cnt);
+}
+
+size_t
+Subnets4ListConfigParser::parse(Subnet4Collection& subnets,
+ data::ConstElementPtr subnets_list,
+ bool encapsulate_options) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ auto parser = createSubnetConfigParser();
+ Subnet4Ptr subnet = parser->parse(subnet_json, encapsulate_options);
+ if (subnet) {
+ try {
+ auto ret = subnets.insert(subnet);
+ if (!ret.second) {
+ isc_throw(Unexpected,
+ "can't store subnet because of conflict");
+ }
+ ++cnt;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << subnet_json->getPosition() << ")");
+ }
+ }
+ }
+ return (cnt);
+}
+
+boost::shared_ptr<Subnet4ConfigParser>
+Subnets4ListConfigParser::createSubnetConfigParser() const {
+ auto parser = boost::make_shared<Subnet4ConfigParser>(check_iface_);
+ return (parser);
+}
+
+//**************************** Pool6Parser *********************************
+
+PoolPtr
+Pool6Parser::poolMaker(IOAddress &addr, uint32_t len, int32_t ptype)
+{
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Lease::Type>
+ (ptype), addr, len)));
+}
+
+PoolPtr
+Pool6Parser::poolMaker(IOAddress &min, IOAddress &max, int32_t ptype)
+{
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Lease::Type>
+ (ptype), min, max)));
+}
+
+
+//**************************** Pool6ListParser ***************************
+
+void
+Pools6ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list,
+ bool encapsulate_options) {
+ BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) {
+ auto parser = createPoolConfigParser();
+ parser->parse(pools, pool, AF_INET6, encapsulate_options);
+ }
+}
+
+boost::shared_ptr<PoolParser>
+Pools6ListParser::createPoolConfigParser() const {
+ auto parser = boost::make_shared<Pool6Parser>();
+ return (parser);
+}
+
+//**************************** PdPoolParser ******************************
+
+PdPoolParser::PdPoolParser() {
+}
+
+void
+PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_,
+ bool encapsulate_options) {
+ checkKeywords(SimpleParser6::PD_POOL6_PARAMETERS, pd_pool_);
+
+ std::string addr_str = getString(pd_pool_, "prefix");
+
+ uint8_t prefix_len = getUint8(pd_pool_, "prefix-len");
+
+ uint8_t delegated_len = getUint8(pd_pool_, "delegated-len");
+
+ std::string excluded_prefix_str = "::";
+ if (pd_pool_->contains("excluded-prefix")) {
+ excluded_prefix_str = getString(pd_pool_, "excluded-prefix");
+ }
+
+ uint8_t excluded_prefix_len = 0;
+ if (pd_pool_->contains("excluded-prefix-len")) {
+ excluded_prefix_len = getUint8(pd_pool_, "excluded-prefix-len");
+ }
+
+ ConstElementPtr user_context = pd_pool_->get("user-context");
+ if (user_context) {
+ user_context_ = user_context;
+ }
+
+ ConstElementPtr client_class = pd_pool_->get("client-class");
+ if (client_class) {
+ client_class_ = client_class;
+ }
+
+ ConstElementPtr class_list = pd_pool_->get("require-client-classes");
+
+ // Check the pool parameters. It will throw an exception if any
+ // of the required parameters are invalid.
+ try {
+ // Attempt to construct the local pool.
+ pool_.reset(new Pool6(IOAddress(addr_str),
+ prefix_len,
+ delegated_len,
+ IOAddress(excluded_prefix_str),
+ excluded_prefix_len));
+ } catch (const std::exception& ex) {
+ // Some parameters don't exist or are invalid. Since we are not
+ // aware whether they don't exist or are invalid, let's append
+ // the position of the pool map element.
+ isc_throw(isc::dhcp::DhcpConfigError, ex.what()
+ << " (" << pd_pool_->getPosition() << ")");
+ }
+
+ // We create subnet first and then parse the options straight into the subnet's
+ // CfgOption structure. Previously, we first parsed the options and then copied
+ // them into the CfgOption after creating the subnet but it had two issues. First,
+ // it cost performance. Second, copying options reset the isEncapsulated() flag.
+ // If the options have been encapsulated we want to preserve the flag to ensure
+ // they are not encapsulated several times.
+ ConstElementPtr option_data = pd_pool_->get("option-data");
+ if (option_data) {
+ auto opts_parser = createOptionDataListParser();
+ opts_parser->parse(pool_->getCfgOption(), option_data, encapsulate_options);
+ }
+
+ if (user_context_) {
+ pool_->setContext(user_context_);
+ }
+
+ if (client_class_) {
+ string cclass = client_class_->stringValue();
+ if (!cclass.empty()) {
+ pool_->allowClientClass(cclass);
+ }
+ }
+
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ pool_->requireClientClass((*cclass)->stringValue());
+ }
+ }
+
+ // Add the local pool to the external storage ptr.
+ pools->push_back(pool_);
+}
+
+boost::shared_ptr<OptionDataListParser>
+PdPoolParser::createOptionDataListParser() const {
+ auto parser = boost::make_shared<OptionDataListParser>(AF_INET6);
+ return (parser);
+}
+
+//**************************** PdPoolsListParser ************************
+
+void
+PdPoolsListParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_list) {
+ // Loop through the list of pd pools.
+ BOOST_FOREACH(ConstElementPtr pd_pool, pd_pool_list->listValue()) {
+ auto parser = createPdPoolConfigParser();
+ parser->parse(pools, pd_pool);
+ }
+}
+
+boost::shared_ptr<PdPoolParser>
+PdPoolsListParser::createPdPoolConfigParser() const {
+ auto parser = boost::make_shared<PdPoolParser>();
+ return (parser);
+}
+
+//**************************** Subnet6ConfigParser ***********************
+
+Subnet6ConfigParser::Subnet6ConfigParser(bool check_iface)
+ : SubnetConfigParser(AF_INET6, check_iface) {
+}
+
+Subnet6Ptr
+Subnet6ConfigParser::parse(ConstElementPtr subnet, bool encapsulate_options) {
+ // Check parameters.
+ checkKeywords(SimpleParser6::SUBNET6_PARAMETERS, subnet);
+
+ /// Parse all pools first.
+ ConstElementPtr pools = subnet->get("pools");
+ if (pools) {
+ auto parser = createPoolsListParser();
+ parser->parse(pools_, pools, encapsulate_options);
+ }
+ ConstElementPtr pd_pools = subnet->get("pd-pools");
+ if (pd_pools) {
+ auto parser = createPdPoolsListParser();
+ parser->parse(pools_, pd_pools);
+ }
+
+ SubnetPtr generic = SubnetConfigParser::parse(subnet, encapsulate_options);
+
+ if (!generic) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError,
+ "Failed to create an IPv6 subnet (" <<
+ subnet->getPosition() << ")");
+ }
+
+ Subnet6Ptr sn6ptr = boost::dynamic_pointer_cast<Subnet6>(subnet_);
+ if (!sn6ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid Subnet6 cast in Subnet6ConfigParser::parse");
+ }
+
+ // Set relay information if it was provided
+ if (relay_info_) {
+ sn6ptr->setRelayInfo(*relay_info_);
+ }
+
+ // Parse Host Reservations for this subnet if any.
+ ConstElementPtr reservations = subnet->get("reservations");
+ if (reservations) {
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser6> parser;
+ parser.parse(subnet_->getID(), reservations, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ validateResvs(sn6ptr, *h);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }
+
+ // Parse allocator specification.
+ auto network = boost::dynamic_pointer_cast<Network>(sn6ptr);
+ parseAllocatorParams(subnet, network);
+
+ // Parse pd-allocator specification.
+ auto network6 = boost::dynamic_pointer_cast<Network6>(sn6ptr);
+ parsePdAllocatorParams(subnet, network6);
+
+ // Instantiate the allocators.
+ sn6ptr->createAllocators();
+
+ return (sn6ptr);
+}
+
+// Unused?
+void
+Subnet6ConfigParser::duplicateOptionWarning(uint32_t code,
+ asiolink::IOAddress& addr) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_OPTION_DUPLICATE)
+ .arg(code).arg(addr.toText());
+}
+
+void
+Subnet6ConfigParser::initSubnet(data::ConstElementPtr params,
+ asiolink::IOAddress addr, uint8_t len) {
+ // Subnet ID is optional. If it is not supplied the value of 0 is used,
+ // which means autogenerate. The value was inserted earlier by calling
+ // SimpleParser6::setAllDefaults.
+ int64_t subnet_id_max = static_cast<int64_t>(SUBNET_ID_MAX);
+ SubnetID subnet_id = static_cast<SubnetID>(getInteger(params, "id", 0,
+ subnet_id_max));
+
+ // We want to log whether rapid-commit is enabled, so we get this
+ // before the actual subnet creation.
+ Optional<bool> rapid_commit;
+ if (params->contains("rapid-commit")) {
+ rapid_commit = getBoolean(params, "rapid-commit");
+ }
+
+ // Parse preferred lifetime as it is not parsed by the common function.
+ Triplet<uint32_t> pref = parseIntTriplet(params, "preferred-lifetime");
+
+ // Create a new subnet.
+ auto subnet6 = Subnet6::create(addr, len, Triplet<uint32_t>(),
+ Triplet<uint32_t>(),
+ pref,
+ Triplet<uint32_t>(),
+ subnet_id);
+ subnet_ = subnet6;
+
+ // Move from reservation mode to new reservations flags.
+ ElementPtr mutable_params;
+ mutable_params = boost::const_pointer_cast<Element>(params);
+ // @todo add warning
+ BaseNetworkParser::moveReservationMode(mutable_params);
+
+ // Parse parameters common to all Network derivations.
+ NetworkPtr network = boost::dynamic_pointer_cast<Network>(subnet_);
+ parseCommon(mutable_params, network);
+
+ // Enable or disable Rapid Commit option support for the subnet.
+ if (!rapid_commit.unspecified()) {
+ subnet6->setRapidCommit(rapid_commit);
+ }
+
+ std::ostringstream output;
+ output << addr << "/" << static_cast<int>(len) << " with params: ";
+ // t1 and t2 are optional may be not specified.
+
+ bool has_renew = !subnet6->getT1().unspecified();
+ bool has_rebind = !subnet6->getT2().unspecified();
+ int64_t renew = -1;
+ int64_t rebind = -1;
+
+ if (has_renew) {
+ renew = subnet6->getT1().get();
+ output << "t1=" << renew << ", ";
+ }
+ if (has_rebind) {
+ rebind = subnet6->getT2().get();
+ output << "t2=" << rebind << ", ";
+ }
+
+ if (!subnet6->getPreferred().unspecified()) {
+ output << "preferred-lifetime=" << subnet6->getPreferred().get() << ", ";
+ }
+ if (!subnet6->getValid().unspecified()) {
+ output << "valid-lifetime=" << subnet6->getValid().get();
+ }
+ if (!subnet6->getRapidCommit().unspecified()) {
+ output << ", rapid-commit is "
+ << boolalpha << subnet6->getRapidCommit().get();
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_NEW_SUBNET6).arg(output.str());
+
+ // Get interface-id option content. For now we support string
+ // representation only
+ Optional<std::string> ifaceid;
+ if (params->contains("interface-id")) {
+ ifaceid = getString(params, "interface-id");
+ }
+
+ Optional<std::string> iface;
+ if (params->contains("interface")) {
+ iface = getString(params, "interface");
+ }
+
+ // Specifying both interface for locally reachable subnets and
+ // interface id for relays is mutually exclusive. Need to test for
+ // this condition.
+ if (!ifaceid.unspecified() && !iface.unspecified() && !ifaceid.empty() &&
+ !iface.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "parser error: interface (defined for locally reachable "
+ "subnets) and interface-id (defined for subnets reachable"
+ " via relays) cannot be defined at the same time for "
+ "subnet " << addr << "/" << (int)len << "("
+ << params->getPosition() << ")");
+ }
+
+ // Configure interface-id for remote interfaces, if defined
+ if (!ifaceid.unspecified() && !ifaceid.empty()) {
+ std::string ifaceid_value = ifaceid.get();
+ OptionBuffer tmp(ifaceid_value.begin(), ifaceid_value.end());
+ OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet6->setInterfaceId(opt);
+ }
+
+ // Get interface name. If it is defined, then the subnet is available
+ // directly over specified network interface.
+ if (!iface.unspecified() && !iface.empty()) {
+ if (check_iface_ && !IfaceMgr::instance().getIface(iface)) {
+ ConstElementPtr error = params->get("interface");
+ isc_throw(DhcpConfigError, "Specified network interface name " << iface
+ << " for subnet " << subnet6->toText()
+ << " is not present in the system ("
+ << error->getPosition() << ")");
+ }
+
+ subnet6->setIface(iface);
+ }
+
+ // Try setting up client class.
+ if (params->contains("client-class")) {
+ string client_class = getString(params, "client-class");
+ if (!client_class.empty()) {
+ subnet6->allowClientClass(client_class);
+ }
+ }
+
+ if (params->contains("require-client-classes")) {
+ // Try setting up required client classes.
+ ConstElementPtr class_list = params->get("require-client-classes");
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ subnet6->requireClientClass((*cclass)->stringValue());
+ }
+ }
+ }
+
+ /// client-class processing is now generic and handled in the common
+ /// code (see isc::data::SubnetConfigParser::createSubnet)
+
+ // Parse t1-percent and t2-percent
+ parseTeePercents(params, network);
+
+ // Parse DDNS parameters
+ parseDdnsParams(params, network);
+
+ // Parse lease cache parameters
+ parseCacheParams(params, network);
+}
+
+void
+Subnet6ConfigParser::validateResvs(const Subnet6Ptr& subnet, ConstHostPtr host) {
+ IPv6ResrvRange range = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ for (auto it = range.first; it != range.second; ++it) {
+ const IOAddress& address = it->second.getPrefix();
+ if (!subnet->inRange(address)) {
+ isc_throw(DhcpConfigError, "specified reservation '" << address
+ << "' is not within the IPv6 subnet '"
+ << subnet->toText() << "'");
+ }
+ }
+}
+
+boost::shared_ptr<PoolsListParser>
+Subnet6ConfigParser::createPoolsListParser() const {
+ auto parser = boost::make_shared<Pools6ListParser>();
+ return (parser);
+}
+
+boost::shared_ptr<PdPoolsListParser>
+Subnet6ConfigParser::createPdPoolsListParser() const {
+ auto parser = boost::make_shared<PdPoolsListParser>();
+ return (parser);
+}
+
+//**************************** Subnet6ListConfigParser ********************
+
+Subnets6ListConfigParser::Subnets6ListConfigParser(bool check_iface)
+ : check_iface_(check_iface) {
+}
+
+size_t
+Subnets6ListConfigParser::parse(SrvConfigPtr cfg,
+ ConstElementPtr subnets_list,
+ bool encapsulate_options) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ auto parser = createSubnetConfigParser();
+ Subnet6Ptr subnet = parser->parse(subnet_json, encapsulate_options);
+
+ // Adding a subnet to the Configuration Manager may fail if the
+ // subnet id is invalid (duplicate). Thus, we catch exceptions
+ // here to append a position in the configuration string.
+ try {
+ cfg->getCfgSubnets6()->add(subnet);
+ cnt++;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << subnet_json->getPosition() << ")");
+ }
+ }
+ return (cnt);
+}
+
+size_t
+Subnets6ListConfigParser::parse(Subnet6Collection& subnets,
+ ConstElementPtr subnets_list,
+ bool encapsulate_options) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ auto parser = createSubnetConfigParser();
+ Subnet6Ptr subnet = parser->parse(subnet_json, encapsulate_options);
+ if (subnet) {
+ try {
+ auto ret = subnets.insert(subnet);
+ if (!ret.second) {
+ isc_throw(Unexpected,
+ "can't store subnet because of conflict");
+ }
+ ++cnt;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << subnet_json->getPosition() << ")");
+ }
+ }
+ }
+ return (cnt);
+}
+
+boost::shared_ptr<Subnet6ConfigParser>
+Subnets6ListConfigParser::createSubnetConfigParser() const {
+ auto parser = boost::make_shared<Subnet6ConfigParser>(check_iface_);
+ return (parser);
+}
+
+//**************************** D2ClientConfigParser **********************
+
+dhcp_ddns::NameChangeProtocol
+D2ClientConfigParser::getProtocol(ConstElementPtr scope,
+ const std::string& name) {
+ return (getAndConvert<dhcp_ddns::NameChangeProtocol,
+ dhcp_ddns::stringToNcrProtocol>
+ (scope, name, "NameChangeRequest protocol"));
+}
+
+dhcp_ddns::NameChangeFormat
+D2ClientConfigParser::getFormat(ConstElementPtr scope,
+ const std::string& name) {
+ return (getAndConvert<dhcp_ddns::NameChangeFormat,
+ dhcp_ddns::stringToNcrFormat>
+ (scope, name, "NameChangeRequest format"));
+}
+
+D2ClientConfig::ReplaceClientNameMode
+D2ClientConfigParser::getMode(ConstElementPtr scope,
+ const std::string& name) {
+ return (getAndConvert<D2ClientConfig::ReplaceClientNameMode,
+ D2ClientConfig::stringToReplaceClientNameMode>
+ (scope, name, "ReplaceClientName mode"));
+}
+
+D2ClientConfigPtr
+D2ClientConfigParser::parse(isc::data::ConstElementPtr client_config) {
+ D2ClientConfigPtr new_config;
+
+ // Get all parameters that are needed to create the D2ClientConfig.
+ bool enable_updates = getBoolean(client_config, "enable-updates");
+
+ IOAddress server_ip = getAddress(client_config, "server-ip");
+
+ uint32_t server_port = getUint32(client_config, "server-port");
+
+ std::string sender_ip_str = getString(client_config, "sender-ip");
+
+ uint32_t sender_port = getUint32(client_config, "sender-port");
+
+ uint32_t max_queue_size = getUint32(client_config, "max-queue-size");
+
+ dhcp_ddns::NameChangeProtocol ncr_protocol =
+ getProtocol(client_config, "ncr-protocol");
+
+ dhcp_ddns::NameChangeFormat ncr_format =
+ getFormat(client_config, "ncr-format");
+
+ IOAddress sender_ip(0);
+ if (sender_ip_str.empty()) {
+ // The default sender IP depends on the server IP family
+ sender_ip = (server_ip.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() :
+ IOAddress::IPV6_ZERO_ADDRESS());
+ } else {
+ try {
+ sender_ip = IOAddress(sender_ip_str);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "invalid address (" << sender_ip_str
+ << ") specified for parameter 'sender-ip' ("
+ << getPosition("sender-ip", client_config) << ")");
+ }
+ }
+
+ // Now we check for logical errors. This repeats what is done in
+ // D2ClientConfig::validate(), but doing it here permits us to
+ // emit meaningful parameter position info in the error.
+ if (ncr_format != dhcp_ddns::FMT_JSON) {
+ isc_throw(D2ClientError, "D2ClientConfig error: NCR Format: "
+ << dhcp_ddns::ncrFormatToString(ncr_format)
+ << " is not supported. ("
+ << getPosition("ncr-format", client_config) << ")");
+ }
+
+ if (ncr_protocol != dhcp_ddns::NCR_UDP) {
+ isc_throw(D2ClientError, "D2ClientConfig error: NCR Protocol: "
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol)
+ << " is not supported. ("
+ << getPosition("ncr-protocol", client_config) << ")");
+ }
+
+ if (sender_ip.getFamily() != server_ip.getFamily()) {
+ isc_throw(D2ClientError,
+ "D2ClientConfig error: address family mismatch: "
+ << "server-ip: " << server_ip.toText()
+ << " is: " << (server_ip.isV4() ? "IPv4" : "IPv6")
+ << " while sender-ip: " << sender_ip.toText()
+ << " is: " << (sender_ip.isV4() ? "IPv4" : "IPv6")
+ << " (" << getPosition("sender-ip", client_config) << ")");
+ }
+
+ if (server_ip == sender_ip && server_port == sender_port) {
+ isc_throw(D2ClientError,
+ "D2ClientConfig error: server and sender cannot"
+ " share the exact same IP address/port: "
+ << server_ip.toText() << "/" << server_port
+ << " (" << getPosition("sender-ip", client_config) << ")");
+ }
+
+ try {
+ // Attempt to create the new client config.
+ new_config.reset(new D2ClientConfig(enable_updates,
+ server_ip,
+ server_port,
+ sender_ip,
+ sender_port,
+ max_queue_size,
+ ncr_protocol,
+ ncr_format));
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << client_config->getPosition() << ")");
+ }
+
+ // Add user context
+ ConstElementPtr user_context = client_config->get("user-context");
+ if (user_context) {
+ new_config->setContext(user_context);
+ }
+
+ return (new_config);
+}
+
+/// @brief This table defines default values for D2 client configuration
+const SimpleDefaults D2ClientConfigParser::D2_CLIENT_CONFIG_DEFAULTS = {
+ // enable-updates is unconditionally required
+ { "server-ip", Element::string, "127.0.0.1" },
+ { "server-port", Element::integer, "53001" },
+ // default sender-ip depends on server-ip family, so we leave default blank
+ // parser knows to use the appropriate ZERO address based on server-ip
+ { "sender-ip", Element::string, "" },
+ { "sender-port", Element::integer, "0" },
+ { "max-queue-size", Element::integer, "1024" },
+ { "ncr-protocol", Element::string, "UDP" },
+ { "ncr-format", Element::string, "JSON" }
+};
+
+size_t
+D2ClientConfigParser::setAllDefaults(isc::data::ConstElementPtr d2_config) {
+ ElementPtr mutable_d2 = boost::const_pointer_cast<Element>(d2_config);
+ return (SimpleParser::setDefaults(mutable_d2, D2_CLIENT_CONFIG_DEFAULTS));
+}
+
+} // namespace dhcp
+} // namespace isc