// 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
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") {
}
}
- 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
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.
///
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
#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;
/// 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
// 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