#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaprefix.h>
#include <dhcp/option6_status_code.h>
+#include <dhcp/option6_pdexclude.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_vendor.h>
#include <dhcp/option_vendor_class.h>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string/erase.hpp>
+#include <algorithm>
#include <stdlib.h>
#include <time.h>
#include <iomanip>
void
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ AllocEngine::ClientContext6& ctx,
const CfgOptionList& co_list) {
// Client requests some options using ORO option. Try to
boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
(question->getOption(D6O_ORO));
- // Option ORO not found? We're done here then.
- if (!option_oro || co_list.empty()) {
+ // If there is no ORO option, there is nothing more to do.
+ if (!option_oro) {
return;
+
}
// Get the list of options that client requested.
const std::vector<uint16_t>& requested_opts = option_oro->getValues();
+
+ if (co_list.empty()) {
+ // If there are no options configured, we at least have to check if
+ // the client has requested PD exclude, which is configured as
+ // part of the pool configuration.
+ ctx.pd_exclude_requested_ = (std::find(requested_opts.begin(),
+ requested_opts.end(),
+ D6O_PD_EXCLUDE) !=
+ requested_opts.end());
+ return;
+ }
+
BOOST_FOREACH(uint16_t opt, requested_opts) {
+ // Prefix Exclude option requires special handling, as it can
+ // be configured as part of the pool configuration.
+ if (opt == D6O_PD_EXCLUDE) {
+ ctx.pd_exclude_requested_ = true;
+ // Prefix Exclude can only be included in the IA Prefix option
+ // of IA_PD. Thus there is nothing more to do here.
+ continue;
+ }
// Iterate on the configured option list
for (CfgOptionList::const_iterator copts = co_list.begin();
copts != co_list.end(); ++copts) {
- OptionDescriptor desc = (*copts)->get("dhcp6", opt);
+ OptionDescriptor desc = (*copts)->get(DHCP6_OPTION_SPACE, opt);
// Got it: add it and jump to the outer loop
if (desc.option_) {
answer->addOption(desc.option_);
(*l)->prefixlen_, (*l)->preferred_lft_,
(*l)->valid_lft_));
ia_rsp->addOption(addr);
+
+ if (ctx.pd_exclude_requested_) {
+ // PD exclude option has been requested via ORO, thus we need to
+ // include it if the pool configuration specifies this option.
+ Pool6Ptr pool = ctx.currentIA().pool_;
+ if (pool && pool->getExcludedPrefixLength() > 0) {
+ OptionPtr opt(new Option6PDExclude((*l)->addr_,
+ (*l)->prefixlen_,
+ pool->getExcludedPrefix(),
+ pool->getExcludedPrefixLength()));
+ addr->addOption(opt);
+ }
+ }
}
// It would be possible to insert status code=0(success) as well,
// For all the leases we have now, add the IAPPREFIX with non-zero lifetimes
for (Lease6Collection::const_iterator l = leases.begin(); l != leases.end(); ++l) {
+
Option6IAPrefixPtr prf(new Option6IAPrefix(D6O_IAPREFIX,
(*l)->addr_, (*l)->prefixlen_,
(*l)->preferred_lft_, (*l)->valid_lft_));
ia_rsp->addOption(prf);
+
+ if (ctx.pd_exclude_requested_) {
+ // PD exclude option has been requested via ORO, thus we need to
+ // include it if the pool configuration specifies this option.
+ Pool6Ptr pool = ctx.currentIA().pool_;
+ if (pool && pool->getExcludedPrefixLength() > 0) {
+ OptionPtr opt(new Option6PDExclude((*l)->addr_,
+ (*l)->prefixlen_,
+ pool->getExcludedPrefix(),
+ pool->getExcludedPrefixLength()));
+ prf->addOption(opt);
+ }
+ }
+
+
LOG_INFO(lease6_logger, DHCP6_PD_LEASE_RENEW)
.arg(query->getLabel())
.arg((*l)->addr_.toText())
CfgOptionList co_list;
buildCfgOptionList(solicit, ctx, co_list);
appendDefaultOptions(solicit, response, co_list);
- appendRequestedOptions(solicit, response, co_list);
+ appendRequestedOptions(solicit, response, ctx, co_list);
appendRequestedVendorOptions(solicit, response, ctx, co_list);
processClientFqdn(solicit, response, ctx);
CfgOptionList co_list;
buildCfgOptionList(request, ctx, co_list);
appendDefaultOptions(request, reply, co_list);
- appendRequestedOptions(request, reply, co_list);
+ appendRequestedOptions(request, reply, ctx, co_list);
appendRequestedVendorOptions(request, reply, ctx, co_list);
processClientFqdn(request, reply, ctx);
CfgOptionList co_list;
buildCfgOptionList(renew, ctx, co_list);
appendDefaultOptions(renew, reply, co_list);
- appendRequestedOptions(renew, reply, co_list);
+ appendRequestedOptions(renew, reply, ctx, co_list);
appendRequestedVendorOptions(renew, reply, ctx, co_list);
processClientFqdn(renew, reply, ctx);
CfgOptionList co_list;
buildCfgOptionList(rebind, ctx, co_list);
appendDefaultOptions(rebind, reply, co_list);
- appendRequestedOptions(rebind, reply, co_list);
+ appendRequestedOptions(rebind, reply, ctx, co_list);
appendRequestedVendorOptions(rebind, reply, ctx, co_list);
processClientFqdn(rebind, reply, ctx);
CfgOptionList co_list;
buildCfgOptionList(confirm, ctx, co_list);
appendDefaultOptions(confirm, reply, co_list);
- appendRequestedOptions(confirm, reply, co_list);
+ appendRequestedOptions(confirm, reply, ctx, co_list);
appendRequestedVendorOptions(confirm, reply, ctx, co_list);
// Indicates if at least one address has been verified. If no addresses
// are verified it means that the client has sent no IA_NA options
appendDefaultOptions(inf_request, reply, co_list);
// Try to assign options that were requested by the client.
- appendRequestedOptions(inf_request, reply, co_list);
+ appendRequestedOptions(inf_request, reply, ctx, co_list);
// Try to assigne vendor options that were requested by the client.
appendRequestedVendorOptions(inf_request, reply, ctx, co_list);
///
/// @param question client's message
/// @param answer server's message (options will be added here)
+ /// @param [out] ctx client context. This method sets the
+ /// ctx.pd_exclude_requested_ field to 'true' if the Prefix Exclude
+ /// option has been requested.
+ ///
/// @param co_list configured option list
void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ AllocEngine::ClientContext6& ctx,
const CfgOptionList& co_list);
/// @brief Appends requested vendor options to server's answer.
#include <dhcp/docsis3_option_defs.h>
#include <dhcp/option_string.h>
#include <dhcp/option_vendor.h>
+#include <dhcp/option6_pdexclude.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp6/json_config_parser.h>
#include <dhcp6/tests/dhcp6_message_test.h>
/// - 1 subnet with 2001:db8:1::/64 pool
/// - DOCSIS vendor config file sub-option
///
+/// - Configuration 4:
+/// - addresses and prefixes
+/// - 1 subnet with one address pool and one prefix pool
+/// - address pool: 2001:db8:1::/64
+/// - prefix pool: 3000::/72
+/// - excluded prefix 3000::1000/120 in a prefix pool.
+///
const char* RENEW_CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
" \"interface-id\": \"\","
" \"interface\": \"eth0\""
" } ],"
- "\"valid-lifetime\": 4000 }"
+ "\"valid-lifetime\": 4000 }",
+// Configuration 4
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"pd-pools\": ["
+ " { \"prefix\": \"3000::\", "
+ " \"prefix-len\": 72, "
+ " \"delegated-len\": 80,"
+ " \"excluded-prefix\": \"3000::1000\","
+ " \"excluded-prefix-len\": 120"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
};
/// @brief Test fixture class for testing Renew.
EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
}
+// Test that it is possible to renew a prefix lease with a Prefix Exclude
+// option being included during renew.
+TEST_F(RenewTest, renewWithExcludedPrefix) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.requestAddress(na_iaid_);
+ client.requestPrefix(pd_iaid_);
+
+ // Request Prefix Exclude option.
+ client.requestOption(D6O_PD_EXCLUDE);
+
+ // Configure the server with NA pools only.
+ ASSERT_NO_THROW(configure(RENEW_CONFIGS[2], *client.getServer()));
+
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Simulate aging of leases.
+ client.fastFwdTime(1000);
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // The client should also acquire a PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+ ASSERT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // Send Renew message to the server, including IA_NA and IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ std::vector<Lease6> leases_client_na_renewed =
+ client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ std::vector<Lease6> leases_client_pd_renewed =
+ client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // Make sure that Prefix Exclude option hasn't been included.
+ OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_PD_EXCLUDE);
+ ASSERT_FALSE(option);
+
+ // Reconfigure the server to use the prefix pool with excluded prefix.
+ configure(RENEW_CONFIGS[4], *client.getServer());
+
+ // Send Renew message to the server, including IA_NA and IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that the client has acquired NA lease.
+ leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+ // Make sure that the client has acquired PD lease.
+ leases_client_pd_renewed = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd_renewed.size());
+ EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+ // The leases should have been renewed.
+ EXPECT_EQ(1000, leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_);
+ EXPECT_EQ(1000, leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_);
+
+ // This time, the Prefix Exclude option should be included.
+ option = client.getContext().response_->getOption(D6O_IA_PD);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(option);
+ option = option->getOption(D6O_PD_EXCLUDE);
+ ASSERT_TRUE(option);
+ Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
+ ASSERT_TRUE(pd_exclude);
+ EXPECT_EQ("3000::1000", pd_exclude->getExcludedPrefix().toText());
+ EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
+}
+
// This test verifies that the client can request a prefix delegation
// with a hint, while it is renewing an address lease.
TEST_F(RenewTest, requestPrefixInRenewUseHint) {
#include <config.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_pdexclude.h>
#include <dhcp6/tests/dhcp6_test_utils.h>
#include <dhcp6/tests/dhcp6_client.h>
#include <dhcpsrv/cfgmgr.h>
/// one
/// - DNS updates enabled
///
+/// - Configuration 2:
+/// - one subnet 3000::/32 used on eth0 interface
+/// - prefixes of length 64, delegated from the pool: 2001:db8:3::/48
+/// - Excluded Prefix specified (RFC 6603).
+///
const char* CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
" \"dhcp-ddns\" : {"
" \"enable-updates\" : True, "
" \"qualifying-suffix\" : \"example.com\" }"
- "}"
+ "}",
+
+ // Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pd-pools\": ["
+ " { \"prefix\": \"2001:db8:3::\", "
+ " \"prefix-len\": 48, "
+ " \"delegated-len\": 64,"
+ " \"excluded-prefix\": \"2001:db8:3::1000\","
+ " \"excluded-prefix-len\": 120"
+ " } ],"
+ " \"subnet\": \"3000::/32\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
};
/// @brief Test fixture class for testing 4-way exchange: Solicit-Advertise,
ASSERT_TRUE(lease_server);
}
+/// This test verifies that it is possible to specify an excluded prefix
+/// (RFC 6603) and send it back to the client requesting prefix delegation.
+TEST_F(SARRTest, directClientExcludedPrefix) {
+ Dhcp6Client client;
+ // Configure client to request IA_PD.
+ client.requestPrefix();
+ client.requestOption(D6O_PD_EXCLUDE);
+ configure(CONFIGS[2], *client.getServer());
+ // Make sure we ended-up having expected number of subnets configured.
+ const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+ getCfgSubnets6()->getAll();
+ ASSERT_EQ(1, subnets->size());
+ // Perform 4-way exchange.
+ ASSERT_NO_THROW(client.doSARR());
+ // Server should have assigned a prefix.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ(64, lease_client.prefixlen_);
+ EXPECT_EQ(3000, lease_client.preferred_lft_);
+ EXPECT_EQ(4000, lease_client.valid_lft_);
+ Lease6Ptr lease_server = checkLease(lease_client);
+ // Check that the server recorded the lease.
+ ASSERT_TRUE(lease_server);
+
+ OptionPtr option = client.getContext().response_->getOption(D6O_IA_PD);
+ ASSERT_TRUE(option);
+ Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(option);
+ ASSERT_TRUE(ia);
+ option = ia->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(option);
+ Option6IAPrefixPtr pd_option = boost::dynamic_pointer_cast<Option6IAPrefix>(option);
+ ASSERT_TRUE(pd_option);
+ option = pd_option->getOption(D6O_PD_EXCLUDE);
+ ASSERT_TRUE(option);
+ Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast<Option6PDExclude>(option);
+ ASSERT_TRUE(pd_exclude);
+ EXPECT_EQ("2001:db8:3::1000", pd_exclude->getExcludedPrefix().toText());
+ EXPECT_EQ(120, static_cast<unsigned>(pd_exclude->getExcludedPrefixLength()));
+}
+
// Check that when the client includes the Rapid Commit option in its
// Solicit, the server responds with Reply and commits the lease.
TEST_F(SARRTest, rapidCommitEnable) {
duid_(duid), hwaddr_(), host_identifiers_(), host_(),
fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
hostname_(hostname), callout_handle_(callout_handle),
- allocated_resources_(), ias_() {
+ allocated_resources_(), ias_(), pd_exclude_requested_(false) {
// Initialize host identifiers.
if (duid) {
AllocEngine::ClientContext6::IAContext::IAContext()
: iaid_(0), type_(Lease::TYPE_NA), hints_(), old_leases_(),
- changed_leases_(), ia_rsp_() {
+ changed_leases_(), ia_rsp_(), pool_() {
}
void
// check if the hint is in pool and is available
// This is equivalent of subnet->inPool(hint), but returns the pool
- Pool6Ptr pool = boost::dynamic_pointer_cast<
+ ctx.currentIA().pool_ = boost::dynamic_pointer_cast<
Pool6>(ctx.subnet_->getPool(ctx.currentIA().type_, hint, false));
- if (pool) {
+ if (ctx.currentIA().pool_) {
/// @todo: We support only one hint for now
Lease6Ptr lease =
// The hint is valid and not currently used, let's create a
// lease for it
- lease = createLease6(ctx, hint, pool->getLength());
+ lease = createLease6(ctx, hint, ctx.currentIA().pool_->getLength());
// It can happen that the lease allocation failed (we could
// have lost the race condition. That means that the hint is
ctx.currentIA().old_leases_.push_back(old_lease);
/// We found a lease and it is expired, so we can reuse it
- lease = reuseExpiredLease(lease, ctx, pool->getLength());
+ lease = reuseExpiredLease(lease, ctx,
+ ctx.currentIA().pool_->getLength());
/// @todo: We support only one lease per ia for now
leases.push_back(lease);
}
}
+ ctx.currentIA().pool_.reset();
+
// The hint was useless (it was not provided at all, was used by someone else,
// was out of pool or reserved for someone else). Search the pool until first
// of the following occurs:
// non-PD leases.
uint8_t prefix_len = 128;
if (ctx.currentIA().type_ == Lease::TYPE_PD) {
- Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(
+ ctx.currentIA().pool_ = boost::dynamic_pointer_cast<Pool6>(
ctx.subnet_->getPool(ctx.currentIA().type_, candidate, false));
/// @todo: verify that the pool is non-null
- prefix_len = pool->getLength();
+ prefix_len = ctx.currentIA().pool_->getLength();
}
Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
/// response
Option6IAPtr ia_rsp_;
+ /// @brief A pointer to a pool from which an address or prefix has
+ /// been assigned in this IA.
+ Pool6Ptr pool_;
+
/// @brief Default constructor.
///
/// Initializes @ref type_ to @c Lease::TYPE_NA and @ref iaid_ to 0.
/// @brief Container holding IA specific contexts.
std::vector<IAContext> ias_;
+ /// @brief Indicates if PD exclude option has been requested by a
+ /// client.
+ bool pd_exclude_requested_;
+
/// @brief Convenience method adding allocated prefix or address.
///
/// @param prefix Prefix or address.