Browse Source

[4319] Extend host reservation parser to parse options.

Marcin Siodelski 9 years ago
parent
commit
f3e4bc8228

+ 5 - 2
src/lib/dhcpsrv/cfg_hosts.cc

@@ -454,7 +454,9 @@ CfgHosts::add4(const HostPtr& host) {
     // address, IPv6 address or prefix.
     if (host->getHostname().empty() &&
         (host->getIPv4Reservation().isV4Zero()) &&
-        (!host->hasIPv6Reservation())) {
+        (!host->hasIPv6Reservation()) &&
+        host->getCfgOption4()->empty() &&
+        host->getCfgOption6()->empty()) {
         std::ostringstream s;
         if (hwaddr) {
             s << "for DUID: " << hwaddr->toText();
@@ -463,7 +465,8 @@ CfgHosts::add4(const HostPtr& host) {
         }
         isc_throw(BadValue, "specified reservation " << s.str()
                   << " must include at least one resource, i.e. "
-                  "hostname, IPv4 address or IPv6 address/prefix");
+                  "hostname, IPv4 address, IPv6 address/prefix, "
+                  "options");
     }
 
     // Check for duplicates for the specified IPv4 subnet.

+ 3 - 2
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -608,8 +608,9 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
         } else if (name_param.isSpecified() && !code_param.isSpecified()) {
             isc_throw(DhcpConfigError, "definition for the option '"
                       << space_param << "." << name_param
-                      << " does not exist ("
-                      << string_values_->getPosition("name", option_data));
+                      << "' does not exist ("
+                      << string_values_->getPosition("name", option_data)
+                      << ")");
         }
     }
 

+ 37 - 13
src/lib/dhcpsrv/parsers/host_reservation_parser.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 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
@@ -7,9 +7,11 @@
 #include <config.h>
 #include <asiolink/io_address.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dhcpsrv/parsers/host_reservation_parser.h>
 #include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
+#include <sys/socket.h>
 #include <string>
 
 using namespace isc::asiolink;
@@ -25,7 +27,8 @@ const std::set<std::string>& getSupportedParams4() {
     static std::set<std::string> params_set;
     if (params_set.empty()) {
         const char* params[] = {
-            "duid", "hw-address", "hostname", "ip-address", NULL
+            "duid", "hw-address", "hostname", "ip-address",
+            "option-data", NULL
         };
         for (int i = 0; params[i] != NULL; ++i) {
             params_set.insert(std::string(params[i]));
@@ -42,7 +45,8 @@ const std::set<std::string>& getSupportedParams6() {
     static std::set<std::string> params_set;
     if (params_set.empty()) {
         const char* params[] = {
-            "duid", "hw-address", "hostname", "ip-addresses", "prefixes", NULL
+            "duid", "hw-address", "hostname", "ip-addresses", "prefixes",
+            "option-data", NULL
         };
         for (int i = 0; params[i] != NULL; ++i) {
             params_set.insert(std::string(params[i]));
@@ -136,17 +140,28 @@ HostReservationParser4::build(isc::data::ConstElementPtr 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()));
+        // For 'option-data' element we will use another parser which
+        // already returns errors with position appended, so don't
+        // surround it with try-catch.
+        if (element.first == "option-data") {
+            CfgOptionPtr cfg_option = host_->getCfgOption4();
+            OptionDataListParser parser(element.first, cfg_option, AF_INET);
+            parser.build(element.second);
+
+       // Everything else should be surrounded with try-catch to append
+       // position.
+        } else {
+            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() << ")");
             }
         }
-        catch (const std::exception& ex) {
-        // Append line number where the error occurred.
-        isc_throw(DhcpConfigError, ex.what() << " ("
-                  << reservation_data->getPosition() << ")");
-        }
     }
 
     addHost(reservation_data);
@@ -168,7 +183,16 @@ HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
     host_->setIPv6SubnetID(subnet_id_);
 
     BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
-        if (element.first == "ip-addresses" || element.first == "prefixes") {
+        // 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();
+            OptionDataListParser parser(element.first, cfg_option, AF_INET6);
+            parser.build(element.second);
+
+        } else if (element.first == "ip-addresses" || element.first == "prefixes") {
             BOOST_FOREACH(ConstElementPtr prefix_element,
                           element.second->listValue()) {
                 try {

+ 243 - 1
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 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
@@ -10,10 +10,14 @@
 #include <cc/data.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option6_addrlst.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/parsers/host_reservation_parser.h>
 #include <dhcpsrv/testutils/config_result_check.h>
+#include <boost/pointer_cast.hpp>
 #include <gtest/gtest.h>
 #include <iterator>
 #include <string>
@@ -54,10 +58,86 @@ protected:
         return (false);
     }
 
+    /// @brief Retrieves DHCP option from a host.
+    ///
+    /// @param host Reference to a host object for which an option should be
+    /// retrieved.
+    /// @param option_space Option space name.
+    /// @param option_code Code of an option to be retrieved.
+    ///
+    /// @return Pointer to the option retrieved or NULL if option hasn't been
+    /// found.
+    OptionPtr
+    retrieveOption(const Host& host, const std::string& option_space,
+                   const uint16_t option_code) const {
+        if ((option_space != "dhcp6") && (option_space != "dhcp4")) {
+            return (OptionPtr());
+        }
+
+        // Retrieve a pointer to the appropriate container depending if we're
+        // interested in DHCPv4 or DHCPv6 options.
+        ConstCfgOptionPtr cfg_option = (option_space == "dhcp4" ?
+                                        host.getCfgOption4() : host.getCfgOption6());
+
+        // Retrieve options.
+        OptionContainerPtr options = cfg_option->getAll(option_space);
+        if (options) {
+            const OptionContainerTypeIndex& idx = options->get<1>();
+            OptionContainerTypeIndex::const_iterator it = idx.find(option_code);
+            if (it != idx.end()) {
+                return (it->option_);
+            }
+        }
+
+        return (OptionPtr());
+    }
+
     void
     expectFailure(const HostReservationParser& parser,
                   const std::string& config) const;
 
+    /// @brief This test verifies that it is possible to specify an empty list
+    /// of options for a host.
+    ///
+    /// @tparam ParserType Type of the parser to be tested.
+    template<typename ParserType>
+    void testEmptyOptionList() const {
+        // Create configuration with empty option list. Note that we have to
+        // add reservation for at least one resource because host declarations
+        // without any reservations are rejected. Thus, we have added hostname.
+        std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+            "\"hostname\": \"foo.isc.org\","
+            "\"option-data\": [ ]"
+            "}";
+
+        ElementPtr config_element = Element::fromJSON(config);
+
+        ParserType parser(SubnetID(10));
+        ASSERT_NO_THROW(parser.build(config_element));
+
+        // Retrieve a host.
+        HostCollection hosts;
+        CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+        ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+        ASSERT_EQ(1, hosts.size());
+
+        // There should be no options assigned to a host.
+        EXPECT_TRUE(hosts[0]->getCfgOption4()->empty());
+        EXPECT_TRUE(hosts[0]->getCfgOption6()->empty());
+    }
+
+    /// @brief This test verfies that the parser returns an error when
+    /// configuration is invalid.
+    ///
+    /// @param config JSON configuration to be tested.
+    /// @tparam ParserType Type of the parser class to use.
+    template<typename ParserType>
+    void testInvalidConfig(const std::string& config) const {
+        ElementPtr config_element = Element::fromJSON(config);
+        ParserType parser(SubnetID(10));
+        EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+    }
+
     /// @brief HW Address object used by tests.
     HWAddrPtr hwaddr_;
 
@@ -508,4 +588,166 @@ TEST_F(HostReservationParserTest, dhcp6invalidParameterName) {
     EXPECT_THROW(parser.build(config_element), DhcpConfigError);
 }
 
+// This test verifies that it is possible to specify DHCPv4 options for
+// a host.
+TEST_F(HostReservationParserTest, options4) {
+    // Create configuration with three options for a host.
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"option-data\": ["
+        "{"
+           "\"name\": \"name-servers\","
+           "\"data\": \"172.16.15.10, 172.16.15.20\""
+        "},"
+        "{"
+           "\"name\": \"log-servers\","
+           "\"code\": 7,"
+           "\"csv-format\": true,"
+           "\"space\": \"dhcp4\","
+           "\"data\": \"172.16.15.23\""
+        "},"
+        "{"
+           "\"name\": \"default-ip-ttl\","
+           "\"data\": \"64\""
+        "} ]"
+        "}";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    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(hwaddr_));
+    ASSERT_EQ(1, hosts.size());
+
+    // Retrieve and sanity check name servers.
+    Option4AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
+        Option4AddrLst>(retrieveOption(*hosts[0], "dhcp4", DHO_NAME_SERVERS));
+    ASSERT_TRUE(opt_dns);
+    Option4AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
+    ASSERT_EQ(2, dns_addrs.size());
+    EXPECT_EQ("172.16.15.10", dns_addrs[0].toText());
+    EXPECT_EQ("172.16.15.20", dns_addrs[1].toText());
+
+    // Retrieve and sanity check log servers.
+    Option4AddrLstPtr opt_log = boost::dynamic_pointer_cast<
+        Option4AddrLst>(retrieveOption(*hosts[0], "dhcp4", DHO_LOG_SERVERS));
+    ASSERT_TRUE(opt_log);
+    Option4AddrLst::AddressContainer log_addrs = opt_log->getAddresses();
+    ASSERT_EQ(1, log_addrs.size());
+    EXPECT_EQ("172.16.15.23", log_addrs[0].toText());
+
+    // Retrieve and sanity check default IP TTL.
+    OptionUint8Ptr opt_ttl = boost::dynamic_pointer_cast<
+        OptionUint8>(retrieveOption(*hosts[0], "dhcp4", DHO_DEFAULT_IP_TTL));
+    ASSERT_TRUE(opt_ttl);
+    EXPECT_EQ(64, opt_ttl->getValue());
+}
+
+// This test verifies that it is possible to specify DHCPv6 options for
+// a host.
+TEST_F(HostReservationParserTest, options6) {
+    // Create configuration with three options for a host.
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"option-data\": ["
+        "{"
+           "\"name\": \"dns-servers\","
+           "\"data\": \"2001:db8:1::1, 2001:db8:1::2\""
+        "},"
+        "{"
+           "\"name\": \"nis-servers\","
+           "\"code\": 27,"
+           "\"csv-format\": true,"
+           "\"space\": \"dhcp6\","
+           "\"data\": \"2001:db8:1::1204\""
+        "},"
+        "{"
+           "\"name\": \"preference\","
+           "\"data\": \"11\""
+        "} ]"
+        "}";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(10));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    // One host should have been added to the configuration.
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+    ASSERT_EQ(1, hosts.size());
+
+    // Retrieve and sanity check DNS servers option.
+    Option6AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
+        Option6AddrLst>(retrieveOption(*hosts[0], "dhcp6", D6O_NAME_SERVERS));
+    ASSERT_TRUE(opt_dns);
+    Option6AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
+    ASSERT_EQ(2, dns_addrs.size());
+    EXPECT_EQ("2001:db8:1::1", dns_addrs[0].toText());
+    EXPECT_EQ("2001:db8:1::2", dns_addrs[1].toText());
+
+    // Retrieve and sanity check NIS servers option.
+    Option6AddrLstPtr opt_nis = boost::dynamic_pointer_cast<
+        Option6AddrLst>(retrieveOption(*hosts[0], "dhcp6", D6O_NIS_SERVERS));
+    ASSERT_TRUE(opt_nis);
+    Option6AddrLst::AddressContainer nis_addrs = opt_nis->getAddresses();
+    ASSERT_EQ(1, nis_addrs.size());
+    EXPECT_EQ("2001:db8:1::1204", nis_addrs[0].toText());
+
+    // Retrieve and sanity check preference option.
+    OptionUint8Ptr opt_prf = boost::dynamic_pointer_cast<
+        OptionUint8>(retrieveOption(*hosts[0], "dhcp6", D6O_PREFERENCE));
+    ASSERT_TRUE(opt_prf);
+    EXPECT_EQ(11, opt_prf->getValue());
+}
+
+// This test verifies that it is possible to specify an empty list of
+// options for a host declaration.
+TEST_F(HostReservationParserTest, options4Empty) {
+    testEmptyOptionList<HostReservationParser4>();
+}
+
+// This test verifies that it is possible to specify an empty list of
+// options for a host declaration.
+TEST_F(HostReservationParserTest, options6Empty) {
+    testEmptyOptionList<HostReservationParser6>();
+}
+
+// This test checks that specifying DHCPv6 options for the DHCPv4 host
+// reservation parser is not allowed.
+TEST_F(HostReservationParserTest, options4InvalidOptionSpace) {
+    // Create configuration specifying DHCPv6 option for a DHCPv4 host
+    // reservation parser.
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"option-data\": ["
+        "{"
+           "\"name\": \"dns-servers\","
+           "\"space\": \"dhcp6\","
+           "\"data\": \"2001:db8:1::1, 2001:db8:1::2\""
+        "} ]"
+        "}";
+
+    testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test checks that specifying DHCPv4 options for the DHCPv6 host
+// reservation parser is not allowed.
+TEST_F(HostReservationParserTest, options6InvalidOptionSpace) {
+    // Create configuration specifying DHCPv4 option for a DHCPv6 host
+    // reservation parser.
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"option-data\": ["
+        "{"
+           "\"name\": \"name-servers\","
+           "\"space\": \"dhcp4\","
+           "\"data\": \"172.16.15.10, 172.16.15.20\""
+        "} ]"
+        "}";
+
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
+
 } // end of anonymous namespace