From: Marcin Siodelski Date: Thu, 29 Sep 2016 10:56:03 +0000 (+0200) Subject: [github24] Prefix Exclude option is returned to the clients. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=697a4fe1fce4704b2ab82d29ca232142b52777ea;p=thirdparty%2Fkea.git [github24] Prefix Exclude option is returned to the clients. Currently it is only returned for dynamically allocated prefixes from certain pools. It won't work for host reservations. --- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 58893bb15a..05b6e8bdf2 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,7 @@ #include #include +#include #include #include #include @@ -857,6 +859,7 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question, void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, + AllocEngine::ClientContext6& ctx, const CfgOptionList& co_list) { // Client requests some options using ORO option. Try to @@ -865,18 +868,39 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer, boost::dynamic_pointer_cast > (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& 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_); @@ -1531,6 +1555,19 @@ Dhcpv6Srv::assignIA_PD(const Pkt6Ptr& query, const Pkt6Ptr& answer, (*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, @@ -1801,10 +1838,26 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query, // 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()) @@ -2295,7 +2348,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { 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); @@ -2325,7 +2378,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) { 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); @@ -2351,7 +2404,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) { 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); @@ -2377,7 +2430,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) { 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); @@ -2411,7 +2464,7 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) { 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 @@ -2813,7 +2866,7 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) { 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); diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 7513f909bf..9435823499 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -475,8 +475,13 @@ protected: /// /// @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. diff --git a/src/bin/dhcp6/tests/renew_unittest.cc b/src/bin/dhcp6/tests/renew_unittest.cc index 3413d659ca..cac72e226e 100644 --- a/src/bin/dhcp6/tests/renew_unittest.cc +++ b/src/bin/dhcp6/tests/renew_unittest.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,13 @@ namespace { /// - 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\": {" @@ -117,8 +125,29 @@ const char* RENEW_CONFIGS[] = { " \"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. @@ -199,6 +228,91 @@ TEST_F(RenewTest, requestPrefixInRenew) { 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 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 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 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 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(option); + ASSERT_TRUE(pd_exclude); + EXPECT_EQ("3000::1000", pd_exclude->getExcludedPrefix().toText()); + EXPECT_EQ(120, static_cast(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) { diff --git a/src/bin/dhcp6/tests/sarr_unittest.cc b/src/bin/dhcp6/tests/sarr_unittest.cc index 3a500dfebd..1cc568b25e 100644 --- a/src/bin/dhcp6/tests/sarr_unittest.cc +++ b/src/bin/dhcp6/tests/sarr_unittest.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,11 @@ namespace { /// 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\": {" @@ -82,7 +88,29 @@ const char* CONFIGS[] = { " \"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, @@ -173,6 +201,46 @@ TEST_F(SARRTest, directClientPrefixHint) { 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(option); + ASSERT_TRUE(ia); + option = ia->getOption(D6O_IAPREFIX); + ASSERT_TRUE(option); + Option6IAPrefixPtr pd_option = boost::dynamic_pointer_cast(option); + ASSERT_TRUE(pd_option); + option = pd_option->getOption(D6O_PD_EXCLUDE); + ASSERT_TRUE(option); + Option6PDExcludePtr pd_exclude = boost::dynamic_pointer_cast(option); + ASSERT_TRUE(pd_exclude); + EXPECT_EQ("2001:db8:3::1000", pd_exclude->getExcludedPrefix().toText()); + EXPECT_EQ(120, static_cast(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) { diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 1f8d9d4e3d..61543ab8cb 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -350,7 +350,7 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, 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) { @@ -360,7 +360,7 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, AllocEngine::ClientContext6::IAContext::IAContext() : iaid_(0), type_(Lease::TYPE_NA), hints_(), old_leases_(), - changed_leases_(), ia_rsp_() { + changed_leases_(), ia_rsp_(), pool_() { } void @@ -583,10 +583,10 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { // 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 = @@ -608,7 +608,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { // 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 @@ -647,7 +647,8 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { 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); @@ -663,6 +664,8 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { } } + 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: @@ -689,10 +692,10 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { // non-PD leases. uint8_t prefix_len = 128; if (ctx.currentIA().type_ == Lease::TYPE_PD) { - Pool6Ptr pool = boost::dynamic_pointer_cast( + ctx.currentIA().pool_ = boost::dynamic_pointer_cast( 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_, diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 69ac652f04..82708c4e5c 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -381,6 +381,10 @@ public: /// 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. @@ -397,6 +401,10 @@ public: /// @brief Container holding IA specific contexts. std::vector 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.