From: Marcin Siodelski Date: Mon, 4 Mar 2019 11:02:39 +0000 (+0100) Subject: [#488] DHCP subnet and network parsers allow unspecified parameters. X-Git-Tag: 494-dhcp4configparser-sharednetworkssanitychecks-is-buggy_base~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a63eae7463b2684483038ca4eb36ef654750d654;p=thirdparty%2Fkea.git [#488] DHCP subnet and network parsers allow unspecified parameters. --- diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index ad0cfe43da..c698534400 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -683,15 +683,17 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, t2 = getInteger(params, "rebind-timer"); } - // The valid-lifetime is mandatory. It may be specified for a - // particular subnet. If not, the global value should be present. - // If there is no global value, exception is thrown. - Triplet valid = getInteger(params, "valid-lifetime"); + Triplet valid; + if (params->contains("valid-lifetime")) { + valid = getInteger(params, "valid-lifetime"); + } // 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. - SubnetID subnet_id = static_cast(getInteger(params, "id")); + // which means autogenerate. + SubnetID subnet_id = 0; + if (params->contains("id")) { + subnet_id = static_cast(getInteger(params, "id")); + } stringstream s; s << addr << "/" << static_cast(len) << " with params: "; @@ -709,95 +711,106 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid, subnet_id)); subnet_ = subnet4; - // Set the match-client-id value for the subnet. It is always present. - // If not explicitly specified, the default value was filled in when - // SimpleParser4::setAllDefaults was called. - bool match_client_id = getBoolean(params, "match-client-id"); - subnet4->setMatchClientId(match_client_id); + // 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. It is always present. - // If not explicitly specified, the default value was filled in when - // SimpleParser4::setAllDefaults was called. - bool authoritative = getBoolean(params, "authoritative"); - subnet4->setAuthoritative(authoritative); + // 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 "". - 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(); + 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 << ")"); } - isc_throw(DhcpConfigError, "invalid parameter next-server : " - << next_server << "(" << pos << ")"); } // Set 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() << ")"); + 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); } - subnet4->setSname(sname); } // Set 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() << ")"); + 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); } - subnet4->setFilename(filename); } // Get interface name. If it is defined, then the subnet is available // directly over specified network interface. - std::string iface = getString(params, "interface"); - if (!iface.empty()) { - if (!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() << ")"); - } + if (params->contains("interface")) { + std::string iface = getString(params, "interface"); + if (!iface.empty()) { + if (!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); + subnet4->setIface(iface); + } } - // Let's set host reservation mode. If not specified, the default value of - // all will be used. - try { - std::string hr_mode = getString(params, "reservation-mode"); - subnet4->setHostReservationMode(hrModeFromText(hr_mode)); - } catch (const BadValue& ex) { - isc_throw(DhcpConfigError, "Failed to process specified value " - " of reservation-mode parameter: " << ex.what() - << "(" << getPosition("reservation-mode", params) << ")"); + // Let's set host reservation mode. + if (params->contains("reservation-mode")) { + try { + std::string hr_mode = getString(params, "reservation-mode"); + subnet4->setHostReservationMode(hrModeFromText(hr_mode)); + } catch (const BadValue& ex) { + isc_throw(DhcpConfigError, "Failed to process specified value " + " of reservation-mode parameter: " << ex.what() + << "(" << getPosition("reservation-mode", params) << ")"); + } } // Try setting up client class. - string client_class = getString(params, "client-class"); - if (!client_class.empty()) { - subnet4->allowClientClass(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. @@ -815,44 +828,48 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, } } - // 4o6 specific parameter: 4o6-interface. If not explicitly specified, - // it will have the default value of "". - string iface4o6 = getString(params, "4o6-interface"); - if (!iface4o6.empty()) { - subnet4->get4o6().setIface4o6(iface4o6); - subnet4->get4o6().enabled(true); + // 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 not explicitly specified, it - // will have the default value of "". - 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); + // 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(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"); + uint8_t len = 128; + try { + len = boost::lexical_cast(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); } - subnet4->get4o6().setSubnet4o6(IOAddress(prefix), len); - subnet4->get4o6().enabled(true); } // Try 4o6 specific parameter: 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); + 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 @@ -865,24 +882,38 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, // Copy options to the subnet configuration. options_->copyTo(*subnet4->getCfgOption()); - bool calculate_tee_times = getBoolean(params, "calculate-tee-times"); - subnet4->setCalculateTeeTimes(calculate_tee_times); - float t2_percent = getDouble(params, "t2-percent"); - float t1_percent = getDouble(params, "t1-percent"); + bool calculate_tee_times = subnet4->getCalculateTeeTimes(); + if (params->contains("calculate-tee-times")) { + bool calculate_tee_times = getBoolean(params, "calculate-tee-times"); + subnet4->setCalculateTeeTimes(calculate_tee_times); + } + + Optional t2_percent; + if (params->contains("t2-percent")) { + t2_percent = getDouble(params, "t2-percent"); + } + + Optional t1_percent; + if (params->contains("t1-percent")) { + t1_percent = getDouble(params, "t1-percent"); + } if (calculate_tee_times) { - if (t2_percent <= 0.0 || t2_percent >= 1.0) { - isc_throw(DhcpConfigError, "t2-percent: " << t2_percent + if (!t2_percent.unspecified() && ((t2_percent.get() <= 0.0) || + (t2_percent.get() >= 1.0))) { + isc_throw(DhcpConfigError, "t2-percent: " << t2_percent.get() << " is invalid, it must be greater than 0.0 and less than 1.0"); } - if (t1_percent <= 0.0 || t1_percent >= 1.0) { - isc_throw(DhcpConfigError, "t1-percent: " << t1_percent + if (!t1_percent.unspecified() && ((t1_percent.get() <= 0.0) || + (t1_percent.get() >= 1.0))) { + isc_throw(DhcpConfigError, "t1-percent: " << t1_percent.get() << " is invalid it must be greater than 0.0 and less than 1.0"); } - if (t1_percent >= t2_percent) { - isc_throw(DhcpConfigError, "t1-percent: " << t1_percent - << " is invalid, it must be less than t2-percent: " << t2_percent); + if (!t1_percent.unspecified() && !t2_percent.unspecified() && + (t1_percent.get() >= t2_percent.get())) { + isc_throw(DhcpConfigError, "t1-percent: " << t1_percent.get() + << " is invalid, it must be less than t2-percent: " << t2_percent.get()); } } @@ -1131,15 +1162,26 @@ Subnet6ConfigParser::duplicate_option_warning(uint32_t code, void Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, asiolink::IOAddress addr, uint8_t len) { - // Get all 'time' parameters using inheritance. - // If the subnet-specific value is defined then use it, else - // use the global value. The global value must always be - // present. If it is not, it is an internal error and exception - // is thrown. - Triplet t1 = getInteger(params, "renew-timer"); - Triplet t2 = getInteger(params, "rebind-timer"); - Triplet pref = getInteger(params, "preferred-lifetime"); - Triplet valid = getInteger(params, "valid-lifetime"); + // Get all 'time' parameters. + Triplet t1; + if (params->contains("renew-timer")) { + t1 = getInteger(params, "renew-timer"); + } + + Triplet t2; + if (params->contains("rebind-timer")) { + t2 = getInteger(params, "rebind-timer"); + } + + Triplet pref; + if (params->contains("preferred-lifetime")) { + pref = getInteger(params, "preferred-lifetime"); + } + + Triplet valid; + if (params->contains("valid-lifetime")) { + valid = getInteger(params, "valid-lifetime"); + } // 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 @@ -1148,7 +1190,10 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, // We want to log whether rapid-commit is enabled, so we get this // before the actual subnet creation. - bool rapid_commit = getBoolean(params, "rapid-commit"); + Optional rapid_commit; + if (params->contains("rapid-commit")) { + rapid_commit = getBoolean(params, "rapid-commit"); + } std::ostringstream output; output << addr << "/" << static_cast(len) @@ -1162,21 +1207,31 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, // Create a new subnet. Subnet6* subnet6 = new Subnet6(addr, len, t1, t2, pref, valid, - subnet_id); + subnet_id); subnet_.reset(subnet6); // Enable or disable Rapid Commit option support for the subnet. - subnet6->setRapidCommit(rapid_commit); + if (!rapid_commit.unspecified()) { + subnet6->setRapidCommit(rapid_commit); + } // Get interface-id option content. For now we support string // representation only - std::string ifaceid = getString(params, "interface-id"); - std::string iface = getString(params, "interface"); + Optional ifaceid; + if (params->contains("interface-id")) { + ifaceid = getString(params, "interface-id"); + } + + Optional 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.empty() && !iface.empty()) { + 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" @@ -1186,15 +1241,16 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, } // Configure interface-id for remote interfaces, if defined - if (!ifaceid.empty()) { - OptionBuffer tmp(ifaceid.begin(), ifaceid.end()); + 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.empty()) { + if (!iface.unspecified() && !iface.empty()) { if (!IfaceMgr::instance().getIface(iface)) { ConstElementPtr error = params->get("interface"); isc_throw(DhcpConfigError, "Specified network interface name " << iface @@ -1208,33 +1264,39 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, // Let's set host reservation mode. If not specified, the default value of // all will be used. - try { - std::string hr_mode = getString(params, "reservation-mode"); - subnet6->setHostReservationMode(hrModeFromText(hr_mode)); - } catch (const BadValue& ex) { - isc_throw(DhcpConfigError, "Failed to process specified value " - " of reservation-mode parameter: " << ex.what() - << "(" << getPosition("reservation-mode", params) << ")"); + if (params->contains("reservation-mode")) { + try { + std::string hr_mode = getString(params, "reservation-mode"); + subnet6->setHostReservationMode(hrModeFromText(hr_mode)); + } catch (const BadValue& ex) { + isc_throw(DhcpConfigError, "Failed to process specified value " + " of reservation-mode parameter: " << ex.what() + << "(" << getPosition("reservation-mode", params) << ")"); + } } // Try setting up client class. - string client_class = getString(params, "client-class"); - if (!client_class.empty()) { - subnet6->allowClientClass(client_class); + if (params->contains("client-class")) { + string client_class = getString(params, "client-class"); + if (!client_class.empty()) { + subnet6->allowClientClass(client_class); + } } - // Try setting up required client classes. - ConstElementPtr class_list = params->get("require-client-classes"); - if (class_list) { - const std::vector& 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() << ")"); + 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& 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()); } - subnet6->requireClientClass((*cclass)->stringValue()); } } diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index c750b6866f..5a6725f9a7 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2019 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 @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -177,10 +179,11 @@ public: /// the parsed elements. /// /// @param config_set is the set of elements to parse. + /// @param v6 boolean flag indicating if this is a DHCPv6 configuration. /// @return returns an ConstElementPtr containing the numeric result /// code and outcome comment. - isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr - config_set) { + isc::data::ConstElementPtr + parseElementSet(isc::data::ConstElementPtr config_set, bool v6) { // Answer will hold the result. ConstElementPtr answer; if (!config_set) { @@ -254,6 +257,37 @@ public: CfgMgr::instance().setD2ClientConfig(cfg); } + std::map::const_iterator + subnets4_config = values_map.find("subnet4"); + if (subnets4_config != values_map.end()) { + auto srv_config = CfgMgr::instance().getStagingCfg(); + Subnets4ListConfigParser parser; + parser.parse(srv_config, subnets4_config->second); + } + + std::map::const_iterator + subnets6_config = values_map.find("subnet6"); + if (subnets6_config != values_map.end()) { + auto srv_config = CfgMgr::instance().getStagingCfg(); + Subnets6ListConfigParser parser; + parser.parse(srv_config, subnets6_config->second); + } + + std::map::const_iterator + networks_config = values_map.find("shared-networks"); + if (networks_config != values_map.end()) { + if (v6) { + auto cfg_shared_networks = CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6(); + SharedNetworks6ListParser parser; + parser.parse(cfg_shared_networks, networks_config->second); + + } else { + auto cfg_shared_networks = CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4(); + SharedNetworks4ListParser parser; + parser.parse(cfg_shared_networks, networks_config->second); + } + } + // Everything was fine. Configuration is successful. answer = isc::config::createAnswer(0, "Configuration committed."); } catch (const isc::Exception& ex) { @@ -354,19 +388,25 @@ public: /// Given a configuration string, convert it into Elements /// and parse them. /// @param config is the configuration string to parse + /// @param v6 boolean value indicating if this is DHCPv6 configuration. + /// @param set_defaults boolean value indicating if the defaults should + /// be derived before parsing the configuration. /// /// @return returns 0 if the configuration parsed successfully, /// non-zero otherwise failure. - int parseConfiguration(const std::string& config, bool v6 = false) { + int parseConfiguration(const std::string& config, bool v6 = false, + bool set_defaults = true) { int rcode_ = 1; // Turn config into elements. // Test json just to make sure its valid. ElementPtr json = Element::fromJSON(config); EXPECT_TRUE(json); if (json) { - setAllDefaults(json, v6); + if (set_defaults) { + setAllDefaults(json, v6); + } - ConstElementPtr status = parseElementSet(json); + ConstElementPtr status = parseElementSet(json, v6); ConstElementPtr comment = parseAnswer(rcode_, status); error_text_ = comment->stringValue(); // If error was reported, the error string should contain @@ -2314,6 +2354,230 @@ TEST_F(ParseConfigTest, bogusRelayInfo6) { EXPECT_THROW(parser.parse(result, json_bogus3), DhcpConfigError); } +// This test verifies that it is possible to parse an IPv4 subnet for which +// only mandatory parameters are specified without setting the defaults. +TEST_F(ParseConfigTest, defaultSubnet4) { + std::string config = + "{" + " \"subnet4\": [ {" + " \"subnet\": \"192.0.2.0/24\"," + " \"id\": 123" + " } ]" + "}"; + + int rcode = parseConfiguration(config, false, false); + ASSERT_EQ(0, rcode); + + auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getSubnet(123); + ASSERT_TRUE(subnet); + + EXPECT_TRUE(subnet->getIface().unspecified()); + EXPECT_TRUE(subnet->getIface().empty()); + + EXPECT_TRUE(subnet->getClientClass().unspecified()); + EXPECT_TRUE(subnet->getClientClass().empty()); + + EXPECT_TRUE(subnet->getValid().unspecified()); + EXPECT_EQ(0, subnet->getValid().get()); + + EXPECT_TRUE(subnet->getT1().unspecified()); + EXPECT_EQ(0, subnet->getT1().get()); + + EXPECT_TRUE(subnet->getT2().unspecified()); + EXPECT_EQ(0, subnet->getT2().get()); + + EXPECT_TRUE(subnet->getHostReservationMode().unspecified()); + EXPECT_EQ(Network::HR_ALL, subnet->getHostReservationMode().get()); + + EXPECT_TRUE(subnet->getCalculateTeeTimes().unspecified()); + EXPECT_FALSE(subnet->getCalculateTeeTimes().get()); + + EXPECT_TRUE(subnet->getT1Percent().unspecified()); + EXPECT_EQ(0.0, subnet->getT1Percent().get()); + + EXPECT_TRUE(subnet->getT2Percent().unspecified()); + EXPECT_EQ(0.0, subnet->getT2Percent().get()); + + EXPECT_TRUE(subnet->getMatchClientId().unspecified()); + EXPECT_TRUE(subnet->getMatchClientId().get()); + + EXPECT_TRUE(subnet->getAuthoritative().unspecified()); + EXPECT_FALSE(subnet->getAuthoritative().get()); + + EXPECT_TRUE(subnet->getSiaddr().unspecified()); + EXPECT_TRUE(subnet->getSiaddr().get().isV4Zero()); + + EXPECT_TRUE(subnet->getSname().unspecified()); + EXPECT_TRUE(subnet->getSname().empty()); + + EXPECT_TRUE(subnet->getFilename().unspecified()); + EXPECT_TRUE(subnet->getFilename().empty()); + + EXPECT_FALSE(subnet->get4o6().enabled()); + + EXPECT_TRUE(subnet->get4o6().getIface4o6().unspecified()); + EXPECT_TRUE(subnet->get4o6().getIface4o6().empty()); + + EXPECT_TRUE(subnet->get4o6().getSubnet4o6().unspecified()); + EXPECT_TRUE(subnet->get4o6().getSubnet4o6().get().first.isV6Zero()); + EXPECT_EQ(128, subnet->get4o6().getSubnet4o6().get().second); +} + +// This test verifies that it is possible to parse an IPv6 subnet for which +// only mandatory parameters are specified without setting the defaults. +TEST_F(ParseConfigTest, defaultSubnet6) { + std::string config = + "{" + " \"subnet6\": [ {" + " \"subnet\": \"2001:db8:1::/64\"," + " \"id\": 123" + " } ]" + "}"; + + int rcode = parseConfiguration(config, true, false); + ASSERT_EQ(0, rcode); + + auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getSubnet(123); + ASSERT_TRUE(subnet); + + EXPECT_TRUE(subnet->getIface().unspecified()); + EXPECT_TRUE(subnet->getIface().empty()); + + EXPECT_TRUE(subnet->getClientClass().unspecified()); + EXPECT_TRUE(subnet->getClientClass().empty()); + + EXPECT_TRUE(subnet->getValid().unspecified()); + EXPECT_EQ(0, subnet->getValid().get()); + + EXPECT_TRUE(subnet->getT1().unspecified()); + EXPECT_EQ(0, subnet->getT1().get()); + + EXPECT_TRUE(subnet->getT2().unspecified()); + EXPECT_EQ(0, subnet->getT2().get()); + + EXPECT_TRUE(subnet->getHostReservationMode().unspecified()); + EXPECT_EQ(Network::HR_ALL, subnet->getHostReservationMode().get()); + + EXPECT_TRUE(subnet->getCalculateTeeTimes().unspecified()); + EXPECT_FALSE(subnet->getCalculateTeeTimes().get()); + + EXPECT_TRUE(subnet->getT1Percent().unspecified()); + EXPECT_EQ(0.0, subnet->getT1Percent().get()); + + EXPECT_TRUE(subnet->getT2Percent().unspecified()); + EXPECT_EQ(0.0, subnet->getT2Percent().get()); + + EXPECT_TRUE(subnet->getPreferred().unspecified()); + EXPECT_EQ(0, subnet->getPreferred().get()); + + EXPECT_TRUE(subnet->getRapidCommit().unspecified()); + EXPECT_FALSE(subnet->getRapidCommit().get()); +} + +// This test verifies that it is possible to parse an IPv4 shared network +// for which only mandatory parameter is specified without setting the +// defaults. +TEST_F(ParseConfigTest, defaultSharedNetwork4) { + std::string config = + "{" + " \"shared-networks\": [ {" + " \"name\": \"frog\"" + " } ]" + "}"; + + int rcode = parseConfiguration(config, false, false); + ASSERT_EQ(0, rcode); + + auto network = + CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->getByName("frog"); + ASSERT_TRUE(network); + + EXPECT_TRUE(network->getIface().unspecified()); + EXPECT_TRUE(network->getIface().empty()); + + EXPECT_TRUE(network->getClientClass().unspecified()); + EXPECT_TRUE(network->getClientClass().empty()); + + EXPECT_TRUE(network->getValid().unspecified()); + EXPECT_EQ(0, network->getValid().get()); + + EXPECT_TRUE(network->getT1().unspecified()); + EXPECT_EQ(0, network->getT1().get()); + + EXPECT_TRUE(network->getT2().unspecified()); + EXPECT_EQ(0, network->getT2().get()); + + EXPECT_TRUE(network->getHostReservationMode().unspecified()); + EXPECT_EQ(Network::HR_ALL, network->getHostReservationMode().get()); + + EXPECT_TRUE(network->getCalculateTeeTimes().unspecified()); + EXPECT_FALSE(network->getCalculateTeeTimes().get()); + + EXPECT_TRUE(network->getT1Percent().unspecified()); + EXPECT_EQ(0.0, network->getT1Percent().get()); + + EXPECT_TRUE(network->getT2Percent().unspecified()); + EXPECT_EQ(0.0, network->getT2Percent().get()); + + EXPECT_TRUE(network->getMatchClientId().unspecified()); + EXPECT_TRUE(network->getMatchClientId().get()); + + EXPECT_TRUE(network->getAuthoritative().unspecified()); + EXPECT_FALSE(network->getAuthoritative().get()); +} + +// This test verifies that it is possible to parse an IPv6 shared network +// for which only mandatory parameter is specified without setting the +// defaults. +TEST_F(ParseConfigTest, defaultSharedNetwork6) { + std::string config = + "{" + " \"shared-networks\": [ {" + " \"name\": \"frog\"" + " } ]" + "}"; + + int rcode = parseConfiguration(config, true, false); + ASSERT_EQ(0, rcode); + + auto network = + CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6()->getByName("frog"); + ASSERT_TRUE(network); + + EXPECT_TRUE(network->getIface().unspecified()); + EXPECT_TRUE(network->getIface().empty()); + + EXPECT_TRUE(network->getClientClass().unspecified()); + EXPECT_TRUE(network->getClientClass().empty()); + + EXPECT_TRUE(network->getValid().unspecified()); + EXPECT_EQ(0, network->getValid().get()); + + EXPECT_TRUE(network->getT1().unspecified()); + EXPECT_EQ(0, network->getT1().get()); + + EXPECT_TRUE(network->getT2().unspecified()); + EXPECT_EQ(0, network->getT2().get()); + + EXPECT_TRUE(network->getHostReservationMode().unspecified()); + EXPECT_EQ(Network::HR_ALL, network->getHostReservationMode().get()); + + EXPECT_TRUE(network->getCalculateTeeTimes().unspecified()); + EXPECT_FALSE(network->getCalculateTeeTimes().get()); + + EXPECT_TRUE(network->getT1Percent().unspecified()); + EXPECT_EQ(0.0, network->getT1Percent().get()); + + EXPECT_TRUE(network->getT2Percent().unspecified()); + EXPECT_EQ(0.0, network->getT2Percent().get()); + + EXPECT_TRUE(network->getPreferred().unspecified()); + EXPECT_EQ(0, network->getPreferred().get()); + + EXPECT_TRUE(network->getRapidCommit().unspecified()); + EXPECT_FALSE(network->getRapidCommit().get()); +} + // There's no test for ControlSocketParser, as it is tested in the DHCPv4 code // (see CtrlDhcpv4SrvTest.commandSocketBasic in // src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc).