Commit 556f6591 authored by Francis Dupont's avatar Francis Dupont

[5288] Added option-data in DHCPv4 pools

parent f5e88bca
......@@ -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,17 @@
"name": "default-ip-ttl",
"data": "0xf0"
}
]
}
]
],
"pools": [ {
"pool": "192.0.2.10 - 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,57 @@ 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. If the client is assigned multiple leases from
different pools, the server will assign options from all pools
from which the leases have been obtained. However, if the
particular option is specified in multiple pools from which
the client obtains the leases, only one instance of this
option will be handed out to the client. 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",
"code": 6,
"space": "dhcp4",
"csv-format": true,
"data": "192.0.2.3"
},
...
]</userinput>,
...
},
...
],
...
},
...
],
...
}
</screen>
</para>
<para>
The currently supported standard DHCPv4 options are
listed in <xref linkend="dhcp4-std-options-list"/>.
......
......@@ -2739,6 +2739,157 @@ 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));
}
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);
......
......@@ -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::V6, 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