Browse Source

[master] Merge branch 'trac3232'

Marcin Siodelski 11 years ago
parent
commit
3649413932

+ 2 - 2
doc/guide/bind10-guide.xml

@@ -5868,8 +5868,8 @@ Dhcp6/renew-timer	1000	integer	(default)
           <simpara>Prefix delegation is not supported.</simpara>
         </listitem>
         <listitem>
-          <simpara>Rebinding (REBIND), confirmation (CONFIRM),
-          and duplication report (DECLINE) are not yet supported.</simpara>
+          <simpara>Confirmation (CONFIRM), and duplication report (DECLINE)
+          are not yet supported.</simpara>
         </listitem>
         <listitem>
           <simpara>DNS Update is not supported.</simpara>

+ 55 - 40
src/bin/dhcp6/dhcp6_messages.mes

@@ -120,6 +120,55 @@ A "libreload" command was issued to reload the hooks libraries but for
 some reason the reload failed.  Other error messages issued from the
 hooks framework will indicate the nature of the problem.
 
+% DHCP6_EXTEND_LEASE_SUBNET_SELECTED the %1 subnet was selected for client extending its lease
+This is a debug message informing that a given subnet was selected. It will
+be used for extending lifetime of the lease. This is one of the early steps
+in the processing of incoming client message.
+
+% DHCP6_EXTEND_LEASE_SUBNET_SELECT_FAILED failed to select a subnet for received %1: src=%2 type=%3
+This warning message is output when a Renew or Rebind was received from a
+subnet for which the DHCPv6 server has not been configured. The cause is
+most likely due to a misconfiguration of the server. The packet processing
+will continue, but the response will only contain generic configuration
+parameters and no addresses or prefixes.
+
+% DHCP6_EXTEND_NA_UNKNOWN received unknown IA_NA from client (duid=%1, iaid=%2) in subnet %3
+This warning message is printed when client attempts to extend the lease
+for the address (in the IA_NA option) but no such lease is known by the server.
+It typically means that client has attempted to use its lease past its
+lifetime: causes of this include a adjustment of the client's date/time
+setting or poor support on the client for sleep/recovery. A properly
+implemented client will recover from such a situation by restarting the
+lease allocation process after receiving a negative reply from the server.
+
+An alternative cause could be that the server has lost its database
+recently and does not recognize its well-behaving clients. This is more
+probable if you see many such messages. Clients will recover from this,
+but they will most likely get a different IP addresses and experience
+a brief service interruption.
+
+% DHCP6_EXTEND_NA_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 address, but the server does not have any information about the subnet this
+client belongs to. This may mean that faulty the mobile client changed its location
+and is trying to renew its old address (client is supposed to send confirm, not rewew
+in such cases, according to RFC3315) or the server configuration has changed and
+information about existing subnet was removed. Note that in a sense this is worse
+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
+client belongs to.
+
 % DHCP6_HOOK_BUFFER_RCVD_SKIP received DHCPv6 buffer was dropped because a callout set the skip flag
 This debug message is printed when a callout installed on buffer6_receive
 hook point set the skip flag. For this particular hook point, the
@@ -132,6 +181,12 @@ setting of the flag by a callout instructs the server to drop the packet.
 Server completed all the processing (e.g. may have assigned, updated
 or released leases), but the response will not be send to the client.
 
+% DHCP6_HOOK_LEASE6_EXTEND_SKIP DHCPv6 lease lifetime was not extended because a callout set the skip flag for message %1
+or lease6_rebind hook point set the skip flag. For this particular hook
+point, the setting of the flag by a callout instructs the server to not
+extend the lifetime for a lease. If client requested renewal of multiples
+leases (by sending multiple IA options), the server will skip the renewal
+of the one in question and will proceed with other renewals as usual.
 % DHCP6_HOOK_LEASE6_RELEASE_NA_SKIP DHCPv6 address lease was not released because a callout set the skip flag
 This debug message is printed when a callout installed on the
 lease6_release hook point set the skip flag. For this particular hook
@@ -401,16 +456,6 @@ that does belong to it, but the address was expected to be in a different
 IA (identity association) container. This probably means that the client's
 support for multiple prefixes is flawed.
 
-% DHCP6_RENEW_UNKNOWN_SUBNET RENEW message received from client on unknown subnet (duid=%1, iaid=%2)
-A warning message indicating that a client is attempting to renew his lease,
-but the server does not have any information about the subnet this client belongs
-to. This may mean that faulty the mobile client changed its location and is trying to
-renew its old address (client is supposed to send confirm, not rewew in such cases,
-according to RFC3315) or the server configuration has changed and information about
-existing subnet was removed. Note that in a sense this is worse case of DHCP6_UNKNOWN_RENEW,
-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_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
 This message indicates that received DHCPv6 packet is invalid.  This may be due
 to a number of reasons, e.g. the mandatory client-id option is missing,
@@ -519,33 +564,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_NA 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 the IA_NA option) but no such lease is known by the server. It
-typically means that client has attempted to use its lease past its
-lifetime: causes of this include a adjustment of the client's date/time
-setting or poor support on the client for sleep/recovery. A properly
-implemented client will recover from such a situation by restarting the
-lease allocation process after receiving a negative reply from the server.
-
-An alternative cause could be that the server has lost its database
-recently and does not recognize its well-behaving clients. This is more
-probable if you see many such messages. Clients will recover from this,
-but they will most likely get a different IP addresses and experience
-a brief service interruption.
-
-% DHCP6_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.

+ 323 - 174
src/bin/dhcp6/dhcp6_srv.cc

@@ -73,6 +73,7 @@ struct Dhcp6Hooks {
     int hook_index_pkt6_receive_;   ///< index for "pkt6_receive" hook point
     int hook_index_subnet6_select_; ///< index for "subnet6_select" hook point
     int hook_index_lease6_renew_;   ///< index for "lease6_renew" hook point
+    int hook_index_lease6_rebind_;  ///< index for "lease6_rebind" hook point
     int hook_index_lease6_release_; ///< index for "lease6_release" hook point
     int hook_index_pkt6_send_;      ///< index for "pkt6_send" hook point
     int hook_index_buffer6_send_;   ///< index for "buffer6_send" hook point
@@ -83,6 +84,7 @@ struct Dhcp6Hooks {
         hook_index_pkt6_receive_   = HooksManager::registerHook("pkt6_receive");
         hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
         hook_index_lease6_renew_   = HooksManager::registerHook("lease6_renew");
+        hook_index_lease6_rebind_   = HooksManager::registerHook("lease6_rebind");
         hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
         hook_index_pkt6_send_      = HooksManager::registerHook("pkt6_send");
         hook_index_buffer6_send_   = HooksManager::registerHook("buffer6_send");
@@ -113,7 +115,7 @@ const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
 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);
@@ -188,30 +190,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() {
@@ -235,7 +255,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;
         }
@@ -288,8 +309,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)
@@ -1412,18 +1440,28 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
 }
 
 OptionPtr
-Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                      const Pkt6Ptr& query, const Pkt6Ptr& answer,
-                      boost::shared_ptr<Option6IA> ia) {
-    if (!subnet) {
-        // There's no subnet select for this client. There's nothing to renew.
-        boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
+                       const Pkt6Ptr& query, const Pkt6Ptr& answer,
+                       boost::shared_ptr<Option6IA> ia) {
 
-        // Insert status code NoAddrsAvail.
+    // Create empty IA_NA option with IAID matching the request.
+    Option6IAPtr ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+    if (!subnet) {
+        /// @todo For simpliclty and due to limitations of LeaseMgr we don't
+        /// get the binding for the client for which we don't get subnet id.
+        /// Subnet id is a required value when searching for the bindings.
+        /// The fact that we can't identify the subnet for the returning client
+        /// doesn't really mean that the client has no binding. It is possible
+        /// that due to server's reconfiguration the subnet has been removed
+        /// or modified since the client has got his lease. We may need to
+        /// rethink whether it is appropriate to send no binding if the subnet
+        // hasn't been found for the client.
         ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
                           "Sorry, no known leases for this duid/iaid."));
-
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RENEW_UNKNOWN_SUBNET)
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                  DHCP6_EXTEND_NA_UNKNOWN_SUBNET)
+            .arg(query->getName())
             .arg(duid->toText())
             .arg(ia->getIAID());
 
@@ -1434,17 +1472,14 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                                                             *duid, ia->getIAID(),
                                                             subnet->getID());
 
+    // Client extending a lease that we don't know about.
     if (!lease) {
-        // client renewing a lease that we don't know about.
-
-        // Create empty IA_NA option with IAID matching the request.
-        boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
-
-        // Insert status code NoAddrsAvail.
+        // Insert status code NoBinding to indicate that the lease does not
+        // exist for this client.
         ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
-                          "Sorry, no known leases for this duid/iaid."));
+                          "Sorry, no known leases for this duid/iaid/subnet."));
 
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW_NA)
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_EXTEND_NA_UNKNOWN)
             .arg(duid->toText())
             .arg(ia->getIAID())
             .arg(subnet->toText());
@@ -1452,70 +1487,93 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         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;
 
-    // At this point, we have to make make some decisions with respect to the
-    // FQDN option that we have generated as a result of receiving client's
-    // FQDN. In particular, we have to get to know if the DNS update will be
-    // performed or not. It is possible that option is NULL, which is valid
-    // condition if client didn't request DNS updates and server didn't force
-    // the update.
-    bool do_fwd = false;
-    bool do_rev = false;
-    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
-        Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
-    if (fqdn) {
+    bool invalid_addr = false;
+    // Check what address the client has sent. The address should match the one
+    // that we have associated with the IAID. If it doesn't match we have two
+    // options: allocate the address for the client, if the server's
+    // configuration allows to do so, or notify the client that his address is
+    // wrong. For now we will just notify the client that the address is wrong,
+    // but both solutions require that we check the contents of the IA_NA option
+    // sent by the client. Without this check we would extend the existing lease
+    // even if the address being carried in the IA_NA is different than the
+    // one we are extending.
+    Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
+        Option6IAAddr>(ia->getOption(D6O_IAADDR));
+    if (iaaddr && (iaaddr->getAddress() != lease->addr_)) {
+        Option6IAAddrPtr zero_lft_addr(new Option6IAAddr(D6O_IAADDR,
+                                                         iaaddr->getAddress(),
+                                                         0, 0));
+        ia_rsp->addOption(zero_lft_addr);
+        // Mark that the client's notion of the address is invalid, so as
+        // we don't update the actual client's lease.
+        invalid_addr = true;
+
+    } else {
+
+        // At this point, we have to make make some decisions with respect
+        // to the FQDN option that we have generated as a result of receiving
+        // client's FQDN. In particular, we have to get to know if the DNS
+        // update will be performed or not. It is possible that option is NULL,
+        // which is valid condition if client didn't request DNS updates and
+        // server didn't force the update.
+        bool do_fwd = false;
+        bool do_rev = false;
+        Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+            Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+        if (fqdn) {
         // For now, we assert that if we are doing forward we are also
         // doing reverse.
-        if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
-            do_fwd = true;
-            do_rev = true;
+            if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
+                do_fwd = true;
+                do_rev = true;
+            }
         }
-    }
 
-    std::string hostname;
-    if (do_fwd || do_rev) {
-        hostname = fqdn->getDomainName();
-    }
-
-    // If the new FQDN settings have changed for the lease, we need to
-    // delete any existing FQDN records for this lease.
-    if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) ||
-        (lease->fqdn_rev_ != do_rev)) {
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
-                  DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE)
-            .arg(lease->toText())
-            .arg(hostname)
-            .arg(do_rev ? "true" : "false")
-            .arg(do_fwd ? "true" : "false");
+        std::string hostname;
+        if (do_fwd || do_rev) {
+            hostname = fqdn->getDomainName();
+        }
 
-        createRemovalNameChangeRequest(lease);
-    }
+        // If the new FQDN settings have changed for the lease, we need to
+        // delete any existing FQDN records for this lease.
+        if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) ||
+            (lease->fqdn_rev_ != do_rev)) {
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                      DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE)
+                .arg(lease->toText())
+                .arg(hostname)
+                .arg(do_rev ? "true" : "false")
+                .arg(do_fwd ? "true" : "false");
 
-    lease->preferred_lft_ = subnet->getPreferred();
-    lease->valid_lft_ = subnet->getValid();
-    lease->t1_ = subnet->getT1();
-    lease->t2_ = subnet->getT2();
-    lease->cltt_ = time(NULL);
-    lease->hostname_ = hostname;
-    lease->fqdn_fwd_ = do_fwd;
-    lease->fqdn_rev_ = do_rev;
+            createRemovalNameChangeRequest(lease);
+        }
 
-    // Create empty IA_NA option with IAID matching the request.
-    boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+        lease->preferred_lft_ = subnet->getPreferred();
+        lease->valid_lft_ = subnet->getValid();
+        lease->t1_ = subnet->getT1();
+        lease->t2_ = subnet->getT2();
+        lease->cltt_ = time(NULL);
+        lease->hostname_ = hostname;
+        lease->fqdn_fwd_ = do_fwd;
+        lease->fqdn_rev_ = do_rev;
 
-    ia_rsp->setT1(subnet->getT1());
-    ia_rsp->setT2(subnet->getT2());
+        ia_rsp->setT1(subnet->getT1());
+        ia_rsp->setT2(subnet->getT2());
 
-    boost::shared_ptr<Option6IAAddr> addr(new Option6IAAddr(D6O_IAADDR,
-                                          lease->addr_, lease->preferred_lft_,
-                                          lease->valid_lft_));
-    ia_rsp->addOption(addr);
+        Option6IAAddrPtr addr(new Option6IAAddr(D6O_IAADDR, lease->addr_,
+                                                lease->preferred_lft_,
+                                                lease->valid_lft_));
+        ia_rsp->addOption(addr);
+    }
 
     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
@@ -1531,24 +1589,30 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         callout_handle->setArgument("ia_na", ia_rsp);
 
         // Call all installed callouts
-        HooksManager::callCallouts(Hooks.hook_index_lease6_renew_, *callout_handle);
+        HooksManager::callCallouts(hook_point, *callout_handle);
 
         // 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".
         if (callout_handle->getSkip()) {
             skip = true;
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_RENEW_SKIP);
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS,
+                      DHCP6_HOOK_LEASE6_EXTEND_SKIP)
+                .arg(query->getName());
         }
     }
 
     if (!skip) {
-        LeaseMgrFactory::instance().updateLease6(lease);
+        // If the client has sent an invalid address, it shouldn't affect the
+        // lease in our lease database.
+        if (!invalid_addr) {
+            LeaseMgrFactory::instance().updateLease6(lease);
+        }
     } else {
         // 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
-        // in memfile, so the actual update is performed when we manipulate fields
-        // of returned Lease6Ptr, the actual updateLease6() is no-op.
+        // in memfile, so the actual update is performed when we manipulate
+        // fields of returned Lease6Ptr, the actual updateLease6() is no-op.
         *lease = old_data;
     }
 
@@ -1556,66 +1620,133 @@ Dhcpv6Srv::renewIA_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_RENEW_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
@@ -1631,7 +1762,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
@@ -1639,12 +1770,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_RENEW_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
@@ -1657,15 +1793,16 @@ Dhcpv6Srv::renewIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
 }
 
 void
-Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
+Dhcpv6Srv::extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply) {
 
-    // We need to renew addresses for all IA_NA options in the client's
-    // RENEW message.
-    // @todo add support for IA_TA
-    // @todo add support for IA_PD
+    // We will try to extend lease lifetime for all IA options in the client's
+    // Renew or Rebind message.
+    /// @todo add support for IA_TA
 
-    // We need to select a subnet the client is connected in.
-    Subnet6Ptr subnet = selectSubnet(renew);
+    // We need to select a subnet the client is connected in. This is needed
+    // to get the client's bindings from the lease database. The subnet id
+    // is one of the lease search parameters.
+    Subnet6Ptr subnet = selectSubnet(query);
     if (!subnet) {
         // This particular client is out of luck today. We do not have
         // information about the subnet he is connected to. This likely means
@@ -1674,20 +1811,24 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
         // addresses or prefixes, no subnet specific configuration etc. The only
         // thing this client can get is some global information (like DNS
         // servers).
-
-        LOG_WARN(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
-            .arg(renew->getRemoteAddr().toText())
-            .arg(renew->getName());
+        LOG_WARN(dhcp6_logger, DHCP6_EXTEND_LEASE_SUBNET_SELECT_FAILED)
+            .arg(query->getName())
+            .arg(query->getRemoteAddr().toText())
+            .arg(query->getName());
     } else {
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
+                  DHCP6_EXTEND_LEASE_SUBNET_SELECTED)
             .arg(subnet->toText());
     }
 
     // Let's find client's DUID. Client is supposed to include its client-id
-    // option almost all the time (the only exception is an anonymous inf-request,
-    // but that is mostly a theoretical case). Our allocation engine needs DUID
-    // and will refuse to allocate anything to anonymous clients.
-    OptionPtr opt_duid = renew->getOption(D6O_CLIENTID);
+    // option almost all the time (the only exception is an anonymous
+    // inf-request, but that is mostly a theoretical case). Our allocation
+    // engine needs DUID and will refuse to allocate anything to anonymous
+    // clients.
+    /// @todo Consider removing this check from here and rely on what we have
+    /// checked on the earlier processing stage.
+    OptionPtr opt_duid = query->getOption(D6O_CLIENTID);
     if (!opt_duid) {
         // This should not happen. We have checked this before.
         reply->addOption(createStatusCode(STATUS_UnspecFail,
@@ -1696,14 +1837,13 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
     }
     DuidPtr duid(new DUID(opt_duid->getData()));
 
-    for (OptionCollection::iterator opt = renew->options_.begin();
-         opt != renew->options_.end(); ++opt) {
+    for (OptionCollection::iterator opt = query->options_.begin();
+         opt != query->options_.end(); ++opt) {
         switch (opt->second->getType()) {
-
         case D6O_IA_NA: {
-            OptionPtr answer_opt = renewIA_NA(subnet, duid, renew, reply,
-                                              boost::dynamic_pointer_cast<
-                                              Option6IA>(opt->second));
+            OptionPtr answer_opt = extendIA_NA(subnet, duid, query, reply,
+                                               boost::dynamic_pointer_cast<
+                                                   Option6IA>(opt->second));
             if (answer_opt) {
                 reply->addOption(answer_opt);
             }
@@ -1711,9 +1851,9 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
         }
 
         case D6O_IA_PD: {
-            OptionPtr answer_opt = renewIA_PD(subnet, duid, renew,
-                                              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);
             }
@@ -2143,7 +2283,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     appendRequestedOptions(renew, reply);
 
     processClientFqdn(renew, reply);
-    renewLeases(renew, reply);
+    extendLeases(renew, reply);
     generateFqdn(reply);
     createNameChangeRequests(reply);
 
@@ -2153,9 +2293,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

+ 70 - 26
src/bin/dhcp6/dhcp6_srv.h

@@ -36,6 +36,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
@@ -155,6 +163,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
@@ -279,37 +299,59 @@ protected:
                           const Pkt6Ptr& query,
                           boost::shared_ptr<Option6IA> ia);
 
-    /// @brief Renews specific IA_NA option
+    /// @brief Extends lifetime of the specific IA_NA option.
     ///
-    /// Generates response to IA_NA in Renew. This typically includes finding a
-    /// lease that corresponds to the received address. If no such lease is
-    /// found, an IA_NA response is generated with an appropriate status code.
+    /// Generates response to IA_NA in Renew or Rebind. This typically includes
+    /// finding a lease that corresponds to the received address. If no such
+    /// lease is found, an IA_NA response is generated with an appropriate
+    /// status code.
+    ///
+    /// @todo The behavior of this function will need to be extended to support
+    /// draft-ietf-dhc-dhcpv6-stateful-issues. This draft modifies the behavior
+    /// described in RFC3315 with respect to Renew and Rebind processing. Key
+    /// changes are (version -05):
+    /// - Renewing and Rebinding client MAY request additional bindings by
+    /// putting an IA for all bindings it desires but has been unable to obtain.
+    /// Server MAY allocate addresses if it finds that they are appropriate for
+    /// the link that client is attached to.
+    /// - When receiving Rebind, if the server determines that the addresses are
+    /// not appropriate for the link the client is attached to, the server MAY
+    /// send the IA with address lifetimes set to 0 or discard the message.
     ///
     /// @param subnet subnet the sender belongs to
     /// @param duid client's duid
-    /// @param query client's message
+    /// @param query client's message (Renew or Rebind)
     /// @param answer server's response to the client's message. This
     /// message should contain Client FQDN option being sent by the server
     /// to the client (if the client sent this option to the server).
-    /// @param ia IA_NA option that is being renewed
+    /// @param ia IA_NA option which carries adress for which lease lifetime
+    /// will be extended.
     /// @return IA_NA option (server's response)
-    OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                         const Pkt6Ptr& query, const Pkt6Ptr& answer,
-                         boost::shared_ptr<Option6IA> ia);
+    OptionPtr extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
+                          const Pkt6Ptr& query, const Pkt6Ptr& answer,
+                          Option6IAPtr ia);
 
-    /// @brief Renews specific IA_PD option
+    /// @brief Extends lifetime of the 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.
+    /// This function is called by the logic which processes Renew and Rebind
+    /// messages to extend the lifetime of the existing prefix.
+    ///
+    /// 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
     ///
@@ -455,15 +497,17 @@ protected:
     /// records will be performed.
     void createRemovalNameChangeRequest(const Lease6Ptr& lease);
 
-    /// @brief Attempts to renew received addresses
+    /// @brief Attempts to extend the lifetime of IAs.
+    ///
+    /// This function is called when a client sends Renew or Rebind message.
+    /// It iterates through received IA options and attempts to extend
+    /// corresponding lease lifetimes. Internally, it calls
+    /// @c Dhcpv6Srv::extendIA_NA and @c Dhcpv6Srv::extendIA_PD to extend
+    /// the lifetime of IA_NA and IA_PD leases accordingly.
     ///
-    /// It iterates through received IA_NA options and attempts to renew
-    /// received addresses. If no such leases are found, proper status
-    /// code is added to reply message. Renewed addresses are added
-    /// as IA_NA/IAADDR to reply packet.
-    /// @param renew client's message asking for renew
+    /// @param query client's Renew or Rebind message
     /// @param reply server's response
-    void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
+    void extendLeases(const Pkt6Ptr& query, Pkt6Ptr& reply);
 
     /// @brief Attempts to release received addresses
     ///
@@ -624,15 +668,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_;

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

@@ -84,6 +84,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
@@ -91,6 +93,7 @@ nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h
 dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la

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

@@ -0,0 +1,364 @@
+// 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 <cstdlib>
+#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) {
+}
+
+Dhcp6Client::Dhcp6Client(boost::shared_ptr<NakedDhcpv6Srv>& srv) :
+    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_(srv),
+    use_na_(false),
+    use_pd_(false),
+    use_relay_(false) {
+}
+
+void
+Dhcp6Client::applyRcvdConfiguration(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 (!ia) {
+            continue;
+        }
+
+        const Opts& ia_opts = ia->getOptions();
+        for (Opts::const_iterator iter_ia_opt = ia_opts.begin();
+             iter_ia_opt != ia_opts.end(); ++iter_ia_opt) {
+            OptionPtr ia_opt = iter_ia_opt->second;
+            LeaseInfo lease_info;
+            switch (ia_opt->getType()) {
+            case D6O_IAADDR:
+                {
+                    Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
+                        Option6IAAddr>(ia->getOption(D6O_IAADDR));
+                    if (!iaaddr) {
+                        // There is no address. This IA option may simply
+                        // contain a status code, so let's just reset the
+                        // lease and keep IAID around.
+                        lease_info.lease_ = Lease6();
+                        lease_info.lease_.type_ = Lease::TYPE_NA;
+                        lease_info.lease_.iaid_ = ia->getIAID();
+                        break;
+                    }
+                    lease_info.lease_ = Lease6(Lease::TYPE_NA,
+                                               iaaddr->getAddress(),
+                                               duid_, ia->getIAID(),
+                                               iaaddr->getPreferred(),
+                                               iaaddr->getValid(),
+                                               ia->getT1(), ia->getT2(), 0);
+                    lease_info.lease_.cltt_ = time(NULL);
+                }
+                break;
+
+            case D6O_IAPREFIX:
+                {
+                    Option6IAPrefixPtr iaprefix = boost::dynamic_pointer_cast<
+                        Option6IAPrefix>(ia->getOption(D6O_IAPREFIX));
+                    if (!iaprefix) {
+                        // There is no prefix. This IA option may simply
+                        // contain a status code, so let's just reset the
+                        // lease and keep IAID around.
+                        lease_info.lease_ = Lease6();
+                        lease_info.lease_.type_ = Lease::TYPE_PD;
+                        lease_info.lease_.iaid_ = ia->getIAID();
+                        break;
+                    }
+                    lease_info.lease_ = Lease6(Lease::TYPE_PD,
+                                               iaprefix->getAddress(), duid_,
+                                               ia->getIAID(),
+                                               iaprefix->getPreferred(),
+                                               iaprefix->getValid(),
+                                               ia->getT1(), ia->getT2(), 0,
+                                               iaprefix->getLength());
+                    lease_info.lease_.cltt_ = time(NULL);
+                }
+                break;
+
+            case D6O_STATUS_CODE:
+                {
+                    // 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));
+                    lease_info.status_code_ =
+                        status_code ? status_code->readInteger<uint16_t>(0) : 0;
+                }
+                break;
+
+            default:
+                ; // no-op
+            }
+
+            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_)
+            && (existing_lease.type_ == lease_info.lease_.type_)
+            && (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() {
+    doSolicit();
+    // Don't send the Request if there was no Advertise.
+    if (context_.response_) {
+        doRequest();
+    }
+}
+
+void
+Dhcp6Client::doSolicit() {
+    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::doRequest() {
+    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_) {
+        applyRcvdConfiguration(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_) {
+        applyRcvdConfiguration(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));
+    for (int i = 0; i < 4; ++i) {
+        duid.push_back(static_cast<uint8_t>(rand() % 255));
+    }
+    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();
+}
+
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc

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

@@ -0,0 +1,392 @@
+// 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 Default constructor for the structure.
+        LeaseInfo() :
+            lease_(), status_code_(0) { }
+    };
+
+    /// @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:
+    /// - relay link-addr = 3000:1::1
+    /// - first transaction id = 0
+    /// - dest-addr = All_DHCP_Relay_Agents_and_Servers
+    /// - duid (LLT) = <random 4 bytes>00010203040506
+    /// - link-local-addr = fe80::3a60:77ff:fed5:cdef
+    /// - IA_NA not requested
+    /// - IA_PD not requested
+    /// - not relayed
+    Dhcp6Client();
+
+    /// @brief Creates a new client that communicates with a specified server.
+    ///
+    /// This constructor allows passing a pointer to the server object which
+    /// should be used in a test. The server may be preconfigured before passed
+    /// to the constructor. The default configuration used by the client is:
+    /// - relay link-addr = 3000:1::1
+    /// - first transaction id = 0
+    /// - dest-addr = All_DHCP_Relay_Agents_and_Servers
+    /// - duid (LLT) = <random 4 bytes>00010203040506
+    /// - link-local-addr = fe80::3a60:77ff:fed5:cdef
+    /// - IA_NA not requested
+    /// - IA_PD not requested
+    /// - not relayed
+    ///
+    /// @param srv Object representing server under test.
+    Dhcp6Client(boost::shared_ptr<isc::test::NakedDhcpv6Srv>& srv);
+
+    /// @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.
+    ///
+    /// @throw This function doesn't throw exceptions on its own, but it calls
+    /// functions that are not exception safe, so it may throw exceptions if
+    /// error occurs.
+    ///
+    /// @todo Perform sanity checks on returned messages.
+    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_.
+    ///
+    /// @throw This function doesn't throw exceptions on its own, but it calls
+    /// functions that are not exception safe, so it may throw exceptions if
+    /// error occurs.
+    ///
+    /// @todo Perform sanity checks on returned messages.
+    void doSolicit();
+
+    /// @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.
+    ///
+    /// @throw This function doesn't throw exceptions on its own, but it calls
+    /// functions that are not exception safe, so it may throw exceptions if
+    /// error occurs.
+    ///
+    /// @todo Perform sanity checks on returned messages.
+    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.
+    ///
+    /// @throw This function doesn't throw exceptions on its own, but it calls
+    /// functions that are not exception safe, so it may throw exceptions if
+    /// error occurs.
+    ///
+    /// @todo Perform sanity checks on returned messages.
+    void doRequest();
+
+    /// @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.
+    ///
+    /// @warning This method doesn't check if the specified index is out of
+    /// range. The caller is responsible for using a correct offset by
+    /// invoking the @c getLeaseNum function.
+    ///
+    /// @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.
+    ///
+    /// @warning This method doesn't check if the specified index is out of
+    /// range. The caller is responsible for using a correct offset by
+    /// invoking the @c getLeaseNum function.
+    ///
+    /// @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.
+    /// @param link_addr Relay link-addr.
+    void useRelay(const bool use = true,
+                  const asiolink::IOAddress& link_addr = asiolink::IOAddress("3000:1::1")) {
+        use_relay_ = use;
+        relay_link_addr_ = link_addr;
+    }
+
+    /// @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, on the client's side. Client uses these
+    /// leases in any later communication with the server when doing Renew
+    /// or Rebind.
+    ///
+    /// @param reply Server response.
+    ///
+    /// @todo Currently this function supports one IAAddr or IAPrefix option
+    /// within IA. We will need to extend it to support multiple options
+    /// within a single IA once server supports that.
+    void applyRcvdConfiguration(const Pkt6Ptr& reply);
+
+    /// @brief Applies configuration for the single lease.
+    ///
+    /// This method is called by the @c Dhcp6Client::applyRcvdConfiguration 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.
+    ///
+    /// @todo Add support for IA_TA.
+    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.
+    ///
+    /// This function instantly triggers processing of the message by the
+    /// server. The server's response can be gathered by invoking the
+    /// @c receiveOneMsg function.
+    ///
+    /// @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

@@ -1124,6 +1124,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
@@ -122,6 +122,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){
@@ -147,12 +159,18 @@ 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->setIface("eth0");
+    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) {
@@ -166,15 +184,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_;
     using Dhcpv6Srv::VENDOR_CLASS_PREFIX;
 
@@ -401,6 +403,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
@@ -429,7 +437,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
     ///

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

@@ -0,0 +1,752 @@
+// 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 <dhcp/tests/iface_mgr_test_config.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.
+///
+/// - Configuration 0:
+///   - only addresses (no prefixes)
+///   - 2 subnets with 2001:db8:1::/64 and 2001:db8:2::64
+///   - 1 subnet for eth0 and 1 subnet for eth1
+///
+/// - Configuration 1:
+///   - similar to Configuration 0 but different subnets
+///   - pools configured: 2001:db8:3::/64 and 2001:db8:4::/64
+///
+/// - Configuration 2:
+///   - similar to Configuration 0 and Configuration 1
+///   - pools configured: 3000:1::/64 and 3000:2::/64
+///   - this specific configuration is used by tests using relays
+///
+/// - Configuration 3:
+///   - similar to Configuration 2 but with different subnets
+///   - pools configured: 3000:3::/64 and 3000:4::/64
+///   - this specific configuration is used by tests using relays
+///
+/// - Configuration 5:
+///   - only prefixes (no addresses)
+///   - 2 subnets: 2001:db8:1::/40 and 2001:db8:2::/40
+///   - 2 prefix pools: 2001:db8:1::/72 and 2001:db8:2::/72
+///   - 1 subnet for eth0 and 1 subnet for eth1
+///   - this specific configuration is used by tests which don't use relays
+///
+/// - Configuration 6:
+///   - similar to Configuration 5 but with different subnets
+///   - 2 subnets: 2001:db8:3::/40 and 2001:db8:4::/40
+///   - 2 prefix pools: 2001:db8:3::/72 and 2001:db8:4::/72
+///   - delegated length /80
+///   - this specific configuration is used by tests which don't use relays
+const char* 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\": [ { "
+        "    \"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 5
+    "{ \"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 Constructor.
+    ///
+    /// Sets up fake interfaces.
+    RebindTest()
+        : Dhcpv6SrvTest(),
+          iface_mgr_test_config_(true) {
+    }
+
+    /// @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);
+
+    /// @brief Interface Manager's fake configuration control.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+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) {
+    // Check that the index is in the configuration table.
+    ASSERT_LT(config_index, sizeof(REBIND_CONFIGS)/sizeof(REBIND_CONFIGS[0]));
+    // 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 that directly connected client's Rebind message is processed and Reply
+// message is sent back.
+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 that server doesn't extend the lease when the configuration has changed
+// such that the existing subnet is replaced with a different subnet.
+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));
+
+}
+
+// Check that the server doesn't extend the lease for the client when the
+// client sends IAID which doesn't belong to the lease that client has.
+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));
+
+}
+
+// Check that server sends NoBinding when the lease has been lost from
+// the database and client is trying to Rebind it.
+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));
+}
+
+/// @todo Extend tests for direct client changing address.
+
+// Check that the client can Rebind existing lease through a relay.
+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);
+}
+
+// Check that the lease is not extended for the relayed client when the
+// configuration has changed such that the subnet that client is using
+// doesn't exist anymore.
+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));
+
+}
+
+// Check that the lease is not extended for the relayed client when the IAID in
+// the Rebind message doesn't match the one recorded for the client.
+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));
+
+}
+
+// Check that the relayed client receives NoBinding when the lease that he
+// is Rebinding has been lost from the database.
+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));
+}
+
+// Check that relayed client receives the IA with lifetimes of 0, when
+// client is tgrying to Rebind using an address it doesn't have.
+TEST_F(RebindTest, relayedClientChangingAddress) {
+    Dhcp6Client client;
+    // Configure client to request IA_NA.
+    client.useNA();
+    // 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 address of the lease record that client stores. The server
+    // should check that the address is invalid (hasn't been allocated for
+    // the particular IAID).
+    client.config_.leases_[0].lease_.addr_ = IOAddress("3000::100");
+    // Try to Rebind. The client will use correct IAID but will specify a
+    // wrong address. The server will discover that the client has a binding
+    // but the address will not 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);
+    // Make sure that the lease in the data base hasn't been addected.
+    EXPECT_NE(0, lease_server->valid_lft_);
+    EXPECT_NE(0, lease_server->preferred_lft_);
+}
+
+// Check that the server extends the lease for the client having a prefix.
+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(4, 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);
+}
+
+// Check that the prefix lifetime is not extended for the client in case
+// the configuration has been changed such, that the subnet he is using
+// doesn't exist anymore.
+TEST_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(4, 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[5], *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);
+}
+
+// Check that the prefix lifetime is not extended for the client when the
+// IAID used in the Rebind is not matching the one recorded by the server
+// for the particular client.
+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(4, 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);
+}
+
+// Check that the prefix lifetime is not extended for the client when the
+// prefix used in Rebind message doesn't match the one that client has.
+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(4, 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).
+    ASSERT_NE(client.config_.leases_[0].lease_.addr_,
+              IOAddress("2001:db8:1:10::"));
+    client.config_.leases_[0].lease_.addr_ = IOAddress("2001:db8:1:10::");
+    // 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);
+    ASSERT_TRUE(lease_server);
+    // Make sure that the lease in the data base hasn't been addected.
+    EXPECT_NE(0, lease_server->valid_lft_);
+    EXPECT_NE(0, lease_server->preferred_lft_);
+}
+
+/// @todo Extend PD tests for relayed messages.
+/// @todo Extend PD tests to cover same prefix buyt different length.
+
+// 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 destination 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_);
+}
+
+// This test checks that the relayed Rebind message is processed by the server
+// when sent to unicast address.
+TEST_F(RebindTest, relayedUnicast) {
+    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);
+    // Set the unicast destination address.
+    client.setDestAddress(IOAddress("2001:db8:1::1"));
+    // 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);
+}
+
+} // end of anonymous namespace

+ 9 - 0
src/lib/dhcp/option.h

@@ -254,6 +254,15 @@ public:
     /// @return shared_ptr to requested suoption
     OptionPtr getOption(uint16_t type);
 
+    /// @brief Returns all encapsulated options.
+    ///
+    /// @warning This function returns a reference to the container holding
+    /// encapsulated options, which is valid as long as the object which
+    /// returned it exists.
+    const OptionCollection& getOptions() const {
+        return (options_);
+    }
+
     /// Attempts to delete first suboption of requested type
     ///
     /// @param type Type of option to be deleted.

+ 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

+ 2 - 2
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -255,12 +255,12 @@ A debug message issued when the server is attempting to obtain a set of
 IPv4 leases from the memory file database for a client with the specified
 hardware address.
 
-% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv6 leases for IAID %1 and DUID %2
 A debug message issued when the server is attempting to obtain a set of
 IPv6 lease from the memory file database for a client with the specified
 IAID (Identity Association ID) and DUID (DHCP Unique Identifier).
 
-% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv6 leases for IAID %1, Subnet ID %2 and DUID %3
 A debug message issued when the server is attempting to obtain an IPv6
 lease from the memory file database for a client with the specified IAID
 (Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).

+ 7 - 1
src/lib/dhcpsrv/lease.cc

@@ -166,6 +166,12 @@ Lease6::Lease6(Type type, const isc::asiolink::IOAddress& addr,
     cltt_ = time(NULL);
 }
 
+Lease6::Lease6()
+    : Lease(isc::asiolink::IOAddress("::"), 0, 0, 0, 0, 0, false, false, ""),
+      type_(TYPE_NA), prefixlen_(0), iaid_(0), duid_(DuidPtr()),
+      preferred_lft_(0) {
+}
+
 const std::vector<uint8_t>&
 Lease6::getDuidVector() const {
     if (!duid_) {
@@ -180,7 +186,7 @@ std::string
 Lease6::toText() const {
     ostringstream stream;
 
-    stream << "Type:          " << typeToText(type_) << "(" 
+    stream << "Type:          " << typeToText(type_) << "("
            << static_cast<int>(type_) << ") ";
     stream << "Address:       " << addr_ << "\n"
            << "Prefix length: " << static_cast<int>(prefixlen_) << "\n"

+ 1 - 4
src/lib/dhcpsrv/lease.h

@@ -348,10 +348,7 @@ struct Lease6 : public Lease {
     /// @brief Constructor
     ///
     /// Initialize fields that don't have a default constructor.
-    Lease6() : Lease(isc::asiolink::IOAddress("::"), 0, 0, 0, 0, 0,
-                     false, false, ""),
-        type_(TYPE_NA) {
-    }
+    Lease6();
 
     /// @brief Returns a reference to a vector representing a DUID.
     ///