Commit 228c2228 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[3562] HostReservationConfig parser now parses simple host reservations.

The parser works for both IPv4 and IPv6 reservations but it lacks some
validation mechanisms.
parent cd1db81f
......@@ -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
......
......@@ -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
......
......@@ -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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment