Browse Source

[3947] Implemented basic test for the RFC7550 Renew.

This test checks that the client can request allocation of the
prefix when it renews the existing IA_NA binding.

The DHCPv6 test client had to be extended to faciliate this.
Marcin Siodelski 9 years ago
parent
commit
fe3d24975c

+ 1 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -83,6 +83,7 @@ dhcp6_unittests_SOURCES += marker_file.cc
 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

+ 81 - 13
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -23,6 +23,7 @@
 #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>
@@ -210,17 +211,70 @@ Dhcp6Client::appendFQDN() {
 }
 
 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);
+        }
     }
 }
 
@@ -298,17 +352,10 @@ Dhcp6Client::doSolicit() {
     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)));
@@ -336,6 +383,7 @@ Dhcp6Client::doRequest() {
         query->addOption(forced_server_id_);
     }
     copyIAs(context_.response_, query);
+    appendRequestedIAs(query);
 
     // Add Client FQDN if configured.
     appendFQDN();
@@ -384,6 +432,10 @@ Dhcp6Client::doRenew() {
     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();
 
@@ -401,6 +453,10 @@ Dhcp6Client::doRebind() {
     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();
 
@@ -487,6 +543,18 @@ Dhcp6Client::getLeasesByIAID(const uint32_t iaid) const {
     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);

+ 31 - 1
src/bin/dhcp6/tests/dhcp6_client.h

@@ -312,6 +312,13 @@ public:
     /// @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 {
@@ -529,12 +536,35 @@ private:
     /// @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.

+ 1 - 0
src/bin/dhcp6/tests/dhcp6_message_test.h

@@ -78,6 +78,7 @@ public:
     void requestLease(const std::string& config, const int subnets_num,
                       Dhcp6Client& client);
 
+
 protected:
 
     /// @brief Interface Manager's fake configuration control.

+ 141 - 0
src/bin/dhcp6/tests/renew_unittest.cc

@@ -0,0 +1,141 @@
+// 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