Commit e3b2785c authored by Marcin Siodelski's avatar Marcin Siodelski

[master] Merge branch 'trac5022'

parents 9f76f165 ef182c34
......@@ -31,11 +31,16 @@
# Defining a subnet. There are 2 DHCP options returned to the
# clients connected to this subnet. The first option is identified
# by the name. The second option is identified by the code.
# There are two address pools defined within this subnet. Pool
# specific value for option 12 is defined for the pool:
# 2001:db8:1::1 - 2001:db8:1::100. Clients obtaining an address
# from this pool will be assigned option 12 with a value of
# 3001:cafe::21. Clients belonging to this subnet but obtaining
# addresses from the other pool, or the clients obtaining
# stateless configuration will be assigned subnet specific value
# of option 12, i.e. 2001:db8:1:0:ff00::1.
"subnet6": [
{
"pools": [ { "pool": "2001:db8:1::/80" } ],
"subnet": "2001:db8:1::/64",
"interface": "ethX",
"option-data": [
{
"name": "dns-servers",
......@@ -45,7 +50,23 @@
"code": 12,
"data": "2001:db8:1:0:ff00::1"
},
]
],
"pools": [
{
"pool": "2001:db8:1::1 - 2001:db8:1::100",
"option-data": [
{
"code": 12,
"data": "3001:cafe::21"
}
]
},
{
"pool": "2001:db8:1::500 - 2001:db8:2::1000"
}
],
"subnet": "2001:db8:1::/64",
"interface": "ethX",
}
]
},
......
......@@ -932,6 +932,7 @@ temporarily override a list of interface names and listen on all interfaces.
(Dhcp6/option-data), rather you should set only subnet-specific values
(Dhcp6/subnet[X]/option-data[Y]).
</para>
<para>
The following commands override the global
DNS servers option for a particular subnet, setting a single DNS
......@@ -959,6 +960,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 or prefix 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>
"Dhcp6": {
"subnet6": [
{
"pools": [
{
"pool": "2001:db8:1::100-2001:db8:1::300",
<userinput>"option-data": [
{
"name": "dns-servers",
"data": "2001:db8:1::10"
}
]</userinput>
}
]
},
...
],
...
}
</screen>
</para>
<para>
The currently supported standard DHCPv6 options are
listed in <xref linkend="dhcp6-std-options-list"/>.
......
......@@ -32,6 +32,7 @@
#include <limits>
#include <iostream>
#include <netinet/in.h>
#include <vector>
#include <map>
......@@ -61,7 +62,7 @@ public:
/// @param pools storage container in which to store the parsed pool
/// upon "commit"
Pool4Parser(const std::string& param_name, PoolStoragePtr pools)
:PoolParser(param_name, pools) {
:PoolParser(param_name, pools, AF_INET) {
}
protected:
......
......@@ -441,17 +441,70 @@
"item_optional": false,
"item_default": 7200
},
{ "item_name": "pool",
"item_type": "list",
"item_optional": false,
"item_default": [],
"list_item_spec":
{
"item_name": "type",
"item_type": "string",
{ "item_name": "pools",
"item_type": "map",
"item_optional": true,
"item_default" : {},
"map_item_spec": [
{ "item_name": "pool",
"item_type": "list",
"item_optional": false,
"item_default": ""
}
"item_default": [],
"list_item_spec":
{
"item_name": "type",
"item_type": "string",
"item_optional": false,
"item_default": ""
}
},
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
"item_default": [],
"item_description": "Holds a list of pool specific DHCP options.",
"list_item_spec":
{
"item_name": "single-option-data",
"item_type": "map",
"item_optional": false,
"item_default": {},
"item_description": "One of the pool specific DHCP options.",
"map_item_spec": [
{
"item_name": "name",
"item_type": "string",
"item_optional": false,
"item_default": "",
"item_description": "Option name."
},
{ "item_name": "code",
"item_type": "integer",
"item_optional": false,
"item_default": 0,
"item_description": "Option code."
},
{ "item_name": "data",
"item_type": "string",
"item_optional": false,
"item_default": "",
"item_description": "Option value."
},
{ "item_name": "csv-format",
"item_type": "boolean",
"item_optional": false,
"item_default": false,
"item_description": "Indicates if option value is specified as comma separated values."
},
{ "item_name": "space",
"item_type": "string",
"item_optional": false,
"item_default": "dhcp6",
"item_description": "Option space."
} ]
}
} ]
},
{ "item_name": "client-class",
......@@ -506,7 +559,56 @@
"item_type": "integer",
"item_optional": false,
"item_default": 128
}]
},
{
"item_name": "option-data",
"item_type": "list",
"item_optional": false,
"item_default": [],
"item_description": "Holds a list of the pd-pool specific DHCP options.",
"list_item_spec":
{
"item_name": "single-option-data",
"item_type": "map",
"item_optional": false,
"item_default": {},
"item_description": "One of the pd-pool specific DHCP options.",
"map_item_spec": [
{
"item_name": "name",
"item_type": "string",
"item_optional": false,
"item_default": "",
"item_description": "Option name."
},
{ "item_name": "code",
"item_type": "integer",
"item_optional": false,
"item_default": 0,
"item_description": "Option code."
},
{ "item_name": "data",
"item_type": "string",
"item_optional": false,
"item_default": "",
"item_description": "Option value."
},
{ "item_name": "csv-format",
"item_type": "boolean",
"item_optional": false,
"item_default": false,
"item_description": "Indicates if option value is specified as comma separated values."
},
{ "item_name": "space",
"item_type": "string",
"item_optional": false,
"item_default": "dhcp6",
"item_description": "Option space."
} ]
}
} ]
}
},
{ "item_name": "option-data",
......
......@@ -820,6 +820,20 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
co_list.push_back(ctx.host_->getCfgOption6());
}
// Secondly, pool specific options. Pools are defined within a subnet, so
// if there is no subnet, there is nothing to do.
if (ctx.subnet_) {
BOOST_FOREACH(const AllocEngine::ResourceType& resource,
ctx.allocated_resources_) {
PoolPtr pool = ctx.subnet_->getPool(resource.second == 128 ?
Lease::TYPE_NA : Lease::TYPE_PD,
resource.first, false);
if (pool && !pool->getCfgOption()->empty()) {
co_list.push_back(pool->getCfgOption());
}
}
};
// Next, subnet configured options.
if (ctx.subnet_ && !ctx.subnet_->getCfgOption()->empty()) {
co_list.push_back(ctx.subnet_->getCfgOption());
......@@ -2316,6 +2330,9 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
}
}
processClientFqdn(solicit, response, ctx);
assignLeases(solicit, response, ctx);
copyClientOptions(solicit, response);
CfgOptionList co_list;
buildCfgOptionList(solicit, ctx, co_list);
......@@ -2323,9 +2340,6 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
appendRequestedOptions(solicit, response, co_list);
appendRequestedVendorOptions(solicit, response, ctx, co_list);
processClientFqdn(solicit, response, ctx);
assignLeases(solicit, response, ctx);
// Only generate name change requests if sending a Reply as a result
// of receiving Rapid Commit option.
if (response->getType() == DHCPV6_REPLY) {
......@@ -2347,6 +2361,9 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
processClientFqdn(request, reply, ctx);
assignLeases(request, reply, ctx);
copyClientOptions(request, reply);
CfgOptionList co_list;
buildCfgOptionList(request, ctx, co_list);
......@@ -2354,8 +2371,6 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
appendRequestedOptions(request, reply, co_list);
appendRequestedVendorOptions(request, reply, ctx, co_list);
processClientFqdn(request, reply, ctx);
assignLeases(request, reply, ctx);
generateFqdn(reply);
createNameChangeRequests(reply, ctx);
......@@ -2374,6 +2389,9 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
processClientFqdn(renew, reply, ctx);
extendLeases(renew, reply, ctx);
copyClientOptions(renew, reply);
CfgOptionList co_list;
buildCfgOptionList(renew, ctx, co_list);
......@@ -2381,8 +2399,6 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
appendRequestedOptions(renew, reply, co_list);
appendRequestedVendorOptions(renew, reply, ctx, co_list);
processClientFqdn(renew, reply, ctx);
extendLeases(renew, reply, ctx);
generateFqdn(reply);
createNameChangeRequests(reply, ctx);
......@@ -2401,6 +2417,9 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
processClientFqdn(rebind, reply, ctx);
extendLeases(rebind, reply, ctx);
copyClientOptions(rebind, reply);
CfgOptionList co_list;
buildCfgOptionList(rebind, ctx, co_list);
......@@ -2408,8 +2427,6 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
appendRequestedOptions(rebind, reply, co_list);
appendRequestedVendorOptions(rebind, reply, ctx, co_list);
processClientFqdn(rebind, reply, ctx);
extendLeases(rebind, reply, ctx);
generateFqdn(reply);
createNameChangeRequests(reply, ctx);
......
......@@ -43,6 +43,7 @@
#include <iostream>
#include <limits>
#include <map>
#include <netinet/in.h>
#include <vector>
#include <stdint.h>
......@@ -78,7 +79,7 @@ public:
/// @param pools storage container in which to store the parsed pool
/// upon "commit"
Pool6Parser(const std::string& param_name, PoolStoragePtr pools)
:PoolParser(param_name, pools) {
:PoolParser(param_name, pools, AF_INET6) {
}
protected:
......@@ -151,7 +152,8 @@ public:
/// upon "commit"
PdPoolParser(const std::string&, PoolStoragePtr pools)
: uint32_values_(new Uint32Storage()),
string_values_(new StringStorage()), pools_(pools) {
string_values_(new StringStorage()), pools_(pools),
options_(new CfgOption()) {
if (!pools_) {
isc_throw(isc::dhcp::DhcpConfigError,
"PdPoolParser context storage may not be NULL");
......@@ -180,6 +182,12 @@ public:
Uint32ParserPtr code_parser(new Uint32Parser(entry,
uint32_values_));
parser = code_parser;
} else if (entry == "option-data") {
OptionDataListParserPtr option_parser(new OptionDataListParser(entry,
options_,
AF_INET6));
parser = option_parser;
} else {
isc_throw(DhcpConfigError, "unsupported parameter: " << entry
<< " (" << param.second->getPosition() << ")");
......@@ -199,6 +207,8 @@ public:
// Attempt to construct the local pool.
pool_.reset(new Pool6(Lease::TYPE_PD, IOAddress(addr_str),
prefix_len, delegated_len));
// Merge options specified for a pool into pool configuration.
options_->copyTo(*pool_->getCfgOption());
} catch (const std::exception& ex) {
// Some parameters don't exist or are invalid. Since we are not
// aware whether they don't exist or are invalid, let's append
......@@ -229,6 +239,9 @@ protected:
/// Pointer to storage to which the local pool is written upon commit.
isc::dhcp::PoolStoragePtr pools_;
/// A storage for pool specific option values.
CfgOptionPtr options_;
};
/// @brief Parser for a list of prefix delegation pools.
......
......@@ -2556,6 +2556,154 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
sizeof(user_class_expected));
}
// This test verifies that it is possible to specify options on
// pool levels.
TEST_F(Dhcp6ParserTest, optionDataMultiplePools) {
ConstElementPtr x;
string config = "{ " + genIfaceConfig() + ","
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"pools\": [ { "
" \"pool\": \"2001:db8:1::10 - 2001:db8:1::100\","
" \"option-data\": [ {"
" \"name\": \"subscriber-id\","
" \"data\": \"0102030405060708090A\","
" \"csv-format\": False"
" } ]"
" },"
" {"
" \"pool\": \"2001:db8:1::300 - 2001:db8:1::400\","
" \"option-data\": [ {"
" \"name\": \"user-class\","
" \"data\": \"FFFEFDFCFB\","
" \"csv-format\": False"
" } ]"
" } ],"
" \"pd-pools\": [ { "
" \"prefix\": \"3000::\","
" \"prefix-len\": 48,"
" \"delegated-len\": 64,"
" \"option-data\": [ {"
" \"name\": \"subscriber-id\","
" \"data\": \"112233445566\","
" \"csv-format\": False"
" } ]"
" },"
" {"
" \"prefix\": \"3001::\","
" \"prefix-len\": 48,"
" \"delegated-len\": 64,"
" \"option-data\": [ {"
" \"name\": \"user-class\","
" \"data\": \"aabbccddee\","
" \"csv-format\": False"
" } ]"
" } ],"
" \"subnet\": \"2001:db8:1::/64\""
" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
checkResult(x, 0);
Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
selectSubnet(IOAddress("2001:db8:1::5"), classify_);
ASSERT_TRUE(subnet);
PoolPtr pool = subnet->getPool(Lease::TYPE_PD, IOAddress("3000::"), false);
ASSERT_TRUE(pool);
Pool6Ptr pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
ASSERT_TRUE(pool6);
OptionContainerPtr options1 = pool6->getCfgOption()->getAll("dhcp6");
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(D6O_SUBSCRIBER_ID);
// Expect a single Subscriber ID option instance.
ASSERT_EQ(1, std::distance(range1.first, range1.second));
const uint8_t subscriber_id_expected[] = {
0x11, 0x22, 0x33, 0x44, 0x55, 0x66
};
// Check if option is valid in terms of code and carried data.
testOption(*range1.first, D6O_SUBSCRIBER_ID, subscriber_id_expected,
sizeof(subscriber_id_expected));
// Test another pool in the same way.
pool = subnet->getPool(Lease::TYPE_PD, IOAddress("3001::"), false);
ASSERT_TRUE(pool);
pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
ASSERT_TRUE(pool6);
OptionContainerPtr options2 = pool6->getCfgOption()->getAll("dhcp6");
ASSERT_EQ(1, options2->size());
const OptionContainerTypeIndex& idx2 = options2->get<1>();
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> range2 =
idx2.equal_range(D6O_USER_CLASS);
ASSERT_EQ(1, std::distance(range2.first, range2.second));
const uint8_t user_class_expected[] = {
0xAA, 0xBB, 0xCC, 0xDD, 0xEE
};
testOption(*range2.first, D6O_USER_CLASS, user_class_expected,
sizeof(user_class_expected));
// Test options in NA pools.
pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1::10"));
ASSERT_TRUE(pool);
pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
ASSERT_TRUE(pool6);
OptionContainerPtr options3 = pool6->getCfgOption()->getAll("dhcp6");
ASSERT_EQ(1, options3->size());
const OptionContainerTypeIndex& idx3 = options3->get<1>();
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> range3 =
idx3.equal_range(D6O_SUBSCRIBER_ID);
ASSERT_EQ(1, std::distance(range3.first, range3.second));
const uint8_t subscriber_id_expected2[] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A
};
testOption(*range3.first, D6O_SUBSCRIBER_ID, subscriber_id_expected2,
sizeof(subscriber_id_expected2));
pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1::300"));
ASSERT_TRUE(pool);
pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
ASSERT_TRUE(pool6);
OptionContainerPtr options4 = pool6->getCfgOption()->getAll("dhcp6");
ASSERT_EQ(1, options4->size());
const OptionContainerTypeIndex& idx4 = options4->get<1>();
std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> range4 =
idx4.equal_range(D6O_USER_CLASS);
ASSERT_EQ(1, std::distance(range4.first, range4.second));
const uint8_t user_class_expected2[] = {
0xFF, 0xFE, 0xFD, 0xFC, 0xFB
};
testOption(*range4.first, D6O_USER_CLASS, user_class_expected2,
sizeof(user_class_expected2));
}
// The goal of this test is to check that the option carrying a boolean
// value can be configured using one of the values: "true", "false", "0"
// or "1".
......
......@@ -8,11 +8,12 @@
#include <dhcp/dhcp6.h>
#include <dhcp/docsis3_option_defs.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_vendor.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_status_code.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_vendor.h>
#include <dhcp/pkt6.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/pool.h>
......@@ -20,6 +21,7 @@
#include <util/buffer.h>
#include <boost/foreach.hpp>
#include <boost/pointer_cast.hpp>
#include <algorithm>
#include <cstdlib>
#include <time.h>
......@@ -803,6 +805,20 @@ Dhcp6Client::hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix
return (false);
}
bool
Dhcp6Client::hasOptionWithAddress(const uint16_t option_type,
const std::string& expected_address) const {
Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
Option6AddrLst>(config_.findOption(option_type));
if (opt) {
Option6AddrLst::AddressContainer addrs = opt->getAddresses();
if (!addrs.empty()) {
return (std::find(addrs.begin(), addrs.end(),
IOAddress(expected_address)) != addrs.end());
}
}
return (false);
}
uint16_t
Dhcp6Client::getStatusCode(const uint32_t iaid) const {
......
......@@ -468,6 +468,19 @@ public:
bool hasLeaseWithZeroLifetimeForPrefix(const asiolink::IOAddress& prefix,
const uint8_t prefix_len) const;
/// @brief Checks that specified option exists and contains a desired
/// address.
///
/// The option must cast to the @ref Option6AddrLst type. The function
/// expects that this option contains at least one address and checks
/// first address for equality with @ref expected_address.
///
/// @param option_type Option type.
/// @param expected_address Desired address.
/// @param config Configuration obtained from the server.
bool hasOptionWithAddress(const uint16_t option_type,
const std::string& expected_address) const;
/// @brief Returns the value of the global status code for the last
/// transaction.
uint16_t getStatusCode() const {
......
......@@ -624,6 +624,7 @@ public:
/// @param stat_name this statistic is expected to be set to 1
void testReceiveStats(uint8_t pkt_type, const std::string& stat_name);
/// A subnet used in most tests
isc::dhcp::Subnet6Ptr subnet_;
....