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
 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.
 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)
 % 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
 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
 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
 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
 of the status code DHCP6_UNKNOWN_RENEW_PD for possible reasons for
 such behavior.
 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";
 static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
 
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
 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);
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
@@ -211,30 +211,48 @@ void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
 }
 }
 
 
 bool
 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() {
 bool Dhcpv6Srv::run() {
@@ -258,7 +276,8 @@ bool Dhcpv6Srv::run() {
             LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
             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) {
         if (!query) {
             continue;
             continue;
         }
         }
@@ -311,8 +330,15 @@ bool Dhcpv6Srv::run() {
         }
         }
         // Check if received query carries server identifier matching
         // Check if received query carries server identifier matching
         // server identifier being used by the server.
         // 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)
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
@@ -1657,66 +1683,133 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
 }
 }
 
 
 OptionPtr
 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) {
     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) {
     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);
         return (ia_rsp);
+
     }
     }
 
 
     // Keep the old data in case the callout tells us to skip update.
     // Keep the old data in case the callout tells us to skip update.
     Lease6 old_data = *lease;
     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;
     bool skip = false;
     // Execute all callouts registered for packet6_send
     // 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);
         CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
 
         // Delete all previous arguments
         // Delete all previous arguments
@@ -1732,7 +1825,7 @@ Dhcpv6Srv::renewIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
         callout_handle->setArgument("ia_pd", ia_rsp);
         callout_handle->setArgument("ia_pd", ia_rsp);
 
 
         // Call all installed callouts
         // Call all installed callouts
-        HooksManager::callCallouts(Hooks.hook_index_lease6_renew_,
+        HooksManager::callCallouts(hook_point,
                                    *callout_handle);
                                    *callout_handle);
 
 
         // Remember hook's instruction whether we want to skip update or not
         // 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) {
     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 {
     } else {
         // Callouts decided to skip the next processing step. The next
         // 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
         // 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
         // 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: {
         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) {
             if (answer_opt) {
                 reply->addOption(answer_opt);
                 reply->addOption(answer_opt);
             }
             }
@@ -2258,9 +2356,18 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
 Pkt6Ptr
 Pkt6Ptr
 Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
 Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
 
 
-    /// @todo: Implement this
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
     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
 Pkt6Ptr

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

@@ -35,6 +35,14 @@
 namespace isc {
 namespace isc {
 namespace dhcp {
 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.
 /// @brief DHCPv6 server service.
 ///
 ///
 /// This class represents DHCPv6 server. It contains all
 /// This class represents DHCPv6 server. It contains all
@@ -129,6 +137,18 @@ protected:
     /// used by the server; false otherwise.
     /// used by the server; false otherwise.
     bool testServerID(const Pkt6Ptr& pkt);
     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
     /// @brief verifies if specified packet meets RFC requirements
     ///
     ///
     /// Checks if mandatory option is really there, that forbidden option
     /// Checks if mandatory option is really there, that forbidden option
@@ -271,21 +291,29 @@ protected:
     /// @return IA_NA option (server's response)
     /// @return IA_NA option (server's response)
     OptionPtr extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     OptionPtr extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                           const Pkt6Ptr& query, const Pkt6Ptr& answer,
                           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 subnet subnet the sender belongs to
     /// @param duid client's duid
     /// @param duid client's duid
     /// @param query client's message
     /// @param query client's message
     /// @param ia IA_PD option that is being renewed
     /// @param ia IA_PD option that is being renewed
     /// @return IA_PD option (server's response)
     /// @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
     /// @brief Releases specific IA_NA option
     ///
     ///
@@ -602,15 +630,15 @@ private:
     /// Server DUID (to be sent in server-identifier option)
     /// Server DUID (to be sent in server-identifier option)
     OptionPtr serverid_;
     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.
     /// UDP port number on which server listens.
     uint16_t port_;
     uint16_t port_;
 
 
 protected:
 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
     /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects, which
     /// are waiting for sending to b10-dhcp-ddns module.
     /// are waiting for sending to b10-dhcp-ddns module.
     std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;
     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 += ../ctrl_dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
 dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
 dhcp6_unittests_SOURCES += wireshark.cc
 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  = ../dhcp6_messages.h ../dhcp6_messages.cc
 nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h
 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));
     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
 // This test verifies if selectSubnet() selects proper subnet for a given
 // source address.
 // source address.
 TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // 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);
     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
 Lease6Ptr
 Dhcpv6SrvTest::checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
 Dhcpv6SrvTest::checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
                             boost::shared_ptr<Option6IAPrefix> prefix){
                             boost::shared_ptr<Option6IAPrefix> prefix){
@@ -121,11 +133,17 @@ Dhcpv6SrvTest::checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
 Pkt6Ptr
 Pkt6Ptr
 Dhcpv6SrvTest::createMessage(uint8_t message_type, Lease::Type lease_type,
 Dhcpv6SrvTest::createMessage(uint8_t message_type, Lease::Type lease_type,
                              const IOAddress& addr, const uint8_t prefix_len,
                              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));
     Pkt6Ptr msg = Pkt6Ptr(new Pkt6(message_type, 1234));
     msg->setRemoteAddr(IOAddress("fe80::abcd"));
     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;
     uint16_t code;
     OptionPtr subopt;
     OptionPtr subopt;
     switch (lease_type) {
     switch (lease_type) {
@@ -139,15 +157,14 @@ Dhcpv6SrvTest::createMessage(uint8_t message_type, Lease::Type lease_type,
                                          300, 500));
                                          300, 500));
         break;
         break;
     default:
     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);
     ia->addOption(subopt);
-    msg->addOption(ia);
 
 
-    return (msg);
+    return (ia);
 }
 }
 
 
 void
 void

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

@@ -104,11 +104,13 @@ public:
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
     using Dhcpv6Srv::selectSubnet;
     using Dhcpv6Srv::testServerID;
     using Dhcpv6Srv::testServerID;
+    using Dhcpv6Srv::testUnicast;
     using Dhcpv6Srv::sanityCheck;
     using Dhcpv6Srv::sanityCheck;
     using Dhcpv6Srv::classifyPacket;
     using Dhcpv6Srv::classifyPacket;
     using Dhcpv6Srv::loadServerID;
     using Dhcpv6Srv::loadServerID;
     using Dhcpv6Srv::writeServerID;
     using Dhcpv6Srv::writeServerID;
     using Dhcpv6Srv::unpackOptions;
     using Dhcpv6Srv::unpackOptions;
+    using Dhcpv6Srv::shutdown_;
     using Dhcpv6Srv::name_change_reqs_;
     using Dhcpv6Srv::name_change_reqs_;
 
 
     /// @brief packets we pretend to receive
     /// @brief packets we pretend to receive
@@ -413,6 +415,12 @@ public:
         (const isc::dhcp::DuidPtr& duid, const isc::dhcp::OptionPtr& ia_na,
         (const isc::dhcp::DuidPtr& duid, const isc::dhcp::OptionPtr& ia_na,
          boost::shared_ptr<isc::dhcp::Option6IAAddr> addr);
          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
     /// @brief Verifies received IAPrefix option
     ///
     ///
     /// Verifies if the received IAPrefix option matches the lease in the
     /// Verifies if the received IAPrefix option matches the lease in the
@@ -441,7 +449,26 @@ public:
     isc::dhcp::Pkt6Ptr
     isc::dhcp::Pkt6Ptr
     createMessage(uint8_t message_type, isc::dhcp::Lease::Type lease_type,
     createMessage(uint8_t message_type, isc::dhcp::Lease::Type lease_type,
                   const isc::asiolink::IOAddress& addr,
                   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
     /// @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 << " ";
 
 
     tmp << "type=" << type_ << "(IAPREFIX) prefix=" << addr_ << "/"
     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();
     for (OptionCollection::const_iterator opt=options_.begin();
          opt!=options_.end();
          opt!=options_.end();

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

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