params_set.insert("hostname");
params_set.insert("ip-addresses");
params_set.insert("prefixes");
+ params_set.insert("excluded-prefixes");
params_set.insert("option-data");
params_set.insert("client-classes");
params_set.insert("user-context");
return (getSupportedParams4(identifiers_only));
}
+namespace {
+
+// Extract the prefix length from the value specified in
+// the following format: 2001:db8:2000::/64.
+std::pair<IOAddress, uint8_t>
+parsePrefix(std::string prefix, std::string msg) {
+ uint8_t prefix_len = 128;
+ // The slash is mandatory for prefixes. If there is no slash,
+ // return an error.
+ size_t len_pos = prefix.find('/');
+ if (len_pos == std::string::npos) {
+ isc_throw(DhcpConfigError, msg << " requires prefix length "
+ << "be specified in '" << prefix << "'");
+
+ // If there is nothing after the slash, we should also
+ // report an error.
+ } else if (len_pos >= prefix.length() - 1) {
+ isc_throw(DhcpConfigError, "prefix '" << prefix
+ << "' requires length after '/'");
+
+ }
+
+ // Convert the prefix length from the string to the number.
+ // Note, that we don't use the uint8_t type as the lexical cast
+ // would expect a character, e.g. 'a', instead of prefix length,
+ // e.g. '64'.
+
+ try {
+ prefix_len = boost::lexical_cast<unsigned int>(prefix.substr(len_pos + 1));
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(DhcpConfigError, "prefix length value '"
+ << prefix.substr(len_pos + 1) << "' is invalid");
+ }
+
+ if ((prefix_len == 0) || (prefix_len > 128)) {
+ isc_throw(OutOfRange,
+ "'prefix-len' value must be in range of [1..128]");
+ }
+
+ // Remove the slash character and the prefix length from the
+ // parsed value.
+ prefix.erase(len_pos);
+ IOAddress addr(prefix);
+
+ if (prefix_len != 128) {
+ IOAddress first_address = firstAddrInPrefix(addr, prefix_len);
+ if (first_address != addr) {
+ isc_throw(BadValue, "Prefix address: " << addr
+ << " exceeds prefix/prefix-len pair: " << first_address
+ << "/" << static_cast<uint32_t>(prefix_len));
+ }
+ }
+ return (std::pair<IOAddress, uint8_t>(addr, prefix_len));
+}
+
+} // end of anonymous namespace.
+
HostPtr
HostReservationParser6::parseInternal(const SubnetID& subnet_id,
isc::data::ConstElementPtr reservation_data,
host->setIPv6SubnetID(subnet_id);
- for (auto const& element : reservation_data->mapValue()) {
- // Parse option values. Note that the configuration option parser
- // returns errors with position information appended, so there is no
- // need to surround it with try-clause (and rethrow with position
- // appended).
- if (element.first == "option-data") {
- CfgOptionPtr cfg_option = host->getCfgOption6();
-
- // This parser is converted to SimpleParser already. It
- // parses the Element structure immediately, there's no need
- // to go through build/commit phases.
- OptionDataListParser parser(AF_INET6);
- parser.parse(cfg_option, element.second, encapsulate_options);
-
- } else if (element.first == "ip-addresses" || element.first == "prefixes") {
- for (auto const& prefix_element : element.second->listValue()) {
- try {
- // For the IPv6 address the prefix length is 128 and the
- // value specified in the list is a reserved address.
- IPv6Resrv::Type resrv_type = IPv6Resrv::TYPE_NA;
- std::string prefix = prefix_element->stringValue();
- uint8_t prefix_len = 128;
-
- // If we're dealing with prefixes, instead of addresses,
- // we will have to extract the prefix length from the value
- // specified in the following format: 2001:db8:2000::/64.
- if (element.first == "prefixes") {
- // The slash is mandatory for prefixes. If there is no
- // slash, return an error.
- size_t len_pos = prefix.find('/');
- if (len_pos == std::string::npos) {
- isc_throw(DhcpConfigError, "prefix reservation"
- " requires prefix length be specified"
- " in '" << prefix << "'");
-
- // If there is nothing after the slash, we should also
- // report an error.
- } else if (len_pos >= prefix.length() - 1) {
- isc_throw(DhcpConfigError, "prefix '" << prefix
- << "' requires length after '/'");
-
- }
-
- // Convert the prefix length from the string to the
- // number. Note, that we don't use the uint8_t type
- // as the lexical cast would expect a character, e.g.
- // 'a', instead of prefix length, e.g. '64'.
- try {
- prefix_len = boost::lexical_cast<unsigned int>(prefix.substr(len_pos + 1));
-
- } catch (const boost::bad_lexical_cast&) {
- isc_throw(DhcpConfigError, "prefix length value '"
- << prefix.substr(len_pos + 1)
- << "' is invalid");
- }
-
- if ((prefix_len == 0) || (prefix_len > 128)) {
- isc_throw(OutOfRange,
- "'prefix-len' value must be in range of [1..128]");
- }
-
- // Remove the slash character and the prefix length
- // from the parsed value.
- prefix.erase(len_pos);
-
- // Finally, set the reservation type.
- resrv_type = IPv6Resrv::TYPE_PD;
-
- if (prefix_len != 128) {
- IOAddress addr(prefix);
- IOAddress first_address = firstAddrInPrefix(addr, prefix_len);
- if (first_address != addr) {
- isc_throw(BadValue, "Prefix address: " << addr
- << " exceeds prefix/prefix-len pair: " << first_address
- << "/" << static_cast<uint32_t>(prefix_len));
- }
- }
- }
-
- // Create a reservation for an address or prefix.
- host->addReservation(IPv6Resrv(resrv_type,
- IOAddress(prefix),
- prefix_len));
+ ConstElementPtr option_data = reservation_data->get("option-data");
+ ConstElementPtr ip_addresses = reservation_data->get("ip-addresses");
+ ConstElementPtr prefixes = reservation_data->get("prefixes");
+ ConstElementPtr excluded_prefixes = reservation_data->get("excluded-prefixes");
+ ConstElementPtr client_classes = reservation_data->get("client-classes");
+
+ // Parse option values. Note that the configuration option parser
+ // returns errors with position information appended, so there is no
+ // need to surround it with try-clause (and rethrow with position
+ // appended).
+ if (option_data) {
+ CfgOptionPtr cfg_option = host->getCfgOption6();
+
+ // This parser is converted to SimpleParser already. It
+ // parses the Element structure immediately, there's no need
+ // to go through build/commit phases.
+ OptionDataListParser parser(AF_INET6);
+ parser.parse(cfg_option, option_data, encapsulate_options);
+ }
- } catch (const std::exception& ex) {
- // Append line number where the error occurred.
- isc_throw(DhcpConfigError, ex.what() << " ("
- << prefix_element->getPosition() << ")");
- }
+ if (ip_addresses) {
+ for (size_t idx = 0; idx < ip_addresses->size(); ++idx) {
+ ConstElementPtr element = ip_addresses->get(idx);
+ try {
+ // For the IPv6 address the prefix length is 128 and the
+ // value specified in the list is a reserved address.
+ std::string addr = element->stringValue();
+ // Create a reservation for the address.
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress(addr), 128));
+ } catch (const std::exception& ex) {
+ // Append line number where the error occurred.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << element->getPosition() << ")");
}
+ }
+ }
+
+ if (excluded_prefixes) {
+ if (!prefixes) {
+ isc_throw(DhcpConfigError, "'excluded-prefixes' parameter "
+ "requires the 'prefixes' parameter");
+ }
+ if (excluded_prefixes->size() != prefixes->size()) {
+ isc_throw(DhcpConfigError, "'excluded-prefixes' parameter "
+ "does not match the 'prefixes' parameter: "
+ << excluded_prefixes->size() << " != "
+ << prefixes->size());
+ }
+ }
- } else if (element.first == "client-classes") {
+ if (prefixes) {
+ for (size_t idx = 0; idx < prefixes->size(); ++idx) {
+ ConstElementPtr element = prefixes->get(idx);
try {
- for (auto const& class_element : element.second->listValue()) {
- host->addClientClass6(class_element->stringValue());
+ std::string prefix = element->stringValue();
+ auto const p = parsePrefix(prefix, "prefix reservation");
+ IPv6Resrv res(IPv6Resrv::TYPE_PD, p.first, p.second);
+ std::string exclude("");
+ if (excluded_prefixes) {
+ element = excluded_prefixes->get(idx);
+ exclude = element->stringValue();
+ }
+ if (!exclude.empty()) {
+ auto const x = parsePrefix(exclude, "exclude prefix");
+ res.setPDExclude(x.first, x.second);
}
+ host->addReservation(res);
} catch (const std::exception& ex) {
// Append line number where the error occurred.
isc_throw(DhcpConfigError, ex.what() << " ("
- << element.second->getPosition() << ")");
+ << element->getPosition() << ")");
+ }
+ }
+ }
+
+ if (client_classes) {
+ try {
+ for (auto const& class_element : client_classes->listValue()) {
+ host->addClientClass6(class_element->stringValue());
}
+ } catch (const std::exception& ex) {
+ // Append line number where the error occurred.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << client_classes->getPosition() << ")");
}
}
if (prefixes && prefixes->empty()) {
resv->remove("prefixes");
}
+ ConstElementPtr excluded_prefixes = resv->get("excluded-prefixes");
+ if (excluded_prefixes && excluded_prefixes->empty()) {
+ resv->remove("excluded-prefixes");
+ }
ConstElementPtr hostname = resv->get("hostname");
if (hostname && hostname->stringValue().empty()) {
resv->remove("hostname");
testInvalidConfig<HostReservationParser6>(config);
}
+// This test verifies that the configuration parser throws an exception
+// when the excluded prefix list is shorter.
+TEST_F(HostReservationParserTest, dhcp6ExcludedTooShort) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the excluded prefix list is too long.
+TEST_F(HostReservationParserTest, dhcp6ExcludedTooLong) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"\", \"\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
// This test verifies that the configuration parser throws an exception
// when invalid prefix length type is specified.
TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLengthType) {
testInvalidConfig<HostReservationParser6>(config);
}
+// This test verifies that the configuration parser throws an exception
+// when invalid excluded prefix length type is specified.
+TEST_F(HostReservationParserTest, dhcp6InvalidExcludedPrefixLengthType) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"2001:db8:0:1::/abc\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
// This test verifies that the configuration parser throws an exception
// when invalid prefix length is specified.
TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLength) {
testInvalidConfig<HostReservationParser6>(config);
}
+// This test verifies that the configuration parser throws an exception
+// when invalid excluded prefix length is specified.
+TEST_F(HostReservationParserTest, dhcp6InvalidExcludedPrefixLength) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"2001:db8::0:0:1::/64\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
// This test verifies that the configuration parser throws an exception
// when empty prefix is specified.
TEST_F(HostReservationParserTest, dhcp6NullPrefix) {
testInvalidConfig<HostReservationParser6>(config);
}
+// This test verifies that the configuration parser throws an exception
+// when empty excluded prefix is specified.
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"/64\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
// This test verifies that the configuration parser throws an exception
// when only slash is specified for the prefix..
TEST_F(HostReservationParserTest, dhcp6NullPrefix2) {
testInvalidConfig<HostReservationParser6>(config);
}
+// This test verifies that the configuration parser throws an exception
+// when only slash is specified for the excluded prefix..
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix2) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"/\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
// This test verifies that the configuration parser throws an exception
// when slash is missing for the prefix..
TEST_F(HostReservationParserTest, dhcp6NullPrefix3) {
testInvalidConfig<HostReservationParser6>(config);
}
+// This test verifies that the configuration parser throws an exception
+// when slash is missing for the excluded prefix..
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix3) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"2001:db8:0:1::\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
// This test verifies that the configuration parser throws an exception
// when slash is followed by nothing for the prefix..
TEST_F(HostReservationParserTest, dhcp6NullPrefix4) {
testInvalidConfig<HostReservationParser6>(config);
}
+// This test verifies that the configuration parser throws an exception
+// when slash is followed by nothing for the excluded prefix..
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix4) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"2001:db8:0:1::/\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
// This test verifies that the configuration parser throws an exception
// when slash is not followed by a number for the prefix..
TEST_F(HostReservationParserTest, dhcp6NullPrefix5) {
testInvalidConfig<HostReservationParser6>(config);
}
+// This test verifies that the configuration parser throws an exception
+// when slash is not followed by a number for the excluded prefix..
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix5) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"2001:db8:0:1::/foo\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
// This test verifies that the configuration parser throws an exception
// when the same address is reserved twice.
TEST_F(HostReservationParserTest, dhcp6DuplicatedAddress) {
testInvalidConfig<HostReservationParser6>(config);
}
+// This test verifies that the configuration parser throws an exception
+// when the same prefix is reserved twice with different exclude prefixes.
+TEST_F(HostReservationParserTest, dhcp6DuplicatedPrefix2) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\", \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"\", \"2001:db8:0:1::/64\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the excluded prefix is invalid (not in prefix).
+TEST_F(HostReservationParserTest, dhcp6InvalidExcludedPrefix) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"2001:db8:1::/64\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the excluded prefix is invalid (same length).
+TEST_F(HostReservationParserTest, dhcp6InvalidExcludedPrefix2) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8::/48\" ],"
+ "\"excluded-prefixes\": [ \"2001:db8::/48\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
// This test verifies that the configuration parser for host reservations
// throws an exception when unsupported parameter is specified.
TEST_F(HostReservationParserTest, dhcp6invalidParameterName) {
if (prefixes && prefixes->empty()) {
resv->remove("prefixes");
}
+ ConstElementPtr excluded_prefixes = resv->get("excluded-prefixes");
+ if (excluded_prefixes && excluded_prefixes->empty()) {
+ resv->remove("excluded-prefixes");
+ }
ConstElementPtr hostname = resv->get("hostname");
if (hostname && hostname->stringValue().empty()) {
resv->remove("hostname");
runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
}
+// This test verifies that the parser for the list of the host reservations
+// parses IPv6 reservations with Prefix Exclude options correctly.
+TEST_F(HostReservationsListParserTest, prefixExclude) {
+ // hexadecimal in lower case for toElement()
+ std::string config =
+ "[ "
+ " { \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ " \"ip-addresses\": [ ],"
+ " \"prefixes\": [ \"2001:db8::/48\" ],"
+ " \"excluded-prefixes\": [ \"2001:db8:0:1::/64\" ],"
+ " \"hostname\": \"foo.example.com\" "
+ " }, "
+ " { \"hw-address\": \"01:02:03:04:05:06\","
+ " \"ip-addresses\": [ \"2001:db8:1::123\" ],"
+ " \"hostname\": \"bar.example.com\" "
+ " } "
+ "]";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration.
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser6> parser;
+ ASSERT_NO_THROW(parser.parse(SubnetID(2), config_element, hosts));
+
+ for (auto const& h : hosts) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(h);
+ }
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+
+ // Get the reservation for the host identified by the HW address.
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
+ &hwaddr_->hwaddr_[0],
+ hwaddr_->hwaddr_.size()));
+ ASSERT_EQ(1, hosts.size());
+
+ // Make sure it belongs to a valid subnet.
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+
+ // Get the reserved addresses for the host. There should be exactly one
+ // address reserved for this host.
+ IPv6ResrvRange prefixes =
+ hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(prefixes.first, prefixes.second));
+
+ EXPECT_EQ(IPv6Resrv::TYPE_NA, prefixes.first->second.getType());
+ EXPECT_EQ("2001:db8:1::123", prefixes.first->second.getPrefix().toText());
+ EXPECT_EQ(128, prefixes.first->second.getPrefixLen());
+
+ // Validate the second reservation.
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
+ &duid_->getDuid()[0],
+ duid_->getDuid().size()));
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+
+ // This reservation was for a prefix, instead of an IPv6 address.
+ prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(1, std::distance(prefixes.first, prefixes.second));
+
+ EXPECT_EQ(IPv6Resrv::TYPE_PD, prefixes.first->second.getType());
+ EXPECT_EQ("2001:db8::", prefixes.first->second.getPrefix().toText());
+ EXPECT_EQ(48, prefixes.first->second.getPrefixLen());
+ EXPECT_TRUE(prefixes.first->second.getPDExclude());
+ EXPECT_EQ("2001:db8:0:1::/64", prefixes.first->second.PDExcludetoText());
+
+ // Get back the config from cfg_hosts
+ ElementPtr resv = config_element->getNonConst(0);
+ resv->remove("ip-addresses");
+ config = prettyPrint(config_element);
+ boost::algorithm::to_lower(config);
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(2));
+ runToElementTest<CfgHostsSubnet>(config, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(2));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
// This test verifies that an attempt to add two reservations with the
// same identifier value will return an error.
TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue6) {