dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
dhcp6_unittests_SOURCES += rebind_unittest.cc
+dhcp6_unittests_SOURCES += renew_unittest.cc
dhcp6_unittests_SOURCES += sarr_unittest.cc
dhcp6_unittests_SOURCES += config_parser_unittest.cc
dhcp6_unittests_SOURCES += confirm_unittest.cc
#include <dhcpsrv/lease.h>
#include <dhcp6/tests/dhcp6_client.h>
#include <util/buffer.h>
+#include <boost/foreach.hpp>
#include <boost/pointer_cast.hpp>
#include <cstdlib>
#include <time.h>
}
}
+void
+Dhcp6Client::appendRequestedIAs(const Pkt6Ptr& query) const {
+ if (use_na_) {
+ conditionallyAppendRequestedIA(query, D6O_IA_NA, 1234);
+ }
+
+ if (use_pd_) {
+ conditionallyAppendRequestedIA(query, D6O_IA_PD, 5678);
+ }
+}
+
+void
+Dhcp6Client::conditionallyAppendRequestedIA(const Pkt6Ptr& query,
+ const uint8_t ia_type,
+ const uint32_t iaid) const {
+ // Get existing options of the specified type.
+ OptionCollection options = query->getOptions(ia_type);
+ std::pair<unsigned int, OptionPtr> option_pair;
+
+ // Check if the option we want to add is already present.
+ BOOST_FOREACH(option_pair, options) {
+ Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(option_pair.second);
+ // This shouldn't happen.
+ if (!ia) {
+ isc_throw(Unexpected, "Dhcp6Client: IA option has an invalid C++ type;"
+ " this is a programming issue");
+ }
+ // There is an option of the specific type already. If it has our
+ // IAID we return here, because we don't want to duplicate the IA.
+ // If IAID is different, we check other existing IAs.
+ if (ia->getIAID() == iaid) {
+ return;
+ }
+ }
+
+ // If we're here, it means that there is no instance of our IA yet.
+ Option6IAPtr requested_ia(new Option6IA(ia_type, iaid));
+ // Add prefix hint if specified.
+ if (prefix_hint_ && (ia_type == D6O_IA_PD)) {
+ requested_ia->addOption(prefix_hint_);
+ }
+
+ query->addOption(requested_ia);
+}
+
+
void
Dhcp6Client::copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest) {
typedef OptionCollection Opts;
// Copy IA_NAs.
Opts opts = source->getOptions(D6O_IA_NA);
for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
- dest->addOption(opt->second);
+ // Only copy the entire IA_NA if there is at lease one IA Address option.
+ if (opt->second->getOption(D6O_IAADDR)) {
+ dest->addOption(opt->second);
+ }
}
// Copy IA_PDs.
opts = source->getOptions(D6O_IA_PD);
for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
- dest->addOption(opt->second);
+ // Only copy the entire IA_PD if there is at least one IA Prefix option
+ // in it.
+ if (opt->second->getOption(D6O_IAPREFIX)) {
+ dest->addOption(opt->second);
+ }
}
}
if (forced_server_id_) {
context_.query_->addOption(forced_server_id_);
}
- if (use_na_) {
- context_.query_->addOption(Option6IAPtr(new Option6IA(D6O_IA_NA,
- 1234)));
- }
- if (use_pd_) {
- Option6IAPtr ia(new Option6IA(D6O_IA_PD, 5678));
- if (prefix_hint_) {
- ia->addOption(prefix_hint_);
- }
- context_.query_->addOption(ia);
- }
+
+ // Append requested (empty) IAs.
+ appendRequestedIAs(context_.query_);
+
if (use_rapid_commit_) {
context_.query_->addOption(OptionPtr(new Option(Option::V6,
D6O_RAPID_COMMIT)));
query->addOption(forced_server_id_);
}
copyIAs(context_.response_, query);
+ appendRequestedIAs(query);
// Add Client FQDN if configured.
appendFQDN();
query->addOption(context_.response_->getOption(D6O_SERVERID));
copyIAsFromLeases(query);
+ // During the Renew the client may request additional bindings per
+ // RFC7550.
+ appendRequestedIAs(query);
+
// Add Client FQDN if configured.
appendFQDN();
Pkt6Ptr query = createMsg(DHCPV6_REBIND);
copyIAsFromLeases(query);
+ // During the Rebind the client may request additional bindings per
+ // RFC7550.
+ appendRequestedIAs(query);
+
// Add Client FQDN if configured.
appendFQDN();
return (leases);
}
+std::vector<Lease6>
+Dhcp6Client::getLeasesByType(const Lease::Type& lease_type) const {
+ std::vector<Lease6> leases;
+ LeaseInfo lease_info;
+ BOOST_FOREACH(lease_info, config_.leases_) {
+ if (lease_info.lease_.type_ == lease_type) {
+ leases.push_back(lease_info.lease_);
+ }
+ }
+ return (leases);
+}
+
void
Dhcp6Client::setDUID(const std::string& str) {
DUID d = DUID::fromText(str);
/// @return Vector containing leases for the IAID.
std::vector<Lease6> getLeasesByIAID(const uint32_t iaid) const;
+ /// @brief Returns collection of leases by type.
+ ///
+ /// @param type Lease type: D6O_IA_NA or D6O_IA_PD.
+ ///
+ /// @return Vector containing leases of the specified type.
+ std::vector<Lease6> getLeasesByType(const Lease::Type& lease_type) const;
+
/// @brief Returns the value of the global status code for the last
/// transaction.
uint16_t getStatusCode() const {
/// @param lease_info Structure holding new lease information.
void applyLease(const LeaseInfo& lease_info);
- /// @brief Includes CLient FQDN in the client's message.
+ /// @brief Includes Client FQDN in the client's message.
///
/// This method checks if @c fqdn_ is specified and includes it in
/// the client's message.
void appendFQDN();
+ /// @brief Includes IAs to be requested.
+ ///
+ /// This method checks if @c use_na_ and/or @c use_pd_ are specified and
+ /// includes appropriate IA types, if they are not already included.
+ ///
+ /// @param query Pointer to the client's message to which IAs should be
+ /// added.
+ void appendRequestedIAs(const Pkt6Ptr& query) const;
+
+ /// @brief Include IA of the specified type if it doesn't exist yet.
+ ///
+ /// This methods includes an IA option of the specific type, and
+ /// having a given IAID to the query message, if this IA hasn't
+ /// been added yet.
+ ///
+ /// @param query Pointer to the client's message to which IA should be
+ /// added.
+ /// @param ia_type One of the D6O_IA_NA or D6O_IA_PD
+ /// @param iaid IAID of the IA.
+ void conditionallyAppendRequestedIA(const Pkt6Ptr& query,
+ const uint8_t ia_type,
+ const uint32_t iaid) const;
+
/// @brief Copy IA options from one message to another.
///
/// This method copies IA_NA and IA_PD options from one message to another.
void requestLease(const std::string& config, const int subnets_num,
Dhcp6Client& client);
+
protected:
/// @brief Interface Manager's fake configuration control.
--- /dev/null
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp6/json_config_parser.h>
+#include <dhcp6/tests/dhcp6_message_test.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Set of JSON configurations used throughout the Renew tests.
+///
+/// - Configuration 0:
+/// - only addresses (no prefixes)
+/// - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::64
+/// - 1 subnet for eth0 and 1 subnet for eth1
+///
+/// - Configuration 1:
+/// - similar to Configuration 0 but different subnets
+/// - pools configured: 2001:db8:3::/64 and 2001:db8:4::/64
+///
+/// - Configuration 2:
+/// - similar to Configuration 0 and Configuration 1
+/// - pools configured: 3000:1::/64 and 3000:2::/64
+/// - this specific configuration is used by tests using relays
+///
+const char* RENEW_CONFIGS[] = {
+// Configuration 0
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+// Configuration 1
+ "{ \"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"
+ " } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface-id\": \"\","
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }"
+};
+
+/// @brief Test fixture class for testing Renew.
+class RenewTest : public Dhcpv6MessageTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets up fake interfaces.
+ RenewTest()
+ : Dhcpv6MessageTest() {
+ }
+};
+
+// This test verifies that the client can request the prefix delegation
+// while it is renewing an address lease.
+TEST_F(RenewTest, requestPrefixInRenew) {
+ Dhcp6Client client;
+
+ // Configure client to request IA_NA and IA_PD.
+ client.useNA();
+ client.usePD();
+
+ // Configure the server with NA pools only.
+ ASSERT_NO_THROW(configure(RENEW_CONFIGS[0], *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());
+
+ // The client should not acquire a PD lease.
+ std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_TRUE(leases_client_pd.empty());
+
+ // Reconfigure the server to use both NA and PD pools.
+ configure(RENEW_CONFIGS[1], *client.getServer());
+
+ // Send Renew message to the server, including IA_NA and requesting IA_PD.
+ ASSERT_NO_THROW(client.doRenew());
+
+ // Make sure that the client has acquired NA lease.
+ std::vector<Lease6> leases_client_na_renewed = client.getLeasesByType(Lease::TYPE_NA);
+ ASSERT_EQ(1, leases_client_na_renewed.size());
+
+ // The lease should have been renewed.
+ EXPECT_EQ(1000, leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_);
+
+ // The client should now also acquire a PD lease.
+ leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+ ASSERT_EQ(1, leases_client_pd.size());
+}
+
+
+} // end of anonymous namespace