From: Marcin Siodelski Date: Mon, 27 Oct 2014 16:35:09 +0000 (+0100) Subject: [3562] HostReservationConfig parser now parses simple host reservations. X-Git-Tag: kea-eng-20141219~28^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=228c2228a044bd71f69052059d78f051e56171dd;p=thirdparty%2Fkea.git [3562] HostReservationConfig parser now parses simple host reservations. The parser works for both IPv4 and IPv6 reservations but it lacks some validation mechanisms. --- diff --git a/src/lib/dhcpsrv/host_reservation_parser.cc b/src/lib/dhcpsrv/host_reservation_parser.cc index 8d78a4b9f5..26149e6188 100644 --- a/src/lib/dhcpsrv/host_reservation_parser.cc +++ b/src/lib/dhcpsrv/host_reservation_parser.cc @@ -13,17 +13,20 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include #include #include +#include #include using namespace isc::asiolink; +using namespace isc::data; namespace isc { namespace dhcp { -HostReservationParser::HostReservationParser() - : DhcpConfigParser() { +HostReservationParser::HostReservationParser(const SubnetID& subnet_id) + : DhcpConfigParser(), subnet_id_(subnet_id) { } void @@ -32,6 +35,8 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) { std::string identifier_name; std::string hostname; + // Gather those parameters that are common for both IPv4 and IPv6 + // reservations. BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) { try { if (element.first == "hw-address" || element.first == "duid") { @@ -53,8 +58,136 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) { } } - host_.reset(new Host(identifier, identifier_name, SubnetID(0), SubnetID(0), - IOAddress("0.0.0.0"), hostname)); + try { + // hw-address or duid is a must. + if (identifier_name.empty()) { + isc_throw(DhcpConfigError, "'hw-address' or 'duid' is a requirement" + " parameter for host reservation"); + } + + // Create a host object from the basic parameters we already parsed. + host_.reset(new Host(identifier, identifier_name, SubnetID(0), + SubnetID(0), IOAddress("0.0.0.0"), hostname)); + + } catch (const std::exception& ex) { + // Append line number where the error occurred. + isc_throw(DhcpConfigError, ex.what() << " (" + << reservation_data->getPosition() << ")"); + } +} + +void +HostReservationParser::addHost(isc::data::ConstElementPtr reservation_data) { + try { + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host_); + + } catch (const std::exception& ex) { + // Append line number to the exception string. + isc_throw(DhcpConfigError, ex.what() << " (" + << reservation_data->getPosition() << ")"); + } +} + +HostReservationParser4::HostReservationParser4(const SubnetID& subnet_id) + : HostReservationParser(subnet_id) { +} + +void +HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) { + HostReservationParser::build(reservation_data); + + host_->setIPv4SubnetID(subnet_id_); + + BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) { + try { + if (element.first == "ip-address") { + host_->setIPv4Reservation(IOAddress(element.second->stringValue())); + } + } + catch (const std::exception& ex) { + // Append line number where the error occurred. + isc_throw(DhcpConfigError, ex.what() << " (" + << reservation_data->getPosition() << ")"); + } + } + + addHost(reservation_data); +} + +HostReservationParser6::HostReservationParser6(const SubnetID& subnet_id) + : HostReservationParser(subnet_id) { +} + +void +HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) { + HostReservationParser::build(reservation_data); + + host_->setIPv6SubnetID(subnet_id_); + + BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) { + if (element.first == "ip-addresses" || element.first == "prefixes") { + BOOST_FOREACH(ConstElementPtr 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. + 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 chracter, 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"); + } + + // Remove the slash character and the prefix length + // from the parsed value. + prefix.erase(len_pos); + } + + // Create a reservation for an address or prefix. + host_->addReservation(IPv6Resrv(IOAddress(prefix), + prefix_len)); + + } catch (const std::exception& ex) { + // Append line number where the error occurred. + isc_throw(DhcpConfigError, ex.what() << " (" + << prefix_element->getPosition() << ")"); + } + } + } + } + + // This may fail, but the addHost function will handle this on its own. + addHost(reservation_data); } } // end of namespace isc::dhcp diff --git a/src/lib/dhcpsrv/host_reservation_parser.h b/src/lib/dhcpsrv/host_reservation_parser.h index 5708ae32a2..269acf0d1f 100644 --- a/src/lib/dhcpsrv/host_reservation_parser.h +++ b/src/lib/dhcpsrv/host_reservation_parser.h @@ -26,8 +26,11 @@ namespace dhcp { class HostReservationParser : public DhcpConfigParser { public: - /// @brief Default constructor. - HostReservationParser(); + /// @brief Constructor. + /// + /// @param subnet_id Identifier of the subnet that the host is + /// connected to. + HostReservationParser(const SubnetID& subnet_id); /// @brief Parses a single entry for host reservation. /// @@ -42,12 +45,65 @@ public: protected: + /// @brief Inserts @c host_ object to the staging configuration. + /// + /// This method should be called by derived classes to insert the fully + /// parsed host reservation configuration to the @c CfgMgr. + /// + /// @param reservation_data Data element holding host reservation. It + /// used by this method to append the line number to the error string. + /// + /// @throw DhcpConfigError When operation to add a configured host fails. + void addHost(isc::data::ConstElementPtr reservation_data); + + /// @brief Identifier of the subnet that the host is connected to. + SubnetID subnet_id_; + /// @brief Holds a pointer to @c Host object representing a parsed /// host reservation configuration. HostPtr host_; }; +/// @brief Parser for a single host reservation for DHCPv4. +class HostReservationParser4 : public HostReservationParser { +public: + + /// @brief Constructor. + /// + /// @param subnet_id Identifier of the subnet that the host is + /// connected to. + HostReservationParser4(const SubnetID& subnet_id); + + /// @brief Parses a single host reservation for DHCPv4. + /// + /// @param reservation_data Data element holding map with a host + /// reservation configuration. + /// + /// @throw DhcpConfigError If the configuration is invalid. + virtual void build(isc::data::ConstElementPtr reservation_data); +}; + +/// @brief Parser for a single host reservation for DHCPv6. +class HostReservationParser6 : public HostReservationParser { +public: + + /// @brief Constructor. + /// + /// @param subnet_id Identifier of the subnet that the host is + /// connected to. + HostReservationParser6(const SubnetID& subnet_id); + + /// @brief Parses a single host reservation for DHCPv6. + /// + /// @param reservation_data Data element holding map with a host + /// reservation configuration. + /// + /// @throw DhcpConfigError If the configuration is invalid. + virtual void build(isc::data::ConstElementPtr reservation_data); +}; + + } } // end of namespace isc diff --git a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc index 34677e90c6..907e959583 100644 --- a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc @@ -14,14 +14,19 @@ #include +#include #include +#include +#include #include #include #include #include #include +#include #include +using namespace isc::asiolink; using namespace isc::data; using namespace isc::dhcp; @@ -41,11 +46,43 @@ protected: /// Clears the configuration in the @c CfgMgr. virtual void TearDown(); + /// @brief Checks if the reservation is in the range of reservations. + /// + /// @param resrv Reservation to be searched for. + /// @param range Range of reservations returned by the @c Host object + /// in which the reservation will be searched. + bool + reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range) { + for (IPv6ResrvIterator it = range.first; it != range.second; + ++it) { + if (resrv == it->second) { + return (true); + } + } + return (false); + } + + + /// @brief HW Address object used by tests. + HWAddrPtr hwaddr_; + + /// @brief DUID object used by tests. + DuidPtr duid_; + }; void HostReservationParserTest::SetUp() { CfgMgr::instance().clear(); + // Initialize HW address used by tests. + const uint8_t hwaddr_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; + hwaddr_ = HWAddrPtr(new HWAddr(hwaddr_data, sizeof(hwaddr_data), + HTYPE_ETHER)); + + // Initialize DUID used by tests. + const uint8_t duid_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A }; + duid_ = DuidPtr(new DUID(duid_data, sizeof(duid_data))); } void @@ -55,14 +92,131 @@ HostReservationParserTest::TearDown() { // This test verfies that the parser can parse the reservation entry for // which hw-address is a host identifier. -TEST_F(HostReservationParserTest, hwaddr) { +TEST_F(HostReservationParserTest, dhcp4HWaddr) { std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\"," + "\"ip-address\": \"192.0.2.134\"," + "\"hostname\": \"foo.example.com\" }"; + + ElementPtr config_element = Element::fromJSON(config); + + HostReservationParser4 parser(SubnetID(1)); + ASSERT_NO_THROW(parser.build(config_element)); + + CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts(); + HostCollection hosts; + ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr())); + + ASSERT_EQ(1, hosts.size()); + + EXPECT_EQ(1, hosts[0]->getIPv4SubnetID()); + EXPECT_EQ(0, hosts[0]->getIPv6SubnetID()); + EXPECT_EQ("192.0.2.134", hosts[0]->getIPv4Reservation().toText()); + EXPECT_EQ("foo.example.com", hosts[0]->getHostname()); +} + +// This test verfies that the parser can parse the reservation entry for +// which DUID is a host identifier. +TEST_F(HostReservationParserTest, dhcp4DUID) { + std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\"," + "\"ip-address\": \"192.0.2.112\"," "\"hostname\": \"\" }"; ElementPtr config_element = Element::fromJSON(config); - HostReservationParser parser; + HostReservationParser4 parser(SubnetID(10)); ASSERT_NO_THROW(parser.build(config_element)); + + CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts(); + HostCollection hosts; + ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_)); + + ASSERT_EQ(1, hosts.size()); + + EXPECT_EQ(10, hosts[0]->getIPv4SubnetID()); + EXPECT_EQ(0, hosts[0]->getIPv6SubnetID()); + EXPECT_EQ("192.0.2.112", hosts[0]->getIPv4Reservation().toText()); + EXPECT_TRUE(hosts[0]->getHostname().empty()); } +// This test verfies that the parser can parse the IPv6 reservation entry for +// which hw-address is a host identifier. +TEST_F(HostReservationParserTest, dhcp6HWaddr) { + std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\"," + "\"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::2\" ]," + "\"prefixes\": [ \"2001:db8:2000:0101::/64\", " + "\"2001:db8:2000:0102::/64\" ]," + "\"hostname\": \"foo.example.com\" }"; + + ElementPtr config_element = Element::fromJSON(config); + + HostReservationParser6 parser(SubnetID(10)); + ASSERT_NO_THROW(parser.build(config_element)); + + CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts(); + HostCollection hosts; + ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr())); + + ASSERT_EQ(1, hosts.size()); + + EXPECT_EQ(0, hosts[0]->getIPv4SubnetID()); + EXPECT_EQ(10, hosts[0]->getIPv6SubnetID()); + EXPECT_EQ("foo.example.com", hosts[0]->getHostname()); + + IPv6ResrvRange addresses = hosts[0]-> + getIPv6Reservations(IPv6Resrv::TYPE_NA); + ASSERT_EQ(2, std::distance(addresses.first, addresses.second)); + + EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1::1")), + addresses)); + EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1::2")), + addresses)); + + IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD); + ASSERT_EQ(2, std::distance(prefixes.first, prefixes.second)); + EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:2000:0101::"), + 64), + prefixes)); + EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:2000:0102::"), + 64), + prefixes)); + +} + +// This test verfies that the parser can parse the IPv6 reservation entry for +// which DUID is a host identifier. +TEST_F(HostReservationParserTest, dhcp6DUID) { + std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\"," + "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ]," + "\"prefixes\": [ ]," + "\"hostname\": \"foo.example.com\" }"; + + ElementPtr config_element = Element::fromJSON(config); + + HostReservationParser6 parser(SubnetID(12)); + ASSERT_NO_THROW(parser.build(config_element)); + + CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts(); + HostCollection hosts; + ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_)); + + ASSERT_EQ(1, hosts.size()); + + EXPECT_EQ(0, hosts[0]->getIPv4SubnetID()); + EXPECT_EQ(12, hosts[0]->getIPv6SubnetID()); + EXPECT_EQ("foo.example.com", hosts[0]->getHostname()); + + IPv6ResrvRange addresses = hosts[0]-> + getIPv6Reservations(IPv6Resrv::TYPE_NA); + ASSERT_EQ(2, std::distance(addresses.first, addresses.second)); + + EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1::100")), + addresses)); + EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1::200")), + addresses)); + + IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD); + ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second)); +} + + } // end of anonymous namespace