Commit f67708b1 authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[3151] Added prefix delegation configuration support to b10-dhcp6

Added entries to dhcp6.spec file to allow a list of prefix
delegation pools to be defined for subnet6 entries, along
with the necessary parsing logic in dhcp6.
parent 4e7c1fb3
......@@ -1463,6 +1463,7 @@ AC_OUTPUT([doc/version.ent
src/bin/auth/gen-statisticsitems.py.pre
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp6/spec_config.h.pre
src/bin/dhcp6/tests/test_data_files_config.h
src/bin/d2/spec_config.h.pre
src/bin/d2/tests/test_data_files_config.h
src/bin/tests/process_rename_test.py
......
......@@ -166,6 +166,171 @@ protected:
}
};
class PdPoolParser : public DhcpConfigParser {
public:
/// @brief Constructor.
///
/// @param param_name name of the parameter. Note, it is passed through
/// but unused, parameter is currently always "Dhcp6/subnet6[X]/pool"
/// @param pools storage container in which to store the parsed pool
/// upon "commit"
PdPoolParser(const std::string&, PoolStoragePtr pools)
: uint32_values_(new Uint32Storage()),
string_values_(new StringStorage()), pools_(pools) {
if (!pools_) {
isc_throw(isc::dhcp::DhcpConfigError,
"PdPoolParser context storage may not be NULL");
}
}
/// @brief Builds a prefix delegation pool from the given configuration
///
/// This function parses configuration entries and creates an instance
/// of a dhcp::Pool6 configured for prefix delegation.
///
/// @param pd_pool_ pointer to an element that holds configuration entries
/// that define a prefix delegation pool.
///
/// @throw DhcpConfigError if configuration parsing fails.
virtual void build(ConstElementPtr pd_pool_) {
// Parse the elements that make up the option definition.
BOOST_FOREACH(ConfigPair param, pd_pool_->mapValue()) {
std::string entry(param.first);
ParserPtr parser;
if (entry == "prefix") {
StringParserPtr str_parser(new StringParser(entry,
string_values_));
parser = str_parser;
} else if (entry == "prefix-len" || entry == "delegated-len") {
Uint32ParserPtr code_parser(new Uint32Parser(entry,
uint32_values_));
parser = code_parser;
} else {
isc_throw(DhcpConfigError, "invalid parameter: " << entry);
}
parser->build(param.second);
parser->commit();
}
try {
// We should now have all of the pool elements we need to create
// the pool. Fetch them and pass them into the Pool6 constructor.
// The constructor is expected to enforce any value validation.
const std::string addr_str = string_values_->getParam("prefix");
IOAddress addr(addr_str);
uint32_t prefix_len = uint32_values_->getParam("prefix-len");
uint32_t delegated_len = uint32_values_->getParam("delegated-len");
// Attempt to construct the local pool.
pool_.reset(new Pool6(Lease::TYPE_PD, addr, prefix_len,
delegated_len));
} catch (const std::exception& ex) {
isc_throw(isc::dhcp::DhcpConfigError,
"PdPoolParser failed to build pool: " << ex.what());
}
}
// @brief Commits the constructed local pool to the pool storage.
virtual void commit() {
// Add the local pool to the external storage ptr.
pools_->push_back(pool_);
}
protected:
/// Storage for subnet-specific integer values.
Uint32StoragePtr uint32_values_;
/// Storage for subnet-specific string values.
StringStoragePtr string_values_;
/// Parsers are stored here.
ParserCollection parsers_;
/// Pointer to the created pool object.
isc::dhcp::Pool6Ptr pool_;
/// Pointer to storage to which the local pool is written upon commit.
isc::dhcp::PoolStoragePtr pools_;
};
/// @brief Parser for a list of prefix delegation pools.
///
/// This parser iterates over a list of prefix delegation pool entries and
/// creates pool instances for each one. If the parsing is successful, the
/// collection of pools is committed to the provided storage.
class PdPoolListParser : public DhcpConfigParser {
public:
/// @brief Constructor.
///
/// @param dummy first argument is ignored, all Parser constructors
/// accept string as first argument.
/// @param storage is the pool storage in which to store the parsed
/// pools in this list
/// @throw isc::dhcp::DhcpConfigError if storage is null.
PdPoolListParser(const std::string&, PoolStoragePtr pools)
: local_pools_(new PoolStorage()), pools_(pools) {
if (!pools_) {
isc_throw(isc::dhcp::DhcpConfigError,
"PdPoolListParser pools storage may not be NULL");
}
}
/// @brief Parse configuration entries.
///
/// This function parses configuration entries and creates instances
/// of prefix delegation pools .
///
/// @param pd_pool_list pointer to an element that holds entries
/// that define a prefix delegation pool.
///
/// @throw DhcpConfigError if configuration parsing fails.
void build(isc::data::ConstElementPtr pd_pool_list) {
// Make sure the local list is empty.
local_pools_.reset(new PoolStorage());
// Make sure we have a configuration elements to parse.
if (!pd_pool_list) {
isc_throw(DhcpConfigError,
"PdPoolListParser: list of pool definitions is empty");
}
// Loop through the list of pd pools.
BOOST_FOREACH(ConstElementPtr pd_pool, pd_pool_list->listValue()) {
boost::shared_ptr<PdPoolParser>
// Create the PdPool parser.
parser(new PdPoolParser("pd-pool", local_pools_));
// Build the pool instance
parser->build(pd_pool);
// Commit the pool to the local list of pools.
parser->commit();
}
}
/// @brief Commits the pools created to the external storage area.
///
/// Note that this method adds the local list of pools to the storage area
/// rather than replacing its contents. This permits other parsers to
/// contribute to the set of pools.
void commit() {
// local_pools_ holds the values produced by the build function.
// At this point parsing should have completed successfully so
// we can append new data to the supplied storage.
pools_->insert(pools_->end(), local_pools_->begin(),
local_pools_->end());
}
private:
/// @brief storage for local pools
PoolStoragePtr local_pools_;
/// @brief External storage where pools are stored upon list commit.
PoolStoragePtr pools_;
};
/// @brief This class parses a single IPv6 subnet.
///
/// This is the IPv6 derivation of the SubnetConfigParser class and it parses
......@@ -219,6 +384,8 @@ protected:
parser = new StringParser(config_id, string_values_);
} else if (config_id.compare("pool") == 0) {
parser = new Pool6Parser(config_id, pools_);
} else if (config_id.compare("pd-pools") == 0) {
parser = new PdPoolListParser(config_id, pools_);
} else if (config_id.compare("option-data") == 0) {
parser = new OptionDataListParser(config_id, options_,
global_context_,
......
......@@ -16,7 +16,7 @@
"item_default": ""
}
},
{ "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
......@@ -254,6 +254,38 @@
"item_default": ""
}
},
{
"item_name": "pd-pools",
"item_type": "list",
"item_optional": true,
"item_default": [],
"list_item_spec":
{
"item_name": "pd-pool",
"item_type": "map",
"item_optional": false,
"item_default": {},
"map_item_spec": [
{
"item_name": "prefix",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{
"item_name": "prefix-len",
"item_type": "integer",
"item_optional": false,
"item_default": 128
},
{
"item_name": "delegated-len",
"item_type": "integer",
"item_optional": false,
"item_default": 128
}]
}
},
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
......@@ -313,7 +345,7 @@
{
"command_name": "libreload",
"command_description": "Reloads the current hooks libraries.",
"command_description": "Reloads the current hooks libraries.",
"command_args": []
}
]
......
......@@ -26,6 +26,7 @@
#include <dhcpsrv/subnet.h>
#include <hooks/hooks_manager.h>
#include "test_data_files_config.h"
#include "test_libraries.h"
#include "marker_file.h"
......@@ -53,6 +54,17 @@ using namespace std;
namespace {
std::string specfile(const std::string& name) {
return (std::string(DHCP6_SRC_DIR) + "/" + name);
}
/// @brief Tests that the spec file is valid.
/// Verifies that the DHCP6 configuration specification file is valid.
TEST(Dhcp6SpecTest, basicSpec) {
ASSERT_NO_THROW(isc::config::
moduleSpecFromFile(specfile("dhcp6.spec")));
}
class Dhcp6ParserTest : public ::testing::Test {
public:
Dhcp6ParserTest() :rcode_(-1), srv_(0) {
......@@ -712,6 +724,221 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
// Goal of this test is to verify the basic parsing of a prefix delegation
// pool. It uses a single, valid pd pool.
TEST_F(Dhcp6ParserTest, pdPoolBasics) {
ConstElementPtr x;
// Define a single valid pd pool.
string config =
"{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"subnet\": \"2001:db8:1::/64\","
" \"pd-pools\": ["
" { \"prefix\": \"2001:db8:1::\", "
" \"prefix-len\": 64, "
" \"delegated-len\": 128"
" } ],"
"\"valid-lifetime\": 4000 }"
"] }";
// Convert the JSON string into Elements.
ElementPtr json;
ASSERT_NO_THROW(json = Element::fromJSON(config));
// Verify that DHCP6 configuration processing succeeds.
// Returned value must be non-empty ConstElementPtr to config result.
// rcode should be 0 which indicates successful configuration processing.
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
EXPECT_EQ(0, rcode_);
// Test that we can retrieve the subnet.
Subnet6Ptr subnet = CfgMgr::
instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
// Fetch the collection of PD pools. It should have 1 entry.
PoolCollection pc;
ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
EXPECT_EQ(1, pc.size());
// Get a pointer to the pd pool instance, and verify its contents.
Pool6Ptr p6;
ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[0]));
ASSERT_TRUE(p6);
EXPECT_EQ("2001:db8:1::", p6->getFirstAddress().toText());
EXPECT_EQ(128, p6->getLength());
}
// Goal of this test is verify that a list of PD pools can be configured.
TEST_F(Dhcp6ParserTest, pdPoolList) {
ConstElementPtr x;
const char* prefixes[] = {
"2001:db8:1:1::",
"2001:db8:1:2::",
"2001:db8:1:3::"
};
string config =
"{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"subnet\": \"2001:db8:1::/40\","
" \"pd-pools\": ["
" { \"prefix\": \"2001:db8:1:01::\", "
" \"prefix-len\": 72, "
" \"delegated-len\": 80"
" },"
" { \"prefix\": \"2001:db8:1:02::\", "
" \"prefix-len\": 72, "
" \"delegated-len\": 88"
" },"
" { \"prefix\": \"2001:db8:1:03::\", "
" \"prefix-len\": 72, "
" \"delegated-len\": 96"
" }"
"],"
"\"valid-lifetime\": 4000 }"
"] }";
// Convert the JSON string into Elements.
ElementPtr json = Element::fromJSON(config);
ASSERT_NO_THROW(json = Element::fromJSON(config));
// Verify that DHCP6 configuration processing succeeds.
// Returned value must be non-empty ConstElementPtr to config result.
// rcode should be 0 which indicates successful configuration processing.
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
EXPECT_EQ(0, rcode_);
// Test that we can retrieve the subnet.
Subnet6Ptr subnet = CfgMgr::
instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
// Fetch the collection of PD pools. It should have 3 entries.
PoolCollection pc;
ASSERT_NO_THROW(pc = subnet->getPools(Lease::TYPE_PD));
EXPECT_EQ(3, pc.size());
// Loop through the pools and verify their contents.
for (int i = 0; i < 3; i++) {
Pool6Ptr p6;
ASSERT_NO_THROW(p6 = boost::dynamic_pointer_cast<Pool6>(pc[i]));
ASSERT_TRUE(p6);
EXPECT_EQ(prefixes[i], p6->getFirstAddress().toText());
EXPECT_EQ((80 + (i * 8)), p6->getLength());
}
}
// Goal of this test is check for proper handling of invalid prefix delegation
// pool configuration. It uses an array of invalid configurations to check
// a variety of configuration errors.
TEST_F(Dhcp6ParserTest, invalidPdPools) {
ConstElementPtr x;
const char *config[] = {
// No prefix.
"{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"subnet\": \"2001:db8:1::/64\","
" \"pd-pools\": ["
" { "
" \"prefix-len\": 64, "
" \"delegated-len\": 128"
" } ],"
"\"valid-lifetime\": 4000 }"
"] }",
// No prefix-len.
"{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"subnet\": \"2001:db8:1::/64\","
" \"pd-pools\": ["
" { \"prefix\": \"2001:db8:1::\", "
" \"delegated-len\": 128"
" } ],"
"\"valid-lifetime\": 4000 }"
"] }",
// No delegated-len.
"{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"subnet\": \"2001:db8:1::/64\","
" \"pd-pools\": ["
" { \"prefix\": \"2001:db8:1::\", "
" \"prefix-len\": 64 "
" } ],"
"\"valid-lifetime\": 4000 }"
"] }",
// Delegated length is too short.
"{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"subnet\": \"2001:db8:1::/64\","
" \"pd-pools\": ["
" { \"prefix\": \"2001:db8:1::\", "
" \"prefix-len\": 128, "
" \"delegated-len\": 64"
" } ],"
"\"valid-lifetime\": 4000 }"
"] }",
// Pool is not within the subnet.
"{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"subnet\": \"2001:db8:1::/64\","
" \"pd-pools\": ["
" { \"prefix\": \"2001:db8:77::\", "
" \"prefix-len\": 64, "
" \"delegated-len\": 128"
" } ],"
"\"valid-lifetime\": 4000 }"
"] }"
};
ElementPtr json;
int num_msgs = sizeof(config)/sizeof(char*);
for (int i = 0; i < num_msgs; i++) {
// Convert JSON string to Elements.
ASSERT_NO_THROW(json = Element::fromJSON(config[i]));
// Configuration processing should fail without a throw.
ASSERT_NO_THROW(x = configureDhcp6Server(srv_, json));
// Returned value must be non-empty ConstElementPtr to config result.
// rcode should be 1 which indicates configuration error.
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
EXPECT_EQ(1, rcode_);
}
}
// The goal of this test is to check whether an option definition
// that defines an option carrying an IPv6 address can be created.
TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
......
// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/// @brief Path to DHCP6 source dir so tests against the dhcp6.spec file
/// can find it reliably.
#define DHCP6_SRC_DIR "@abs_top_srcdir@/src/bin/dhcp6"
......@@ -37,13 +37,13 @@ namespace {
// Library with load/unload functions creating marker files to check their
// operation.
static const char* CALLOUT_LIBRARY_1 = "/home/thomson/devel/bind10/src/lib/dhcpsrv/tests/.libs/libco1"
static const char* CALLOUT_LIBRARY_1 = "/Users/tmark/build/trac3151/bind10/src/lib/dhcpsrv/tests/.libs/libco1"
DLL_SUFFIX;
static const char* CALLOUT_LIBRARY_2 = "/home/thomson/devel/bind10/src/lib/dhcpsrv/tests/.libs/libco2"
static const char* CALLOUT_LIBRARY_2 = "/Users/tmark/build/trac3151/bind10/src/lib/dhcpsrv/tests/.libs/libco2"
DLL_SUFFIX;
// Name of a library which is not present.
static const char* NOT_PRESENT_LIBRARY = "/home/thomson/devel/bind10/src/lib/dhcpsrv/tests/.libs/libnothere"
static const char* NOT_PRESENT_LIBRARY = "/Users/tmark/build/trac3151/bind10/src/lib/dhcpsrv/tests/.libs/libnothere"
DLL_SUFFIX;
} // 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