Browse Source

[3232] Implemented support for Rebind.

Marcin Siodelski 11 years ago
parent
commit
f1a406e4d2

+ 7 - 15
src/bin/dhcp6/dhcp6_messages.mes

@@ -147,6 +147,13 @@ information about existing subnet was removed. Note that in a sense this is wors
 case of DHCP6_EXTEND_NA_UNKNOWN, as not only the lease is unknown, but also the subnet
 is. Depending on the reasons of this condition, it may or may not correct on its own.
 
+% DHCP6_EXTEND_PD_NO_BINDING client sent a %1 message to extend lifetimes of prefixes for an unknown binding: duid=%2, iaid=%3, subnet=%4
+This warning message is logged when a client attempts to extend the lifetime of the
+prefix it is using, but the server was unable to find the binding to which
+this lease belongs. This error condition has different implications for the
+Renew and Rebind messages. For the former, the server will respond with NoBinding
+status code. For the latter, the server will discard the message.
+
 % DHCP6_EXTEND_PD_UNKNOWN_SUBNET %1 message received from client on unknown subnet (duid=%2, iaid=%3)
 A warning message indicating that a client is attempting to extend lease lifetime
 for the prefix, but the server doesn't have any information about the subnet this
@@ -546,18 +553,3 @@ This warning message is printed when client attempts to release an prefix
 lease, but no such lease is known by the server. See the explanation
 of the status code DHCP6_UNKNOWN_RENEW_PD for possible reasons for
 such behavior.
-
-% DHCP6_UNKNOWN_RENEW_PD received unknown IA_NA RENEW from client (duid=%1, iaid=%2) in subnet %3
-This warning message is printed when client attempts to renew an address lease
-(in 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 clients date/time setting or poor support
-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.
-
-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.

+ 185 - 78
src/bin/dhcp6/dhcp6_srv.cc

@@ -136,7 +136,7 @@ const bool FQDN_REPLACE_CLIENT_NAME = false;
 static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
-:alloc_engine_(), serverid_(), shutdown_(true), port_(port)
+:alloc_engine_(), serverid_(), port_(port), shutdown_(true)
 {
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
@@ -211,30 +211,48 @@ void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
 }
 
 bool
-Dhcpv6Srv::testServerID(const Pkt6Ptr& pkt){
-	/// @todo Currently we always check server identifier regardless if
-	/// it is allowed in the received message or not (per RFC3315).
-	/// If the server identifier is not allowed in the message, the
-	/// sanityCheck function should deal with it. We may rethink this
-	/// design if we decide that it is appropriate to check at this stage
-	/// of message processing that the server identifier must or must not
-	/// be present. In such case however, the logic checking server id
-	/// will have to be removed from sanityCheck and placed here instead,
-	/// to avoid duplicate checks.
-	OptionPtr server_id = pkt->getOption(D6O_SERVERID);
-	if (server_id){
-		// Let us test received ServerID if it is same as ServerID
-		// which is beeing used by server
-		if (getServerID()->getData() != server_id->getData()){
-			LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_PACKET_MISMATCH_SERVERID_DROP)
-				.arg(pkt->getName())
-				.arg(pkt->getTransid())
-				.arg(pkt->getIface());
-			return (false);
-		}
-	}
-	// retun True if: no serverid received or ServerIDs matching
-	return (true);
+Dhcpv6Srv::testServerID(const Pkt6Ptr& pkt) {
+    /// @todo Currently we always check server identifier regardless if
+    /// it is allowed in the received message or not (per RFC3315).
+    /// If the server identifier is not allowed in the message, the
+    /// sanityCheck function should deal with it. We may rethink this
+    /// design if we decide that it is appropriate to check at this stage
+    /// of message processing that the server identifier must or must not
+    /// be present. In such case however, the logic checking server id
+    /// will have to be removed from sanityCheck and placed here instead,
+    /// to avoid duplicate checks.
+    OptionPtr server_id = pkt->getOption(D6O_SERVERID);
+    if (server_id){
+        // Let us test received ServerID if it is same as ServerID
+        // which is beeing used by server
+        if (getServerID()->getData() != server_id->getData()){
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_PACKET_MISMATCH_SERVERID_DROP)
+                .arg(pkt->getName())
+                .arg(pkt->getTransid())
+                .arg(pkt->getIface());
+            return (false);
+        }
+    }
+    // retun True if: no serverid received or ServerIDs matching
+    return (true);
+}
+
+bool
+Dhcpv6Srv::testUnicast(const Pkt6Ptr& pkt) const {
+    switch (pkt->getType()) {
+    case DHCPV6_SOLICIT:
+    case DHCPV6_CONFIRM:
+    case DHCPV6_REBIND:
+    case DHCPV6_INFORMATION_REQUEST:
+        if (pkt->relay_info_.empty() && !pkt->getLocalAddr().isV6Multicast()) {
+            return (false);
+        }
+        break;
+    default:
+        // do nothing
+        ;
+    }
+    return (true);
 }
 
 bool Dhcpv6Srv::run() {
@@ -258,7 +276,8 @@ bool Dhcpv6Srv::run() {
             LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
         }
 
-        // Timeout may be reached or signal received, which breaks select() with no packet received
+        // Timeout may be reached or signal received, which breaks select()
+        // with no packet received
         if (!query) {
             continue;
         }
@@ -311,8 +330,15 @@ bool Dhcpv6Srv::run() {
         }
         // Check if received query carries server identifier matching
         // server identifier being used by the server.
-        if (!testServerID(query)){
-        	continue;
+        if (!testServerID(query)) {
+            continue;
+        }
+
+        // Check if the received query has been sent to unicast or multicast.
+        // The Solicit, Confirm, Rebind and Information Request will be
+        // discarded if sent to unicast address.
+        if (!testUnicast(query)) {
+            continue;
         }
 
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
@@ -1657,66 +1683,133 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
 }
 
 OptionPtr
-Dhcpv6Srv::renewIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                      const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia) {
+Dhcpv6Srv::extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
+                       const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia) {
 
-    // Let's create a IA_NA response and fill it in later
-    boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID()));
+    // Let's create a IA_PD response and fill it in later
+    Option6IAPtr ia_rsp(new Option6IA(D6O_IA_PD, ia->getIAID()));
 
+    // If there is no subnet for the particular client, we can't retrieve
+    // information about client's leases from lease database. We treat this
+    // as no binding for the client.
     if (!subnet) {
-        // Insert status code NoBinding
-        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
-                          "Sorry, no known leases for this duid/iaid."));
+        // Per RFC3633, section 12.2, if there is no binding and we are
+        // processing a Renew, the NoBinding status code should be returned.
+        if (query->getType() == DHCPV6_RENEW) {
+            // Insert status code NoBinding
+            ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+                                               "Sorry, no known PD leases"
+                                               " for this duid/iaid."));
 
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_EXTEND_PD_UNKNOWN_SUBNET)
-            .arg(duid->toText())
-            .arg(ia->getIAID());
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                      DHCP6_EXTEND_PD_UNKNOWN_SUBNET)
+                .arg(duid->toText())
+                .arg(ia->getIAID());
 
-        return (ia_rsp);
+            return (ia_rsp);
+
+        // 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.
+        } else {
+            isc_throw(DHCPv6DiscardMessageError, "no subnet found for the"
+                      " client sending Rebind to extend lifetime of the"
+                      " prefix (DUID=" << duid->toText() << ", IAID="
+                      << ia->getIAID());
+        }
     }
 
-    Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD,
-                                                            *duid, ia->getIAID(),
-                                                            subnet->getID());
+    // There is a subnet selected. Let's pick the lease.
+    Lease6Ptr lease =
+        LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD,
+                                              *duid, ia->getIAID(),
+                                              subnet->getID());
 
+    // There is no binding for the client.
     if (!lease) {
-        // Client is renewing a lease that we don't know about.
-
-        // Insert status code NoBinding
-        ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
-                          "Sorry, no known leases for this duid/iaid."));
-
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW_PD)
-            .arg(duid->toText())
-            .arg(ia->getIAID())
-            .arg(subnet->toText());
-
+        // Per RFC3633, section 12.2, if there is no binding and we are
+        // processing a Renew, the NoBinding status code should be returned.
+        if (query->getType() == DHCPV6_RENEW) {
+            // Insert status code NoBinding.
+            ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+                                               "Sorry, no known PD"
+                                               " leases for this duid/iaid."));
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                      DHCP6_EXTEND_PD_NO_BINDING)
+                .arg(query->getName())
+                .arg(duid->toText())
+                .arg(ia->getIAID())
+                .arg(subnet->toText());
+
+        // 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.
+        } else {
+            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");
+        }
         return (ia_rsp);
+
     }
 
     // Keep the old data in case the callout tells us to skip update.
     Lease6 old_data = *lease;
 
-    // Do the actual lease update
-    lease->preferred_lft_ = subnet->getPreferred();
-    lease->valid_lft_ = subnet->getValid();
-    lease->t1_ = subnet->getT1();
-    lease->t2_ = subnet->getT2();
-    lease->cltt_ = time(NULL);
+    bool invalid_prefix = false;
+    // Check what prefix the client has sent. The prefix should match the
+    // one that we have associated with the IAID. If it doesn't match we
+    // have to return the prefix with the lifetimes set to 0 (see section
+    // 12.2. of RFC3633).
+    Option6IAPrefixPtr ia_prefix = boost::dynamic_pointer_cast<
+        Option6IAPrefix>(ia->getOption(D6O_IAPREFIX));
+    if (ia_prefix && ((ia_prefix->getAddress() != lease->addr_) ||
+                      (ia_prefix->getLength() != lease->prefixlen_))) {
+        Option6IAPrefixPtr prefix(new Option6IAPrefix(D6O_IAPREFIX,
+                                                      ia_prefix->getAddress(),
+                                                      ia_prefix->getLength(),
+                                                      0, 0));
+        ia_rsp->addOption(prefix);
+        invalid_prefix = true;
 
-    // Also update IA_PD container with proper T1, T2 values
-    ia_rsp->setT1(subnet->getT1());
-    ia_rsp->setT2(subnet->getT2());
+    } else {
+        // The prefix sent by a client is correct. Let's extend the lease
+        // for the client.
+        lease->preferred_lft_ = subnet->getPreferred();
+        lease->valid_lft_ = subnet->getValid();
+        // Do the actual lease update
+        lease->t1_ = subnet->getT1();
+        lease->t2_ = subnet->getT2();
+        lease->cltt_ = time(NULL);
+
+        // Also update IA_PD container with proper T1, T2 values
+        ia_rsp->setT1(subnet->getT1());
+        ia_rsp->setT2(subnet->getT2());
+
+        Option6IAPrefixPtr prefix(new Option6IAPrefix(D6O_IAPREFIX,
+                                                      lease->addr_,
+                                                      lease->prefixlen_,
+                                                      lease->preferred_lft_,
+                                                      lease->valid_lft_));
+        ia_rsp->addOption(prefix);
+
+    }
 
-    boost::shared_ptr<Option6IAPrefix>
-        prefix(new Option6IAPrefix(D6O_IAPREFIX, lease->addr_,
-                                   lease->prefixlen_, lease->preferred_lft_,
-                                   lease->valid_lft_));
-    ia_rsp->addOption(prefix);
 
     bool skip = false;
     // Execute all callouts registered for packet6_send
-    if (HooksManager::calloutsPresent(Hooks.hook_index_lease6_renew_)) {
+    // Get the callouts specific for the processed message and execute them.
+    int hook_point = query->getType() == DHCPV6_RENEW ?
+        Hooks.hook_index_lease6_renew_ : Hooks.hook_index_lease6_rebind_;
+    if (HooksManager::calloutsPresent(hook_point)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
         // Delete all previous arguments
@@ -1732,7 +1825,7 @@ Dhcpv6Srv::renewIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
         callout_handle->setArgument("ia_pd", ia_rsp);
 
         // Call all installed callouts
-        HooksManager::callCallouts(Hooks.hook_index_lease6_renew_,
+        HooksManager::callCallouts(hook_point,
                                    *callout_handle);
 
         // Remember hook's instruction whether we want to skip update or not
@@ -1740,12 +1833,17 @@ Dhcpv6Srv::renewIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
     }
 
     if (!skip) {
-        LeaseMgrFactory::instance().updateLease6(lease);
+        // If the prefix specified by the client is wrong, we don't want to
+        // update client's lease.
+        if (!invalid_prefix) {
+            LeaseMgrFactory::instance().updateLease6(lease);
+        }
     } else {
         // Callouts decided to skip the next processing step. The next
-        // processing step would to actually renew the lease, so skip at this
-        // stage means "keep the old lease as it is".
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_EXTEND_SKIP);
+        // processing step would to actually renew/rebind the lease, so skip
+        // at this stage means "keep the old lease as it is".
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_EXTEND_SKIP)
+            .arg(query->getName());
 
         // Copy back the original date to the lease. For MySQL it doesn't make
         // much sense, but for memfile, the Lease6Ptr points to the actual lease
@@ -1816,9 +1914,9 @@ Dhcpv6Srv::extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply) {
         }
 
         case D6O_IA_PD: {
-            OptionPtr answer_opt = renewIA_PD(subnet, duid, query,
-                                              boost::dynamic_pointer_cast<
-                                                  Option6IA>(opt->second));
+            OptionPtr answer_opt = extendIA_PD(subnet, duid, query,
+                                               boost::dynamic_pointer_cast<
+                                                   Option6IA>(opt->second));
             if (answer_opt) {
                 reply->addOption(answer_opt);
             }
@@ -2258,9 +2356,18 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
 Pkt6Ptr
 Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
 
-    /// @todo: Implement this
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
-    return reply;
+
+    copyDefaultOptions(rebind, reply);
+    appendDefaultOptions(rebind, reply);
+    appendRequestedOptions(rebind, reply);
+
+    processClientFqdn(rebind, reply);
+    extendLeases(rebind, reply);
+    generateFqdn(reply);
+    createNameChangeRequests(rebind);
+
+    return (reply);
 }
 
 Pkt6Ptr

+ 39 - 11
src/bin/dhcp6/dhcp6_srv.h

@@ -35,6 +35,14 @@
 namespace isc {
 namespace dhcp {
 
+/// @brief This exception is thrown when DHCP server hits the error which should
+/// result in discarding the message being processed.
+class DHCPv6DiscardMessageError : public Exception {
+public:
+    DHCPv6DiscardMessageError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
 /// @brief DHCPv6 server service.
 ///
 /// This class represents DHCPv6 server. It contains all
@@ -129,6 +137,18 @@ protected:
     /// used by the server; false otherwise.
     bool testServerID(const Pkt6Ptr& pkt);
 
+    /// @brief Check if the message can be sent to unicast.
+    ///
+    /// This function checks if the received message conforms to the section 15
+    /// of RFC3315 which says that: "A server MUST discard any Solicit, Confirm,
+    /// Rebind or Information-request messages it receives with a unicast
+    /// destination address.
+    ///
+    /// @param pkt DHCPv6 message to be checked.
+    /// @return false if the message has been sent to unicast address but it is
+    /// not allowed according to RFC3315, section 15; true otherwise.
+    bool testUnicast(const Pkt6Ptr& pkt) const;
+
     /// @brief verifies if specified packet meets RFC requirements
     ///
     /// Checks if mandatory option is really there, that forbidden option
@@ -271,21 +291,29 @@ protected:
     /// @return IA_NA option (server's response)
     OptionPtr extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                           const Pkt6Ptr& query, const Pkt6Ptr& answer,
-                          boost::shared_ptr<Option6IA> ia);
+                          Option6IAPtr ia);
 
-    /// @brief Renews specific IA_PD option
+    /// @brief Extends lifetime of the prefix.
+    ///
+    /// This function is called by the logic which processes Renew and Rebind
+    /// messages to extend the lifetime of the existing prefix.
     ///
-    /// Generates response to IA_PD in Renew. This typically includes finding a
-    /// lease that corresponds to the received prefix. If no such lease is
-    /// found, an IA_PD response is generated with an appropriate status code.
+    /// The behavior of this function is different in that when there is no
+    /// binding found in the lease database for the particular client the
+    /// NoBinding status code is returned when processing Renew, the exception
+    /// is thrown when there is no binding and the Rebind message is processed
+    /// (see RFC3633, section 12.2. for details).
     ///
     /// @param subnet subnet the sender belongs to
     /// @param duid client's duid
     /// @param query client's message
     /// @param ia IA_PD option that is being renewed
     /// @return IA_PD option (server's response)
-    OptionPtr renewIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                         const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia);
+    /// @throw DHCPv6DiscardMessageError when the message being processed should
+    /// be discarded by the server, i.e. there is no binding for the client doing
+    /// Rebind.
+    OptionPtr extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
+                          const Pkt6Ptr& query, Option6IAPtr ia);
 
     /// @brief Releases specific IA_NA option
     ///
@@ -602,15 +630,15 @@ private:
     /// Server DUID (to be sent in server-identifier option)
     OptionPtr serverid_;
 
-    /// Indicates if shutdown is in progress. Setting it to true will
-    /// initiate server shutdown procedure.
-    volatile bool shutdown_;
-
     /// UDP port number on which server listens.
     uint16_t port_;
 
 protected:
 
+    /// Indicates if shutdown is in progress. Setting it to true will
+    /// initiate server shutdown procedure.
+    volatile bool shutdown_;
+
     /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects, which
     /// are waiting for sending to b10-dhcp-ddns module.
     std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;

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

@@ -81,6 +81,8 @@ dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
 dhcp6_unittests_SOURCES += wireshark.cc
+dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
+dhcp6_unittests_SOURCES += rebind_unittest.cc
 
 nodist_dhcp6_unittests_SOURCES  = ../dhcp6_messages.h ../dhcp6_messages.cc
 nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h

+ 324 - 0
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -0,0 +1,324 @@
+// Copyright (C) 2014 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 <dhcp/dhcp6.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/lease.h>
+#include <dhcp6/tests/dhcp6_client.h>
+#include <util/buffer.h>
+#include <boost/pointer_cast.hpp>
+#include <time.h>
+
+using namespace isc::test;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+Dhcp6Client::Dhcp6Client() :
+    relay_link_addr_("3000:1::1"),
+    curr_transid_(0),
+    dest_addr_(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+    duid_(generateDUID(DUID::DUID_LLT)),
+    link_local_("fe80::3a60:77ff:fed5:cdef"),
+    srv_(boost::shared_ptr<NakedDhcpv6Srv>(new NakedDhcpv6Srv(0))),
+    use_na_(false),
+    use_pd_(false),
+    use_relay_(false) {
+}
+
+void
+Dhcp6Client::applyConfiguration(const Pkt6Ptr& reply) {
+    typedef OptionCollection Opts;
+    // Get all options in the reply message and pick IA_NA and IA_PD.
+    Opts opts = reply->options_;
+    for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
+        Option6IAPtr ia = boost::dynamic_pointer_cast<Option6IA>(opt->second);
+        // If the current one is not IA option, get the next one.
+        if (!ia) {
+            continue;
+        }
+        // The default value of the prefix length is 128 (as for IPv6 address),
+        // as at this point we don't know if we are dealing with the address
+        // of prefix.
+        int prefix_len = 128;
+        // Check if this is the address.
+        Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
+            Option6IAAddr>(ia->getOption(D6O_IAADDR));
+        // If this is not the address it may be a prefix.
+        if (!iaaddr) {
+            iaaddr = boost::dynamic_pointer_cast<
+                Option6IAAddr>(ia->getOption(D6O_IAPREFIX));
+            // If this is a prefix, modify the prefix length accordingly.
+            if (iaaddr) {
+                prefix_len = boost::dynamic_pointer_cast<
+                    Option6IAPrefix>(ia->getOption(D6O_IAPREFIX))->getLength();
+            }
+        }
+        /// Set the lease information if we have a prefix or address.
+        LeaseInfo lease_info;
+        if (iaaddr) {
+            Lease6 lease((prefix_len == 128 ? Lease::TYPE_NA : Lease::TYPE_PD),
+                         iaaddr->getAddress(), duid_,
+                         ia->getIAID(), iaaddr->getPreferred(),
+                         iaaddr->getValid(), ia->getT1(), ia->getT2(), 0,
+                         prefix_len);
+            lease.cltt_ = time(NULL);
+
+            lease_info.lease_ = lease;
+        } else {
+            // There is no prefix and 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_.iaid_ = ia->getIAID();
+        }
+
+        // Check if the server has sent status code. If no status code, assume
+        // the status code to be 0.
+        OptionCustomPtr status_code = boost::dynamic_pointer_cast<
+            OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+        if (status_code) {
+            lease_info.status_code_ = status_code->readInteger<uint16_t>(0);
+        } else {
+            lease_info.status_code_ = 0;
+        }
+        applyLease(lease_info);
+    }
+}
+
+void
+Dhcp6Client::applyLease(const LeaseInfo& lease_info) {
+    // Go over existing leases and try to match the one that we have.
+    for (int i = 0; i < config_.leases_.size(); ++i) {
+        Lease6 existing_lease = config_.leases_[i].lease_;
+        // If IAID is matching and there is an actual address assigned
+        // replace the current lease. The default address is :: if the
+        // server hasn't sent the IA option. In this case, there is no
+        // lease assignment so we keep what we have.
+        if ((existing_lease.iaid_ == lease_info.lease_.iaid_)
+            && (lease_info.lease_.addr_ != asiolink::IOAddress("::"))) {
+            config_.leases_[i] = lease_info;
+            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.
+    config_.leases_.push_back(lease_info);
+}
+
+
+void
+Dhcp6Client::copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest) {
+    typedef OptionCollection Opts;
+    // Copy IA_NAs.
+    Opts opts = source->getOptions(D6O_IA_NA);
+    for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
+        dest->addOption(opt->second);
+    }
+    // Copy IA_PDs.
+    opts = source->getOptions(D6O_IA_PD);
+    for (Opts::const_iterator opt = opts.begin(); opt != opts.end(); ++opt) {
+        dest->addOption(opt->second);
+    }
+}
+
+void
+Dhcp6Client::copyIAsFromLeases(const Pkt6Ptr& dest) const {
+    // Go over leases and create IA_NA and IA_PD options from them.
+    // Create one IA per lease.
+    for (std::vector<LeaseInfo>::const_iterator info = config_.leases_.begin();
+         info != config_.leases_.end(); ++info) {
+        Lease6 lease = info->lease_;
+        if (lease.type_ == Lease::TYPE_NA) {
+            Option6IAPtr opt(new Option6IA(D6O_IA_NA, lease.iaid_));
+            opt->setT1(lease.t1_);
+            opt->setT2(lease.t1_);
+            opt->addOption(Option6IAAddrPtr(new
+                                            Option6IAAddr(D6O_IAADDR,
+                                                          lease.addr_,
+                                                          lease.preferred_lft_,
+                                                          lease.valid_lft_)));
+            dest->addOption(opt);
+        } else if (lease.type_ == Lease::TYPE_PD) {
+            Option6IAPtr opt(new Option6IA(D6O_IA_PD, lease.iaid_));
+            opt->setT1(lease.t1_);
+            opt->setT2(lease.t1_);
+            opt->addOption(Option6IAPrefixPtr(new Option6IAPrefix(D6O_IAPREFIX,
+                                                                  lease.addr_,
+                                                                  lease.prefixlen_,
+                                                                  lease.preferred_lft_,
+                                                                  lease.valid_lft_)));
+            dest->addOption(opt);
+        }
+    }
+}
+
+Pkt6Ptr
+Dhcp6Client::createMsg(const uint8_t msg_type) {
+    Pkt6Ptr msg(new Pkt6(msg_type, curr_transid_++));
+    msg->addOption(getClientId());
+    return (msg);
+}
+
+void
+Dhcp6Client::doSARR() {
+    doSolicitAdvertise();
+    // Don't send the Request if there was no Advertise.
+    if (context_.response_) {
+        doRequestReply();
+    }
+}
+
+void
+Dhcp6Client::doSolicitAdvertise() {
+    context_.query_ = createMsg(DHCPV6_SOLICIT);
+    if (use_na_) {
+        context_.query_->addOption(Option6IAPtr(new Option6IA(D6O_IA_NA,
+                                                              1234)));
+    }
+    if (use_pd_) {
+        context_.query_->addOption(Option6IAPtr(new Option6IA(D6O_IA_PD,
+                                                              5678)));
+    }
+    sendMsg(context_.query_);
+    context_.response_ = receiveOneMsg();
+
+    /// @todo Sanity check here
+}
+
+void
+Dhcp6Client::doRequestReply() {
+    Pkt6Ptr query = createMsg(DHCPV6_REQUEST);
+    query->addOption(context_.response_->getOption(D6O_SERVERID));
+    copyIAs(context_.response_, query);
+    context_.query_ = query;
+    sendMsg(context_.query_);
+    context_.response_ = receiveOneMsg();
+
+    /// @todo sanity check here.
+
+    // Apply new configuration only if the server has responded.
+    if (context_.response_) {
+        applyConfiguration(context_.response_);
+    }
+}
+
+void
+Dhcp6Client::doRebind() {
+    Pkt6Ptr query = createMsg(DHCPV6_REBIND);
+    copyIAsFromLeases(query);
+    context_.query_ = query;
+    sendMsg(context_.query_);
+    context_.response_ = receiveOneMsg();
+    // Apply configuration only if the server has responded.
+    if (context_.response_) {
+        applyConfiguration(context_.response_);
+    }
+}
+
+void
+Dhcp6Client::fastFwdTime(const uint32_t secs) {
+    // Iterate over all leases and move their cltt backwards.
+    for (int i = 0; i < config_.leases_.size(); ++i) {
+        config_.leases_[i].lease_.cltt_ -= secs;
+    }
+}
+
+DuidPtr
+Dhcp6Client::generateDUID(DUID::DUIDType duid_type) const {
+    std::vector<uint8_t> duid;
+
+    /// @todo remove this check once other DUID types are implemented.
+    if (duid_type != DUID::DUID_LLT) {
+        isc_throw(NotImplemented, "currently the Dhcp6Client only supports"
+                  " generation of DUID LLT");
+    }
+    duid.push_back(static_cast<uint8_t>(duid_type));
+    duid.insert(duid.end(), 4, 0);
+    for (int i = 0; i < 6; ++i) {
+        duid.push_back(static_cast<uint8_t>(i));
+    }
+
+    return (DuidPtr(new DUID(duid)));
+}
+
+OptionPtr
+Dhcp6Client::getClientId() const {
+    OptionPtr opt_client_id(new Option(Option::V6,
+                                       D6O_CLIENTID,
+                                       duid_->getDuid().begin(),
+                                       duid_->getDuid().end()));
+    return (opt_client_id);
+}
+
+void
+Dhcp6Client::modifyDUID() {
+    if (!duid_) {
+        duid_ = generateDUID(DUID::DUID_LLT);
+        return;
+    }
+    std::vector<uint8_t> new_duid = duid_->getDuid();
+    // Modify the DUID by adding 1 to its last byte.
+    ++new_duid[new_duid.size() - 1];
+    duid_.reset(new DUID(new_duid));
+}
+
+Pkt6Ptr
+Dhcp6Client::receiveOneMsg() {
+    // Return empty pointer if server hasn't responded.
+    if (srv_->fake_sent_.empty()) {
+        return (Pkt6Ptr());
+    }
+    Pkt6Ptr msg = srv_->fake_sent_.front();
+    srv_->fake_sent_.pop_front();
+    return (msg);
+}
+
+void
+Dhcp6Client::sendMsg(const Pkt6Ptr& msg) {
+    srv_->shutdown_ = false;
+    // The client is configured to send through the relay. We achieve that
+    // adding the relay structure.
+    if (use_relay_) {
+        Pkt6::RelayInfo relay;
+        relay.linkaddr_ = relay_link_addr_;
+        relay.peeraddr_ = asiolink::IOAddress("fe80::1");
+        relay.msg_type_ = DHCPV6_RELAY_FORW;
+        relay.hop_count_ = 1;
+        msg->relay_info_.push_back(relay);
+    }
+    // Repack the message to simulate wire-data parsing.
+    msg->pack();
+    Pkt6Ptr msg_copy(new Pkt6(static_cast<const uint8_t*>
+                              (msg->getBuffer().getData()),
+                              msg->getBuffer().getLength()));
+    msg_copy->setRemoteAddr(link_local_);
+    msg_copy->setLocalAddr(dest_addr_);
+    msg_copy->setIface("eth0");
+    srv_->fakeReceive(msg_copy);
+    srv_->run();
+}
+
+
+}
+}
+}

+ 316 - 0
src/bin/dhcp6/tests/dhcp6_client.h

@@ -0,0 +1,316 @@
+// Copyright (C) 2014 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.
+
+#ifndef DHCP6_CLIENT_H
+#define DHCP6_CLIENT_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/option.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+
+/// @brief DHCPv6 client used for unit testing.
+///
+/// This class implements a DHCPv6 "client" which interoperates with the
+/// @c NakedDhcpv6Srv class. It calls @c NakedDhcpv6Srv::fakeRecive to
+/// deliver client messages to the server for processing. The server places
+/// the response in the @c NakedDhcpv6Srv::fake_sent_ container. The client
+/// pops messages from this container which simulates reception of the
+/// response from the server.
+///
+/// The client maintains the leases it acquired from the server. If it has
+/// acquired the lease as a result of SARR exchange, it will use this lease
+/// when the Rebind process is triggered by the unit test.
+///
+/// The client exposes a set of functions which simulate different exchange
+/// types between the client and the server. It also provides the access to
+/// the objects encapsulating responses from the server so as it is possible
+/// to verify from the unit test that the server's response is correct.
+///
+/// @todo This class has been implemented to simplify the structure of the
+/// unit test and to make unit tests code self-explanatory. Currently,
+/// this class is mostly used by the unit tests which test Rebind processing
+/// logic. At some point we may want to use this class to test some other
+/// message types, e.g. Renew, in which case it may need to be extended.
+/// Also, once we implement the support for multiple IAAddr and IAPrefix
+/// options within single IA, the logic which maintains leases will have
+/// to be extended to support it.
+class Dhcp6Client : public boost::noncopyable {
+public:
+
+    /// @brief Holds an information about single lease.
+    struct LeaseInfo {
+        /// @brief A structure describing the lease.
+        Lease6 lease_;
+
+        /// @brief Holds the last status code that server has sent for
+        /// the particular lease.
+        uint16_t status_code_;
+    };
+
+    /// @brief Holds the current client configuration obtained from the
+    /// server over DHCP.
+    ///
+    /// Currently it simply contains the collection of leases acquired.
+    struct Configuration {
+        std::vector<LeaseInfo> leases_;
+    };
+
+    /// @brief Holds the DHCPv6 messages taking part in transaction between
+    /// the client and the server.
+    struct Context {
+        /// @brief Holds the last sent message from the client to the server.
+        Pkt6Ptr query_;
+        /// @brief Holds the last sent message by the server to the client.
+        Pkt6Ptr response_;
+    };
+
+    /// @brief Creates a new client.
+    ///
+    /// This constructor initializes the class members to default values.
+    Dhcp6Client();
+
+    /// @brief Performs a 4-way echange between the client and the server.
+    ///
+    /// If the 4-way exchange is successful, the client should acquire leases
+    /// according to the server's current configuration and the type of leases
+    /// that have been requested (IA_NA, IA_PD).
+    ///
+    /// The leases acquired are accessible through the @c config_ member.
+    void doSARR();
+
+    /// @brief Send Solicit and receive Advertise.
+    ///
+    /// This function simulates the first transaction of the 4-way exchange,
+    /// i.e. sends a Solicit to the server and receives Advertise. It doesn't
+    /// set the lease configuration in the @c config_.
+    void doSolicitAdvertise();
+
+    /// @brief Sends a Rebind to the server and receives the Reply.
+    ///
+    /// This function simulates sending the Rebind message to the server and
+    /// receiving server's response (if any). The client uses existing leases
+    /// (either address or prefixes) and places them in the Rebind message.
+    /// If the server responds to the Rebind (and extends the lease lifetimes)
+    /// the current lease configuration is updated.
+    void doRebind();
+
+    /// @brief Sends Request to the server and receives Reply.
+    ///
+    /// This function simulates sending the Request message to the server and
+    /// receiving server's response (if any). The client copies IA options
+    /// from the current context (server's Advertise) to request acquisition
+    /// of offered IAs. If the server responds to the Request (leases are
+    /// acquired) the client's lease configuration is updated.
+    void doRequestReply();
+
+    /// @brief Simulates aging of leases by the specified number of seconds.
+    ///
+    /// This function moves back the time of acquired leases by the specified
+    /// number of seconds. It is useful for checking whether the particular
+    /// lease has been later updated (e.g. as a result of Rebind) as it is
+    /// expected that the fresh lease has cltt set to "now" (not to the time
+    /// in the past).
+    void fastFwdTime(const uint32_t secs);
+
+    /// @brief Returns DUID option used by the client.
+    OptionPtr getClientId() const;
+
+    /// @brief Returns current context.
+    const Context& getContext() const {
+        return (context_);
+    }
+
+    /// @brief Returns lease at specified index.
+    ///
+    /// @param at Index of the lease held by the client.
+    /// @return A lease at the specified index.
+    Lease6 getLease(const size_t at) const {
+        return (config_.leases_[at].lease_);
+    }
+
+    /// @brief Returns status code set by the server for the lease.
+    ///
+    /// @param at Index of the lease held by the client.
+    /// @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_);
+    }
+
+    /// @brief Returns number of acquired leases.
+    size_t getLeaseNum() const {
+        return (config_.leases_.size());
+    }
+
+    /// @brief Returns the server that the client is communicating with.
+    boost::shared_ptr<isc::test::NakedDhcpv6Srv> getServer() const {
+        return (srv_);
+    }
+
+    /// @brief Modifies the client's DUID (adds one to it).
+    ///
+    /// The DUID should be modified to test negative scenarios when the client
+    /// acquires a lease and tries to renew it with a different DUID. The server
+    /// should detect the DUID mismatch and react accordingly.
+    ///
+    /// The DUID modification affects the value returned by the
+    /// @c Dhcp6Client::getClientId
+    void modifyDUID();
+
+    /// @brief Sets destination address for the messages being sent by the
+    /// client.
+    ///
+    /// By default, the client uses All_DHCP_Relay_Agents_and_Servers
+    /// multicast address to communicate with the server. In certain cases
+    /// it ay be desired that different address is used (e.g. unicast in Renew).
+    /// This function sets the new address for all future exchanges with the
+    /// server.
+    ///
+    /// @param dest_addr New destination address.
+    void setDestAddress(const asiolink::IOAddress& dest_addr) {
+        dest_addr_ = dest_addr;
+    }
+
+    /// @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
+    /// request address assignment.
+    void useNA(const bool use = true) {
+        use_na_ = use;
+    }
+
+    /// @brief Place IA_PD options to request prefix assignment.
+    ///
+    /// This function configures the client to place IA_PD options in its
+    /// Solicit messages to request the IPv6 address assignment.
+    ///
+    /// @param use Parameter which 'true' value indicates that client should
+    /// request prefix assignment.
+    void usePD(const bool use = true) {
+        use_pd_ = use;
+    }
+
+    /// @brief Simulate sending messages through a relay.
+    ///
+    /// @param use Parameter which 'true' value indicates that client should
+    /// simulate sending messages via relay.
+    void useRelay(const bool use = true) {
+        use_relay_ = use;
+    }
+
+    /// @brief Lease configuration obtained by the client.
+    Configuration config_;
+
+    /// @brief Link address of the relay to be used for relayed messages.
+    asiolink::IOAddress relay_link_addr_;
+
+private:
+
+    /// @brief Applies the new leases for the client.
+    ///
+    /// This method is called when the client obtains a new configuration
+    /// from the server in the Reply message. This function adds new leases
+    /// or replaces existing ones.
+    ///
+    /// @param reply Server response.
+    void applyConfiguration(const Pkt6Ptr& reply);
+
+    /// @brief Applies configuration for the single lease.
+    ///
+    /// This method is called by the @c Dhcp6Client::applyConfiguration for
+    /// each individual lease.
+    ///
+    /// @param lease_info Structure holding new lease information.
+    void applyLease(const LeaseInfo& lease_info);
+
+    /// @brief Copy IA options from one message to another.
+    ///
+    /// This method copies IA_NA and IA_PD options from one message to another.
+    /// It is useful when the client needs to construct the Request message
+    /// using addresses and prefixes returned by the client in Advertise.
+    ///
+    /// @param source Message from which IA options will be copied.
+    /// @param dest Message to which IA options will be copied.
+    void copyIAs(const Pkt6Ptr& source, const Pkt6Ptr& dest);
+
+    /// @brief Creates IA options from existing configuration.
+    ///
+    /// This method iterates over existing leases that client acquired and
+    /// places corresponding IA_NA or IA_PD options into a specified message.
+    /// This is useful to construct Renew or Rebind message from the existing
+    /// configuration that client has obtained using 4-way exchange.
+    ///
+    /// @param dest Message to which the IA options will be added.
+    void copyIAsFromLeases(const Pkt6Ptr& dest) const;
+
+    /// @brief Creates client's side DHCP message.
+    ///
+    /// @param msg_type Type of the message to be created.
+    /// @return An instance of the message created.
+    Pkt6Ptr createMsg(const uint8_t msg_type);
+
+    /// @brief Generates DUID for the client.
+    ///
+    /// @param duid_type Type of the DUID. Currently, only LLT is accepted.
+    /// @return Object encapsulating a DUID.
+    DuidPtr generateDUID(DUID::DUIDType duid_type) const;
+
+    /// @brief Simulates reception of the message from the server.
+    ///
+    /// @return Received message.
+    Pkt6Ptr receiveOneMsg();
+
+    /// @brief Simulates sending a message to the server.
+    ///
+    /// @param msg Message to be sent.
+    void sendMsg(const Pkt6Ptr& msg);
+
+    /// @brief Current context (sent and received message).
+    Context context_;
+
+    /// @biref Current transaction id (altered on each send).
+    uint32_t curr_transid_;
+
+    /// @brief Currently use destination address.
+    asiolink::IOAddress dest_addr_;
+
+    /// @brief Currently used DUID.
+    DuidPtr duid_;
+
+    /// @brief Currently used link local address.
+    asiolink::IOAddress link_local_;
+
+    /// @brief Pointer to the server that the client is communicating with.
+    boost::shared_ptr<isc::test::NakedDhcpv6Srv> srv_;
+
+    bool use_na_;    ///< Enable address assignment.
+    bool use_pd_;    ///< Enable prefix delegation.
+    bool use_relay_; ///< Enable relaying messages to the server.
+};
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // DHCP6_CLIENT

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

@@ -1112,6 +1112,52 @@ TEST_F(Dhcpv6SrvTest, testServerID) {
     EXPECT_TRUE(srv.testServerID(req));
 }
 
+// Test that some messages are discarded by the server if they are sent to
+// unicast address.
+TEST_F(Dhcpv6SrvTest, testUnicast) {
+    NakedDhcpv6Srv srv(0);
+    // Explicitly list client's message types which must be discarded if
+    // sent to unicast address.
+    const uint8_t not_allowed_unicast[] = {
+        DHCPV6_SOLICIT,
+        DHCPV6_CONFIRM,
+        DHCPV6_REBIND,
+        DHCPV6_INFORMATION_REQUEST
+    };
+    // Iterate over these messages and make sure they are discarded.
+    for (int i = 0; i < sizeof(not_allowed_unicast); ++i) {
+        Pkt6Ptr msg = Pkt6Ptr(new Pkt6(not_allowed_unicast[i], 1234));
+        msg->setLocalAddr(IOAddress("2001:db8:1::1"));
+        EXPECT_FALSE(srv.testUnicast(msg))
+            << "server accepts message type "
+            << static_cast<int>(not_allowed_unicast[i])
+            << "being sent to unicast address; this message should"
+            " be discarded according to section 15 of RFC3315";
+    }
+    // Explicitly list client/relay message types which are allowed to
+    // be sent to unicast.
+    const uint8_t allowed_unicast[] = {
+        DHCPV6_REQUEST,
+        DHCPV6_RENEW,
+        DHCPV6_RELEASE,
+        DHCPV6_DECLINE,
+        DHCPV6_RELAY_FORW
+    };
+    // Iterate over these messages and check that they are accepted being
+    // sent to unicast.
+    for (int i = 0; i < sizeof(allowed_unicast); ++i) {
+        Pkt6Ptr msg = Pkt6Ptr(new Pkt6(allowed_unicast[i], 1234));
+        msg->setLocalAddr(IOAddress("2001:db8:1::1"));
+        msg->addOption(srv.getServerID());
+        EXPECT_TRUE(srv.testUnicast(msg))
+            << "server doesn't accept message type "
+            << static_cast<int>(allowed_unicast[i])
+            << "being sent to unicast address";
+    }
+
+
+}
+
 // This test verifies if selectSubnet() selects proper subnet for a given
 // source address.
 TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {

+ 25 - 8
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 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
@@ -96,6 +96,18 @@ Dhcpv6SrvTest::checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
     return (lease);
 }
 
+isc::dhcp::Lease6Ptr
+Dhcpv6SrvTest::checkLease(const isc::dhcp::Lease6& lease) {
+    Lease6Ptr lease_db = LeaseMgrFactory::instance().getLease6(lease.type_,
+                                                               lease.addr_);
+    if (!lease_db) {
+        return (Lease6Ptr());
+    }
+
+    EXPECT_TRUE(lease_db->matches(lease));
+    return (lease_db);
+}
+
 Lease6Ptr
 Dhcpv6SrvTest::checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
                             boost::shared_ptr<Option6IAPrefix> prefix){
@@ -121,11 +133,17 @@ Dhcpv6SrvTest::checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
 Pkt6Ptr
 Dhcpv6SrvTest::createMessage(uint8_t message_type, Lease::Type lease_type,
                              const IOAddress& addr, const uint8_t prefix_len,
-                             uint32_t iaid) {
-    // Let's create a RENEW
+                             const uint32_t iaid) {
     Pkt6Ptr msg = Pkt6Ptr(new Pkt6(message_type, 1234));
     msg->setRemoteAddr(IOAddress("fe80::abcd"));
+    msg->addOption(createIA(lease_type, addr, prefix_len, iaid));
+    return (msg);
+}
 
+Option6IAPtr
+Dhcpv6SrvTest::createIA(isc::dhcp::Lease::Type lease_type,
+                        const isc::asiolink::IOAddress& addr,
+                        const uint8_t prefix_len, const uint32_t iaid) {
     uint16_t code;
     OptionPtr subopt;
     switch (lease_type) {
@@ -139,15 +157,14 @@ Dhcpv6SrvTest::createMessage(uint8_t message_type, Lease::Type lease_type,
                                          300, 500));
         break;
     default:
-        isc_throw(BadValue, "Invalid lease type specified");
+        isc_throw(BadValue, "Invalid lease type specified "
+                  << static_cast<int>(lease_type));
     }
 
-    boost::shared_ptr<Option6IA> ia = generateIA(code, iaid, 1500, 3000);
-
+    Option6IAPtr ia = generateIA(code, iaid, 1500, 3000);
     ia->addOption(subopt);
-    msg->addOption(ia);
 
-    return (msg);
+    return (ia);
 }
 
 void

+ 28 - 1
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -104,11 +104,13 @@ public:
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
     using Dhcpv6Srv::testServerID;
+    using Dhcpv6Srv::testUnicast;
     using Dhcpv6Srv::sanityCheck;
     using Dhcpv6Srv::classifyPacket;
     using Dhcpv6Srv::loadServerID;
     using Dhcpv6Srv::writeServerID;
     using Dhcpv6Srv::unpackOptions;
+    using Dhcpv6Srv::shutdown_;
     using Dhcpv6Srv::name_change_reqs_;
 
     /// @brief packets we pretend to receive
@@ -413,6 +415,12 @@ public:
         (const isc::dhcp::DuidPtr& duid, const isc::dhcp::OptionPtr& ia_na,
          boost::shared_ptr<isc::dhcp::Option6IAAddr> addr);
 
+    /// @brief Check if the specified lease is present in the data base.
+    ///
+    /// @param lease Lease to be searched in the database.
+    /// @return Pointer to the lease in the database.
+    isc::dhcp::Lease6Ptr checkLease(const isc::dhcp::Lease6& lease);
+
     /// @brief Verifies received IAPrefix option
     ///
     /// Verifies if the received IAPrefix option matches the lease in the
@@ -441,7 +449,26 @@ public:
     isc::dhcp::Pkt6Ptr
     createMessage(uint8_t message_type, isc::dhcp::Lease::Type lease_type,
                   const isc::asiolink::IOAddress& addr,
-                  const uint8_t prefix_len, uint32_t iaid);
+                  const uint8_t prefix_len, const uint32_t iaid);
+
+    /// @brief Creates instance of IA option holding single address or prefix.
+    ///
+    /// Utility function that creates an IA option instance with a single
+    /// IPv6 address or prefix. This function is internally called by the
+    /// @c createMessage function. It may be also used to add additional
+    /// IA options to the message generated by @c createMessage (which adds
+    /// a single IA option by itself.).
+    ///
+    /// @param lease_type type of the lease (TYPE_NA or TYPE_PD).
+    /// @param addr address or prefix to use in IADDRESS or IAPREFIX options.
+    /// @param prefix_len length of the prefix (used for PD, ignored for NA).
+    /// @param iaid IA identifier.
+    ///
+    /// @return Created instance of the IA option.
+    isc::dhcp::Option6IAPtr
+    createIA(isc::dhcp::Lease::Type lease_type,
+             const isc::asiolink::IOAddress& addr,
+             const uint8_t prefix_len, const uint32_t iaid);
 
     /// @brief Performs basic (positive) RENEW test
     ///

+ 618 - 0
src/bin/dhcp6/tests/rebind_unittest.cc

@@ -0,0 +1,618 @@
+// Copyright (C) 2014  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 <dhcp6/config_parser.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.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 Rebind tests.
+const std::string REBIND_CONFIGS[] = {
+// Configuration 0
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/64\" ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 1
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:3::/64\" ],"
+        "    \"subnet\": \"2001:db8:3::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:4::/64\" ],"
+        "    \"subnet\": \"2001:db8:4::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 2
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"3000:1::/64\" ],"
+        "    \"subnet\": \"3000:1::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " },"
+        " {"
+        "    \"pool\": [ \"3000:2::/64\" ],"
+        "    \"subnet\": \"3000:2::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 3
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"3000:3::/64\" ],"
+        "    \"subnet\": \"3000:3::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " },"
+        " {"
+        "    \"pool\": [ \"3000:4::/64\" ],"
+        "    \"subnet\": \"3000:4::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 4
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"3000:3::/64\" ],"
+        "    \"subnet\": \"3000:3::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " },"
+        " {"
+        "    \"pool\": [ \"3000:4::/64\" ],"
+        "    \"subnet\": \"3000:4::/48\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 5
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:1:01::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:1::/40\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " },"
+        " {"
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:2:01::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:2::/40\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+// Configuration 6
+    "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:3:01::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:3::/40\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth1\""
+        " },"
+        " {"
+        "    \"pd-pools\": ["
+        "        { \"prefix\": \"2001:db8:4:01::\", "
+        "          \"prefix-len\": 72, "
+        "          \"delegated-len\": 80"
+        "        } ],"
+        "    \"subnet\": \"2001:db8:4::/40\", "
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }",
+
+};
+
+/// @brief Test fixture class for testing Rebind.
+class RebindTest : public Dhcpv6SrvTest {
+public:
+    /// @brief Configure the DHCPv6 server using the JSON string.
+    ///
+    /// @param config New configuration in JSON format.
+    /// @param srv Server to be configured.
+    void configure(const std::string& config, NakedDhcpv6Srv& srv);
+
+    /// @brief Make 4-way exchange to obtain a lease.
+    ///
+    /// @param config_index Index of the configuration held in @c REBIND_CONFIGS
+    /// to use to configure the server.
+    /// @param subnets_num Number of subnets being created with the specified
+    /// configuration.
+    /// @param client Object representing a test DHCPv6 client to use.
+    void requestLease(const int config_index, const int subnets_num,
+                      Dhcp6Client& client);
+
+};
+
+void
+RebindTest::configure(const std::string& config, NakedDhcpv6Srv& srv) {
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv, json));
+    ASSERT_TRUE(status);
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    ASSERT_EQ(0, rcode);
+}
+
+void
+RebindTest::requestLease(const int config_index, const int subnets_num,
+                         Dhcp6Client& client) {
+    // Configure the server.
+    configure(REBIND_CONFIGS[config_index], *client.getServer());
+    // Make sure we ended-up having expected number of subnets configured.
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_EQ(subnets_num, subnets->size());
+    // Do the actual 4-way exchange.
+    ASSERT_NO_THROW(client.doSARR());
+    // Simulate aging of leases, by moving their cltt_ back by 1000s.
+    client.fastFwdTime(1000);
+    // Make sure that we have obtained a lease that belongs to one of the
+    // subnets.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client = client.getLease(0);
+    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client.addr_,
+                                              ClientClasses()));
+    // Check that the client's lease matches the information on the server
+    // side.
+    Lease6Ptr lease_server = checkLease(lease_client);
+    ASSERT_TRUE(lease_server);
+    // And that status code was OK.
+    EXPECT_EQ(STATUS_Success, client.getStatusCode(0));
+}
+
+TEST_F(RebindTest, directClient) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(0, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Send Rebind message to the server.
+    ASSERT_NO_THROW(client.doRebind());
+    // The client should still have one lease which belong to one of the
+    // subnets.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client2 = client.getLease(0);
+    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client2.addr_,
+                                              ClientClasses()));
+    // The client's lease should have been extended. The client will
+    // update the cltt to current time when the lease gets extended.
+    ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+    // Make sure, that the client's lease matches the lease held by the
+    // server.
+    Lease6Ptr lease_server2 = checkLease(lease_client2);
+    EXPECT_TRUE(lease_server2);
+}
+
+TEST_F(RebindTest, directClientChangingSubnet) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(0, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Reconfigure the server so as the new subnet is served on the
+    // client's interface. Note that there will also be a new subnet
+    // id assigned to the subnet on this interface.
+    configure(REBIND_CONFIGS[1], *client.getServer());
+    // Try to rebind, using the address that the client had acquired using
+    // previous server configuration.
+    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);
+    // 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);
+    // Client should have received NoBinding status code.
+    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
+
+}
+
+TEST_F(RebindTest, directClientChangingIAID) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(0, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Modify the IAID of the lease record that client stores. By adding
+    // 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.
+    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));
+
+}
+
+TEST_F(RebindTest, directClientLostLease) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(0, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // The lease has been acquired. Now, let's explicitly remove it from the
+    // lease database.
+    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.
+    ASSERT_NO_THROW(client.doRebind());
+    ASSERT_EQ(1, client.getLeaseNum());
+    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
+}
+
+TEST_F(RebindTest, relayedClient) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Configure DHCPv6 client to simulate sending the message through a relay
+    // agent. The default link-addr is 3001:1::1. This address should be used
+    // by the server to pick the suitable subnet.
+    client.useRelay();
+    // Make 4-way exchange to get the lease. Pick the configuration #2 as it
+    // specifies the subnet for the relay agent's link address.
+    ASSERT_NO_FATAL_FAILURE(requestLease(2, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Send Rebind message to the server.
+    ASSERT_NO_THROW(client.doRebind());
+    // The client should still have one lease which belongs to one of the
+    // subnets.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client2 = client.getLease(0);
+    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client2.addr_,
+                                              ClientClasses()));
+    // The client's lease should have been extended. The client will
+    // update the cltt to current time when the lease gets extended.
+    ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+    // Make sure, that the client's lease matches the lease held by the
+    // server.
+    Lease6Ptr lease_server2 = checkLease(lease_client2);
+    EXPECT_TRUE(lease_server2);
+}
+
+TEST_F(RebindTest, relayedClientChangingSubnet) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Configure DHCPv6 client to simulate sending the message through a relay
+    // agent. The default link-addr is 3001:1::1. This address should be used
+    // by the server to pick the suitable subnet.
+    client.useRelay();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(2, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Reconfigure the server so as the new subnet is served on the
+    // client's interface. Note that there will also be a new subnet
+    // id assigned to the subnet on this interface.
+    configure(REBIND_CONFIGS[3], *client.getServer());
+    // Update relay link address to match the new subnet.
+    client.relay_link_addr_ = IOAddress("3001:4::1");
+    // Try to rebind, using the address that the client had acquired using
+    // previous server configuration.
+    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);
+    // 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);
+    // Client should have received NoBinding status code.
+    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
+
+}
+
+TEST_F(RebindTest, relayedClientChangingIAID) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Configure DHCPv6 client to simulate sending the message through a relay
+    // agent. The default link-addr is 3001:1::1. This address should be used
+    // by the server to pick the suitable subnet.
+    client.useRelay();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(2, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Modify the IAID of the lease record that client stores. By adding
+    // 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.
+    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));
+
+}
+
+TEST_F(RebindTest, relayedClientLostLease) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Configure DHCPv6 client to simulate sending the message through a relay
+    // agent. The default link-addr is 3001:1::1. This address should be used
+    // by the server to pick the suitable subnet.
+    client.useRelay();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(2, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // The lease has been acquired. Now, let's explicitly remove it from the
+    // lease database.
+    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.
+    ASSERT_NO_THROW(client.doRebind());
+    ASSERT_EQ(1, client.getLeaseNum());
+    EXPECT_EQ(STATUS_NoBinding, client.getStatusCode(0));
+}
+
+TEST_F(RebindTest, directClientPD) {
+    Dhcp6Client client;
+    // Configure client to request IA_PD.
+    client.usePD();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(5, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Send Rebind message to the server.
+    ASSERT_NO_THROW(client.doRebind());
+    // The client should still have one lease which belong to one of the
+    // subnets.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client2 = client.getLease(0);
+    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client2.addr_,
+                                              ClientClasses()));
+    // The client's lease should have been extended. The client will
+    // update the cltt to current time when the lease gets extended.
+    ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
+    // Make sure, that the client's lease matches the lease held by the
+    // server.
+    Lease6Ptr lease_server2 = checkLease(lease_client2);
+    EXPECT_TRUE(lease_server2);
+}
+
+TEST_F(RebindTest, directClientPDChangingSubnet) {
+    Dhcp6Client client;
+    // Configure client to request IA_PD.
+    client.usePD();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(5, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Reconfigure the server so as the new subnet is served on the
+    // client's interface. Note that there will also be a new subnet
+    // id assigned to the subnet on this interface.
+    configure(REBIND_CONFIGS[6], *client.getServer());
+    // Try to rebind, using the address that the client had acquired using
+    // 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);
+    // 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);
+}
+
+TEST_F(RebindTest, directClientPDChangingIAID) {
+    Dhcp6Client client;
+    // Configure client to request IA_PD.
+    client.usePD();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(5, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Modify the IAID of the lease record that client stores. By adding
+    // 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.
+    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);
+}
+
+TEST_F(RebindTest, directClientPDChangingPrefix) {
+    Dhcp6Client client;
+    // Configure client to request IA_PD.
+    client.usePD();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(5, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Modify the Prefix of the lease record that client stores. The server
+    // should check that the prefix is invalid (hasn't been allocated for
+    // the particular IAID).
+    client.config_.leases_[0].lease_.addr_ = IOAddress("2001:db8:1:10::1");
+    // 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
+    // but the prefix will not match. According to the RFC3633, section 12.2.
+    // the server has to return the lease with lifetimes set to 0, when there
+    // is a binding for the client but the prefix doesn't match.
+    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_TRUE(client.getContext().response_)
+        << "The server discarded the Rebind message, while it should have"
+        " sent a response indicating that the client should stop using the"
+        " lease, by setting lifetime values to 0.";
+    // Get the client's lease.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client2 = client.getLease(0);
+    // The lifetimes should be set to 0, as an explicit notification to the
+    // client to stop using invalid prefix.
+    EXPECT_EQ(0, lease_client2.valid_lft_);
+    EXPECT_EQ(0, lease_client2.preferred_lft_);
+    // Check that server still has the same lease.
+    Lease6Ptr lease_server = checkLease(lease_client);
+    EXPECT_TRUE(lease_server);
+}
+
+/// @todo Extend PD tests for relayed messages.
+
+// This test checks that the Rebind message is discarded by the server if it
+// has been sent to unicast address (RFC3315, section 15).
+TEST_F(RebindTest, unicast) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // Make 4-way exchange to get the lease.
+    ASSERT_NO_FATAL_FAILURE(requestLease(0, 2, client));
+    // Keep the client's lease for future reference.
+    Lease6 lease_client = client.getLease(0);
+    // Set the unicast destionation address for the Rebind message.
+    // The Rebind should be discarded when sent to unicast address,
+    // according to section 15 of RFC3315.
+    client.setDestAddress(IOAddress("2001:db8:1::1"));
+    // Send the Rebind message to a unicast address.
+    ASSERT_NO_THROW(client.doRebind());
+    // The client's lease should remain with no change (shouldn't be extended)
+    // because server is supposed to drop the message sent to a unicast address.
+    ASSERT_EQ(1, client.getLeaseNum());
+    Lease6 lease_client2 = client.getLease(0);
+    ASSERT_TRUE(lease_client2 == lease_client);
+    // Check that server still has the lease.
+    Lease6Ptr lease_server2 = checkLease(lease_client2);
+    EXPECT_TRUE(lease_server2);
+    // 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_);
+}
+
+
+} // end of anonymous namespace

+ 2 - 2
src/lib/dhcp/option6_iaprefix.cc

@@ -99,8 +99,8 @@ std::string Option6IAPrefix::toText(int indent /* =0 */) {
         tmp << " ";
 
     tmp << "type=" << type_ << "(IAPREFIX) prefix=" << addr_ << "/"
-        << prefix_len_ << ", preferred-lft=" << preferred_ << ", valid-lft="
-        << valid_ << endl;
+        << static_cast<int>(prefix_len_) << ", preferred-lft="
+        << preferred_ << ", valid-lft=" << valid_ << endl;
 
     for (OptionCollection::const_iterator opt=options_.begin();
          opt!=options_.end();

+ 4 - 1
src/lib/dhcp/option6_iaprefix.h

@@ -104,7 +104,10 @@ protected:
     uint8_t prefix_len_;
 };
 
+/// @brief Pointer to the @c Option6IAPrefix object.
+typedef boost::shared_ptr<Option6IAPrefix> Option6IAPrefixPtr;
+
 } // isc::dhcp namespace
 } // isc namespace
 
-#endif // OPTION_IA_H
+#endif // OPTION_IAPREFIX_H