Commit 3310b338 authored by Francis Dupont's avatar Francis Dupont
Browse files

[master] Merged trac5288 (option-data in DHCPv4 pools)

parents d1cec89d 5420e596
......@@ -32,9 +32,11 @@
// clients connected to this subnet. The first two options are
// identified by the name. The third option is identified by the
// option code.
// There is an address pool defined within this subnet. Pool
// specific value for option domain-name-servers is defined
// for the pool.
"subnet4": [
{
"pools": [ { "pool": "192.0.2.10 - 192.0.2.200" } ],
"subnet": "192.0.2.0/24",
"interface": "ethX",
"option-data": [
......@@ -124,9 +126,29 @@
"name": "default-ip-ttl",
"data": "0xf0"
}
]
}
]
],
// Now we define pools. There are two pools here.
"pools": [ {
// This is the first pool. Nothing spectacular here, just a range
// of addresses.
"pool": "192.0.2.10 - 192.0.2.100"
}, {
// This second pool is more interesting. Anyone who gets an
// address from this pool will also get this specific option
// value if asks for DNS servers configuration. This value,
// being more specific, overrides any values that were specified
// on either global or subnet scope.
"pool": "192.0.2.101 - 192.0.2.200",
"option-data": [
{
"name": "domain-name-servers",
"data": "192.0.2.3, 192.0.2.4"
}
]
} ]
} ]
},
// The following configures logging. It assumes that messages with at
......
......@@ -1057,6 +1057,48 @@ temporarily override a list of interface names and listen on all interfaces.
</screen>
</para>
<para>
In some cases it is useful to associate some options with an
address pool from which a client is assigned a lease. Pool
specific option values override subnet specific and global
option values. The server's administrator must not try to
prioritize assignment of pool specific options by trying to
order pools declarations in the server configuration. Future
Kea releases may change the order in which options are
assigned from the pools without any notice.
</para>
<para>
The following configuration snippet demonstrates how to specify the
DNS servers option, which will be assigned to a client only if the
client obtains an address from the given pool:
<screen>
"Dhcp4": {
"subnet4": [
{
"pools": [
{
"pool": "192.0.2.1 - 192.0.2.200",
<userinput>"option-data": [
{
"name": "domain-name-servers",
"data": "192.0.2.3"
},
...
]</userinput>,
...
},
...
],
...
},
...
],
...
}
</screen>
</para>
<para>
The currently supported standard DHCPv4 options are
listed in <xref linkend="dhcp4-std-options-list"/>.
......
......@@ -2764,6 +2764,159 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
}
// This test verifies that it is possible to specify options on
// pool levels.
TEST_F(Dhcp4ParserTest, optionDataSinglePool) {
ConstElementPtr x;
string config = "{ " + genIfaceConfig() + ","
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pools\": [ { "
" \"pool\": \"192.0.2.1 - 192.0.2.100\","
" \"option-data\": [ {"
" \"name\": \"dhcp-message\","
" \"data\": \"ABCDEF0105\","
" \"csv-format\": false"
" },"
" {"
" \"name\": \"default-ip-ttl\","
" \"data\": \"01\","
" \"csv-format\": false"
" } ]"
" } ],"
" \"subnet\": \"192.0.2.0/24\""
" } ],"
"\"valid-lifetime\": 4000 }";
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->
selectSubnet(IOAddress("192.0.2.24"), classify_);
ASSERT_TRUE(subnet);
PoolPtr pool = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.24"), false);
ASSERT_TRUE(pool);
Pool4Ptr pool4 = boost::dynamic_pointer_cast<Pool4>(pool);
ASSERT_TRUE(pool4);
OptionContainerPtr options = pool4->getCfgOption()->getAll("dhcp4");
ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
const OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(56);
// Expect a single option with the code equal to 100.
ASSERT_EQ(1, std::distance(range.first, range.second));
const uint8_t foo_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
range = idx.equal_range(23);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
const uint8_t foo2_expected[] = {
0x01
};
testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
}
// This test verifies that it's possible to define different options in
// different pools and those options are not confused.
TEST_F(Dhcp4ParserTest, optionDataMultiplePools) {
ConstElementPtr x;
string config = "{ " + genIfaceConfig() + ","
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pools\": [ { "
" \"pool\": \"192.0.2.1 - 192.0.2.100\","
" \"option-data\": [ {"
" \"name\": \"dhcp-message\","
" \"data\": \"ABCDEF0105\","
" \"csv-format\": false"
" } ]"
" },"
" {"
" \"pool\": \"192.0.2.200 - 192.0.2.250\","
" \"option-data\": [ {"
" \"name\": \"default-ip-ttl\","
" \"data\": \"01\","
" \"csv-format\": false"
" } ]"
" } ],"
" \"subnet\": \"192.0.2.0/24\""
" } ],"
"\"valid-lifetime\": 4000 }";
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->
selectSubnet(IOAddress("192.0.2.24"), classify_);
ASSERT_TRUE(subnet);
PoolPtr pool1 = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.24"), false);
ASSERT_TRUE(pool1);
Pool4Ptr pool41 = boost::dynamic_pointer_cast<Pool4>(pool1);
ASSERT_TRUE(pool41);
OptionContainerPtr options1 = pool41->getCfgOption()->getAll("dhcp4");
ASSERT_EQ(1, options1->size());
// Get the search index. Index #1 is to search using option code.
const OptionContainerTypeIndex& idx1 = options1->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> range1 =
idx1.equal_range(56);
// Expect a single option with the code equal to 100.
ASSERT_EQ(1, std::distance(range1.first, range1.second));
const uint8_t foo_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
// Test another pool in the same way.
PoolPtr pool2 = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.2.240"), false);
ASSERT_TRUE(pool2);
Pool4Ptr pool42 = boost::dynamic_pointer_cast<Pool4>(pool2);
ASSERT_TRUE(pool42);
OptionContainerPtr options2 = pool42->getCfgOption()->getAll("dhcp4");
ASSERT_EQ(1, options2->size());
const OptionContainerTypeIndex& idx2 = options2->get<1>();
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> range2 =
idx2.equal_range(23);
ASSERT_EQ(1, std::distance(range2.first, range2.second));
const uint8_t foo2_expected[] = {
0x01
};
testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
}
// Verify that empty option name is rejected in the configuration.
......
This diff is collapsed.
......@@ -340,6 +340,6 @@ TEST_P(Dhcp4GetConfigTest, run) {
/// Define the parametrized test loop
INSTANTIATE_TEST_CASE_P(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
::testing::Range(0UL, max_config_counter));
::testing::Range(static_cast<size_t>(0), max_config_counter));
};
......@@ -335,7 +335,9 @@ CfgSubnets4::toElement() const {
if (!isNull(context)) {
pool_map->set("user-context", context);
}
// Set pool options (not yet supported)
// Set pool options
ConstCfgOptionPtr opts = (*pool)->getCfgOption();
pool_map->set("option-data", opts->toElement());
// Push on the pool list
pool_list->add(pool_map);
}
......
......@@ -749,12 +749,6 @@ PoolParser::parse(PoolStoragePtr pools,
ConstElementPtr option_data = pool_structure->get("option-data");
if (option_data) {
try {
// Currently we don't support specifying options for the DHCPv4 server.
if (address_family == AF_INET) {
isc_throw(DhcpConfigError, "option-data is not supported for DHCPv4"
" address pools");
}
CfgOptionPtr cfg = pool->getCfgOption();
OptionDataListParser option_parser(address_family);
option_parser.parse(cfg, option_data);
......
......@@ -544,8 +544,10 @@ TEST(CfgSubnets4Test, unparsePool) {
" \"option-data\": [],\n"
" \"pools\": [\n"
" {\n"
" \"option-data\": [ ],\n"
" \"pool\": \"192.0.2.1-192.0.2.10\"\n"
" },{\n"
" \"option-data\": [ ],\n"
" \"pool\": \"192.0.2.64/26\"\n"
" }\n"
" ]\n"
......
......@@ -120,6 +120,58 @@ TEST(Pool4Test, toText) {
EXPECT_EQ("type=V4, 192.0.2.128-192.0.2.143", pool2.toText());
}
// This test checks that it is possible to specify pool specific options.
TEST(Pool4Test, addOptions) {
// Create a pool to add options to it.
Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
IOAddress("192.0.2.255")));
// Differentiate options by their codes (100-109)
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, "dhcp4"));
}
// Add 7 options to another option space. The option codes partially overlap
// with option codes that we have added to dhcp4 option space.
for (uint16_t code = 105; code < 112; ++code) {
OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, "isc"));
}
// Get options from the pool and check if all 10 are there.
OptionContainerPtr options = pool->getCfgOption()->getAll("dhcp4");
ASSERT_TRUE(options);
ASSERT_EQ(10, options->size());
// Validate codes of options added to dhcp4 option space.
uint16_t expected_code = 100;
for (OptionContainer::const_iterator option_desc = options->begin();
option_desc != options->end(); ++option_desc) {
ASSERT_TRUE(option_desc->option_);
EXPECT_EQ(expected_code, option_desc->option_->getType());
++expected_code;
}
options = pool->getCfgOption()->getAll("isc");
ASSERT_TRUE(options);
ASSERT_EQ(7, options->size());
// Validate codes of options added to isc option space.
expected_code = 105;
for (OptionContainer::const_iterator option_desc = options->begin();
option_desc != options->end(); ++option_desc) {
ASSERT_TRUE(option_desc->option_);
EXPECT_EQ(expected_code, option_desc->option_->getType());
++expected_code;
}
// Try to get options from a non-existing option space.
options = pool->getCfgOption()->getAll("abcd");
ASSERT_TRUE(options);
EXPECT_TRUE(options->empty());
}
TEST(Pool6Test, constructor_first_last) {
// let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool
......@@ -329,7 +381,7 @@ TEST(Pool6Test, unique_id) {
}
// Simple check if toText returns reasonable values
TEST(Pool6Test,toText) {
TEST(Pool6Test, toText) {
Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8::1"),
IOAddress("2001:db8::2"));
EXPECT_EQ("type=IA_NA, 2001:db8::1-2001:db8::2, delegated_len=128",
......
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