Browse Source

[master] Merge branch 'trac3947'

Marcin Siodelski 9 years ago
parent
commit
c06ab97a4e

+ 54 - 0
doc/guide/dhcp6-srv.xml

@@ -2379,6 +2379,53 @@ should include options from the isc option space:
       even if it is not used.</para>
       even if it is not used.</para>
     </section>
     </section>
 
 
+    <section id="dhcp6-rfc7550">
+      <title>Support for RFC 7550</title>
+      <para>The <ulink url="http://tools.ietf.org/html/rfc7550">RFC 7550</ulink>
+      has introduced some changes to the DHCPv6 protocol to resolve a few issues
+      with the coexistence of multiple stateful options in the messages sent
+      between the clients and servers.</para>
+
+      <para>The typical example is when the client, such as a requesting
+      router, requests an allocation of both addresses and prefixes when
+      it performs the 4-way (SARR) exchange with the server. If the
+      server is not configured to allocate any prefixes but it can allocate
+      some addresses, it will respond with the IA_NA(s) containing allocated
+      addresses and the IA_PD(s) containing the NoPrefixAvail status code. If
+      the client can operate without prefixes it may transition to the
+      'bound' state when it sends Renew/Rebind messages to the server,
+      according to the T1 and T2 times, to extend the lifetimes of the
+      allocated addresses. If the client is still interested in obtaining
+      prefixes from the server it may also include IA_PD in the Renew/Rebind
+      to request allocation of the prefixes. If the server still cannot
+      allocate the prefixes, it will respond with the IA_PD(s) containing
+      NoPrefixAvail status code. However, if the server can now allocate
+      the prefixes it will do so, and send them in the IA_PD(s) to the client.
+      Allocation of leases during the Renew/Rebind was not supported in the
+      <ulink url="http://tools.ietf.org/html/rfc3315">RFC 3315</ulink>
+      and <ulink url="http://tools.ietf.org/html/rfc3633">RFC 3633</ulink>,
+      and has been introduced in
+      <ulink url="http://tools.ietf.org/html/rfc7550">RFC 7550</ulink>.
+      Kea supports this new behavior and it doesn't provide any configuration
+      mechanisms to disable it.
+      </para>
+
+      <para>
+        The following are the other behaviors specified in the
+        <ulink url="http://tools.ietf.org/html/rfc7550">RFC 7550</ulink>
+        supported by the Kea DHCPv6 server:
+        <itemizedlist>
+          <listitem><simpara>set T1/T2 timers to the same value for all
+          stateful (IA_NA and IA_PD) options to facilitate renewal of all
+          client's leases at the same time (in a single message exchange),
+          </simpara></listitem>
+          <listitem><simpara>NoAddrsAvail and NoPrefixAvail status codes
+          are placed in the IA_NA and IA_PD options in the Advertise message,
+          rather than as the top level options.</simpara></listitem>
+        </itemizedlist>
+      </para>
+    </section>
+
     <section id="dhcp6-relay-override">
     <section id="dhcp6-relay-override">
       <title>Using specific relay agent for a subnet</title>
       <title>Using specific relay agent for a subnet</title>
       <para>
       <para>
@@ -2964,6 +3011,13 @@ should include options from the isc option space:
             6939</ulink>: Supported option is client link-layer
             6939</ulink>: Supported option is client link-layer
             address option.</simpara>
             address option.</simpara>
           </listitem>
           </listitem>
+          <listitem>
+            <simpara><emphasis>Issues and Recommendations with Multiple
+            Stateful DHCPv6 Options</emphasis>,
+            <ulink url="http://tools.ietf.org/html/rfc7550">RFC
+            7550</ulink>: All recommendations related to the DHCPv6 server
+            operation are supported.</simpara>
+          </listitem>
       </itemizedlist>
       </itemizedlist>
     </section>
     </section>
 
 

+ 0 - 18
src/bin/dhcp6/dhcp6_messages.mes

@@ -224,24 +224,6 @@ as a result of receiving SIGHUP signal.
 This is an error message logged when the dynamic reconfiguration of the
 This is an error message logged when the dynamic reconfiguration of the
 DHCP server failed.
 DHCP server failed.
 
 
-% DHCP6_EXTEND_NA_UNKNOWN %1: received unknown IA_NA with iaid=%2 in subnet %3
-This warning message is printed when client attempts to extend the lease
-for the address (in the IA_NA option) but no such lease is known by the server.
-It typically means that client has attempted to use its lease past its
-lifetime: causes of this include a adjustment of the client's date/time
-setting or poor support on the client for sleep/recovery. A properly
-implemented client will recover from such a situation by restarting the
-lease allocation process after receiving a negative reply from the server.
-The first argument includes the client and the transaction identification
-information. The second argument holds IAID. The third argument holds the
-subnet information.
-
-An alternative cause could be that the server has lost its database
-recently and does not recognize its well-behaving clients. This is more
-probable if you see many such messages. Clients will recover from this,
-but they will most likely get a different IP addresses and experience
-a brief service interruption.
-
 % DHCP6_HANDLE_SIGNAL_EXCEPTION An exception was thrown while handing signal: %1
 % DHCP6_HANDLE_SIGNAL_EXCEPTION An exception was thrown while handing signal: %1
 This error message is printed when an exception was raised during signal
 This error message is printed when an exception was raised during signal
 processing. This likely indicates a coding error and should be reported to ISC.
 processing. This likely indicates a coding error and should be reported to ISC.

+ 21 - 75
src/bin/dhcp6/dhcp6_srv.cc

@@ -497,7 +497,7 @@ bool Dhcpv6Srv::run() {
         LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC_DATA, DHCP6_PACKET_RECEIVED)
         LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC_DATA, DHCP6_PACKET_RECEIVED)
             .arg(query->getLabel())
             .arg(query->getLabel())
             .arg(query->getName())
             .arg(query->getName())
-            .arg(query->getType())
+            .arg(static_cast<int>(query->getType()))
             .arg(query->getRemoteAddr())
             .arg(query->getRemoteAddr())
             .arg(query->getLocalAddr())
             .arg(query->getLocalAddr())
             .arg(query->getIface());
             .arg(query->getIface());
@@ -1754,19 +1754,6 @@ Dhcpv6Srv::extendIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
         ctx.hints_.push_back(make_pair(iaaddr->getAddress(), 128));
         ctx.hints_.push_back(make_pair(iaaddr->getAddress(), 128));
     }
     }
 
 
-    // We need to remember it as we'll be removing hints from this list as
-    // we extend, cancel or otherwise deal with the leases.
-    bool hints_present = !ctx.hints_.empty();
-
-    /// @todo: This was clarified in draft-ietf-dhc-dhcpv6-stateful-issues that
-    /// the server is allowed to assign new leases in both Renew and Rebind. For
-    /// now, we only support it in Renew, because it breaks a lot of Rebind
-    /// unit-tests. Ultimately, whether we allow it or not, should be exposed
-    /// as configurable policy. See ticket #3717.
-    if (query->getType() == DHCPV6_RENEW) {
-        ctx.allow_new_leases_in_renewals_ = true;
-    }
-
     Lease6Collection leases = alloc_engine_->renewLeases6(ctx);
     Lease6Collection leases = alloc_engine_->renewLeases6(ctx);
 
 
     // Ok, now we have the leases extended. We have:
     // Ok, now we have the leases extended. We have:
@@ -1825,26 +1812,13 @@ Dhcpv6Srv::extendIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
 
 
     // All is left is to insert the status code.
     // All is left is to insert the status code.
     if (leases.empty()) {
     if (leases.empty()) {
-        // We did not assign anything. If client has sent something, then
-        // the status code is NoBinding, if he sent an empty IA_NA, then it's
-        // NoAddrsAvailable
-        if (hints_present) {
-            // Insert status code NoBinding to indicate that the lease does not
-            // exist for this client.
-            ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoBinding,
-                              "Sorry, no known leases for this duid/iaid/subnet."));
 
 
-            LOG_DEBUG(lease6_logger, DBG_DHCP6_DETAIL, DHCP6_EXTEND_NA_UNKNOWN)
-                .arg(query->getLabel())
-                .arg(ia->getIAID())
-                .arg(subnet->toText());
-        } else {
-            ia_rsp->addOption(createStatusCode(*query, *ia_rsp, STATUS_NoAddrsAvail,
-                              "Sorry, no addresses could be assigned at this time."));
-        }
-    } else {
-        // Yay, the client still has something. For now, let's not insert
-        // status-code=success to conserve bandwidth.
+        // The server wasn't able allocate new lease and renew an exising
+        // lease. In that case, the server sends NoAddrsAvail per RFC7550.
+        ia_rsp->addOption(createStatusCode(*query, *ia_rsp,
+                                           STATUS_NoAddrsAvail,
+                                           "Sorry, no addresses could be"
+                                           " assigned at this time."));
     }
     }
 
 
     return (ia_rsp);
     return (ia_rsp);
@@ -1931,15 +1905,6 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query,
         // Put the client's prefix into the hints list.
         // Put the client's prefix into the hints list.
         ctx.hints_.push_back(make_pair(prf->getAddress(), prf->getLength()));
         ctx.hints_.push_back(make_pair(prf->getAddress(), prf->getLength()));
     }
     }
-    // We need to remember it as we'll be removing hints from this list as
-    // we extend, cancel or otherwise deal with the leases.
-    bool hints_present = !ctx.hints_.empty();
-
-    /// @todo: The draft-ietf-dhc-dhcpv6-stateful-issues added a new capability
-    /// of the server to to assign new PD leases in both Renew and Rebind.
-    /// There's allow_new_leases_in_renewals_ in the ClientContext6, but we
-    /// currently not use it in PD yet. This should be implemented as part
-    /// of the stateful-issues implementation effort. See ticket #3718.
 
 
     // Call Allocation Engine and attempt to renew leases. Number of things
     // Call Allocation Engine and attempt to renew leases. Number of things
     // may happen. Leases may be extended, revoked (if the lease is no longer
     // may happen. Leases may be extended, revoked (if the lease is no longer
@@ -1973,44 +1938,25 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query,
     // already, inform the client that he can't have them.
     // already, inform the client that he can't have them.
     for (AllocEngine::HintContainer::const_iterator prefix = ctx.hints_.begin();
     for (AllocEngine::HintContainer::const_iterator prefix = ctx.hints_.begin();
          prefix != ctx.hints_.end(); ++prefix) {
          prefix != ctx.hints_.end(); ++prefix) {
-        OptionPtr prefix_opt(new Option6IAPrefix(D6O_IAPREFIX, prefix->first,
-                                                 prefix->second, 0, 0));
-        ia_rsp->addOption(prefix_opt);
+        // Send the prefix with the zero lifetimes only if the prefix
+        // contains non-zero value. A zero value indicates that the hint was
+        // for the prefix length.
+        if (!prefix->first.isV6Zero()) {
+            OptionPtr prefix_opt(new Option6IAPrefix(D6O_IAPREFIX, prefix->first,
+                                                     prefix->second, 0, 0));
+            ia_rsp->addOption(prefix_opt);
+        }
     }
     }
 
 
     // All is left is to insert the status code.
     // All is left is to insert the status code.
     if (leases.empty()) {
     if (leases.empty()) {
-        if (query->getType() == DHCPV6_RENEW) {
 
 
-            // We did not assign anything. If client has sent something, then
-            // the status code is NoBinding, if he sent an empty IA_NA, then it's
-            // NoAddrsAvailable
-            if (hints_present) {
-                // Insert status code NoBinding to indicate that the lease does not
-                // exist for this client.
-                ia_rsp->addOption(createStatusCode(*query, *ia_rsp,
-                                                   STATUS_NoBinding,
-                                                   "Sorry, no known PD leases for"
-                                                   " this duid/iaid/subnet."));
-            } else {
-                ia_rsp->addOption(createStatusCode(*query, *ia_rsp,
-                                                   STATUS_NoPrefixAvail,
-                                                   "Sorry, no prefixes could be"
-                                                   " assigned at this time."));
-            }
-        } else {
-            // Per RFC3633, section 12.2, if there is no binding and we are
-            // processing Rebind, the message has to be discarded (assuming that
-            // the server doesn't know if the prefix in the IA_PD option is
-            // appropriate for the client's link). The exception being thrown
-            // here should propagate to the main loop and cause the message to
-            // be discarded.
-            isc_throw(DHCPv6DiscardMessageError, "no binding found for the"
-                      " DUID=" << duid->toText() << ", IAID="
-                      << ia->getIAID() << ", subnet="
-                      << subnet->toText() << " when processing a Rebind"
-                      " message with IA_PD option");
-        }
+        // The server wasn't able allocate new lease and renew an exising
+        // lease. In that case, the server sends NoPrefixAvail per RFC7550.
+        ia_rsp->addOption(createStatusCode(*query, *ia_rsp,
+                                           STATUS_NoPrefixAvail,
+                                           "Sorry, no prefixes could be"
+                                           " assigned at this time."));
     }
     }
 
 
     return (ia_rsp);
     return (ia_rsp);

+ 2 - 0
src/bin/dhcp6/json_config_parser.cc

@@ -473,6 +473,7 @@ protected:
             }
             }
         }
         }
 
 
+        // Gather boolean parameters values.
         bool rapid_commit = boolean_values_->getOptionalParam("rapid-commit", false);
         bool rapid_commit = boolean_values_->getOptionalParam("rapid-commit", false);
 
 
         std::ostringstream output;
         std::ostringstream output;
@@ -482,6 +483,7 @@ protected:
                << ", valid-lifetime=" << valid
                << ", valid-lifetime=" << valid
                << ", rapid-commit is " << (rapid_commit ? "enabled" : "disabled");
                << ", rapid-commit is " << (rapid_commit ? "enabled" : "disabled");
 
 
+
         LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(output.str());
         LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(output.str());
 
 
         // Create a new subnet.
         // Create a new subnet.

+ 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 += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
 dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
 dhcp6_unittests_SOURCES += rebind_unittest.cc
 dhcp6_unittests_SOURCES += rebind_unittest.cc
+dhcp6_unittests_SOURCES += renew_unittest.cc
 dhcp6_unittests_SOURCES += sarr_unittest.cc
 dhcp6_unittests_SOURCES += sarr_unittest.cc
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += confirm_unittest.cc
 dhcp6_unittests_SOURCES += confirm_unittest.cc

+ 2 - 2
src/bin/dhcp6/tests/confirm_unittest.cc

@@ -276,8 +276,8 @@ TEST_F(ConfirmTest, relayedClientNoSubnet) {
 
 
     // Set lifetimes to 0 so as the Confirm will ignore the specific address
     // Set lifetimes to 0 so as the Confirm will ignore the specific address
     // and send an empty IA_NA.
     // and send an empty IA_NA.
-    client.config_.leases_[0].lease_.preferred_lft_ = 0;
-    client.config_.leases_[0].lease_.valid_lft_ = 0;
+    client.config_.leases_[0].preferred_lft_ = 0;
+    client.config_.leases_[0].valid_lft_ = 0;
     ASSERT_NO_THROW(client.doConfirm());
     ASSERT_NO_THROW(client.doConfirm());
     EXPECT_FALSE(client.getContext().response_);
     EXPECT_FALSE(client.getContext().response_);
 
 

+ 234 - 86
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -23,12 +23,57 @@
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
+#include <boost/foreach.hpp>
 #include <boost/pointer_cast.hpp>
 #include <boost/pointer_cast.hpp>
 #include <cstdlib>
 #include <cstdlib>
 #include <time.h>
 #include <time.h>
 
 
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
 using namespace isc::test;
 using namespace isc::test;
 
 
+namespace {
+
+/// @brief Functor searching for the leases using a specified property.
+///
+/// @tparam BaseType Base type to which the property belongs: @c Lease or
+/// @c Lease6.
+/// @tparam PropertyType A type of the property, e.g. @c uint32_t for IAID.
+/// @tparam MemberPointer A pointer to the member, e.g. @c &Lease6::iaid_.
+template<typename BaseType, typename PropertyType,
+         PropertyType BaseType::*MemberPointer>
+struct getLeasesByPropertyFun {
+
+    /// @brief Returns leases matching the specified condition.
+    ///
+    /// @param config DHCP client configuration structure holding leases.
+    /// @param property A value of the lease property used to search the lease.
+    /// @param equals A flag which indicates if the operator should search for
+    /// the leases which property is equal to the value of @c property parameter
+    /// (if true), or unequal (if false).
+    /// @param [out] leases A vector in which the operator will store leases
+    /// found.
+    void operator()(const Dhcp6Client::Configuration& config,
+                    const PropertyType& property, const bool equals,
+                    std::vector<Lease6>& leases) {
+
+        // Iterate over the leases and match the property with a given lease
+        //field.
+        for (typename std::vector<Lease6>::const_iterator lease =
+                 config.leases_.begin(); lease != config.leases_.end();
+             ++lease) {
+            // Check if fulfils the condition.
+            if ((equals && ((*lease).*MemberPointer) == property) ||
+                (!equals && ((*lease).*MemberPointer) != property)) {
+                // Found the matching lease.
+                leases.push_back(*lease);
+            }
+        }
+    }
+};
+
+}; // end of anonymous namespace
+
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 namespace test {
 namespace test {
@@ -47,8 +92,11 @@ Dhcp6Client::Dhcp6Client() :
     use_oro_(false),
     use_oro_(false),
     use_client_id_(true),
     use_client_id_(true),
     use_rapid_commit_(false),
     use_rapid_commit_(false),
+    address_hint_(),
     prefix_hint_(),
     prefix_hint_(),
-    fqdn_() {
+    fqdn_(),
+    na_iaid_(1234),
+    pd_iaid_(5678) {
 }
 }
 
 
 Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
 Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
@@ -65,8 +113,11 @@ Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
     use_oro_(false),
     use_oro_(false),
     use_client_id_(true),
     use_client_id_(true),
     use_rapid_commit_(false),
     use_rapid_commit_(false),
+    address_hint_(),
     prefix_hint_(),
     prefix_hint_(),
-    fqdn_() {
+    fqdn_(),
+    na_iaid_(1234),
+    pd_iaid_(5678) {
 }
 }
 
 
 void
 void
@@ -79,8 +130,6 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
     // Let's try to get a MAC
     // Let's try to get a MAC
     HWAddrPtr hwaddr = reply->getMAC(HWAddr::HWADDR_SOURCE_ANY);
     HWAddrPtr hwaddr = reply->getMAC(HWAddr::HWADDR_SOURCE_ANY);
 
 
-    // Set the global status code to default: success and not received.
-    config_.resetGlobalStatusCode();
     for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
     for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
         Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt->second);
         Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt->second);
         if (!ia) {
         if (!ia) {
@@ -93,31 +142,27 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
         for (Opts::const_iterator iter_ia_opt = ia_opts.begin();
         for (Opts::const_iterator iter_ia_opt = ia_opts.begin();
              iter_ia_opt != ia_opts.end(); ++iter_ia_opt) {
              iter_ia_opt != ia_opts.end(); ++iter_ia_opt) {
             OptionPtr ia_opt = iter_ia_opt->second;
             OptionPtr ia_opt = iter_ia_opt->second;
-            LeaseInfo lease_info;
+            Lease6 lease;
+            lease.type_ = (ia->getType() == D6O_IA_NA ? Lease::TYPE_NA : Lease::TYPE_PD);
+            lease.iaid_ = ia->getIAID();
+
             switch (ia_opt->getType()) {
             switch (ia_opt->getType()) {
             case D6O_IAADDR:
             case D6O_IAADDR:
                 {
                 {
                     Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
                     Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
                         Option6IAAddr>(ia_opt);
                         Option6IAAddr>(ia_opt);
 
 
-                    if (!iaaddr) {
-                        // There is no address. This IA option may simply
-                        // contain a status code, so let's just reset the
-                        // lease and keep IAID around.
-                        lease_info.lease_ = Lease6();
-                        lease_info.lease_.type_ = Lease::TYPE_NA;
-                        lease_info.lease_.iaid_ = ia->getIAID();
-                        break;
+                    if (iaaddr) {
+                        lease = Lease6(Lease::TYPE_NA,
+                                       iaaddr->getAddress(),
+                                       duid_, ia->getIAID(),
+                                       iaaddr->getPreferred(),
+                                       iaaddr->getValid(),
+                                       ia->getT1(), ia->getT2(), 0,
+                                       hwaddr);
+                        lease.cltt_ = time(NULL);
+                        applyLease(lease);
                     }
                     }
-
-                    lease_info.lease_ = Lease6(Lease::TYPE_NA,
-                                               iaaddr->getAddress(),
-                                               duid_, ia->getIAID(),
-                                               iaaddr->getPreferred(),
-                                               iaaddr->getValid(),
-                                               ia->getT1(), ia->getT2(), 0,
-                                               hwaddr);
-                    lease_info.lease_.cltt_ = time(NULL);
                 }
                 }
                 break;
                 break;
 
 
@@ -125,24 +170,19 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
                 {
                 {
                     Option6IAPrefixPtr iaprefix = boost::dynamic_pointer_cast<
                     Option6IAPrefixPtr iaprefix = boost::dynamic_pointer_cast<
                         Option6IAPrefix>(ia_opt);
                         Option6IAPrefix>(ia_opt);
-                    if (!iaprefix) {
-                        // There is no prefix. This IA option may simply
-                        // contain a status code, so let's just reset the
-                        // lease and keep IAID around.
-                        lease_info.lease_ = Lease6();
-                        lease_info.lease_.type_ = Lease::TYPE_PD;
-                        lease_info.lease_.iaid_ = ia->getIAID();
-                        break;
+
+                    if (iaprefix) {
+                        lease = Lease6(Lease::TYPE_PD,
+                                       iaprefix->getAddress(), duid_,
+                                       ia->getIAID(),
+                                       iaprefix->getPreferred(),
+                                       iaprefix->getValid(),
+                                       ia->getT1(), ia->getT2(), 0,
+                                       hwaddr,
+                                       iaprefix->getLength());
+                        lease.cltt_ = time(NULL);
+                        applyLease(lease);
                     }
                     }
-                    lease_info.lease_ = Lease6(Lease::TYPE_PD,
-                                               iaprefix->getAddress(), duid_,
-                                               ia->getIAID(),
-                                               iaprefix->getPreferred(),
-                                               iaprefix->getValid(),
-                                               ia->getT1(), ia->getT2(), 0,
-                                               hwaddr,
-                                               iaprefix->getLength());
-                    lease_info.lease_.cltt_ = time(NULL);
                 }
                 }
                 break;
                 break;
 
 
@@ -152,8 +192,8 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
                     // code, assume the status code to be 0.
                     // code, assume the status code to be 0.
                     Option6StatusCodePtr status_code = boost::dynamic_pointer_cast<
                     Option6StatusCodePtr status_code = boost::dynamic_pointer_cast<
                         Option6StatusCode>(ia->getOption(D6O_STATUS_CODE));
                         Option6StatusCode>(ia->getOption(D6O_STATUS_CODE));
-                    lease_info.status_code_ =
-                        status_code ? status_code->getStatusCode() : 0;
+                    config_.status_codes_[ia->getIAID()] =
+                        (status_code ? status_code->getStatusCode() : 0);
                 }
                 }
                 break;
                 break;
 
 
@@ -161,7 +201,6 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
                 ; // no-op
                 ; // no-op
             }
             }
 
 
-            applyLease(lease_info);
         }
         }
     }
     }
 
 
@@ -177,29 +216,25 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
 }
 }
 
 
 void
 void
-Dhcp6Client::applyLease(const LeaseInfo& lease_info) {
+Dhcp6Client::applyLease(const Lease6& lease) {
     // Go over existing leases and try to match the one that we have.
     // Go over existing leases and try to match the one that we have.
     for (size_t i = 0; i < config_.leases_.size(); ++i) {
     for (size_t i = 0; i < config_.leases_.size(); ++i) {
-        Lease6 existing_lease = config_.leases_[i].lease_;
+        Lease6 existing_lease = config_.leases_[i];
         // If IAID is matching and there is an actual address assigned
         // If IAID is matching and there is an actual address assigned
         // replace the current lease. The default address is :: if the
         // replace the current lease. The default address is :: if the
         // server hasn't sent the IA option. In this case, there is no
         // server hasn't sent the IA option. In this case, there is no
         // lease assignment so we keep what we have.
         // lease assignment so we keep what we have.
-        if ((existing_lease.iaid_ == lease_info.lease_.iaid_)
-            && (existing_lease.type_ == lease_info.lease_.type_)
-            && (lease_info.lease_.addr_ != asiolink::IOAddress("::"))
-            && (existing_lease.addr_ == lease_info.lease_.addr_)) {
-            config_.leases_[i] = lease_info;
+        if ((existing_lease.iaid_ == lease.iaid_)
+            && (existing_lease.type_ == lease.type_)
+            && (lease.addr_ != asiolink::IOAddress("::"))
+            && (existing_lease.addr_ == lease.addr_)) {
+            config_.leases_[i] = lease;
             return;
             return;
-
-        } else if (lease_info.lease_.addr_ == asiolink::IOAddress("::")) {
-            config_.leases_[i].status_code_ = lease_info.status_code_;
-            return;
-
         }
         }
     }
     }
+
     // It is a new lease. Add it.
     // It is a new lease. Add it.
-    config_.leases_.push_back(lease_info);
+    config_.leases_.push_back(lease);
 }
 }
 
 
 void
 void
@@ -210,17 +245,73 @@ Dhcp6Client::appendFQDN() {
 }
 }
 
 
 void
 void
+Dhcp6Client::appendRequestedIAs(const Pkt6Ptr& query) const {
+    if (use_na_) {
+        conditionallyAppendRequestedIA(query, D6O_IA_NA, na_iaid_);
+    }
+
+    if (use_pd_) {
+        conditionallyAppendRequestedIA(query, D6O_IA_PD, pd_iaid_);
+    }
+}
+
+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_);
+
+    } else if (address_hint_ && (ia_type == D6O_IA_NA)) {
+        requested_ia->addOption(address_hint_);
+    }
+
+    query->addOption(requested_ia);
+}
+
+
+void
 Dhcp6Client::copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest) {
 Dhcp6Client::copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest) {
     typedef OptionCollection Opts;
     typedef OptionCollection Opts;
     // Copy IA_NAs.
     // Copy IA_NAs.
     Opts opts = source->getOptions(D6O_IA_NA);
     Opts opts = source->getOptions(D6O_IA_NA);
     for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
     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.
     // Copy IA_PDs.
     opts = source->getOptions(D6O_IA_PD);
     opts = source->getOptions(D6O_IA_PD);
     for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
     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);
+        }
     }
     }
 }
 }
 
 
@@ -232,6 +323,11 @@ Dhcp6Client::copyIAsFromLeases(const Pkt6Ptr& dest) const {
     for (std::set<uint32_t>::const_iterator iaid = iaids.begin();
     for (std::set<uint32_t>::const_iterator iaid = iaids.begin();
          iaid != iaids.end(); ++iaid) {
          iaid != iaids.end(); ++iaid) {
         std::vector<Lease6> leases = getLeasesByIAID(*iaid);
         std::vector<Lease6> leases = getLeasesByIAID(*iaid);
+        // Only a valid lease should be included. Do not copy a
+        // lease which have been marked by the server as invalid.
+        if (leases[0].valid_lft_ == 0) {
+            continue;
+        }
         Option6IAPtr opt(new Option6IA(leases[0].type_ == Lease::TYPE_NA ?
         Option6IAPtr opt(new Option6IA(leases[0].type_ == Lease::TYPE_NA ?
                                        D6O_IA_NA : D6O_IA_PD, *iaid));
                                        D6O_IA_NA : D6O_IA_PD, *iaid));
         opt->setT1(leases[0].t1_);
         opt->setT1(leases[0].t1_);
@@ -261,9 +357,7 @@ Dhcp6Client::copyIAsFromLeases(const Pkt6Ptr& dest) const {
 
 
 void
 void
 Dhcp6Client::createLease(const Lease6& lease) {
 Dhcp6Client::createLease(const Lease6& lease) {
-    LeaseInfo info;
-    info.lease_ = lease;
-    applyLease(info);
+    applyLease(lease);
 }
 }
 
 
 Pkt6Ptr
 Pkt6Ptr
@@ -298,17 +392,10 @@ Dhcp6Client::doSolicit() {
     if (forced_server_id_) {
     if (forced_server_id_) {
         context_.query_->addOption(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_) {
     if (use_rapid_commit_) {
         context_.query_->addOption(OptionPtr(new Option(Option::V6,
         context_.query_->addOption(OptionPtr(new Option(Option::V6,
                                                         D6O_RAPID_COMMIT)));
                                                         D6O_RAPID_COMMIT)));
@@ -323,6 +410,7 @@ Dhcp6Client::doSolicit() {
     // let's apply received configuration.
     // let's apply received configuration.
     if (use_rapid_commit_ && context_.response_ &&
     if (use_rapid_commit_ && context_.response_ &&
         context_.response_->getType() == DHCPV6_REPLY) {
         context_.response_->getType() == DHCPV6_REPLY) {
+        config_.clear();
         applyRcvdConfiguration(context_.response_);
         applyRcvdConfiguration(context_.response_);
     }
     }
 }
 }
@@ -336,6 +424,7 @@ Dhcp6Client::doRequest() {
         query->addOption(forced_server_id_);
         query->addOption(forced_server_id_);
     }
     }
     copyIAs(context_.response_, query);
     copyIAs(context_.response_, query);
+    appendRequestedIAs(query);
 
 
     // Add Client FQDN if configured.
     // Add Client FQDN if configured.
     appendFQDN();
     appendFQDN();
@@ -348,6 +437,7 @@ Dhcp6Client::doRequest() {
 
 
     // Apply new configuration only if the server has responded.
     // Apply new configuration only if the server has responded.
     if (context_.response_) {
     if (context_.response_) {
+        config_.clear();
         applyRcvdConfiguration(context_.response_);
         applyRcvdConfiguration(context_.response_);
     }
     }
 }
 }
@@ -359,15 +449,15 @@ Dhcp6Client::doInfRequest() {
     // IA_NA, IA_TA and IA_PD options are not allowed in INF-REQUEST,
     // IA_NA, IA_TA and IA_PD options are not allowed in INF-REQUEST,
     // but hey! Let's test it.
     // but hey! Let's test it.
     if (use_na_) {
     if (use_na_) {
-        // Insert IA_NA option with iaid=1234.
+        // Insert IA_NA option.
         context_.query_->addOption(Option6IAPtr(new Option6IA(D6O_IA_NA,
         context_.query_->addOption(Option6IAPtr(new Option6IA(D6O_IA_NA,
-                                                              1234)));
+                                                              na_iaid_)));
     }
     }
 
 
     // IA-PD is also not allowed. So it may be useful in testing, too.
     // IA-PD is also not allowed. So it may be useful in testing, too.
     if (use_pd_) {
     if (use_pd_) {
-        // Insert IA_PD option with iaid=5678
-        Option6IAPtr ia(new Option6IA(D6O_IA_PD, 5678));
+        // Insert IA_PD option.
+        Option6IAPtr ia(new Option6IA(D6O_IA_PD, pd_iaid_));
         if (prefix_hint_) {
         if (prefix_hint_) {
             ia->addOption(prefix_hint_);
             ia->addOption(prefix_hint_);
         }
         }
@@ -384,6 +474,10 @@ Dhcp6Client::doRenew() {
     query->addOption(context_.response_->getOption(D6O_SERVERID));
     query->addOption(context_.response_->getOption(D6O_SERVERID));
     copyIAsFromLeases(query);
     copyIAsFromLeases(query);
 
 
+    // During the Renew the client may request additional bindings per
+    // RFC7550.
+    appendRequestedIAs(query);
+
     // Add Client FQDN if configured.
     // Add Client FQDN if configured.
     appendFQDN();
     appendFQDN();
 
 
@@ -392,6 +486,7 @@ Dhcp6Client::doRenew() {
     context_.response_ = receiveOneMsg();
     context_.response_ = receiveOneMsg();
     // Apply configuration only if the server has responded.
     // Apply configuration only if the server has responded.
     if (context_.response_) {
     if (context_.response_) {
+        config_.clear();
         applyRcvdConfiguration(context_.response_);
         applyRcvdConfiguration(context_.response_);
     }
     }
 }
 }
@@ -401,6 +496,10 @@ Dhcp6Client::doRebind() {
     Pkt6Ptr query = createMsg(DHCPV6_REBIND);
     Pkt6Ptr query = createMsg(DHCPV6_REBIND);
     copyIAsFromLeases(query);
     copyIAsFromLeases(query);
 
 
+    // During the Rebind the client may request additional bindings per
+    // RFC7550.
+    appendRequestedIAs(query);
+
     // Add Client FQDN if configured.
     // Add Client FQDN if configured.
     appendFQDN();
     appendFQDN();
 
 
@@ -409,6 +508,7 @@ Dhcp6Client::doRebind() {
     context_.response_ = receiveOneMsg();
     context_.response_ = receiveOneMsg();
     // Apply configuration only if the server has responded.
     // Apply configuration only if the server has responded.
     if (context_.response_) {
     if (context_.response_) {
+        config_.clear();
         applyRcvdConfiguration(context_.response_);
         applyRcvdConfiguration(context_.response_);
     }
     }
 }
 }
@@ -422,6 +522,7 @@ Dhcp6Client::doConfirm() {
     // Set the global status code to default: success and not received.
     // Set the global status code to default: success and not received.
     config_.resetGlobalStatusCode();
     config_.resetGlobalStatusCode();
     if (context_.response_) {
     if (context_.response_) {
+        config_.resetGlobalStatusCode();
         applyRcvdConfiguration(context_.response_);
         applyRcvdConfiguration(context_.response_);
     }
     }
 }
 }
@@ -430,7 +531,7 @@ void
 Dhcp6Client::fastFwdTime(const uint32_t secs) {
 Dhcp6Client::fastFwdTime(const uint32_t secs) {
     // Iterate over all leases and move their cltt backwards.
     // Iterate over all leases and move their cltt backwards.
     for (size_t i = 0; i < config_.leases_.size(); ++i) {
     for (size_t i = 0; i < config_.leases_.size(); ++i) {
-        config_.leases_[i].lease_.cltt_ -= secs;
+        config_.leases_[i].cltt_ -= secs;
     }
     }
 }
 }
 
 
@@ -466,10 +567,9 @@ Dhcp6Client::getClientId() const {
 std::set<uint32_t>
 std::set<uint32_t>
 Dhcp6Client::getIAIDs() const {
 Dhcp6Client::getIAIDs() const {
     std::set<uint32_t> iaids;
     std::set<uint32_t> iaids;
-    for (std::vector<LeaseInfo>::const_iterator lease_info =
-             config_.leases_.begin(); lease_info != config_.leases_.end();
-         ++lease_info) {
-        iaids.insert(lease_info->lease_.iaid_);
+    for (std::vector<Lease6>::const_iterator lease = config_.leases_.begin();
+         lease != config_.leases_.end(); ++lease) {
+        iaids.insert(lease->iaid_);
     }
     }
     return (iaids);
     return (iaids);
 }
 }
@@ -477,16 +577,56 @@ Dhcp6Client::getIAIDs() const {
 std::vector<Lease6>
 std::vector<Lease6>
 Dhcp6Client::getLeasesByIAID(const uint32_t iaid) const {
 Dhcp6Client::getLeasesByIAID(const uint32_t iaid) const {
     std::vector<Lease6> leases;
     std::vector<Lease6> leases;
-    for (std::vector<LeaseInfo>::const_iterator lease_info =
-             config_.leases_.begin(); lease_info != config_.leases_.end();
-         ++lease_info) {
-        if (lease_info->lease_.iaid_ == iaid) {
-            leases.push_back(lease_info->lease_);
+    getLeasesByProperty<Lease6, uint32_t, &Lease6::iaid_>(iaid, true, leases);
+    return (leases);
+}
+
+template<typename BaseType, typename PropertyType, PropertyType BaseType::*MemberPointer>
+void
+Dhcp6Client::getLeasesByProperty(const PropertyType& property, const bool equals,
+                                 std::vector<Lease6>& leases) const {
+    getLeasesByPropertyFun<BaseType, PropertyType, MemberPointer> fun;
+    fun(config_, property, equals, leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesByType(const Lease6::Type& lease_type) const {
+    std::vector<Lease6> leases;
+    getLeasesByProperty<Lease6, Lease6::Type, &Lease6::type_>(lease_type, true, leases);
+    return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesWithNonZeroLifetime() const {
+    std::vector<Lease6> leases;
+    getLeasesByProperty<Lease, uint32_t, &Lease::valid_lft_>(0, false, leases);
+    return (leases);
+}
+
+std::vector<Lease6>
+Dhcp6Client::getLeasesWithZeroLifetime() const {
+    std::vector<Lease6> leases;
+    getLeasesByProperty<Lease, uint32_t, &Lease::valid_lft_>(0, true, leases);
+    return (leases);
+}
+
+uint16_t
+Dhcp6Client::getStatusCode(const uint32_t iaid) const {
+    std::map<uint32_t, uint16_t>::const_iterator status_code =
+        config_.status_codes_.find(iaid);
+    if (status_code == config_.status_codes_.end()) {
+        if (!getLeasesByIAID(iaid).empty()) {
+            return (STATUS_Success);
         }
         }
+
+    } else {
+        return (status_code->second);
     }
     }
-    return (leases);
+
+    return (0xFFFF);
 }
 }
 
 
+
 void
 void
 Dhcp6Client::setDUID(const std::string& str) {
 Dhcp6Client::setDUID(const std::string& str) {
     DUID d = DUID::fromText(str);
     DUID d = DUID::fromText(str);
@@ -549,6 +689,14 @@ Dhcp6Client::sendMsg(const Pkt6Ptr& msg) {
 
 
 void
 void
 Dhcp6Client::useHint(const uint32_t pref_lft, const uint32_t valid_lft,
 Dhcp6Client::useHint(const uint32_t pref_lft, const uint32_t valid_lft,
+                     const std::string& address) {
+    address_hint_.reset(new Option6IAAddr(D6O_IAADDR,
+                                          asiolink::IOAddress(address),
+                                          pref_lft, valid_lft));
+}
+
+void
+Dhcp6Client::useHint(const uint32_t pref_lft, const uint32_t valid_lft,
                      const uint8_t len, const std::string& prefix) {
                      const uint8_t len, const std::string& prefix) {
     prefix_hint_.reset(new Option6IAPrefix(D6O_IAPREFIX,
     prefix_hint_.reset(new Option6IAPrefix(D6O_IAPREFIX,
                                            asiolink::IOAddress(prefix),
                                            asiolink::IOAddress(prefix),

+ 115 - 10
src/bin/dhcp6/tests/dhcp6_client.h

@@ -70,6 +70,15 @@ public:
         /// @brief Default constructor for the structure.
         /// @brief Default constructor for the structure.
         LeaseInfo() :
         LeaseInfo() :
             lease_(), status_code_(0) { }
             lease_(), status_code_(0) { }
+
+        /// @brief Constructor which sets the lease type.
+        ///
+        /// @param lease_type One of the D6O_IA_NA or D6O_IA_PD.
+        LeaseInfo(const uint16_t lease_type) :
+            lease_(), status_code_(0) {
+            lease_.type_ = lease_type == D6O_IA_NA ? Lease::TYPE_NA :
+                Lease::TYPE_PD;
+        }
     };
     };
 
 
     /// @brief Holds the current client configuration obtained from the
     /// @brief Holds the current client configuration obtained from the
@@ -81,7 +90,10 @@ public:
     /// server-id and client-id.
     /// server-id and client-id.
     struct Configuration {
     struct Configuration {
         /// @brief List of received leases
         /// @brief List of received leases
-        std::vector<LeaseInfo> leases_;
+        std::vector<Lease6> leases_;
+
+        /// @brief A map of IAID, status code tuples.
+        std::map<uint32_t, uint16_t> status_codes_;
 
 
         /// @brief List of received options
         /// @brief List of received options
         OptionCollection options_;
         OptionCollection options_;
@@ -101,6 +113,7 @@ public:
         /// @brief Clears configuration.
         /// @brief Clears configuration.
         void clear() {
         void clear() {
             leases_.clear();
             leases_.clear();
+            status_codes_.clear();
             resetGlobalStatusCode();
             resetGlobalStatusCode();
         }
         }
 
 
@@ -302,7 +315,7 @@ public:
     /// @param at Index of the lease held by the client.
     /// @param at Index of the lease held by the client.
     /// @return A lease at the specified index.
     /// @return A lease at the specified index.
     Lease6 getLease(const size_t at) const {
     Lease6 getLease(const size_t at) const {
-        return (config_.leases_[at].lease_);
+        return (config_.leases_[at]);
     }
     }
 
 
     /// @brief Returns collection of leases for specified IAID.
     /// @brief Returns collection of leases for specified IAID.
@@ -312,13 +325,26 @@ public:
     /// @return Vector containing leases for the IAID.
     /// @return Vector containing leases for the IAID.
     std::vector<Lease6> getLeasesByIAID(const uint32_t iaid) const;
     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 leases with non-zero lifetimes.
+    std::vector<Lease6> getLeasesWithNonZeroLifetime() const;
+
+    /// @brief Returns leases with zero lifetimes.
+    std::vector<Lease6> getLeasesWithZeroLifetime() const;
+
     /// @brief Returns the value of the global status code for the last
     /// @brief Returns the value of the global status code for the last
     /// transaction.
     /// transaction.
     uint16_t getStatusCode() const {
     uint16_t getStatusCode() const {
         return (config_.status_code_);
         return (config_.status_code_);
     }
     }
 
 
-    /// @brief Returns status code set by the server for the lease.
+    /// @brief Returns status code set by the server for the IAID.
     ///
     ///
     /// @warning This method doesn't check if the specified index is out of
     /// @warning This method doesn't check if the specified index is out of
     /// range. The caller is responsible for using a correct offset by
     /// range. The caller is responsible for using a correct offset by
@@ -326,9 +352,7 @@ public:
     ///
     ///
     /// @param at Index of the lease held by the client.
     /// @param at Index of the lease held by the client.
     /// @return A status code for the lease at the specified index.
     /// @return A status code for the lease at the specified index.
-    uint16_t getStatusCode(const size_t at) const {
-        return (config_.leases_[at].status_code_);
-    }
+    uint16_t getStatusCode(const uint32_t iaid) const;
 
 
     /// @brief Returns number of acquired leases.
     /// @brief Returns number of acquired leases.
     size_t getLeaseNum() const {
     size_t getLeaseNum() const {
@@ -391,6 +415,14 @@ public:
         iface_name_ = iface_name;
         iface_name_ = iface_name;
     }
     }
 
 
+    /// @brief Set an address hint to be sent to a server.
+    ///
+    /// @param pref_lft Preferred lifetime.
+    /// @param valid_lft Valid lifetime.
+    /// @param address Address for which the client has a preference.
+    void useHint(const uint32_t pref_lft, const uint32_t valid_lft,
+                 const std::string& address);
+
     /// @brief Sets a prefix hint to be sent to a server.
     /// @brief Sets a prefix hint to be sent to a server.
     ///
     ///
     /// @param pref_lft Preferred lifetime.
     /// @param pref_lft Preferred lifetime.
@@ -405,10 +437,32 @@ public:
     /// This function configures the client to place IA_NA options in its
     /// This function configures the client to place IA_NA options in its
     /// Solicit messages to request the IPv6 address assignment.
     /// Solicit messages to request the IPv6 address assignment.
     ///
     ///
+    /// @param iaid IAID to be used in the IA_NA.
+    void useNA(const uint32_t iaid) {
+        useNA(true, iaid);
+    }
+
+    /// @brief Place IA_NA options to request address assignment.
+    ///
+    /// This function configures the client to place IA_NA options in its
+    /// Solicit messages to request the IPv6 address assignment.
+    ///
     /// @param use Parameter which 'true' value indicates that client should
     /// @param use Parameter which 'true' value indicates that client should
     /// request address assignment.
     /// request address assignment.
-    void useNA(const bool use = true) {
+    /// @param iaid IAID to be used in the IA_NA.
+    void useNA(const bool use = true, const uint32_t iaid = 1234) {
         use_na_ = use;
         use_na_ = use;
+        na_iaid_ = iaid;
+    }
+
+    /// @brief Place IA_PD options to request address assignment.
+    ///
+    /// This function configures the client to place IA_NA options in its
+    /// Solicit messages to request the IPv6 address assignment.
+    ///
+    /// @param iaid IAID to be used in the IA_PD.
+    void usePD(const uint32_t iaid) {
+        usePD(true, iaid);
     }
     }
 
 
     /// @brief Place IA_PD options to request prefix assignment.
     /// @brief Place IA_PD options to request prefix assignment.
@@ -418,8 +472,10 @@ public:
     ///
     ///
     /// @param use Parameter which 'true' value indicates that client should
     /// @param use Parameter which 'true' value indicates that client should
     /// request prefix assignment.
     /// request prefix assignment.
-    void usePD(const bool use = true) {
+    /// @param iaid IAID to be used in the IA_NA.
+    void usePD(const bool use = true, const uint32_t iaid = 5678) {
         use_pd_ = use;
         use_pd_ = use;
+        pd_iaid_ = iaid;
     }
     }
 
 
     /// @brief Simulate sending messages through a relay.
     /// @brief Simulate sending messages through a relay.
@@ -527,14 +583,37 @@ private:
     /// each individual lease.
     /// each individual lease.
     ///
     ///
     /// @param lease_info Structure holding new lease information.
     /// @param lease_info Structure holding new lease information.
-    void applyLease(const LeaseInfo& lease_info);
+    void applyLease(const Lease6& lease);
 
 
-    /// @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
     /// This method checks if @c fqdn_ is specified and includes it in
     /// the client's message.
     /// the client's message.
     void appendFQDN();
     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.
     /// @brief Copy IA options from one message to another.
     ///
     ///
     /// This method copies IA_NA and IA_PD options from one message to another.
     /// This method copies IA_NA and IA_PD options from one message to another.
@@ -573,6 +652,24 @@ private:
     /// @return Object encapsulating a DUID.
     /// @return Object encapsulating a DUID.
     DuidPtr generateDUID(DUID::DUIDType duid_type) const;
     DuidPtr generateDUID(DUID::DUIDType duid_type) const;
 
 
+    /// @brief Returns client's leases which match the specified condition.
+    ///
+    /// @param property A value of the lease property used to search the lease.
+    /// @param equals A flag which indicates if the operator should search
+    /// for the leases which property is equal to the value of @c property
+    /// parameter (if true), or unequal (if false).
+    /// @param [out] leases A vector in which the operator will store leases
+    /// found.
+    ///
+    /// @tparam BaseType Base type to which the property belongs: @c Lease or
+    /// @c Lease6.
+    /// @tparam PropertyType A type of the property, e.g. @c uint32_t for IAID.
+    /// @tparam MemberPointer A pointer to the member, e.g. @c &Lease6::iaid_.
+    template<typename BaseType, typename PropertyType,
+             PropertyType BaseType::*MemberPointer>
+    void getLeasesByProperty(const PropertyType& property, const bool equals,
+                             std::vector<Lease6>& leases) const;
+
     /// @brief Simulates reception of the message from the server.
     /// @brief Simulates reception of the message from the server.
     ///
     ///
     /// @return Received message.
     /// @return Received message.
@@ -616,6 +713,9 @@ private:
     bool use_client_id_;
     bool use_client_id_;
     bool use_rapid_commit_;
     bool use_rapid_commit_;
 
 
+    /// @brief Pointer to the option holding an address hint.
+    Option6IAAddrPtr address_hint_;
+
     /// @brief Pointer to the option holding a prefix hint.
     /// @brief Pointer to the option holding a prefix hint.
     Option6IAPrefixPtr prefix_hint_;
     Option6IAPrefixPtr prefix_hint_;
 
 
@@ -630,6 +730,11 @@ private:
 
 
     /// @brief FQDN requested by the client.
     /// @brief FQDN requested by the client.
     Option6ClientFqdnPtr fqdn_;
     Option6ClientFqdnPtr fqdn_;
+
+    /// @bref IAID used by the client when requesting address assignment.
+    uint32_t na_iaid_;
+    /// @brief IAID used by the client when requesting prefix delegation.
+    uint32_t pd_iaid_;
 };
 };
 
 
 } // end of namespace isc::dhcp::test
 } // end of namespace isc::dhcp::test

+ 1 - 1
src/bin/dhcp6/tests/dhcp6_message_test.cc

@@ -81,7 +81,7 @@ Dhcpv6MessageTest::requestLease(const std::string& config,
     Lease6Ptr lease_server = checkLease(lease_client);
     Lease6Ptr lease_server = checkLease(lease_client);
     ASSERT_TRUE(lease_server);
     ASSERT_TRUE(lease_server);
     // And that status code was OK.
     // And that status code was OK.
-    ASSERT_EQ(STATUS_Success, client.getStatusCode(0));
+    ASSERT_EQ(STATUS_Success, client.getStatusCode(lease_client.iaid_));
 }
 }
 
 
 }
 }

+ 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,
     void requestLease(const std::string& config, const int subnets_num,
                       Dhcp6Client& client);
                       Dhcp6Client& client);
 
 
+
 protected:
 protected:
 
 
     /// @brief Interface Manager's fake configuration control.
     /// @brief Interface Manager's fake configuration control.

+ 0 - 17
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -939,23 +939,6 @@ TEST_F(Dhcpv6SrvTest, RenewSomeoneElesesLease) {
     testRenewSomeoneElsesLease(Lease::TYPE_NA, IOAddress("2001:db8::1"));
     testRenewSomeoneElsesLease(Lease::TYPE_NA, IOAddress("2001:db8::1"));
 }
 }
 
 
-// This test verifies that incoming (invalid) RENEW with a prefix
-// can be handled properly.
-//
-// This test checks 3 scenarios:
-// 1. there is no such lease at all
-// 2. there is such a lease, but it is assigned to a different IAID
-// 3. there is such a lease, but it belongs to a different client
-//
-// expected:
-// - returned REPLY message has copy of client-id
-// - returned REPLY message has server-id
-// - returned REPLY message has IA_PD that includes STATUS-CODE
-// - No lease in LeaseMgr
-TEST_F(Dhcpv6SrvTest, pdRenewReject) {
-    testRenewReject(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"));
-}
-
 // This test verifies that incoming (positive) RELEASE with address can be
 // This test verifies that incoming (positive) RELEASE with address can be
 // handled properly, that a REPLY is generated, that the response has status
 // handled properly, that a REPLY is generated, that the response has status
 // code and that the lease is indeed removed from the database.
 // code and that the lease is indeed removed from the database.

+ 0 - 140
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -421,146 +421,6 @@ Dhcpv6SrvTest::testRenewSomeoneElsesLease(Lease::Type type, const IOAddress& add
 }
 }
 
 
 void
 void
-Dhcpv6SrvTest::testRenewReject(Lease::Type type, const IOAddress& addr) {
-
-    NakedDhcpv6Srv srv(0);
-
-    const uint32_t transid = 1234;
-    const uint32_t valid_iaid = 234;
-    const uint32_t bogus_iaid = 456;
-
-    uint32_t code;
-    uint8_t prefix_len;
-    if (type == Lease::TYPE_NA) {
-        code = D6O_IA_NA;
-        prefix_len = 128;
-    } else if (type == Lease::TYPE_PD) {
-        code = D6O_IA_PD;
-        prefix_len = pd_pool_->getLength();
-    } else {
-        isc_throw(BadValue, "Invalid lease type");
-    }
-
-    // Quick sanity check that the address we're about to use is ok
-    ASSERT_TRUE(subnet_->inPool(type, addr));
-
-    // GenerateClientId() also sets duid_
-    OptionPtr clientid = generateClientId();
-
-    // Check that the lease is NOT in the database
-    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(type, addr);
-    ASSERT_FALSE(l);
-
-    // Let's create a RENEW
-    Pkt6Ptr req = createMessage(DHCPV6_RENEW, type, IOAddress(addr), prefix_len,
-                                bogus_iaid);
-    req->addOption(clientid);
-    req->addOption(srv.getServerID());
-
-    // Case 1: No lease known to server
-
-    // Pass it to the server and hope for a REPLY
-    Pkt6Ptr reply = srv.processRenew(req);
-
-    // Check if we get response at all
-    checkResponse(reply, DHCPV6_REPLY, transid);
-    OptionPtr tmp = reply->getOption(code);
-    ASSERT_TRUE(tmp);
-
-    // Check that IA_?? was returned and that there's proper status code
-    boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
-    ASSERT_TRUE(ia);
-
-    if (type == Lease::TYPE_PD) {
-        // For PD, the check is easy. NoBinding and no prefixes
-        checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
-    } else {
-        // For IA, it's more involved, as the server will reject the address
-        // (and send it with 0 lifetimes), but will also assign a new address.
-
-        // First, check that the requested address is rejected.
-        bool found = false;
-
-        dhcp::OptionCollection options = ia->getOptions();
-        for (isc::dhcp::OptionCollection::iterator opt = options.begin();
-             opt != options.end(); ++opt) {
-            if (opt->second->getType() != D6O_IAADDR) {
-                continue;
-            }
-
-            dhcp::Option6IAAddrPtr opt_addr =
-                boost::dynamic_pointer_cast<isc::dhcp::Option6IAAddr>(opt->second);
-            ASSERT_TRUE(opt_addr);
-
-            if (opt_addr->getAddress() != addr) {
-                // There may be other addresses, e.g. the newly assigned one
-                continue;
-            }
-
-            found = true;
-            EXPECT_NE(0, opt_addr->getPreferred());
-            EXPECT_NE(0, opt_addr->getValid());
-        }
-
-        EXPECT_TRUE(found) << "Expected address " << addr.toText()
-                           << " with zero lifetimes not found.";
-    }
-
-    // Check that there is no lease added
-    l = LeaseMgrFactory::instance().getLease6(type, addr);
-    ASSERT_FALSE(l);
-
-    // CASE 2: Lease is known and belongs to this client, but to a different IAID
-
-    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
-    // value on purpose. They should be updated during RENEW.
-    Lease6Ptr lease(new Lease6(type, addr, duid_, valid_iaid,
-                               501, 502, 503, 504, subnet_->getID(),
-                               HWAddrPtr(), prefix_len));
-    lease->cltt_ = 123; // Let's use it as an indicator that the lease
-                        // was NOT updated.
-    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
-
-    // Pass it to the server and hope for a REPLY
-    reply = srv.processRenew(req);
-    checkResponse(reply, DHCPV6_REPLY, transid);
-    tmp = reply->getOption(code);
-    ASSERT_TRUE(tmp);
-
-    // Check that IA_?? was returned and that there's proper status code
-    ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
-    ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
-
-    // There is a iaid mis-match, so server should respond that there is
-    // no such address to renew.
-
-    // CASE 3: Lease belongs to a client with different client-id
-    req->delOption(D6O_CLIENTID);
-    ia = boost::dynamic_pointer_cast<Option6IA>(req->getOption(code));
-    ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
-    req->addOption(generateClientId(13)); // generate different DUID
-                                          // (with length 13)
-
-    reply = srv.processRenew(req);
-    checkResponse(reply, DHCPV6_REPLY, transid);
-    tmp = reply->getOption(code);
-    ASSERT_TRUE(tmp);
-
-    // Check that IA_?? was returned and that there's proper status code
-    ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
-    ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
-
-    lease = LeaseMgrFactory::instance().getLease6(type, addr);
-    ASSERT_TRUE(lease);
-    // Verify that the lease was not updated.
-    EXPECT_EQ(123, lease->cltt_);
-
-    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
-}
-
-void
 Dhcpv6SrvTest::testReleaseBasic(Lease::Type type, const IOAddress& existing,
 Dhcpv6SrvTest::testReleaseBasic(Lease::Type type, const IOAddress& existing,
                                 const IOAddress& release_addr) {
                                 const IOAddress& release_addr) {
     NakedDhcpv6Srv srv(0);
     NakedDhcpv6Srv srv(0);

+ 0 - 13
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -468,19 +468,6 @@ public:
     testRenewSomeoneElsesLease(isc::dhcp::Lease::Type type,
     testRenewSomeoneElsesLease(isc::dhcp::Lease::Type type,
                                const asiolink::IOAddress& addr);
                                const asiolink::IOAddress& addr);
 
 
-    /// @brief Performs negative RENEW test
-    ///
-    /// See renewReject and pdRenewReject tests for detailed explanation.
-    /// In essence the test attempts to perform couple failed RENEW scenarios.
-    ///
-    /// This method does not throw, but uses gtest macros to signify failures.
-    ///
-    /// @param type type (TYPE_NA or TYPE_PD)
-    /// @param addr address being sent in RENEW
-    void
-    testRenewReject(isc::dhcp::Lease::Type type,
-                    const isc::asiolink::IOAddress& addr);
-
     /// @brief Performs basic (positive) RELEASE test
     /// @brief Performs basic (positive) RELEASE test
     ///
     ///
     /// See releaseBasic and pdReleaseBasic tests for detailed explanation.
     /// See releaseBasic and pdReleaseBasic tests for detailed explanation.

+ 267 - 116
src/bin/dhcp6/tests/rebind_unittest.cc

@@ -32,7 +32,7 @@ namespace {
 ///
 ///
 /// - Configuration 0:
 /// - Configuration 0:
 ///   - only addresses (no prefixes)
 ///   - only addresses (no prefixes)
-///   - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::64
+///   - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::/64
 ///   - 1 subnet for eth0 and 1 subnet for eth1
 ///   - 1 subnet for eth0 and 1 subnet for eth1
 ///
 ///
 /// - Configuration 1:
 /// - Configuration 1:
@@ -57,11 +57,17 @@ namespace {
 ///   - this specific configuration is used by tests which don't use relays
 ///   - this specific configuration is used by tests which don't use relays
 ///
 ///
 /// - Configuration 5:
 /// - Configuration 5:
-///   - similar to Configuration 5 but with different subnets
+///   - similar to Configuration 4 but with different subnets
 ///   - 2 subnets: 2001:db8:3::/40 and 2001:db8:4::/40
 ///   - 2 subnets: 2001:db8:3::/40 and 2001:db8:4::/40
 ///   - 2 prefix pools: 2001:db8:3::/72 and 2001:db8:4::/72
 ///   - 2 prefix pools: 2001:db8:3::/72 and 2001:db8:4::/72
 ///   - delegated length /80
 ///   - delegated length /80
 ///   - this specific configuration is used by tests which don't use relays
 ///   - this specific configuration is used by tests which don't use relays
+///
+/// - Configuration 6:
+///   - addresses and prefixes
+///   - address pool: 2001:db8:1::/64
+///   - prefix pool: 3000::/72
+///
 const char* REBIND_CONFIGS[] = {
 const char* REBIND_CONFIGS[] = {
 // Configuration 0
 // Configuration 0
     "{ \"interfaces-config\": {"
     "{ \"interfaces-config\": {"
@@ -205,6 +211,25 @@ const char* REBIND_CONFIGS[] = {
         " } ],"
         " } ],"
         "\"valid-lifetime\": 4000 }",
         "\"valid-lifetime\": 4000 }",
 
 
+// Configuration 6
+    "{ \"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 Rebind.
 /// @brief Test fixture class for testing Rebind.
@@ -246,8 +271,10 @@ TEST_F(RebindTest, directClient) {
     EXPECT_TRUE(lease_server2);
     EXPECT_TRUE(lease_server2);
 }
 }
 
 
-// Test that server doesn't extend the lease when the configuration has changed
-// such that the existing subnet is replaced with a different subnet.
+// Test that server allocates a lease from a new subnet when the server
+// is reconfigured such that the previous subnet is replaced with a
+// new subnet. The client should get the new lease and an old lease
+// with zero lifetimes in the Reply.
 TEST_F(RebindTest, directClientChangingSubnet) {
 TEST_F(RebindTest, directClientChangingSubnet) {
     Dhcp6Client client;
     Dhcp6Client client;
     // Configure client to request IA_NA.
     // Configure client to request IA_NA.
@@ -265,29 +292,26 @@ TEST_F(RebindTest, directClientChangingSubnet) {
 
 
     ASSERT_NO_THROW(client.doRebind());
     ASSERT_NO_THROW(client.doRebind());
 
 
-    // We are expecting that the server didn't extend the lease because
-    // the address that client is using doesn't match the new subnet.
-    // But, the client still has an old lease.
-    ASSERT_EQ(1, client.getLeaseNum());
-    Lease6 lease_client2 = client.getLease(0);
+    // We are expecting that the server has allocated a lease from the new
+    // subnet and sent zero lifetimes for a previous lease.
+
+    std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+    ASSERT_EQ(1, old_leases.size());
+    EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
 
 
-    // The current lease should be exactly the same as old lease,
-    // because server shouldn't have extended.
-    EXPECT_TRUE(lease_client.addr_ == lease_client2.addr_);
-    EXPECT_EQ(0, lease_client2.preferred_lft_);
-    EXPECT_EQ(0, lease_client2.valid_lft_);
+    std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+    ASSERT_EQ(1, new_leases.size());
 
 
     // Make sure, that the lease that client has, is matching the lease
     // Make sure, that the lease that client has, is matching the lease
     // in the lease database.
     // in the lease database.
-    Lease6Ptr lease_server2 = checkLease(lease_client2);
+    Lease6Ptr lease_server2 = checkLease(new_leases[0]);
     EXPECT_TRUE(lease_server2);
     EXPECT_TRUE(lease_server2);
-    // Client should have received NoBinding status code.
-    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
-
+    // Client should have received Success status code.
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
 }
 }
 
 
-// Check that the server doesn't extend the lease for the client when the
-// client sends IAID which doesn't belong to the lease that client has.
+// Check that the server allocates a new lease when the client sends IA_NA
+// with a new IAID.
 TEST_F(RebindTest, directClientChangingIAID) {
 TEST_F(RebindTest, directClientChangingIAID) {
     Dhcp6Client client;
     Dhcp6Client client;
     // Configure client to request IA_NA.
     // Configure client to request IA_NA.
@@ -298,23 +322,29 @@ TEST_F(RebindTest, directClientChangingIAID) {
     Lease6 lease_client = client.getLease(0);
     Lease6 lease_client = client.getLease(0);
     // Modify the IAID of the lease record that client stores. By adding
     // Modify the IAID of the lease record that client stores. By adding
     // one to IAID we guarantee that the IAID will change.
     // one to IAID we guarantee that the IAID will change.
-    ++client.config_.leases_[0].lease_.iaid_;
-    // Try to Rebind. Note that client will use a different IAID (which
-    // is not matching IAID that server retains for the client). Server
-    // should not find the lease that client is trying to extend and
-    // should return NoBinding.
+    client.config_.leases_[0].iaid_ = 1235;
+    client.useNA(true, 1235);
+
+    // Try to Rebind. The server should allocate new lease for this IAID.
     ASSERT_NO_THROW(client.doRebind());
     ASSERT_NO_THROW(client.doRebind());
-    // The lease obtained in 4-way exchange should not change after the Rebind
-    // attempt.
-    Lease6Ptr lease_server2 = checkLease(lease_client);
-    EXPECT_TRUE(lease_server2);
-    // The Status code returned to the client, should be NoBinding.
-    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
 
 
+    // The old lease should be returned with 0 lifetimes.
+    std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+    ASSERT_EQ(1, old_leases.size());
+    EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
+
+    // The new lease should be allocated.
+    std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+    ASSERT_EQ(1, new_leases.size());
+
+    Lease6Ptr lease_server2 = checkLease(new_leases[0]);
+    EXPECT_TRUE(lease_server2);
+    // The Status code returned to the client, should be Success.
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(1235));
 }
 }
 
 
-// Check that server sends NoBinding when the lease has been lost from
-// the database and client is trying to Rebind it.
+// Check that the server allocates a requested lease for the client when
+// this lease has been lost from the database.
 TEST_F(RebindTest, directClientLostLease) {
 TEST_F(RebindTest, directClientLostLease) {
     Dhcp6Client client;
     Dhcp6Client client;
     // Configure client to request IA_NA.
     // Configure client to request IA_NA.
@@ -326,11 +356,15 @@ TEST_F(RebindTest, directClientLostLease) {
     // The lease has been acquired. Now, let's explicitly remove it from the
     // The lease has been acquired. Now, let's explicitly remove it from the
     // lease database.
     // lease database.
     LeaseMgrFactory::instance().deleteLease(lease_client.addr_);
     LeaseMgrFactory::instance().deleteLease(lease_client.addr_);
-    // An attempt to Rebind should fail. The lease should not be found by
-    // the server and the server should return NoBinding status code.
+    // Send Rebind.
     ASSERT_NO_THROW(client.doRebind());
     ASSERT_NO_THROW(client.doRebind());
-    ASSERT_EQ(1, client.getLeaseNum());
-    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
+
+    // The server should re-allocate this lease to the client.
+    std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+    ASSERT_EQ(1, new_leases.size());
+    EXPECT_EQ(lease_client.addr_, new_leases[0].addr_);
+    // Status code should be Success.
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
 }
 }
 
 
 /// @todo Extend tests for direct client changing address.
 /// @todo Extend tests for direct client changing address.
@@ -392,18 +426,9 @@ TEST_F(RebindTest, relayedClientChangingSubnet) {
     ASSERT_NO_THROW(client.doRebind());
     ASSERT_NO_THROW(client.doRebind());
     // We are expecting that the server didn't extend the lease because
     // We are expecting that the server didn't extend the lease because
     // the address that client is using doesn't match the new subnet.
     // the address that client is using doesn't match the new subnet.
-    // But, the client still has an old lease.
-    ASSERT_EQ(1, client.getLeaseNum());
-    Lease6 lease_client2 = client.getLease(0);
-    // The current lease should be exactly the same as old lease,
-    // because server shouldn't have extended.
-    EXPECT_TRUE(lease_client == lease_client2);
-    // Make sure, that the lease that client has, is matching the lease
-    // in the lease database.
-    Lease6Ptr lease_server2 = checkLease(lease_client2);
-    EXPECT_TRUE(lease_server2);
+    ASSERT_EQ(0, client.getLeaseNum());
     // Client should have received NoBinding status code.
     // Client should have received NoBinding status code.
-    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
+    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(1234));
 
 
 }
 }
 
 
@@ -421,25 +446,32 @@ TEST_F(RebindTest, relayedClientChangingIAID) {
     ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[2], 2, client));
     ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[2], 2, client));
     // Keep the client's lease for future reference.
     // Keep the client's lease for future reference.
     Lease6 lease_client = client.getLease(0);
     Lease6 lease_client = client.getLease(0);
+
     // Modify the IAID of the lease record that client stores. By adding
     // Modify the IAID of the lease record that client stores. By adding
     // one to IAID we guarantee that the IAID will change.
     // one to IAID we guarantee that the IAID will change.
-    ++client.config_.leases_[0].lease_.iaid_;
-    // Try to Rebind. Note that client will use a different IAID (which
-    // is not matching IAID that server retains for the client). Server
-    // should not find the lease that client is trying to extend and
-    // should return NoBinding.
+    client.config_.leases_[0].iaid_ = 1235;
+    client.useNA(true, 1235);
+
+    // Try to Rebind. The server should allocate new lease for this IAID.
     ASSERT_NO_THROW(client.doRebind());
     ASSERT_NO_THROW(client.doRebind());
-    // The lease obtained in 4-way exchange should not change after the Rebind
-    // attempt.
-    Lease6Ptr lease_server2 = checkLease(lease_client);
-    EXPECT_TRUE(lease_server2);
-    // The Status code returned to the client, should be NoBinding.
-    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
 
 
+    // The old lease should be returned with 0 lifetimes.
+    std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+    ASSERT_EQ(1, old_leases.size());
+    EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
+
+    // The new lease should be allocated.
+    std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+    ASSERT_EQ(1, new_leases.size());
+
+    Lease6Ptr lease_server2 = checkLease(new_leases[0]);
+    EXPECT_TRUE(lease_server2);
+    // The Status code returned to the client, should be Success.
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(1235));
 }
 }
 
 
-// Check that the relayed client receives NoBinding when the lease that he
-// is Rebinding has been lost from the database.
+// Check that the server allocates a requested lease for the client when
+// this lease has been lost from the database.
 TEST_F(RebindTest, relayedClientLostLease) {
 TEST_F(RebindTest, relayedClientLostLease) {
     Dhcp6Client client;
     Dhcp6Client client;
     // Configure client to request IA_NA.
     // Configure client to request IA_NA.
@@ -455,11 +487,16 @@ TEST_F(RebindTest, relayedClientLostLease) {
     // The lease has been acquired. Now, let's explicitly remove it from the
     // The lease has been acquired. Now, let's explicitly remove it from the
     // lease database.
     // lease database.
     LeaseMgrFactory::instance().deleteLease(lease_client.addr_);
     LeaseMgrFactory::instance().deleteLease(lease_client.addr_);
-    // An attempt to Rebind should fail. The lease should not be found by
-    // the server and the server should return NoBinding status code.
+
+    // Send Rebind.
     ASSERT_NO_THROW(client.doRebind());
     ASSERT_NO_THROW(client.doRebind());
-    ASSERT_EQ(1, client.getLeaseNum());
-    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
+
+    // The server should re-allocate this lease to the client.
+    std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+    ASSERT_EQ(1, new_leases.size());
+    EXPECT_EQ(lease_client.addr_, new_leases[0].addr_);
+    // Status code should be Success.
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
 }
 }
 
 
 // Check that relayed client receives the IA with lifetimes of 0, when
 // Check that relayed client receives the IA with lifetimes of 0, when
@@ -475,7 +512,7 @@ TEST_F(RebindTest, relayedClientChangingAddress) {
     // Modify the address of the lease record that client stores. The server
     // Modify the address of the lease record that client stores. The server
     // should check that the address is invalid (hasn't been allocated for
     // should check that the address is invalid (hasn't been allocated for
     // the particular IAID).
     // the particular IAID).
-    client.config_.leases_[0].lease_.addr_ = IOAddress("3000::100");
+    client.config_.leases_[0].addr_ = IOAddress("3000::100");
     // Try to Rebind. The client will use correct IAID but will specify a
     // Try to Rebind. The client will use correct IAID but will specify a
     // wrong address. The server will discover that the client has a binding
     // wrong address. The server will discover that the client has a binding
     // but the address will not match.
     // but the address will not match.
@@ -543,9 +580,10 @@ TEST_F(RebindTest, directClientPD) {
     EXPECT_TRUE(lease_server2);
     EXPECT_TRUE(lease_server2);
 }
 }
 
 
-// Check that the prefix lifetime is not extended for the client in case
-// the configuration has been changed such, that the subnet he is using
-// doesn't exist anymore.
+// Test that server allocates a lease from a new subnet when the server
+// is reconfigured such that the previous subnet is replaced with a
+// new subnet. The client should get the new lease and an old lease
+// with zero lifetimes in the Reply.
 TEST_F(RebindTest, directClientPDChangingSubnet) {
 TEST_F(RebindTest, directClientPDChangingSubnet) {
     Dhcp6Client client;
     Dhcp6Client client;
     // Configure client to request IA_PD.
     // Configure client to request IA_PD.
@@ -558,31 +596,31 @@ TEST_F(RebindTest, directClientPDChangingSubnet) {
     // client's interface. Note that there will also be a new subnet
     // client's interface. Note that there will also be a new subnet
     // id assigned to the subnet on this interface.
     // id assigned to the subnet on this interface.
     configure(REBIND_CONFIGS[5], *client.getServer());
     configure(REBIND_CONFIGS[5], *client.getServer());
-    // Try to rebind, using the address that the client had acquired using
+
+    // Try to rebind, using the prefix that the client had acquired using
     // previous server configuration.
     // previous server configuration.
-    ASSERT_NO_THROW(client.doRebind());
-    // Make sure that the server has discarded client's message. In such case,
-    // the message sent back to the client should be NULL.
-    EXPECT_FALSE(client.getContext().response_)
-        << "The server responded to the Rebind message, while it should have"
-        " discarded it because there is no binding for the client.";
-    // We are expecting that the server didn't extend the lease because
-    // the address that client is using doesn't match the new subnet.
-    // But, the client still has an old lease.
-    ASSERT_EQ(1, client.getLeaseNum());
-    Lease6 lease_client2 = client.getLease(0);
-    // The current lease should be exactly the same as old lease,
-    // because server shouldn't have extended.
-    EXPECT_TRUE(lease_client == lease_client2);
+    client.doRebind();
+
+    // We are expecting that the server has allocated a lease from the new
+    // subnet and sent zero lifetimes for a previous lease.
+
+    std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+    ASSERT_EQ(1, old_leases.size());
+    EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
+
+    std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+    ASSERT_EQ(1, new_leases.size());
+
     // Make sure, that the lease that client has, is matching the lease
     // Make sure, that the lease that client has, is matching the lease
     // in the lease database.
     // in the lease database.
-    Lease6Ptr lease_server2 = checkLease(lease_client2);
+    Lease6Ptr lease_server2 = checkLease(new_leases[0]);
     EXPECT_TRUE(lease_server2);
     EXPECT_TRUE(lease_server2);
+    // Client should have received Success status code.
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(5678));
 }
 }
 
 
-// Check that the prefix lifetime is not extended for the client when the
-// IAID used in the Rebind is not matching the one recorded by the server
-// for the particular client.
+// Check that the server allocates a new lease when the client sends IA_PD
+// with a new IAID.
 TEST_F(RebindTest, directClientPDChangingIAID) {
 TEST_F(RebindTest, directClientPDChangingIAID) {
     Dhcp6Client client;
     Dhcp6Client client;
     // Configure client to request IA_PD.
     // Configure client to request IA_PD.
@@ -591,24 +629,28 @@ TEST_F(RebindTest, directClientPDChangingIAID) {
     ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[4], 2, client));
     ASSERT_NO_FATAL_FAILURE(requestLease(REBIND_CONFIGS[4], 2, client));
     // Keep the client's lease for future reference.
     // Keep the client's lease for future reference.
     Lease6 lease_client = client.getLease(0);
     Lease6 lease_client = client.getLease(0);
+
     // Modify the IAID of the lease record that client stores. By adding
     // Modify the IAID of the lease record that client stores. By adding
     // one to IAID we guarantee that the IAID will change.
     // one to IAID we guarantee that the IAID will change.
-    ++client.config_.leases_[0].lease_.iaid_;
-    // Try to Rebind. Note that client will use a different IAID (which
-    // is not matching IAID that server retains for the client). This is
-    // a condition described in RFC3633, section 12.2 as the server finds
-    // no binding for the client. It is an indication that some other
-    // server has probably allocated the lease for the client. Hence, our
-    // server should discard the message.
+    client.config_.leases_[0].iaid_ = 5679;
+    client.usePD(true, 5679);
+
+    // Try to Rebind. The server should allocate new lease for this IAID.
     ASSERT_NO_THROW(client.doRebind());
     ASSERT_NO_THROW(client.doRebind());
-    // Make sure that the server has discarded client's message. In such case,
-    // the message sent back to the client should be NULL.
-    EXPECT_FALSE(client.getContext().response_)
-        << "The server responded to the Rebind message, while it should have"
-        " discarded it because there is no binding for the client.";
-    // Check that server still has the same lease.
-    Lease6Ptr lease_server = checkLease(lease_client);
-    EXPECT_TRUE(lease_server);
+
+    // The old lease should be returned with 0 lifetimes.
+    std::vector<Lease6> old_leases = client.getLeasesWithZeroLifetime();
+    ASSERT_EQ(1, old_leases.size());
+    EXPECT_EQ(lease_client.addr_, old_leases[0].addr_);
+
+    // The new lease should be allocated.
+    std::vector<Lease6> new_leases = client.getLeasesWithNonZeroLifetime();
+    ASSERT_EQ(1, new_leases.size());
+
+    Lease6Ptr lease_server2 = checkLease(new_leases[0]);
+    EXPECT_TRUE(lease_server2);
+    // The Status code returned to the client, should be Success.
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(5679));
 }
 }
 
 
 // Check that the prefix lifetime is not extended for the client when the
 // Check that the prefix lifetime is not extended for the client when the
@@ -624,9 +666,9 @@ TEST_F(RebindTest, directClientPDChangingPrefix) {
     // Modify the Prefix of the lease record that client stores. The server
     // Modify the Prefix of the lease record that client stores. The server
     // should check that the prefix is invalid (hasn't been allocated for
     // should check that the prefix is invalid (hasn't been allocated for
     // the particular IAID).
     // the particular IAID).
-    ASSERT_NE(client.config_.leases_[0].lease_.addr_,
+    ASSERT_NE(client.config_.leases_[0].addr_,
               IOAddress("2001:db8:1:10::"));
               IOAddress("2001:db8:1:10::"));
-    client.config_.leases_[0].lease_.addr_ = IOAddress("2001:db8:1:10::");
+    client.config_.leases_[0].addr_ = IOAddress("2001:db8:1:10::");
     // Try to Rebind. The client will use correct IAID but will specify a
     // Try to Rebind. The client will use correct IAID but will specify a
     // wrong prefix. The server will discover that the client has a binding
     // wrong prefix. The server will discover that the client has a binding
     // but the prefix will not match. According to the RFC3633, section 12.2.
     // but the prefix will not match. According to the RFC3633, section 12.2.
@@ -645,21 +687,19 @@ TEST_F(RebindTest, directClientPDChangingPrefix) {
     // Client should get two entries. One with the invalid address he requested
     // Client should get two entries. One with the invalid address he requested
     // with zeroed lifetimes and a second one with the actual prefix he has
     // with zeroed lifetimes and a second one with the actual prefix he has
     // with non-zero lifetimes.
     // with non-zero lifetimes.
-    Lease6 lease_client1 = client.getLease(0);
-    Lease6 lease_client2 = client.getLease(1);
 
 
-    // The lifetimes should be set to 0, as an explicit notification to the
-    // client to stop using invalid prefix.
-    EXPECT_EQ(0, lease_client1.valid_lft_);
-    EXPECT_EQ(0, lease_client1.preferred_lft_);
+    // Get the lease with 0 lifetimes.
+    std::vector<Lease6> invalid_leases = client.getLeasesWithZeroLifetime();
+    ASSERT_EQ(1, invalid_leases.size());
+    EXPECT_EQ(0, invalid_leases[0].valid_lft_);
+    EXPECT_EQ(0, invalid_leases[0].preferred_lft_);
 
 
-    // The lifetimes should be set to 0, as an explicit notification to the
-    // client to stop using invalid prefix.
-    EXPECT_NE(0, lease_client2.valid_lft_);
-    EXPECT_NE(0, lease_client2.preferred_lft_);
+    // Get the valid lease with non-zero lifetime.
+    std::vector<Lease6> valid_leases = client.getLeasesWithNonZeroLifetime();
+    ASSERT_EQ(1, valid_leases.size());
 
 
     // Check that server still has the same lease.
     // Check that server still has the same lease.
-    Lease6Ptr lease_server = checkLease(lease_client);
+    Lease6Ptr lease_server = checkLease(valid_leases[0]);
     ASSERT_TRUE(lease_server);
     ASSERT_TRUE(lease_server);
     // Make sure that the lease in the data base hasn't been added.
     // Make sure that the lease in the data base hasn't been added.
     EXPECT_NE(0, lease_server->valid_lft_);
     EXPECT_NE(0, lease_server->valid_lft_);
@@ -732,4 +772,115 @@ TEST_F(RebindTest, relayedUnicast) {
     EXPECT_TRUE(lease_server2);
     EXPECT_TRUE(lease_server2);
 }
 }
 
 
+// This test verifies that the client can request the prefix delegation
+// while it is rebinding an address lease.
+TEST_F(RebindTest, requestPrefixInRebind) {
+    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(REBIND_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());
+    ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(5678));
+
+    // Send Rebind message to the server, including IA_NA and requesting IA_PD.
+    ASSERT_NO_THROW(client.doRebind());
+    ASSERT_TRUE(client.getContext().response_);
+    leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_TRUE(leases_client_pd.empty());
+    ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(5678));
+
+    // Reconfigure the server to use both NA and PD pools.
+    configure(REBIND_CONFIGS[6], *client.getServer());
+
+    // Send Rebind message to the server, including IA_NA and requesting IA_PD.
+    ASSERT_NO_THROW(client.doRebind());
+
+    // Make sure that the client has acquired NA lease.
+    std::vector<Lease6> leases_client_na_rebound =
+        client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na_rebound.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
+
+    // The lease should have been rebound.
+    EXPECT_EQ(1000, leases_client_na_rebound[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());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(5678));
+}
+
+// This test verifies that the client can request the prefix delegation
+// while it is rebinding an address lease.
+TEST_F(RebindTest, requestAddressInRebind) {
+    Dhcp6Client client;
+
+    // Configure client to request IA_NA and IA_PD.
+    client.useNA();
+    client.usePD();
+
+    // Configure the server with PD pools only.
+    ASSERT_NO_THROW(configure(REBIND_CONFIGS[4], *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 PD lease.
+    std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_EQ(1, leases_client_pd.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(5678));
+
+    // The client should not acquire a NA lease.
+    std::vector<Lease6> leases_client_na =
+        client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(0, leases_client_na.size());
+    ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(1234));
+
+    // Send Rebind message to the server, including IA_PD and requesting IA_NA.
+    // The server should return NoAddrsAvail status code in this case.
+    ASSERT_NO_THROW(client.doRebind());
+    leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(0, leases_client_na.size());
+    ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(1234));
+
+    // Reconfigure the server to use both NA and PD pools.
+    configure(REBIND_CONFIGS[6], *client.getServer());
+
+    // Send Rebind message to the server, including IA_PD and requesting IA_NA.
+    ASSERT_NO_THROW(client.doRebind());
+
+    // Make sure that the client has renewed PD lease.
+    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(5678));
+    EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+    // The client should now also acquire a NA lease.
+    leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(1234));
+}
+
+
 } // end of anonymous namespace
 } // end of anonymous namespace

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

@@ -0,0 +1,383 @@
+// 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)
+///   - 1 subnet with 2001:db8:1::/64 pool
+///
+/// - Configuration 1:
+///   - only prefixes (no addresses)
+///   - prefix pool: 3000::/72
+///
+/// - Configuration 2:
+///   - addresses and prefixes
+///   - 1 subnet with one address pool and one prefix pool
+///   - address pool: 2001:db8:1::/64
+///   - prefix pool: 3000::/72
+///
+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\": [ { "
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"3000::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 2
+    "{ \"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(), na_iaid_(1234), pd_iaid_(5678) {
+    }
+
+    /// @brief IAID used for IA_NA.
+    uint32_t na_iaid_;
+
+    /// @brief IAID used for IA_PD.
+    uint32_t pd_iaid_;
+
+};
+
+// 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(na_iaid_);
+    client.usePD(pd_iaid_);
+
+    // 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());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+
+    // 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());
+    ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+    // Send Renew message to the server, including IA_NA and requesting IA_PD.
+    ASSERT_NO_THROW(client.doRenew());
+    leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_TRUE(leases_client_pd.empty());
+    ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+    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_));
+
+    // Reconfigure the server to use both NA and PD pools.
+    configure(RENEW_CONFIGS[2], *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.
+    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_));
+
+    // 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());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+}
+
+// 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) {
+    Dhcp6Client client;
+
+    // Configure client to request IA_NA and IA_PD.
+    client.useNA(na_iaid_);
+    client.usePD(pd_iaid_);
+
+    // 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());
+    ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+    // Send Renew message to the server, including IA_NA and requesting IA_PD.
+    ASSERT_NO_THROW(client.doRenew());
+    leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_TRUE(leases_client_pd.empty());
+    ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+    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_));
+
+    // Specify the hint used for IA_PD.
+    client.useHint(0, 0, 64, "::");
+
+    // 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.
+    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_));
+
+    leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_TRUE(leases_client_pd.empty());
+    ASSERT_EQ(STATUS_NoPrefixAvail, client.getStatusCode(pd_iaid_));
+
+    // Reconfigure the server to use both NA and PD pools.
+    configure(RENEW_CONFIGS[2], *client.getServer());
+
+    // Specify the hint used for IA_PD.
+    client.useHint(0, 0, 64, "::");
+
+    // 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.
+    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_));
+
+    // The lease should have been renewed.
+    EXPECT_GE(leases_client_na_renewed[0].cltt_ - leases_client_na[0].cltt_, 1000);
+
+    // The client should now also acquire a PD lease.
+    leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_EQ(1, leases_client_pd.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+}
+
+// This test verifies that the client can request the prefix delegation
+// while it is renewing an address lease.
+TEST_F(RenewTest, requestAddressInRenew) {
+    Dhcp6Client client;
+
+    // Configure client to request IA_NA and IA_PD.
+    client.useNA(na_iaid_);
+    client.usePD(pd_iaid_);
+
+    // Configure the server with PD pools only.
+    ASSERT_NO_THROW(configure(RENEW_CONFIGS[1], *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 PD lease.
+    std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_EQ(1, leases_client_pd.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+    // The client should not acquire a NA lease.
+    std::vector<Lease6> leases_client_na =
+        client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(0, leases_client_na.size());
+    ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(na_iaid_));
+
+    // Send Renew message to the server, including IA_PD and requesting IA_NA.
+    // The server should return NoAddrsAvail status code in this case.
+    ASSERT_NO_THROW(client.doRenew());
+    leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(0, leases_client_na.size());
+    ASSERT_EQ(STATUS_NoAddrsAvail, 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_));
+    EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+    // Reconfigure the server to use both NA and PD pools.
+    configure(RENEW_CONFIGS[2], *client.getServer());
+
+    // Send Renew message to the server, including IA_PD and requesting IA_NA.
+    ASSERT_NO_THROW(client.doRenew());
+
+    // Make sure that the client has renewed 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_));
+    EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+    // The client should now also acquire a NA lease.
+    leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+}
+
+// This test verifies that the client can request address assignment
+// while it is renewing an address lease, with a hint.
+TEST_F(RenewTest, requestAddressInRenewHint) {
+    Dhcp6Client client;
+
+    // Configure client to request IA_NA and IA_PD.
+    client.useNA(na_iaid_);
+    client.usePD(pd_iaid_);
+
+    // Configure the server with PD pools only.
+    ASSERT_NO_THROW(configure(RENEW_CONFIGS[1], *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 PD lease.
+    std::vector<Lease6> leases_client_pd = client.getLeasesByType(Lease::TYPE_PD);
+    ASSERT_EQ(1, leases_client_pd.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(pd_iaid_));
+
+    // The client should not acquire a NA lease.
+    std::vector<Lease6> leases_client_na =
+        client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(0, leases_client_na.size());
+    ASSERT_EQ(STATUS_NoAddrsAvail, client.getStatusCode(na_iaid_));
+
+    client.useHint(0, 0, "2001:db8:1::100");
+
+    // Send Renew message to the server, including IA_PD and requesting IA_NA.
+    // The server should return NoAddrsAvail status code in this case.
+    ASSERT_NO_THROW(client.doRenew());
+    leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+    // The server should return the hint with the zero lifetimes.
+    ASSERT_EQ(1, leases_client_na.size());
+    EXPECT_EQ(0, leases_client_na[0].preferred_lft_);
+    EXPECT_EQ(0, leases_client_na[0].valid_lft_);
+    ASSERT_EQ(STATUS_NoAddrsAvail, 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_));
+    EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+    // Reconfigure the server to use both NA and PD pools.
+    configure(RENEW_CONFIGS[2], *client.getServer());
+
+    // Send Renew message to the server, including IA_PD and requesting IA_NA.
+    ASSERT_NO_THROW(client.doRenew());
+
+    // Make sure that the client has renewed 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_));
+    EXPECT_GE(leases_client_pd_renewed[0].cltt_ - leases_client_pd[0].cltt_, 1000);
+
+    // The client should now also acquire a NA lease.
+    leases_client_na = client.getLeasesByType(Lease::TYPE_NA);
+    ASSERT_EQ(1, leases_client_na.size());
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(na_iaid_));
+}
+
+
+} // end of anonymous namespace

+ 3 - 4
src/lib/dhcpsrv/alloc_engine.cc

@@ -297,7 +297,7 @@ AllocEngine::ClientContext6::ClientContext6()
     : subnet_(), duid_(), iaid_(0), type_(Lease::TYPE_NA), hwaddr_(),
     : subnet_(), duid_(), iaid_(0), type_(Lease::TYPE_NA), hwaddr_(),
       hints_(), fwd_dns_update_(false), rev_dns_update_(false), hostname_(""),
       hints_(), fwd_dns_update_(false), rev_dns_update_(false), hostname_(""),
       callout_handle_(), fake_allocation_(false), old_leases_(), host_(),
       callout_handle_(), fake_allocation_(false), old_leases_(), host_(),
-      query_(), ia_rsp_(), allow_new_leases_in_renewals_(false) {
+      query_(), ia_rsp_() {
 }
 }
 
 
 AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, const DuidPtr& duid,
 AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, const DuidPtr& duid,
@@ -310,8 +310,7 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, const Duid
     subnet_(subnet), duid_(duid), iaid_(iaid), type_(type), hwaddr_(),
     subnet_(subnet), duid_(duid), iaid_(iaid), type_(type), hwaddr_(),
     hints_(), fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
     hints_(), fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
     hostname_(hostname), fake_allocation_(fake_allocation),
     hostname_(hostname), fake_allocation_(fake_allocation),
-    old_leases_(), host_(), query_(), ia_rsp_(),
-    allow_new_leases_in_renewals_(false) {
+    old_leases_(), host_(), query_(), ia_rsp_() {
 
 
     static asiolink::IOAddress any("::");
     static asiolink::IOAddress any("::");
 
 
@@ -1121,7 +1120,7 @@ AllocEngine::renewLeases6(ClientContext6& ctx) {
         // Depending on the configuration, we may enable or disable granting
         // Depending on the configuration, we may enable or disable granting
         // new leases during renewals. This is controlled with the
         // new leases during renewals. This is controlled with the
         // allow_new_leases_in_renewals_ field.
         // allow_new_leases_in_renewals_ field.
-        if (leases.empty() && ctx.allow_new_leases_in_renewals_) {
+        if (leases.empty()) {
 
 
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
             LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                       ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED)
                       ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED)

+ 0 - 12
src/lib/dhcpsrv/alloc_engine.h

@@ -357,18 +357,6 @@ public:
         /// @brief A pointer to the IA_NA/IA_PD option to be sent in response
         /// @brief A pointer to the IA_NA/IA_PD option to be sent in response
         Option6IAPtr ia_rsp_;
         Option6IAPtr ia_rsp_;
 
 
-
-        /// @brief Specifies whether new leases in Renew/Rebind are allowed
-        ///
-        /// This field controls what to do when renewing or rebinding client
-        /// does not have any leases. RFC3315 and the stateful-issues draft do
-        /// not specify it and it is left up to the server configuration policy.
-        /// False (the default) means that the client will not get any new
-        /// unreserved leases if his existing leases are no longer suitable.
-        /// True means that the allocation engine will do its best to assign
-        /// something.
-        bool allow_new_leases_in_renewals_;
-
         /// @brief Default constructor.
         /// @brief Default constructor.
         ClientContext6();
         ClientContext6();
 
 

+ 1 - 0
src/lib/dhcpsrv/subnet.h

@@ -683,6 +683,7 @@ private:
     /// It's default value is false, which indicates that the Rapid
     /// It's default value is false, which indicates that the Rapid
     /// Commit is disabled for the subnet.
     /// Commit is disabled for the subnet.
     bool rapid_commit_;
     bool rapid_commit_;
+
 };
 };
 
 
 /// @brief A pointer to a Subnet6 object
 /// @brief A pointer to a Subnet6 object

+ 0 - 2
src/lib/dhcpsrv/tests/alloc_engine_utils.cc

@@ -233,7 +233,6 @@ AllocEngine6Test::simpleAlloc6Test(const Pool6Ptr& pool, const IOAddress& hint,
 Lease6Collection
 Lease6Collection
 AllocEngine6Test::renewTest(AllocEngine& engine, const Pool6Ptr& pool,
 AllocEngine6Test::renewTest(AllocEngine& engine, const Pool6Ptr& pool,
                             AllocEngine::HintContainer& hints,
                             AllocEngine::HintContainer& hints,
-                            bool allow_new_leases_in_renewal,
                             bool in_pool) {
                             bool in_pool) {
 
 
     Lease::Type type = pool->getType();
     Lease::Type type = pool->getType();
@@ -243,7 +242,6 @@ AllocEngine6Test::renewTest(AllocEngine& engine, const Pool6Ptr& pool,
                                     type, false, false, "", false);
                                     type, false, false, "", false);
     ctx.hints_ = hints;
     ctx.hints_ = hints;
     ctx.query_.reset(new Pkt6(DHCPV6_RENEW, 123));
     ctx.query_.reset(new Pkt6(DHCPV6_RENEW, 123));
-    ctx.allow_new_leases_in_renewals_ = allow_new_leases_in_renewal;
     ctx.query_.reset(new Pkt6(DHCPV6_RENEW, 1234));
     ctx.query_.reset(new Pkt6(DHCPV6_RENEW, 1234));
 
 
     findReservation(engine, ctx);
     findReservation(engine, ctx);

+ 0 - 3
src/lib/dhcpsrv/tests/alloc_engine_utils.h

@@ -246,13 +246,10 @@ public:
     /// @param engine a reference to Allocation Engine
     /// @param engine a reference to Allocation Engine
     /// @param pool pool from which the lease will be allocated from
     /// @param pool pool from which the lease will be allocated from
     /// @param hints address to be used as a hint
     /// @param hints address to be used as a hint
-    /// @param allow_new_leases_in_renewal - specifies how to set the
-    ///        allow_new_leases_in_renewal flag in ClientContext6
     /// @param in_pool specifies whether the lease is expected to be in pool
     /// @param in_pool specifies whether the lease is expected to be in pool
     /// @return allocated lease(s) (may be empty)
     /// @return allocated lease(s) (may be empty)
     Lease6Collection renewTest(AllocEngine& engine, const Pool6Ptr& pool,
     Lease6Collection renewTest(AllocEngine& engine, const Pool6Ptr& pool,
                                AllocEngine::HintContainer& hints,
                                AllocEngine::HintContainer& hints,
-                               bool allow_new_leases_in_renewal,
                                bool in_pool = true);
                                bool in_pool = true);
 
 
     /// @brief Checks if the address allocation with a hint that is in range,
     /// @brief Checks if the address allocation with a hint that is in range,

+ 1 - 0
src/lib/stats/tests/.gitignore

@@ -0,0 +1 @@
+/libstats_unittests