Browse Source

[3562] HostReservationConfig parser now parses simple host reservations.

The parser works for both IPv4 and IPv6 reservations but it lacks some
validation mechanisms.
Marcin Siodelski 10 years ago
parent
commit
228c2228a0

+ 137 - 4
src/lib/dhcpsrv/host_reservation_parser.cc

@@ -13,17 +13,20 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <asiolink/io_address.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/host_reservation_parser.h>
 #include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
 #include <string>
 
 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

+ 58 - 2
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
 

+ 156 - 2
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -14,14 +14,19 @@
 
 #include <config.h>
 
+#include <asiolink/io_address.h>
 #include <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/host_reservation_parser.h>
 #include <dhcpsrv/testutils/config_result_check.h>
 #include <gtest/gtest.h>
+#include <iterator>
 #include <string>
 
+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