Browse Source

[master] Merge branch 'trac3677' (DHCPv6 renewals with reservations)

Conflicts:
	src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
Tomek Mrugalski 10 years ago
parent
commit
9ce50790c9

+ 2 - 3
src/bin/dhcp6/dhcp6_messages.mes

@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2014  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-2015  Internet Systems Consortium, Inc. ("ISC")
 #
 # Permission to use, copy, modify, and/or distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -91,8 +91,7 @@ to perform the DNS Update, which removes RRs from the DNS.
 This debug message is logged when FQDN mapping for a particular lease has
 been changed by the recent Request message. This mapping will be changed in DNS.
 
-% DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE FQDN for the renewed lease: %1 has changed
-New  values: hostname = %2, reverse mapping = %3, forward mapping = %4
+% DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE FQDN for the renewed lease: %1 has changed. New values: hostname = %2, reverse mapping = %3, forward mapping = %4
 This debug message is logged when FQDN mapping for a particular lease has been
 changed by the recent Renew message. This mapping will be changed in DNS.
 

+ 258 - 269
src/bin/dhcp6/dhcp6_srv.cc

@@ -75,8 +75,6 @@ struct Dhcp6Hooks {
     int hook_index_buffer6_receive_;///< index for "buffer6_receive" hook point
     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
@@ -86,8 +84,6 @@ struct Dhcp6Hooks {
         hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
         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");
@@ -1272,8 +1268,6 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         fake_allocation = true;
     }
 
-    CalloutHandlePtr callout_handle = getCalloutHandle(query);
-
     // 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
@@ -1301,7 +1295,7 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(),
                                     hint, Lease::TYPE_NA, do_fwd, do_rev,
                                     hostname, fake_allocation);
-    ctx.callout_handle_ = callout_handle;
+    ctx.callout_handle_ = getCalloutHandle(query);
 
     // Attempt to get MAC address using configured mechanisms.
     // It's ok if there response is NULL. Hardware address is optional in Lease6.
@@ -1343,27 +1337,25 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         // but this is considered waste of bandwidth as absence of status
         // code is considered a success.
 
-        Lease6Ptr old_lease;
-        if (!ctx.old_leases_.empty()) {
-            old_lease = *ctx.old_leases_.begin();
-        }
-        // Allocation engine may have returned an existing lease. If so, we
-        // have to check that the FQDN settings we provided are the same
-        // that were set. If they aren't, we will have to remove existing
-        // DNS records and update the lease with the new settings.
-        if (!fake_allocation && old_lease &&
-            !lease->hasIdenticalFqdn(*old_lease)) {
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
-                      DHCP6_DDNS_LEASE_ASSIGN_FQDN_CHANGE)
-                .arg(old_lease->toText())
-                .arg(hostname)
-                .arg(do_rev ? "true" : "false")
-                .arg(do_fwd ? "true" : "false");
+        if (!fake_allocation) {
+            Lease6Ptr old_lease;
+            if (!ctx.changed_leases_.empty()) {
+                old_lease = *ctx.changed_leases_.begin();
 
-            // Schedule removal of the existing lease.
-            createRemovalNameChangeRequest(old_lease);
-        }
+                // Allocation engine has returned an existing lease. If so, we
+                // have to check that the FQDN settings we provided are the same
+                // that were set. If they aren't, we will have to remove existing
+                // DNS records and update the lease with the new settings.
+                conditionalNCRRemoval(old_lease, lease, hostname, do_fwd, do_rev);
+            }
 
+            // We need to repeat that check for leases that used to be used, but
+            // are no longer valid.
+            if (!ctx.old_leases_.empty()) {
+                old_lease = *ctx.old_leases_.begin();
+                conditionalNCRRemoval(old_lease, lease, hostname, do_fwd, do_rev);
+            }
+        }
     } else {
         // Allocation engine did not allocate a lease. The engine logged
         // cause of that failure. The only thing left is to insert
@@ -1380,6 +1372,22 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     return (ia_rsp);
 }
 
+void
+Dhcpv6Srv::conditionalNCRRemoval(Lease6Ptr& old_lease, Lease6Ptr& new_lease,
+                                 const std::string& hostname, bool do_fwd,
+                                 bool do_rev) {
+    if (old_lease && !new_lease->hasIdenticalFqdn(*old_lease)) {
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_DDNS_LEASE_ASSIGN_FQDN_CHANGE)
+            .arg(old_lease->toText())
+            .arg(hostname)
+            .arg(do_rev ? "true" : "false")
+            .arg(do_fwd ? "true" : "false");
+
+        // Schedule removal of the existing lease.
+        createRemovalNameChangeRequest(old_lease);
+    }
+}
+
 OptionPtr
 Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
                        const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia) {
@@ -1423,8 +1431,6 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // allocation.
     bool fake_allocation = (query->getType() == DHCPV6_SOLICIT);
 
-    CalloutHandlePtr callout_handle = getCalloutHandle(query);
-
     // Use allocation engine to pick a lease for this client. Allocation engine
     // will try to honour the hint, but it is just a hint - some other address
     // may be used instead. If fake_allocation is set to false, the lease will
@@ -1432,7 +1438,7 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
     Lease6Collection old_leases;
     AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(), hint, Lease::TYPE_PD,
                                     false, false, string(), fake_allocation);
-    ctx.callout_handle_ = callout_handle;
+    ctx.callout_handle_ = getCalloutHandle(query);
 
     // Attempt to get MAC address using any of available mechanisms.
     // It's ok if there response is NULL. Hardware address is optional in Lease6
@@ -1513,152 +1519,144 @@ Dhcpv6Srv::extendIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         return (ia_rsp);
     }
 
-    Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
-                                                            *duid, ia->getIAID(),
-                                                            subnet->getID());
+    // Set up T1, T2 timers
+    ia_rsp->setT1(subnet->getT1());
+    ia_rsp->setT2(subnet->getT2());
 
-    // Client extending a lease that we don't know about.
-    if (!lease) {
-        // 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/subnet."));
-
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_EXTEND_NA_UNKNOWN)
-            .arg(duid->toText())
-            .arg(ia->getIAID())
-            .arg(subnet->toText());
+    // 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;
+    std::string hostname;
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+        Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+    if (fqdn) {
+        CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn, do_fwd, do_rev);
 
-        return (ia_rsp);
+        if (do_fwd || do_rev) {
+            hostname = fqdn->getDomainName();
+        }
     }
 
-    // Keep the old data in case the callout tells us to skip update.
-    Lease6 old_data = *lease;
+    // Create client context for this renewal
+    static const IOAddress none("::");
+    AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(),
+                                    none, Lease::TYPE_NA, do_fwd, do_rev,
+                                    hostname, false);
 
-    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;
+    ctx.callout_handle_ = getCalloutHandle(query);
+    ctx.query_ = query;
+    ctx.ia_rsp_ = ia_rsp;
 
-    } else {
+    // Attempt to get MAC address using configured mechanisms.
+    // It's ok if there response is NULL. Hardware address is optional in Lease6.
+    ctx.hwaddr_ = getMAC(query);
 
-        // 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) {
-            CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*fqdn,
-                                                                    do_fwd,
-                                                                    do_rev);
+    // Extract the addresses that the client is trying to obtain.
+    OptionCollection addrs = ia->getOptions();
+    for (OptionCollection::const_iterator it = addrs.begin();
+         it != addrs.end(); ++it) {
+        if (it->second->getType() != D6O_IAADDR) {
+            continue;
         }
-
-        std::string hostname;
-        if (do_fwd || do_rev) {
-            hostname = fqdn->getDomainName();
+        Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(it->second);
+        if (!iaaddr) {
+            // That's weird. Option code was ok, but the object type was not.
+            // As we use Dhcpv6Srv::unpackOptions() that is guaranteed to use
+            // Option7IAAddr for D6O_IAADDR, this should never happen. The only
+            // case would be with badly mis-implemented hook libraries that
+            // insert invalid option objects. There's no way to protect against
+            // this.
+            continue;
         }
+        ctx.hints_.push_back(make_pair(iaaddr->getAddress(), 128));
+    }
+
+    // We need to remember it as we'll be removing hints from this list as
+    // we extend, cancel or otherwise deal with the leases.
+    bool hints_present = !ctx.hints_.empty();
+
+    Lease6Collection leases = alloc_engine_->renewLeases6(ctx);
+
+    // Ok, now we have the leases extended. We have:
+    // - what the client tried to renew in ctx.hints_
+    // - what we actually assigned in leases
+    // - old leases that are no longer valid in ctx.old_leases_
+
+    // For all leases we have now, add the IAADDR with non-zero lifetimes.
+    for (Lease6Collection::const_iterator l = leases.begin(); l != leases.end(); ++l) {
+        Option6IAAddrPtr iaaddr(new Option6IAAddr(D6O_IAADDR,
+                                (*l)->addr_, (*l)->preferred_lft_, (*l)->valid_lft_));
+        ia_rsp->addOption(iaaddr);
+
+        // Now remove this address from the hints list.
+        AllocEngine::HintType tmp((*l)->addr_, 128);
+        ctx.hints_.erase(std::remove(ctx.hints_.begin(), ctx.hints_.end(), tmp),
+                         ctx.hints_.end());
+    }
+
+    // For the leases that we just retired, send the addresses with 0 lifetimes.
+    for (Lease6Collection::const_iterator l = ctx.old_leases_.begin();
+                                          l != ctx.old_leases_.end(); ++l) {
+        Option6IAAddrPtr iaaddr(new Option6IAAddr(D6O_IAADDR,
+                                                  (*l)->addr_, 0, 0));
+        ia_rsp->addOption(iaaddr);
+
+        // Now remove this address from the hints list.
+        AllocEngine::HintType tmp((*l)->addr_, 128);
+        ctx.hints_.erase(std::remove(ctx.hints_.begin(), ctx.hints_.end(), tmp),
+                         ctx.hints_.end());
 
         // 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)) {
+        if (((*l)->hostname_ != hostname) || ((*l)->fqdn_fwd_ != do_fwd) ||
+            ((*l)->fqdn_rev_ != do_rev)) {
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                       DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE)
-                .arg(lease->toText())
+                .arg((*l)->toText())
                 .arg(hostname)
                 .arg(do_rev ? "true" : "false")
                 .arg(do_fwd ? "true" : "false");
 
-            createRemovalNameChangeRequest(lease);
+            createRemovalNameChangeRequest(*l);
         }
-
-        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;
-
-        /// @todo: check if hardware address has changed since last update.
-        /// And modify lease->hwaddr_ if it did.
-
-        ia_rsp->setT1(subnet->getT1());
-        ia_rsp->setT2(subnet->getT2());
-
-        Option6IAAddrPtr addr(new Option6IAAddr(D6O_IAADDR, lease->addr_,
-                                                lease->preferred_lft_,
-                                                lease->valid_lft_));
-        ia_rsp->addOption(addr);
     }
 
-    bool skip = false;
-    // 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
-        callout_handle->deleteAllArguments();
-
-        // Pass the original packet
-        callout_handle->setArgument("query6", query);
-
-        // Pass the lease to be updated
-        callout_handle->setArgument("lease6", lease);
-
-        // Pass the IA option to be sent in response
-        callout_handle->setArgument("ia_na", ia_rsp);
-
-        // Call all installed callouts
-        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_EXTEND_SKIP)
-                .arg(query->getName());
-        }
+    // Finally, if there are any addresses requested that we haven't dealt with
+    // already, inform the client that he can't have them.
+    for (AllocEngine::HintContainer::const_iterator hint = ctx.hints_.begin();
+         hint != ctx.hints_.end(); ++hint) {
+        Option6IAAddrPtr iaaddr(new Option6IAAddr(D6O_IAADDR,
+                                                  hint->first, 0, 0));
+        ia_rsp->addOption(iaaddr);
     }
 
-    if (!skip) {
-        // 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);
+    // All is left is to insert the status code.
+    if (leases.empty()) {
+        // We did not assign anything. If client has sent something, then
+        // the status code is NoBinding, if he sent an empty IA_NA, then it's
+        // NoAddrsAvailable
+        if (hints_present) {
+            // Insert status code NoBinding to indicate that the lease does not
+            // exist for this client.
+            ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+                              "Sorry, no known leases for this duid/iaid/subnet."));
+
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_EXTEND_NA_UNKNOWN)
+                .arg(duid->toText())
+                .arg(ia->getIAID())
+                .arg(subnet->toText());
+        } else {
+            ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
+                              "Sorry, no addresses could be assigned at this time."));
         }
     } 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.
-        *lease = old_data;
+        // Yay, the client still has something. For now, let's not insert
+        // status-code=success to conserve bandwidth.
     }
 
     return (ia_rsp);
@@ -1697,141 +1695,132 @@ Dhcpv6Srv::extendIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
         // here should propagate to the main loop and cause the message to
         // be discarded.
         } else {
+
+            /// @todo: RFC3315bis will probably change that behavior. Client
+            /// may rebind prefixes and addresses at the same time.
             isc_throw(DHCPv6DiscardMessageError, "no subnet found for the"
                       " client sending Rebind to extend lifetime of the"
                       " prefix (DUID=" << duid->toText() << ", IAID="
-                      << ia->getIAID());
+                      << ia->getIAID() << ")");
         }
     }
 
-    // There is a subnet selected. Let's pick the lease.
-    Lease6Ptr lease =
-        LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD,
-                                              *duid, ia->getIAID(),
-                                              subnet->getID());
+    // Set up T1, T2 timers
+    ia_rsp->setT1(subnet->getT1());
+    ia_rsp->setT2(subnet->getT2());
 
-    // There is no binding for the client.
-    if (!lease) {
-        // Per RFC3633, section 12.2, if there is no binding and we are
-        // processing a Renew, the NoBinding status code should be returned.
+    // Create client context for this renewal
+    static const IOAddress none("::");
+    AllocEngine::ClientContext6 ctx(subnet, duid, ia->getIAID(), none,
+                                    Lease::TYPE_PD, false, false, string(""),
+                                    false);
+    ctx.callout_handle_ = getCalloutHandle(query);
+    ctx.query_ = query;
+    ctx.ia_rsp_ = ia_rsp;
+
+    // Attempt to get MAC address using configured mechanisms.
+    // It's ok if there response is NULL. Hardware address is optional in Lease6.
+    ctx.hwaddr_ = getMAC(query);
+
+    // Extract prefixes that the client is trying to renew.
+    OptionCollection addrs = ia->getOptions();
+    for (OptionCollection::const_iterator it = addrs.begin();
+         it != addrs.end(); ++it) {
+        if (it->second->getType() != D6O_IAPREFIX) {
+            continue;
+        }
+        Option6IAPrefixPtr prf = boost::dynamic_pointer_cast<Option6IAPrefix>(it->second);
+        if (!prf) {
+            // That's weird. Option code was ok, but the object type was not.
+            // As we use Dhcpv6Srv::unpackOptions() that is guaranteed to use
+            // Option7IAAddr for D6O_IAADDR, this should never happen. The only
+            // case would be with badly mis-implemented hook libraries that
+            // insert invalid option objects. There's no way to protect against
+            // this.
+            continue;
+        }
+
+        // Put the client's prefix into the hints list.
+        ctx.hints_.push_back(make_pair(prf->getAddress(), prf->getLength()));
+    }
+    // We need to remember it as we'll be removing hints from this list as
+    // we extend, cancel or otherwise deal with the leases.
+    bool hints_present = !ctx.hints_.empty();
+
+    // Call Allocation Engine and attempt to renew leases. Number of things
+    // may happen. Leases may be extended, revoked (if the lease is no longer
+    // valid or reserved for someone else), or new leases may be added.
+    // Important parameters are:
+    // - returned container - current valid leases
+    // - old_leases - leases that used to be, but are no longer valid
+    // - changed_leases - leases that have FQDN changed (not really important
+    //                    in PD context)
+    Lease6Collection leases = alloc_engine_->renewLeases6(ctx);
+
+    // For all the leases we have now, add the IAPPREFIX with non-zero lifetimes
+    for (Lease6Collection::const_iterator l = leases.begin(); l != leases.end(); ++l) {
+        Option6IAPrefixPtr prf(new Option6IAPrefix(D6O_IAPREFIX,
+                               (*l)->addr_, (*l)->prefixlen_,
+                               (*l)->preferred_lft_, (*l)->valid_lft_));
+        ia_rsp->addOption(prf);
+
+        // Now remove this address from the hints list.
+        AllocEngine::HintType tmp((*l)->addr_, (*l)->prefixlen_);
+        ctx.hints_.erase(std::remove(ctx.hints_.begin(), ctx.hints_.end(), tmp),
+                                     ctx.hints_.end());
+    }
+
+    /// @todo: Maybe we should iterate over ctx.old_leases_, i.e. the leases
+    /// that used to be valid, but they are not anymore.
+
+    // For all the leases the client had requested, but we didn't assign, put them with
+    // zero lifetimes
+    // Finally, if there are any addresses requested that we haven't dealt with
+    // already, inform the client that he can't have them.
+    for (AllocEngine::HintContainer::const_iterator prefix = ctx.hints_.begin();
+         prefix != ctx.hints_.end(); ++prefix) {
+        OptionPtr prefix_opt(new Option6IAPrefix(D6O_IAPREFIX, prefix->first,
+                                                 prefix->second, 0, 0));
+        ia_rsp->addOption(prefix_opt);
+    }
+
+    // All is left is to insert the status code.
+    if (leases.empty()) {
         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.
+            // We did not assign anything. If client has sent something, then
+            // the status code is NoBinding, if he sent an empty IA_NA, then it's
+            // NoAddrsAvailable
+            if (hints_present) {
+                // Insert status code NoBinding to indicate that the lease does not
+                // exist for this client.
+                ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+                    "Sorry, no known PD leases for this duid/iaid/subnet."));
+
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_EXTEND_PD_UNKNOWN_SUBNET)
+                    .arg(duid->toText())
+                    .arg(ia->getIAID())
+                    .arg(subnet->toText());
+            } else {
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_EXTEND_PD_NO_BINDING)
+                    .arg(query->getName()).arg(duid->toText()).arg(ia->getIAID())
+                    .arg(subnet->toText());
+                ia_rsp->addOption(createStatusCode(STATUS_NoPrefixAvail,
+                    "Sorry, no prefixes could be assigned at this time."));
+            }
         } else {
+            // Per RFC3633, section 12.2, if there is no binding and we are
+            // processing Rebind, the message has to be discarded (assuming that
+            // the server doesn't know if the prefix in the IA_PD option is
+            // appropriate for the client's link). The exception being thrown
+            // here should propagate to the main loop and cause the message to
+            // be discarded.
             isc_throw(DHCPv6DiscardMessageError, "no binding found for the"
                       " DUID=" << duid->toText() << ", IAID="
                       << ia->getIAID() << ", subnet="
                       << subnet->toText() << " when processing a Rebind"
                       " message with IA_PD option");
         }
-        return (ia_rsp);
-
-    }
-
-    // Keep the old data in case the callout tells us to skip update.
-    Lease6 old_data = *lease;
-
-    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;
-
-    } 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);
-
-    }
-
-
-    bool skip = false;
-    // Execute all callouts registered for packet6_send
-    // 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
-        callout_handle->deleteAllArguments();
-
-        // Pass the original packet
-        callout_handle->setArgument("query6", query);
-
-        // Pass the lease to be updated
-        callout_handle->setArgument("lease6", lease);
-
-        // Pass the IA option to be sent in response
-        callout_handle->setArgument("ia_pd", ia_rsp);
-
-        // Call all installed callouts
-        HooksManager::callCallouts(hook_point,
-                                   *callout_handle);
-
-        // Remember hook's instruction whether we want to skip update or not
-        skip = callout_handle->getSkip();
-    }
-
-    if (!skip) {
-        // 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/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
-        // in memfile, so the actual update is performed when we manipulate
-        // fields of returned Lease6Ptr, the actual updateLease6() is no-op.
-        *lease = old_data;
     }
 
     return (ia_rsp);

+ 16 - 1
src/bin/dhcp6/dhcp6_srv.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -667,6 +667,21 @@ private:
     /// as a programmatic error.
     void generateFqdn(const Pkt6Ptr& answer);
 
+
+    /// @brief Triggers removal Name Change Request if FQDN data changes in leases
+    ///
+    /// If there are any differences (different fwd or rev flags, or different
+    /// hostname) a DNS update for removing entry will be generated.
+    ///
+    /// @param old_lease old version of the lease
+    /// @param new_lease new version of the lease (may be NULL)
+    /// @param hostname specifies hostname (for printing purposes)
+    /// @param do_fwd specifies if reverse updates are enabled (for printing purposes)
+    /// @param do_rev specifies if reverse updates are enabled (for printing purposes)
+    void conditionalNCRRemoval(Lease6Ptr& old_lease, Lease6Ptr& new_lease,
+                               const std::string& hostname,
+                               bool do_fwd, bool do_rev);
+
     /// @brief Allocation Engine.
     /// Pointer to the allocation engine that we are currently using
     /// It must be a pointer, because we will support changing engines

+ 3 - 2
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -88,7 +88,8 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
             case D6O_IAADDR:
                 {
                     Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
-                        Option6IAAddr>(ia->getOption(D6O_IAADDR));
+                        Option6IAAddr>(ia_opt);
+
                     if (!iaaddr) {
                         // There is no address. This IA option may simply
                         // contain a status code, so let's just reset the
@@ -113,7 +114,7 @@ Dhcp6Client::applyRcvdConfiguration(const Pkt6Ptr& reply) {
             case D6O_IAPREFIX:
                 {
                     Option6IAPrefixPtr iaprefix = boost::dynamic_pointer_cast<
-                        Option6IAPrefix>(ia->getOption(D6O_IAPREFIX));
+                        Option6IAPrefix>(ia_opt);
                     if (!iaprefix) {
                         // There is no prefix. This IA option may simply
                         // contain a status code, so let's just reset the

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

@@ -406,6 +406,12 @@ public:
         oro_.push_back(option_code);
     }
 
+    /// @brief returns client-id
+    /// @return client-id
+    DuidPtr getDuid() const {
+        return (duid_);
+    }
+
 private:
 
     /// @brief Applies the new leases for the client.

+ 7 - 4
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -81,7 +81,8 @@ TEST_F(NakedDhcpv6SrvTest, SolicitNoSubnet) {
     Pkt6Ptr reply = srv.processSolicit(sol);
 
     // check that we get the right NAK
-    checkNakResponse (reply, DHCPV6_ADVERTISE, 1234, STATUS_NoAddrsAvail);
+    checkNakResponse(reply, DHCPV6_ADVERTISE, 1234, STATUS_NoAddrsAvail,
+                     0, 0);
 }
 
 // This test verifies that incoming REQUEST can be handled properly when
@@ -113,7 +114,8 @@ TEST_F(NakedDhcpv6SrvTest, RequestNoSubnet) {
     Pkt6Ptr reply = srv.processRequest(req);
 
     // check that we get the right NAK
-    checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoAddrsAvail);
+    checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoAddrsAvail,
+                      0, 0);
 }
 
 // This test verifies that incoming RENEW can be handled properly, even when
@@ -148,7 +150,8 @@ TEST_F(NakedDhcpv6SrvTest, RenewNoSubnet) {
     Pkt6Ptr reply = srv.processRenew(req);
 
     // check that we get the right NAK
-    checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding);
+    checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding,
+                      0, 0);
 }
 
 // This test verifies that incoming RELEASE can be handled properly, even when
@@ -183,7 +186,7 @@ TEST_F(NakedDhcpv6SrvTest, ReleaseNoSubnet) {
     Pkt6Ptr reply = srv.processRelease(req);
 
     // check that we get the right NAK
-    checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding);
+    checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding, 0, 0);
 }
 
 // Test verifies that the Dhcpv6_srv class can be instantiated. It checks a mode

+ 61 - 11
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -285,10 +285,10 @@ Dhcpv6SrvTest::testRenewBasic(Lease::Type type, const std::string& existing_addr
     }
 
     // Check that T1, T2, preferred, valid and cltt were really updated
-    EXPECT_EQ(l->t1_, subnet_->getT1());
-    EXPECT_EQ(l->t2_, subnet_->getT2());
-    EXPECT_EQ(l->preferred_lft_, subnet_->getPreferred());
-    EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+    EXPECT_EQ(subnet_->getT1(), l->t1_);
+    EXPECT_EQ(subnet_->getT2(), l->t2_);
+    EXPECT_EQ(subnet_->getPreferred(), l->preferred_lft_);
+    EXPECT_EQ(subnet_->getValid(), l->valid_lft_);
 
     // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
     int32_t cltt = static_cast<int32_t>(l->cltt_);
@@ -349,7 +349,7 @@ Dhcpv6SrvTest::testRenewReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_?? was returned and that there's proper status code
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
 
     // Check that there is no lease added
     l = LeaseMgrFactory::instance().getLease6(type, addr);
@@ -375,7 +375,7 @@ Dhcpv6SrvTest::testRenewReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_?? was returned and that there's proper status code
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
 
     // There is a iaid mis-match, so server should respond that there is
     // no such address to renew.
@@ -395,7 +395,7 @@ Dhcpv6SrvTest::testRenewReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_?? was returned and that there's proper status code
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, subnet_->getT1(), subnet_->getT2());
 
     lease = LeaseMgrFactory::instance().getLease6(type, addr);
     ASSERT_TRUE(lease);
@@ -457,7 +457,7 @@ Dhcpv6SrvTest::testReleaseBasic(Lease::Type type, const IOAddress& existing,
 
     // Check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
-    checkIA_NAStatusCode(ia, STATUS_Success);
+    checkIA_NAStatusCode(ia, STATUS_Success, 0, 0);
     checkMsgStatusCode(reply, STATUS_Success);
 
     // There should be no address returned in RELEASE (see RFC3315, 18.2.6)
@@ -528,7 +528,7 @@ Dhcpv6SrvTest::testReleaseReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_NA/IA_PD was returned and that there's status code in it
     boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, 0, 0);
     checkMsgStatusCode(reply, STATUS_NoBinding);
 
     // Check that the lease is not there
@@ -556,7 +556,7 @@ Dhcpv6SrvTest::testReleaseReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_?? was returned and that there's proper status code
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, 0, 0);
     checkMsgStatusCode(reply, STATUS_NoBinding);
 
     // Check that the lease is still there
@@ -580,7 +580,7 @@ Dhcpv6SrvTest::testReleaseReject(Lease::Type type, const IOAddress& addr) {
     // Check that IA_?? was returned and that there's proper status code
     ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
     ASSERT_TRUE(ia);
-    checkIA_NAStatusCode(ia, STATUS_NoBinding);
+    checkIA_NAStatusCode(ia, STATUS_NoBinding, 0, 0);
     checkMsgStatusCode(reply, STATUS_NoBinding);
 
     // Check that the lease is still there
@@ -651,6 +651,56 @@ Dhcpv6SrvTest::compareOptions(const isc::dhcp::OptionPtr& option1,
     return (!memcmp(buf1.getData(), buf2.getData(), buf1.getLength()));
 }
 
+void
+NakedDhcpv6SrvTest::checkIA_NAStatusCode(
+    const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
+    uint16_t expected_status_code, uint32_t expected_t1, uint32_t expected_t2)
+{
+    // Make sure there is no address assigned. Depending on the situation,
+    // the server will either not return the address at all and sometimes
+    // it will return it with zeroed lifetimes.
+    dhcp::OptionCollection options = ia->getOptions();
+    for (isc::dhcp::OptionCollection::iterator opt = options.begin();
+         opt != options.end(); ++opt) {
+        if (opt->second->getType() != D6O_IAADDR) {
+            continue;
+        }
+
+        dhcp::Option6IAAddrPtr addr =
+            boost::dynamic_pointer_cast<isc::dhcp::Option6IAAddr>(opt->second);
+        ASSERT_TRUE(addr);
+
+        EXPECT_EQ(0, addr->getPreferred());
+        EXPECT_EQ(0, addr->getValid());
+    }
+
+    // T1, T2 should NOT be zeroed. draft-ietf-dhc-dhcpv6-stateful-issues-10,
+    // section 4.4.6 says says that T1,T2 should be consistent along all
+    // provided IA options.
+    EXPECT_EQ(expected_t1, ia->getT1());
+    EXPECT_EQ(expected_t2, ia->getT2());
+
+    isc::dhcp::OptionCustomPtr status =
+        boost::dynamic_pointer_cast<isc::dhcp::OptionCustom>
+        (ia->getOption(D6O_STATUS_CODE));
+
+    // It is ok to not include status success as this is the default
+    // behavior
+    if (expected_status_code == STATUS_Success && !status) {
+        return;
+    }
+
+    EXPECT_TRUE(status);
+
+    if (status) {
+        // We don't have dedicated class for status code, so let's
+        // just interpret first 2 bytes as status. Remainder of the
+        // status code option content is just a text explanation
+        // what went wrong.
+        EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
+                  status->readInteger<uint16_t>(0));
+    }
+}
 
 }; // end of isc::test namespace
 }; // end of isc namespace

+ 9 - 34
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -27,6 +27,7 @@
 #include <dhcp/option6_iaprefix.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_custom.h>
+#include <dhcp/option.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -211,7 +212,8 @@ public:
     void checkNakResponse(const isc::dhcp::Pkt6Ptr& rsp,
                           uint8_t expected_message_type,
                           uint32_t expected_transid,
-                          uint16_t expected_status_code)
+                          uint16_t expected_status_code,
+                          uint32_t expected_t1, uint32_t expected_t2)
     {
         // Check if we get response at all
         checkResponse(rsp, expected_message_type, expected_transid);
@@ -225,7 +227,8 @@ public:
             boost::dynamic_pointer_cast<isc::dhcp::Option6IA>(option_ia_na);
         ASSERT_TRUE(ia);
 
-        checkIA_NAStatusCode(ia, expected_status_code);
+        checkIA_NAStatusCode(ia, expected_status_code, expected_t1,
+                             expected_t2);
     }
 
     // Checks that server rejected IA_NA, i.e. that it has no addresses and
@@ -238,36 +241,8 @@ public:
     // as this is the default result and it saves bandwidth)
     void checkIA_NAStatusCode
         (const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
-         uint16_t expected_status_code)
-    {
-        // Make sure there is no address assigned.
-        EXPECT_FALSE(ia->getOption(D6O_IAADDR));
-
-        // T1, T2 should be zeroed
-        EXPECT_EQ(0, ia->getT1());
-        EXPECT_EQ(0, ia->getT2());
-
-        isc::dhcp::OptionCustomPtr status =
-            boost::dynamic_pointer_cast<isc::dhcp::OptionCustom>
-                (ia->getOption(D6O_STATUS_CODE));
-
-        // It is ok to not include status success as this is the default
-        // behavior
-        if (expected_status_code == STATUS_Success && !status) {
-            return;
-        }
-
-        EXPECT_TRUE(status);
-
-        if (status) {
-            // We don't have dedicated class for status code, so let's
-            // just interpret first 2 bytes as status. Remainder of the
-            // status code option content is just a text explanation
-            // what went wrong.
-            EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
-                      status->readInteger<uint16_t>(0));
-        }
-    }
+         uint16_t expected_status_code, uint32_t expected_t1,
+         uint32_t expected_t2);
 
     void checkMsgStatusCode(const isc::dhcp::Pkt6Ptr& msg,
                             uint16_t expected_status)
@@ -397,8 +372,8 @@ public:
         // an ostream, which means it can't be used in EXPECT_EQ.
         EXPECT_TRUE(subnet_->inPool(type, addr->getAddress()));
         EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
-        EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
-        EXPECT_EQ(addr->getValid(), subnet_->getValid());
+        EXPECT_EQ(subnet_->getPreferred(), addr->getPreferred());
+        EXPECT_EQ(subnet_->getValid(), addr->getValid());
     }
 
     // Checks if the lease sent to client is present in the database

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2014  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -414,7 +414,7 @@ public:
             ASSERT_NO_THROW(reply = srv_->processRequest(req));
 
         } else if (msg_type == DHCPV6_RENEW) {
-            ASSERT_NO_THROW(reply = srv_->processRequest(req));
+            ASSERT_NO_THROW(reply = srv_->processRenew(req));
 
         } else if (msg_type == DHCPV6_RELEASE) {
             // For Release no lease will be acquired so we have to leave

+ 46 - 10
src/bin/dhcp6/tests/rebind_unittest.cc

@@ -262,15 +262,21 @@ TEST_F(RebindTest, directClientChangingSubnet) {
     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);
+    EXPECT_TRUE(lease_client.addr_ == lease_client2.addr_);
+    EXPECT_EQ(0, lease_client2.preferred_lft_);
+    EXPECT_EQ(0, lease_client2.valid_lft_);
+
     // Make sure, that the lease that client has, is matching the lease
     // in the lease database.
     Lease6Ptr lease_server2 = checkLease(lease_client2);
@@ -480,13 +486,31 @@ TEST_F(RebindTest, relayedClientChangingAddress) {
         << "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);
+    // Get the client's leases. He should get two addresses:
+    // the first one for the bogus 3000::100 address with 0 lifetimes.
+    // the second one with the actual lease with non-zero lifetimes.
+    ASSERT_EQ(2, client.getLeaseNum());
+
+    // Let's check the first one
+    Lease6 lease_client1 = client.getLease(0);
+    Lease6 lease_client2 = client.getLease(1);
+
+    if (lease_client1.addr_.toText() != "3000::100") {
+        lease_client1 = client.getLease(1);
+        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_);
+    EXPECT_EQ(0, lease_client1.valid_lft_);
+    EXPECT_EQ(0, lease_client1.preferred_lft_);
+
+    // Let's check the second lease
+    // The lifetimes should be set to 0, as an explicit notification to the
+    // client to stop using invalid prefix.
+    EXPECT_NE(0, lease_client2.valid_lft_);
+    EXPECT_NE(0, lease_client2.preferred_lft_);
+
     // Check that server still has the same lease.
     Lease6Ptr lease_server = checkLease(lease_client);
     EXPECT_TRUE(lease_server);
@@ -616,12 +640,24 @@ TEST_F(RebindTest, directClientPDChangingPrefix) {
         " 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);
+    ASSERT_EQ(2, client.getLeaseNum());
+
+    // Client should get two entries. One with the invalid address he requested
+    // with zeroed lifetimes and a second one with the actual prefix he has
+    // with non-zero lifetimes.
+    Lease6 lease_client1 = client.getLease(0);
+    Lease6 lease_client2 = client.getLease(1);
+
     // The lifetimes should be set to 0, as an explicit notification to the
     // client to stop using invalid prefix.
-    EXPECT_EQ(0, lease_client2.valid_lft_);
-    EXPECT_EQ(0, lease_client2.preferred_lft_);
+    EXPECT_EQ(0, lease_client1.valid_lft_);
+    EXPECT_EQ(0, lease_client1.preferred_lft_);
+
+    // The lifetimes should be set to 0, as an explicit notification to the
+    // client to stop using invalid prefix.
+    EXPECT_NE(0, lease_client2.valid_lft_);
+    EXPECT_NE(0, lease_client2.preferred_lft_);
+
     // Check that server still has the same lease.
     Lease6Ptr lease_server = checkLease(lease_client);
     ASSERT_TRUE(lease_server);

+ 216 - 21
src/lib/dhcpsrv/alloc_engine.cc

@@ -17,6 +17,7 @@
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp/dhcp6.h>
 
 #include <hooks/server_hooks.h>
 #include <hooks/hooks_manager.h>
@@ -37,12 +38,16 @@ struct AllocEngineHooks {
     int hook_index_lease4_select_; ///< index for "lease4_receive" hook point
     int hook_index_lease4_renew_;  ///< index for "lease4_renew" hook point
     int hook_index_lease6_select_; ///< index for "lease6_receive" hook point
+    int hook_index_lease6_renew_;  ///< index for "lease6_renew" hook point
+    int hook_index_lease6_rebind_; ///< index for "lease6_rebind" hook point
 
     /// Constructor that registers hook points for AllocationEngine
     AllocEngineHooks() {
         hook_index_lease4_select_ = HooksManager::registerHook("lease4_select");
         hook_index_lease4_renew_  = HooksManager::registerHook("lease4_renew");
         hook_index_lease6_select_ = HooksManager::registerHook("lease6_select");
+        hook_index_lease6_renew_   = HooksManager::registerHook("lease6_renew");
+        hook_index_lease6_rebind_   = HooksManager::registerHook("lease6_rebind");
     }
 };
 
@@ -381,9 +386,7 @@ AllocEngine::allocateLeases6(ClientContext6& ctx) {
 
             if (!leases.empty()) {
                 // Return old leases so the server can see what has changed.
-                return (updateFqdnData(leases, ctx.fwd_dns_update_,
-                                       ctx.rev_dns_update_,
-                                       ctx.hostname_, ctx.fake_allocation_));
+                return (updateFqdnData(ctx, leases));
             }
 
             // If leases are empty at this stage, it means that we used to have
@@ -485,7 +488,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
     IOAddress hint("::");
     if (!ctx.hints_.empty()) {
         /// @todo: We support only one hint for now
-        hint = ctx.hints_[0];
+        hint = ctx.hints_[0].first;
     }
 
     // check if the hint is in pool and is available
@@ -598,6 +601,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
         if (ctx.type_ == Lease::TYPE_PD) {
             Pool6Ptr pool = boost::dynamic_pointer_cast<Pool6>(
                 ctx.subnet_->getPool(ctx.type_, candidate, false));
+            /// @todo: verify that the pool is non-null
             prefix_len = pool->getLength();
         }
 
@@ -666,17 +670,45 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx, Lease6Collection& exis
         IOAddress addr = resv->second.getPrefix();
         uint8_t prefix_len = resv->second.getPrefixLen();
 
+        // Check if already have this lease on the existing_leases list.
+        for (Lease6Collection::const_iterator l = existing_leases.begin();
+             l != existing_leases.end(); ++l) {
+
+            // Ok, we already have a lease for this reservation and it's usable
+            if (((*l)->addr_ == addr) && (*l)->valid_lft_ != 0) {
+                return;
+            }
+        }
+
         // If there's a lease for this address, let's not create it.
         // It doesn't matter whether it is for this client or for someone else.
-        if (LeaseMgrFactory::instance().getLease6(ctx.type_, addr)) {
-            continue;
-        }
+        if (!LeaseMgrFactory::instance().getLease6(ctx.type_, addr)) {
+            // Ok, let's create a new lease...
+            Lease6Ptr lease = createLease6(ctx, addr, prefix_len);
+
+            // ... and add it to the existing leases list.
+            existing_leases.push_back(lease);
 
-        // Ok, let's create a new lease...
-        Lease6Ptr lease = createLease6(ctx, addr, prefix_len);
+            if (ctx.type_ == Lease::TYPE_NA) {
+                LOG_INFO(dhcpsrv_logger, DHCPSRV_HR_RESERVED_ADDR_GRANTED)
+                    .arg(addr.toText()).arg(ctx.duid_->toText());
+            } else {
+                LOG_INFO(dhcpsrv_logger, DHCPSRV_HR_RESERVED_PREFIX_GRANTED)
+                    .arg(addr.toText()).arg(static_cast<int>(prefix_len))
+                    .arg(ctx.duid_->toText());
+            }
 
-        // ... and add it to the existing leases list.
-        existing_leases.push_back(lease);
+            // We found a lease for this client and this IA. Let's return.
+            // Returning after the first lease was assigned is useful if we
+            // have multiple reservations for the same client. If the client
+            // sends 2 IAs, the first time we call allocateReservedLeases6 will
+            // use the first reservation and return. The second time, we'll
+            // go over the first reservation, but will discover that there's
+            // a lease corresponding to it and will skip it and then pick
+            // the second reservation and turn it into the lease. This approach
+            // would work for any number of reservations.
+            return;
+        }
     }
 }
 
@@ -710,11 +742,25 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
 
         // Ok, we have a problem. This host has a lease that is reserved
         // for someone else. We need to recover from this.
+        if (ctx.type_ == Lease::TYPE_NA) {
+            LOG_INFO(dhcpsrv_logger, DHCPSRV_HR_REVOKED_ADDR6_LEASE)
+                .arg((*candidate)->addr_.toText()).arg(ctx.duid_->toText())
+                .arg(host->getIdentifierAsText());
+        } else {
+            LOG_INFO(dhcpsrv_logger, DHCPSRV_HR_REVOKED_PREFIX6_LEASE)
+                .arg((*candidate)->addr_.toText())
+                .arg(static_cast<int>((*candidate)->prefixlen_))
+                .arg(ctx.duid_->toText())
+                .arg(host->getIdentifierAsText());
+        }
 
         // Remove this lease from LeaseMgr
         LeaseMgrFactory::instance().deleteLease((*candidate)->addr_);
 
-        /// @todo: Probably trigger a hook here
+        // In principle, we could trigger a hook here, but we will do this
+        // only if we get serious complaints from actual users. We want the
+        // conflict resolution procedure to really work and user libraries
+        // should not interfere with it.
 
         // Add this to the list of removed leases.
         ctx.old_leases_.push_back(*candidate);
@@ -1603,22 +1649,19 @@ AllocEngine::updateLease4Information(const Lease4Ptr& lease,
 }
 
 Lease6Collection
-AllocEngine::updateFqdnData(const Lease6Collection& leases,
-                            const bool fwd_dns_update,
-                            const bool rev_dns_update,
-                            const std::string& hostname,
-                            const bool fake_allocation) {
+AllocEngine::updateFqdnData(ClientContext6& ctx, const Lease6Collection& leases) {
     Lease6Collection updated_leases;
     for (Lease6Collection::const_iterator lease_it = leases.begin();
          lease_it != leases.end(); ++lease_it) {
         Lease6Ptr lease(new Lease6(**lease_it));
-        lease->fqdn_fwd_ = fwd_dns_update;
-        lease->fqdn_rev_ = rev_dns_update;
-        lease->hostname_ = hostname;
-        if (!fake_allocation &&
+        lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+        lease->fqdn_rev_ = ctx.rev_dns_update_;
+        lease->hostname_ = ctx.hostname_;
+        if (!ctx.fake_allocation_ &&
             ((lease->fqdn_fwd_ != (*lease_it)->fqdn_fwd_) ||
              (lease->fqdn_rev_ != (*lease_it)->fqdn_rev_) ||
              (lease->hostname_ != (*lease_it)->hostname_))) {
+            ctx.changed_leases_.push_back(*lease_it);
             LeaseMgrFactory::instance().updateLease6(lease);
         }
         updated_leases.push_back(lease);
@@ -1636,6 +1679,158 @@ AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
     return (alloc->second);
 }
 
+Lease6Collection
+AllocEngine::renewLeases6(ClientContext6& ctx) {
+    try {
+        if (!ctx.subnet_) {
+            isc_throw(InvalidOperation, "Subnet is required for allocation");
+        }
+
+        if (!ctx.duid_) {
+            isc_throw(InvalidOperation, "DUID is mandatory for allocation");
+        }
+
+        // Check which host reservation mode is supported in this subnet.
+        Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
+
+        // Check if there's a host reservation for this client. Attempt to get
+        // host info only if reservations are not disabled.
+        if (hr_mode != Subnet::HR_DISABLED) {
+
+            ctx.host_ = HostMgr::instance().get6(ctx.subnet_->getID(), ctx.duid_,
+                                                 ctx.hwaddr_);
+        } else {
+            // Host reservations disabled? Then explicitly set host to NULL
+            ctx.host_.reset();
+        }
+
+        // Check if there are any leases for this client.
+        Lease6Collection leases = LeaseMgrFactory::instance()
+            .getLeases6(ctx.type_, *ctx.duid_, ctx.iaid_, ctx.subnet_->getID());
+
+        if (!leases.empty()) {
+            // Check if the existing leases are reserved for someone else.
+            // If they're not, we're ok to keep using them.
+            removeNonmatchingReservedLeases6(ctx, leases);
+        }
+
+        if (ctx.host_) {
+            // If we have host reservation, allocate those leases.
+            allocateReservedLeases6(ctx, leases);
+
+            // There's one more check to do. Let's remove leases that are not
+            // matching reservations, i.e. if client X has address A, but there's
+            // a reservation for address B, we should release A and reassign B.
+            // Caveat: do this only if we have at least one reserved address.
+            removeNonreservedLeases6(ctx, leases);
+        }
+
+        // If we happen to removed all leases, get something new for this guy.
+        // Depending on the configuration, we may enable or disable granting
+        // new leases during renewals. This is controlled with the
+        // allow_new_leases_in_renewals_ field.
+        if (leases.empty() && ctx.allow_new_leases_in_renewals_) {
+            leases = allocateUnreservedLeases6(ctx);
+        }
+
+        // Extend all existing leases that passed all checks.
+        for (Lease6Collection::iterator l = leases.begin(); l != leases.end(); ++l) {
+            extendLease6(ctx, *l);
+        }
+
+        return (leases);
+
+    } catch (const isc::Exception& e) {
+
+        // Some other error, return an empty lease.
+        LOG_ERROR(dhcpsrv_logger, DHCPSRV_RENEW6_ERROR).arg(e.what());
+    }
+
+    return (Lease6Collection());
+}
+
+void
+AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
+
+    if (!lease || !ctx.subnet_) {
+        return;
+    }
+
+    // Check if the lease still belongs to the subnet. If it doesn't,
+    // we'll need to remove it.
+    if ((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) {
+        // Oh dear, the lease is no longer valid. We need to get rid of it.
+
+        // Remove this lease from LeaseMgr
+        LeaseMgrFactory::instance().deleteLease(lease->addr_);
+
+        // Add it to the removed leases list.
+        ctx.old_leases_.push_back(lease);
+
+        return;
+    }
+
+    // Keep the old data in case the callout tells us to skip update.
+    Lease6 old_data = *lease;
+
+    lease->preferred_lft_ = ctx.subnet_->getPreferred();
+    lease->valid_lft_ = ctx.subnet_->getValid();
+    lease->t1_ = ctx.subnet_->getT1();
+    lease->t2_ = ctx.subnet_->getT2();
+    lease->cltt_ = time(NULL);
+    lease->hostname_ = ctx.hostname_;
+    lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+    lease->fqdn_rev_ = ctx.rev_dns_update_;
+    lease->hwaddr_ = ctx.hwaddr_;
+
+    bool skip = false;
+    // Get the callouts specific for the processed message and execute them.
+    int hook_point = ctx.query_->getType() == DHCPV6_RENEW ?
+        Hooks.hook_index_lease6_renew_ : Hooks.hook_index_lease6_rebind_;
+    if (HooksManager::calloutsPresent(hook_point)) {
+        CalloutHandlePtr callout_handle = ctx.callout_handle_;
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass the original packet
+        callout_handle->setArgument("query6", ctx.query_);
+
+        // Pass the lease to be updated
+        callout_handle->setArgument("lease6", lease);
+
+        // Pass the IA option to be sent in response
+        if (lease->type_ == Lease::TYPE_NA) {
+            callout_handle->setArgument("ia_na", ctx.ia_rsp_);
+        } else {
+            callout_handle->setArgument("ia_pd", ctx.ia_rsp_);
+        }
+
+        // Call all installed callouts
+        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(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+                      DHCPSRV_HOOK_LEASE6_EXTEND_SKIP)
+                .arg(ctx.query_->getName());
+        }
+    }
+
+    if (!skip) {
+        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.
+        *lease = old_data;
+    }
+}
+
 AllocEngine::~AllocEngine() {
     // no need to delete allocator. smart_ptr will do the trick for us
 }

+ 98 - 23
src/lib/dhcpsrv/alloc_engine.h

@@ -18,6 +18,8 @@
 #include <asiolink/io_address.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/option6_ia.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -301,6 +303,15 @@ protected:
         }
     };
 
+    /// @brief Defines a single hint (an address + prefix-length).
+    ///
+    /// This is an entry that represents what the client had requested,
+    /// either an address or a prefix. Prefix length is 128 for regular
+    /// addresses.
+    typedef std::pair<isc::asiolink::IOAddress, uint8_t> HintType;
+
+    /// @brief Container for client's hints.
+    typedef std::vector<HintType> HintContainer;
 
     /// @brief Context information for the DHCPv6 leases allocation.
     ///
@@ -344,7 +355,7 @@ protected:
         ///
         /// There will typically be just one address, but the protocol allows
         /// more than one address or prefix for each IA container.
-        std::vector<isc::asiolink::IOAddress> hints_;
+        HintContainer hints_;
 
         /// @brief A boolean value which indicates that server takes
         ///        responsibility for the forward DNS Update for this lease
@@ -381,16 +392,44 @@ protected:
         /// and give a new one to this client.
         Lease6Collection old_leases_;
 
+        /// @brief A pointer to any leases that have changed FQDN information.
+        ///
+        /// This list may contain old versions of the leases that are still
+        /// valid. In particular, it will contain a lease if the client's
+        /// FQDN has changed.
+        Lease6Collection changed_leases_;
+
         /// @brief A pointer to the object identifying host reservations.
         ///
         /// May be NULL if there are no reservations.
         ConstHostPtr host_;
 
+        /// @brief A pointer to the client's message
+        ///
+        /// This is used exclusively for hook purposes.
+        Pkt6Ptr query_;
+
+        /// @brief A pointer to the IA_NA/IA_PD option to be sent in response
+        Option6IAPtr ia_rsp_;
+
+
+        /// @brief Specifies whether new leases in Renew/Rebind are allowed
+        ///
+        /// This field controls what to do when renewing or rebinding client
+        /// does not have any leases. RFC3315 and the stateful-issues draft does
+        /// not specify it and it is left up to the server configuration policy.
+        /// False (the default) means that the client will not get any new
+        /// unreserved leases if his existing leases are no longer suitable.
+        /// True means that the allocation engine will do its best to assign
+        /// something.
+        bool allow_new_leases_in_renewals_;
+
         /// @brief Default constructor.
         ClientContext6()
            : subnet_(), duid_(), iaid_(0), type_(Lease::TYPE_NA), hwaddr_(),
              hints_(), fwd_dns_update_(false), rev_dns_update_(false), hostname_(""),
-             callout_handle_(), fake_allocation_(false), old_leases_(), host_() {
+             callout_handle_(), fake_allocation_(false), old_leases_(), host_(),
+             query_(), ia_rsp_(), allow_new_leases_in_renewals_(false) {
         }
 
         /// @brief Constructor with parameters.
@@ -418,11 +457,17 @@ protected:
                        const Lease::Type type, const bool fwd_dns, const bool
                        rev_dns, const std::string& hostname, const bool
                        fake_allocation):
-        subnet_(subnet), duid_(duid), iaid_(iaid), type_(type), hwaddr_(),
+            subnet_(subnet), duid_(duid), iaid_(iaid), type_(type), hwaddr_(),
             hints_(), fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
             hostname_(hostname), fake_allocation_(fake_allocation),
-            old_leases_(), host_() {
-            hints_.push_back(hint);
+            old_leases_(), host_(), query_(), ia_rsp_(),
+            allow_new_leases_in_renewals_(false){
+
+            static asiolink::IOAddress any("::");
+
+            if (hint != any) {
+                hints_.push_back(std::make_pair(hint, 128));
+            }
             // callout_handle, host pointers initiated to NULL by their
             // respective constructors.
         }
@@ -644,6 +689,31 @@ protected:
     Lease6Collection
     allocateLeases6(ClientContext6& ctx);
 
+
+    /// @brief Renews existing DHCPv6 leases for a given IA.
+    ///
+    /// This method updates the leases associated with a specified IA container.
+    /// It will extend the leases under normal circumstances, but sometimes
+    /// there may be reasons why not to do so. Such a reasons may be:
+    /// - client attempts to renew an address that is not valid
+    /// - client attempts to renew an address that is now reserved for someone
+    ///   else (see host reservation)
+    /// - client's leases does not match his reservations
+    ///
+    /// This method will call  the lease4_renew callout.
+    ///
+    /// @param ctx Message processing context. It holds various information
+    /// extracted from the client's message and required to allocate a lease.
+    /// In particular, @ref ClientContext6::hints_ provides list of addresses or
+    /// prefixes the client had sent. @ref ClientContext6::old_leases_ will
+    /// contain removed leases in this case.
+    ///
+    /// @return Returns renewed lease.
+    Lease6Collection
+    renewLeases6(ClientContext6& ctx);
+
+
+
     /// @brief returns allocator for a given pool type
     /// @param type type of pool (V4, IA, TA or PD)
     /// @throw BadValue if allocator for a given type is missing
@@ -867,10 +937,11 @@ private:
     ///        responsibility for the reverse DNS Update for this lease
     ///        (if true).
     /// @ref ClientContext6::hostname_ A fully qualified domain-name of the client.
-    /// @ref ClientContext6::callout_handle_ a callout handle (used in hooks). A lease callouts
-    ///        will be executed if this parameter is passed.
-    /// @ref ClientContext6::fake_allocation_ is this real i.e. REQUEST (false) or just picking
-    ///        an address for SOLICIT that is not really allocated (true)
+    /// @ref ClientContext6::callout_handle_ a callout handle (used in hooks). A
+    ///        lease callouts will be executed if this parameter is passed.
+    /// @ref ClientContext6::fake_allocation_ is this real i.e. REQUEST (false)
+    ///        or just picking an address for SOLICIT that is not really
+    ///        allocated (true)
     ///
     /// @return refreshed lease
     /// @throw BadValue if trying to recycle lease that is still valid
@@ -880,25 +951,17 @@ private:
 
     /// @brief Updates FQDN data for a collection of leases.
     ///
+    /// @param ctx IPv6 client context (old versions of the leases that had
+    ///            FQDN data changed will be stored in ctx.changed_leases_,
+    ///            ctx.fwd_dns_update, ctx.rev_dns_update, ctx.hostname_
+    ///            and ctx.fake_allocation_ will be used.
     /// @param leases Collection of leases for which FQDN data should be
     /// updated.
-    /// @param fwd_dns_update Boolean value which indicates whether forward FQDN
-    /// update was performed for each lease (true) or not (false).
-    /// @param rev_dns_update Boolean value which indicates whether reverse FQDN
-    /// update was performed for each lease (true) or not (false).
-    /// @param hostname Client hostname associated with a lease.
-    /// @param fake_allocation Boolean value which indicates that it is a real
-    /// lease allocation, e.g. Request message is processed (false), or address
-    /// is just being picked as a result of processing Solicit (true). In the
-    /// latter case, the FQDN data should not be updated in the lease database.
     ///
     /// @return Collection of leases with updated FQDN data. Note that returned
     /// collection holds updated FQDN data even for fake allocation.
-    Lease6Collection updateFqdnData(const Lease6Collection& leases,
-                                    const bool fwd_dns_update,
-                                    const bool rev_dns_update,
-                                    const std::string& hostname,
-                                    const bool fake_allocation);
+    Lease6Collection updateFqdnData(ClientContext6& ctx,
+                                    const Lease6Collection& leases);
 
     /// @brief Utility function that removes all leases with a specified address
     /// @param container A collection of Lease6 pointers
@@ -908,6 +971,18 @@ private:
     removeLeases(Lease6Collection& container,
                  const asiolink::IOAddress& addr);
 
+    /// @brief Extends specified IPv6 lease
+    ///
+    /// This method attempts to extend the lease. It will call the lease6_renew
+    /// or lease6_rebind hooks (depending on the client's message specified in
+    /// ctx.query). The lease will be extended in LeaseMgr, unless the hooks
+    /// library will set the skip flag.
+    ///
+    /// @param ctx client context that passes all necessary information. See
+    ///        @ref ClientContext6 for details.
+    /// @param lease IPv6 lease to be extended.
+    void extendLease6(ClientContext6& ctx, Lease6Ptr lease);
+
     /// @brief a pointer to currently used allocator
     ///
     /// For IPv4, there will be only one allocator: TYPE_V4

+ 2 - 1
src/lib/dhcpsrv/cfg_hosts.cc

@@ -215,7 +215,8 @@ CfgHosts::getAllInternal6(const SubnetID& subnet_id,
 
     // Let's get all reservations that match subnet_id, address.
     const HostContainer6Index1& idx = hosts6_.get<1>();
-    HostContainer6Index1Range r = idx.equal_range(boost::make_tuple(subnet_id, address));
+    HostContainer6Index1Range r = make_pair(idx.lower_bound(boost::make_tuple(subnet_id, address)),
+                                            idx.upper_bound(boost::make_tuple(subnet_id, address)));
 
     // For each IPv6 reservation, add the host to the results list. Fortunately,
     // in all sane cases, there will be only one such host. (Each host can have

+ 41 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -228,12 +228,46 @@ hook point sets the skip flag. It means that the server was told that
 no lease4 should be assigned. The server will not put that lease in its
 database and the client will get a NAK packet.
 
+% DHCPSRV_HOOK_LEASE6_EXTEND_SKIP DHCPv6 lease lifetime was not extended because a callout set the skip flag for message %1
+This debug message is printed when a callout installed on lease6_renew
+or the 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 the client requested renewal of multiple
+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.
+
 % DHCPSRV_HOOK_LEASE6_SELECT_SKIP Lease6 (non-temporary) creation was skipped, because of callout skip flag.
 This debug message is printed when a callout installed on lease6_select
 hook point sets the skip flag. It means that the server was told that
 no lease6 should be assigned. The server will not put that lease in its
 database and the client will get a NoAddrsAvail for that IA_NA option.
 
+% DHCPSRV_HR_RESERVED_ADDR_GRANTED reserved address %1 was was assigned to client (duid=%2)
+This informational message signals that the specified client was assigned the address
+reserved for it.
+
+% DHCPSRV_HR_RESERVED_PREFIX_GRANTED reserved prefix %1/%2 was was assigned to client (duid=%3)
+This informational message signals that the specified client was assigned the prefix
+reserved for it.
+
+% DHCPSRV_HR_REVOKED_ADDR6_LEASE address %1 was revoked from client %2 as it is reserved for client %3
+This informational message is an indication that the specified IPv6
+address was used by client A but it is now reserved for client B. Client
+A has been told to stop using it so that it can be leased to client B.
+This is a normal occurrence during conflict resolution, which can occur
+in cases such as the system administrator adding a reservation for an
+address that is currently in use by another client.  The server will fully
+recover from this situation, but clients will change their addresses.
+
+% DHCPSRV_HR_REVOKED_PREFIX6_LEASE Prefix %1/%2 was revoked from client %3 as it is reserved for client %4
+This informational message is an indication that the specified IPv6
+prefix was used by client A but it is now reserved for client B. Client
+A has been told to stop using it so that it can be leased to client B.
+This is a normal occurrence during conflict resolution, which can occur
+in cases such as the system administrator adding a reservation for an
+address that is currently in use by another client.  The server will fully
+recover from this situation, but clients will change their prefixes.
+
 % DHCPSRV_INVALID_ACCESS invalid database access string: %1
 This is logged when an attempt has been made to parse a database access string
 and the attempt ended in error.  The access string in question - which
@@ -552,6 +586,13 @@ lease from the PostgreSQL database for the specified address.
 A debug message issued when the server is attempting to update IPv6
 lease from the PostgreSQL database for the specified address.
 
+% DHCPSRV_RENEW6_ERROR allocation engine experienced error with attempting to renew a lease: %1
+This error message indicates that an error was experienced during Renew
+or Rebind processing. Additional explanation is provided with this
+message. Depending on its nature, manual intervention may be required to
+continue processing messages from this particular client; other clients
+will be unaffected.
+
 % DHCPSRV_UNEXPECTED_NAME database access parameters passed through '%1', expected 'lease-database'
 The parameters for access the lease database were passed to the server through
 the named configuration parameter, but the code was expecting them to be

+ 17 - 0
src/lib/dhcpsrv/host.cc

@@ -230,5 +230,22 @@ Host::addClientClassInternal(ClientClasses& classes,
     }
 }
 
+std::string
+Host::getIdentifierAsText() const {
+    std::string txt;
+    if (hw_address_) {
+        txt = "hwaddr=" + hw_address_->toText(false);
+    } else {
+        txt = "duid=";
+        if (duid_) {
+            txt += duid_->toText();
+        } else {
+            txt += "(none)";
+        }
+    }
+
+    return (txt);
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 8 - 0
src/lib/dhcpsrv/host.h

@@ -295,10 +295,18 @@ public:
         return (duid_);
     }
 
+    /// @brief Returns the identifier (MAC or DUID) in binary form.
+    /// @return const reference to MAC or DUID in vector<uint8_t> form
     const std::vector<uint8_t>& getIdentifier() const;
 
+    /// @brief Returns the identifier type.
+    /// @return the identifier type
     IdentifierType getIdentifierType() const;
 
+    /// @brief Returns host identifier (mac or DUID) in printer friendly form.
+    /// @return text form of the identifier, including (duid= or mac=).
+    std::string getIdentifierAsText() const;
+
     /// @brief Sets new IPv4 subnet identifier.
     ///
     /// @param ipv4_subnet_id New subnet identifier.

+ 1 - 1
src/lib/dhcpsrv/lease_mgr.h

@@ -294,7 +294,7 @@ public:
     /// transition from single to multi address per IA. It may also be used
     /// in other cases where at most one lease is expected in the database.
     ///
-    /// It is a wrapper around getLease6(), which returns a collection of
+    /// It is a wrapper around getLeases6(), which returns a collection of
     /// leases. That collection can be converted into a single pointer if
     /// there are no leases (NULL pointer) or one lease (use that single lease).
     /// If there are more leases in the collection, the function will

+ 5 - 2
src/lib/dhcpsrv/tests/Makefile.am

@@ -18,7 +18,7 @@ AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
 CLEANFILES = *.gcno *.gcda
 
 TESTS_ENVIRONMENT = \
-	$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+        $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
 
 TESTS =
 if HAVE_GTEST
@@ -54,7 +54,10 @@ TESTS += libdhcpsrv_unittests
 
 libdhcpsrv_unittests_SOURCES  = run_unittests.cc
 libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
-libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
+libdhcpsrv_unittests_SOURCES += alloc_engine_utils.cc alloc_engine_utils.h
+libdhcpsrv_unittests_SOURCES += alloc_engine_hooks_unittest.cc
+libdhcpsrv_unittests_SOURCES += alloc_engine4_unittest.cc
+libdhcpsrv_unittests_SOURCES += alloc_engine6_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_hosts_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc

File diff suppressed because it is too large
+ 1347 - 0
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc


File diff suppressed because it is too large
+ 1238 - 0
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc


+ 480 - 0
src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc

@@ -0,0 +1,480 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <dhcpsrv/tests/test_utils.h>
+
+#include <hooks/server_hooks.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_manager.h>
+
+using namespace std;
+using namespace isc::hooks;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief helper class used in Hooks testing in AllocEngine6
+///
+/// It features a couple of callout functions and buffers to store
+/// the data that is accessible via callouts.
+class HookAllocEngine6Test : public AllocEngine6Test {
+public:
+    HookAllocEngine6Test() {
+        resetCalloutBuffers();
+    }
+
+    virtual ~HookAllocEngine6Test() {
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+            "lease6_select");
+    }
+
+    /// @brief clears out buffers, so callouts can store received arguments
+    void resetCalloutBuffers() {
+        callback_name_ = string("");
+        callback_subnet6_.reset();
+        callback_fake_allocation_ = false;
+        callback_lease6_.reset();
+        callback_argument_names_.clear();
+        callback_addr_original_ = IOAddress("::");
+        callback_addr_updated_ = IOAddress("::");
+    }
+
+    /// callback that stores received callout name and received values
+    static int
+    lease6_select_callout(CalloutHandle& callout_handle) {
+
+        callback_name_ = string("lease6_select");
+
+        callout_handle.getArgument("subnet6", callback_subnet6_);
+        callout_handle.getArgument("fake_allocation", callback_fake_allocation_);
+        callout_handle.getArgument("lease6", callback_lease6_);
+
+        callback_addr_original_ = callback_lease6_->addr_;
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// callback that overrides the lease with different values
+    static int
+    lease6_select_different_callout(CalloutHandle& callout_handle) {
+
+        // Let's call the basic callout, so it can record all parameters
+        lease6_select_callout(callout_handle);
+
+        // Now we need to tweak the least a bit
+        Lease6Ptr lease;
+        callout_handle.getArgument("lease6", lease);
+        callback_addr_updated_ = addr_override_;
+        lease->addr_ = callback_addr_updated_;
+        lease->t1_ = t1_override_;
+        lease->t2_ = t2_override_;
+        lease->preferred_lft_ = pref_override_;
+        lease->valid_lft_ = valid_override_;
+
+        return (0);
+    }
+
+    // Values to be used in callout to override lease6 content
+    static const IOAddress addr_override_;
+    static const uint32_t t1_override_;
+    static const uint32_t t2_override_;
+    static const uint32_t pref_override_;
+    static const uint32_t valid_override_;
+
+    // Callback will store original and overridden values here
+    static IOAddress callback_addr_original_;
+    static IOAddress callback_addr_updated_;
+
+    // Buffers (callback will store received values here)
+    static string callback_name_;
+    static Subnet6Ptr callback_subnet6_;
+    static Lease6Ptr callback_lease6_;
+    static bool callback_fake_allocation_;
+    static vector<string> callback_argument_names_;
+};
+
+// For some reason intialization within a class makes the linker confused.
+// linker complains about undefined references if they are defined within
+// the class declaration.
+const IOAddress HookAllocEngine6Test::addr_override_("2001:db8::abcd");
+const uint32_t HookAllocEngine6Test::t1_override_ = 6000;
+const uint32_t HookAllocEngine6Test::t2_override_ = 7000;
+const uint32_t HookAllocEngine6Test::pref_override_ = 8000;
+const uint32_t HookAllocEngine6Test::valid_override_ = 9000;
+
+IOAddress HookAllocEngine6Test::callback_addr_original_("::");
+IOAddress HookAllocEngine6Test::callback_addr_updated_("::");
+
+string HookAllocEngine6Test::callback_name_;
+Subnet6Ptr HookAllocEngine6Test::callback_subnet6_;
+Lease6Ptr HookAllocEngine6Test::callback_lease6_;
+bool HookAllocEngine6Test::callback_fake_allocation_;
+vector<string> HookAllocEngine6Test::callback_argument_names_;
+
+// This test checks if the lease6_select callout is executed and expected
+// parameters as passed.
+TEST_F(HookAllocEngine6Test, lease6_select) {
+
+    // Note: The following order is working as expected:
+    // 1. create AllocEngine (that register hook points)
+    // 2. call loadLibraries()
+    //
+    // This order, however, causes segfault in HooksManager
+    // 1. call loadLibraries()
+    // 2. create AllocEngine (that register hook points)
+
+    // Create allocation engine (hook names are registered in its ctor)
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Initialize Hooks Manager
+    vector<string> libraries; // no libraries at this time
+    HooksManager::loadLibraries(libraries);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_select", lease6_select_callout));
+
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+    Lease6Ptr lease;
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
+                                    Lease::TYPE_NA, false, false, "", false);
+    ctx.callout_handle_ = callout_handle;
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // Do all checks on the lease
+    checkLease6(lease, Lease::TYPE_NA, 128);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+                                                               lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Check that callouts were indeed called
+    EXPECT_EQ("lease6_select", callback_name_);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    ASSERT_TRUE(callback_lease6_);
+    detailCompareLease(callback_lease6_, from_mgr);
+
+    ASSERT_TRUE(callback_subnet6_);
+    EXPECT_EQ(subnet_->toText(), callback_subnet6_->toText());
+
+    EXPECT_FALSE(callback_fake_allocation_);
+
+    // Check if all expected parameters are reported. It's a bit tricky, because
+    // order may be different. If the test starts failing, because someone tweaked
+    // hooks engine, we'll have to implement proper vector matching (ignoring order)
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("fake_allocation");
+    expected_argument_names.push_back("lease6");
+    expected_argument_names.push_back("subnet6");
+
+    sort(callback_argument_names_.begin(), callback_argument_names_.end());
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+}
+
+// This test checks if lease6_select callout is able to override the values
+// in a lease6.
+TEST_F(HookAllocEngine6Test, change_lease6_select) {
+
+    // Make sure that the overridden values are different than the ones from
+    // subnet originally used to create the lease
+    ASSERT_NE(t1_override_, subnet_->getT1());
+    ASSERT_NE(t2_override_, subnet_->getT2());
+    ASSERT_NE(pref_override_, subnet_->getPreferred());
+    ASSERT_NE(valid_override_, subnet_->getValid());
+    ASSERT_FALSE(subnet_->inRange(addr_override_));
+
+    // Create allocation engine (hook names are registered in its ctor)
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Initialize Hooks Manager
+    vector<string> libraries; // no libraries at this time
+    HooksManager::loadLibraries(libraries);
+
+    // Install a callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_select", lease6_select_different_callout));
+
+    // Normally, dhcpv6_srv would passed the handle when calling allocateLeases6,
+    // but in tests we need to create it on our own.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+    // Call allocateLeases6. Callouts should be triggered here.
+    Lease6Ptr lease;
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
+                                    Lease::TYPE_NA, false, false, "", false);
+    ctx.callout_handle_ = callout_handle;
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // See if the values overridden by callout are there
+    EXPECT_TRUE(lease->addr_.equals(addr_override_));
+    EXPECT_EQ(t1_override_, lease->t1_);
+    EXPECT_EQ(t2_override_, lease->t2_);
+    EXPECT_EQ(pref_override_, lease->preferred_lft_);
+    EXPECT_EQ(valid_override_, lease->valid_lft_);
+
+    // Now check if the lease is in the database
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+                                                               lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Check if values in the database are overridden
+    EXPECT_TRUE(from_mgr->addr_.equals(addr_override_));
+    EXPECT_EQ(t1_override_, from_mgr->t1_);
+    EXPECT_EQ(t2_override_, from_mgr->t2_);
+    EXPECT_EQ(pref_override_, from_mgr->preferred_lft_);
+    EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
+}
+
+
+/// @brief helper class used in Hooks testing in AllocEngine4
+///
+/// It features a couple of callout functions and buffers to store
+/// the data that is accessible via callouts.
+///
+/// Note: lease4_renew callout is tested from DHCPv4 server.
+/// See HooksDhcpv4SrvTest.basic_lease4_renew in
+/// src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+class HookAllocEngine4Test : public AllocEngine4Test {
+public:
+    HookAllocEngine4Test() {
+        resetCalloutBuffers();
+    }
+
+    virtual ~HookAllocEngine4Test() {
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+            "lease4_select");
+    }
+
+    /// @brief clears out buffers, so callouts can store received arguments
+    void resetCalloutBuffers() {
+        callback_name_ = string("");
+        callback_subnet4_.reset();
+        callback_fake_allocation_ = false;
+        callback_lease4_.reset();
+        callback_argument_names_.clear();
+        callback_addr_original_ = IOAddress("::");
+        callback_addr_updated_ = IOAddress("::");
+    }
+
+    /// callback that stores received callout name and received values
+    static int
+    lease4_select_callout(CalloutHandle& callout_handle) {
+
+        callback_name_ = string("lease4_select");
+
+        callout_handle.getArgument("subnet4", callback_subnet4_);
+        callout_handle.getArgument("fake_allocation", callback_fake_allocation_);
+        callout_handle.getArgument("lease4", callback_lease4_);
+
+        callback_addr_original_ = callback_lease4_->addr_;
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// callback that overrides the lease with different values
+    static int
+    lease4_select_different_callout(CalloutHandle& callout_handle) {
+
+        // Let's call the basic callout, so it can record all parameters
+        lease4_select_callout(callout_handle);
+
+        // Now we need to tweak the least a bit
+        Lease4Ptr lease;
+        callout_handle.getArgument("lease4", lease);
+        callback_addr_updated_ = addr_override_;
+        lease->addr_ = callback_addr_updated_;
+        lease->t1_ = t1_override_;
+        lease->t2_ = t2_override_;
+        lease->valid_lft_ = valid_override_;
+
+        return (0);
+    }
+
+    // Values to be used in callout to override lease4 content
+    static const IOAddress addr_override_;
+    static const uint32_t t1_override_;
+    static const uint32_t t2_override_;
+    static const uint32_t valid_override_;
+
+    // Callback will store original and overridden values here
+    static IOAddress callback_addr_original_;
+    static IOAddress callback_addr_updated_;
+
+    // Buffers (callback will store received values here)
+    static string callback_name_;
+    static Subnet4Ptr callback_subnet4_;
+    static Lease4Ptr callback_lease4_;
+    static bool callback_fake_allocation_;
+    static vector<string> callback_argument_names_;
+};
+
+// For some reason intialization within a class makes the linker confused.
+// linker complains about undefined references if they are defined within
+// the class declaration.
+const IOAddress HookAllocEngine4Test::addr_override_("192.0.3.1");
+const uint32_t HookAllocEngine4Test::t1_override_ = 4000;
+const uint32_t HookAllocEngine4Test::t2_override_ = 7000;
+const uint32_t HookAllocEngine4Test::valid_override_ = 9000;
+
+IOAddress HookAllocEngine4Test::callback_addr_original_("::");
+IOAddress HookAllocEngine4Test::callback_addr_updated_("::");
+
+string HookAllocEngine4Test::callback_name_;
+Subnet4Ptr HookAllocEngine4Test::callback_subnet4_;
+Lease4Ptr HookAllocEngine4Test::callback_lease4_;
+bool HookAllocEngine4Test::callback_fake_allocation_;
+vector<string> HookAllocEngine4Test::callback_argument_names_;
+
+// This test checks if the lease4_select callout is executed and expected
+// parameters as passed.
+TEST_F(HookAllocEngine4Test, lease4_select) {
+
+    // Note: The following order is working as expected:
+    // 1. create AllocEngine (that register hook points)
+    // 2. call loadLibraries()
+    //
+    // This order, however, causes segfault in HooksManager
+    // 1. call loadLibraries()
+    // 2. create AllocEngine (that register hook points)
+
+    // Create allocation engine (hook names are registered in its ctor)
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+                                                 100, false)));
+    ASSERT_TRUE(engine);
+
+    // Initialize Hooks Manager
+    vector<string> libraries; // no libraries at this time
+    HooksManager::loadLibraries(libraries);
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease4_select", lease4_select_callout));
+
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+    Lease4Ptr lease = engine->allocateLease4(subnet_, clientid_, hwaddr_,
+                                               IOAddress("0.0.0.0"),
+                                               false, false, "",
+                                               false, callout_handle,
+                                               old_lease_);
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // Do all checks on the lease
+    checkLease4(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Check that callouts were indeed called
+    EXPECT_EQ("lease4_select", callback_name_);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    ASSERT_TRUE(callback_lease4_);
+    detailCompareLease(callback_lease4_, from_mgr);
+
+    ASSERT_TRUE(callback_subnet4_);
+    EXPECT_EQ(subnet_->toText(), callback_subnet4_->toText());
+
+    EXPECT_EQ(callback_fake_allocation_, false);
+
+    // Check if all expected parameters are reported. It's a bit tricky, because
+    // order may be different. If the test starts failing, because someone tweaked
+    // hooks engine, we'll have to implement proper vector matching (ignoring order)
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("fake_allocation");
+    expected_argument_names.push_back("lease4");
+    expected_argument_names.push_back("subnet4");
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+}
+
+// This test checks if lease4_select callout is able to override the values
+// in a lease4.
+TEST_F(HookAllocEngine4Test, change_lease4_select) {
+
+    // Make sure that the overridden values are different than the ones from
+    // subnet originally used to create the lease
+    ASSERT_NE(t1_override_, subnet_->getT1());
+    ASSERT_NE(t2_override_, subnet_->getT2());
+    ASSERT_NE(valid_override_, subnet_->getValid());
+    ASSERT_FALSE(subnet_->inRange(addr_override_));
+
+    // Create allocation engine (hook names are registered in its ctor)
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+                                                 100, false)));
+    ASSERT_TRUE(engine);
+
+    // Initialize Hooks Manager
+    vector<string> libraries; // no libraries at this time
+    HooksManager::loadLibraries(libraries);
+
+    // Install a callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease4_select", lease4_select_different_callout));
+
+    // Normally, dhcpv4_srv would passed the handle when calling allocateLease4,
+    // but in tests we need to create it on our own.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+    // Call allocateLease4. Callouts should be triggered here.
+    Lease4Ptr lease = engine->allocateLease4(subnet_, clientid_, hwaddr_,
+                                             IOAddress("0.0.0.0"),
+                                             false, false, "",
+                                             false, callout_handle,
+                                             old_lease_);
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // See if the values overridden by callout are there
+    EXPECT_TRUE(lease->addr_.equals(addr_override_));
+    EXPECT_EQ(t1_override_, lease->t1_);
+    EXPECT_EQ(t2_override_, lease->t2_);
+    EXPECT_EQ(valid_override_, lease->valid_lft_);
+
+    // Now check if the lease is in the database
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Check if values in the database are overridden
+    EXPECT_TRUE(from_mgr->addr_.equals(addr_override_));
+    EXPECT_EQ(t1_override_, from_mgr->t1_);
+    EXPECT_EQ(t2_override_, from_mgr->t2_);
+    EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
+}
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc

File diff suppressed because it is too large
+ 0 - 3304
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc


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

@@ -0,0 +1,343 @@
+// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcp/duid.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
+
+#include <dhcpsrv/tests/test_utils.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+
+#include <hooks/hooks_manager.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+#include <set>
+#include <time.h>
+
+using namespace std;
+using namespace isc::hooks;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+AllocEngine6Test::AllocEngine6Test() {
+    CfgMgr::instance().clear();
+
+    duid_ = DuidPtr(new DUID(std::vector<uint8_t>(8, 0x42)));
+    iaid_ = 42;
+
+    // Initialize a subnet and short address pool.
+    initSubnet(IOAddress("2001:db8:1::"),
+               IOAddress("2001:db8:1::10"),
+               IOAddress("2001:db8:1::20"));
+
+    initFqdn("", false, false);
+
+    factory_.create("type=memfile universe=6 persist=false");
+}
+
+void
+AllocEngine6Test::initSubnet(const asiolink::IOAddress& subnet,
+                    const asiolink::IOAddress& pool_start,
+                    const asiolink::IOAddress& pool_end) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    subnet_ = Subnet6Ptr(new Subnet6(subnet, 56, 1, 2, 3, 4));
+    pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, pool_start, pool_end));
+
+    subnet_->addPool(pool_);
+
+    pd_pool_ = Pool6Ptr(new Pool6(Lease::TYPE_PD, subnet, 56, 64));
+    subnet_->addPool(pd_pool_);
+
+    cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
+    cfg_mgr.commit();
+}
+
+Lease6Collection
+AllocEngine6Test::allocateTest(AllocEngine& engine, const Pool6Ptr& pool,
+                               const asiolink::IOAddress& hint, bool fake,
+                               bool in_pool) {
+    Lease::Type type = pool->getType();
+    uint8_t expected_len = pool->getLength();
+
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, hint, type,
+                                    false, false, "", fake);
+
+    Lease6Collection leases;
+    EXPECT_NO_THROW(leases = engine.allocateLeases6(ctx));
+
+    for (Lease6Collection::iterator it = leases.begin(); it != leases.end(); ++it) {
+
+        // Do all checks on the lease
+        checkLease6(*it, type, expected_len, in_pool, in_pool);
+
+        // Check that the lease is indeed in LeaseMgr
+        Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(type,
+                                                                   (*it)->addr_);
+        if (!fake) {
+            // This is a real (REQUEST) allocation, the lease must be in the DB
+            EXPECT_TRUE(from_mgr) << "Lease " << from_mgr->addr_.toText()
+                                  << " returned by allocateLeases6(), "
+                                  << "but was not present in LeaseMgr";
+            if (!from_mgr) {
+                return (leases);
+            }
+
+            // Now check that the lease in LeaseMgr has the same parameters
+            detailCompareLease(*it, from_mgr);
+        } else {
+            // This is a fake (SOLICIT) allocation, the lease must not be in DB
+            EXPECT_FALSE(from_mgr) << "Lease " << from_mgr->addr_.toText()
+                                   << " returned by allocateLeases6(), "
+                                   << "was present in LeaseMgr (expected to be"
+                                   << " not present)";
+            if (from_mgr) {
+                return (leases);
+            }
+        }
+    }
+
+    return (leases);
+}
+
+Lease6Ptr
+AllocEngine6Test::simpleAlloc6Test(const Pool6Ptr& pool, const IOAddress& hint,
+                                   bool fake, bool in_pool) {
+    Lease::Type type = pool->getType();
+    uint8_t expected_len = pool->getLength();
+
+    boost::scoped_ptr<AllocEngine> engine;
+    EXPECT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
+                                                 100)));
+    // We can't use ASSERT macros in non-void methods
+    EXPECT_TRUE(engine);
+    if (!engine) {
+        return (Lease6Ptr());
+    }
+
+    Lease6Ptr lease;
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, hint, type,
+                                    false, false, "", fake);
+
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+    // Check that we got a lease
+    EXPECT_TRUE(lease);
+    if (!lease) {
+        return (Lease6Ptr());
+    }
+
+    // Do all checks on the lease
+    checkLease6(lease, type, expected_len, in_pool, in_pool);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(type, lease->addr_);
+    if (!fake) {
+        // This is a real (REQUEST) allocation, the lease must be in the DB
+        EXPECT_TRUE(from_mgr);
+        if (!from_mgr) {
+            return (Lease6Ptr());
+        }
+
+        // Now check that the lease in LeaseMgr has the same parameters
+        detailCompareLease(lease, from_mgr);
+    } else {
+        // This is a fake (SOLICIT) allocation, the lease must not be in DB
+        EXPECT_FALSE(from_mgr);
+        if (from_mgr) {
+            return (Lease6Ptr());
+        }
+    }
+
+    return (lease);
+}
+
+Lease6Collection
+AllocEngine6Test::renewTest(AllocEngine& engine, const Pool6Ptr& pool,
+                            AllocEngine::HintContainer& hints,
+                            bool allow_new_leases_in_renewal,
+                            bool in_pool) {
+
+    Lease::Type type = pool->getType();
+    uint8_t expected_len = pool->getLength();
+
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, IOAddress("::"),
+                                    type, false, false, "", false);
+    ctx.hints_ = hints;
+    ctx.query_.reset(new Pkt6(DHCPV6_RENEW, 123));
+    ctx.allow_new_leases_in_renewals_ = allow_new_leases_in_renewal;
+
+    Lease6Collection leases = engine.renewLeases6(ctx);
+
+    for (Lease6Collection::iterator it = leases.begin(); it != leases.end(); ++it) {
+
+        // Do all checks on the lease
+        checkLease6(*it, type, expected_len, in_pool, in_pool);
+
+        // Check that the lease is indeed in LeaseMgr
+        Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(type,
+                                                                   (*it)->addr_);
+
+        // This is a real (REQUEST) allocation, the lease must be in the DB
+        EXPECT_TRUE(from_mgr) << "Lease " << from_mgr->addr_.toText()
+                              << " returned by allocateLeases6(), "
+                              << "but was not present in LeaseMgr";
+        if (!from_mgr) {
+            return (leases);
+        }
+
+        // Now check that the lease in LeaseMgr has the same parameters
+        detailCompareLease(*it, from_mgr);
+    }
+
+    return (leases);
+}
+
+void
+AllocEngine6Test::allocWithUsedHintTest(Lease::Type type, IOAddress used_addr,
+                                        IOAddress requested,
+                                        uint8_t expected_pd_len) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Let's create a lease and put it in the LeaseMgr
+    DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
+    time_t now = time(NULL);
+    Lease6Ptr used(new Lease6(type, used_addr,
+                              duid2, 1, 2, 3, 4, now, subnet_->getID()));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Another client comes in and request an address that is in pool, but
+    // unfortunately it is used already. The same address must not be allocated
+    // twice.
+    Lease6Ptr lease;
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, requested, type,
+                                    false, false, "", false);
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // Allocated address must be different
+    EXPECT_NE(used_addr, lease->addr_);
+
+    // We should NOT get what we asked for, because it is used already
+    EXPECT_NE(requested, lease->addr_);
+
+    // Do all checks on the lease
+    checkLease6(lease, type, expected_pd_len);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+                                                               lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+}
+
+void
+AllocEngine6Test::allocBogusHint6(Lease::Type type, asiolink::IOAddress hint,
+                                  uint8_t expected_pd_len) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Client would like to get a 3000::abc lease, which does not belong to any
+    // supported lease. Allocation engine should ignore it and carry on
+    // with the normal allocation
+    Lease6Ptr lease;
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, iaid_, hint, type, false,
+                                    false, "", false);
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // We should NOT get what we asked for, because it is used already
+    EXPECT_NE(hint, lease->addr_);
+
+    // Do all checks on the lease
+    checkLease6(lease, type, expected_pd_len);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+                                                               lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    detailCompareLease(lease, from_mgr);
+}
+
+void
+AllocEngine4Test::initSubnet(const asiolink::IOAddress& pool_start,
+                             const asiolink::IOAddress& pool_end) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+    pool_ = Pool4Ptr(new Pool4(pool_start, pool_end));
+    subnet_->addPool(pool_);
+
+    cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+}
+
+AllocEngine4Test::AllocEngine4Test() {
+    // Create fresh instance of the HostMgr, and drop any previous HostMgr state.
+    HostMgr::instance().create();
+
+    clientid_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x44)));
+    uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
+
+    // Let's use odd hardware type to check if there is no Ethernet
+    // hardcoded anywhere.
+    hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
+
+    // Allocate different MAC address for the tests that require two
+    // different MAC addresses.
+    ++mac[sizeof(mac) - 1];
+    hwaddr2_ = HWAddrPtr(new HWAddr(mac, sizeof (mac), HTYPE_FDDI));
+
+    // instantiate cfg_mgr
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.109"));
+    cfg_mgr.commit();
+
+    factory_.create("type=memfile universe=4 persist=false");
+
+    // Create a default context. Note that remaining parameters must be
+    // assigned when needed.
+    ctx_.subnet_ = subnet_;
+    ctx_.clientid_ = clientid_;
+    ctx_.hwaddr_ = hwaddr_;
+    ctx_.callout_handle_ = HooksManager::createCalloutHandle();
+}
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc

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

@@ -0,0 +1,377 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LIBDHCPSRV_ALLOC_ENGINE_UTILS_H
+#define LIBDHCPSRV_ALLOC_ENGINE_UTILS_H
+
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/alloc_engine.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <asiolink/io_address.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @file   alloc_engine_utils.h
+///
+/// @brief This is a header file for all Allocation Engine tests.
+///
+/// There used to be one, huge (over 3kloc) alloc_engine_unittest.cc. It is now
+/// split into serveral smaller files:
+/// alloc_engine_utils.h - contains test class definitions (this file)
+/// alloc_engine_utils.cc - contains test class implementation
+/// alloc_engine4_unittest.cc - all unit-tests dedicated to IPv4
+/// alloc_engine6_unittest.cc - all unit-tests dedicated to IPv6
+/// alloc_engine_hooks_unittest.cc - all unit-tests dedicated to hooks
+
+/// @brief Allocation engine with some internal methods exposed
+class NakedAllocEngine : public AllocEngine {
+public:
+
+    /// @brief the sole constructor
+    /// @param engine_type specifies engine type (e.g. iterative)
+    /// @param attempts number of lease selection attempts before giving up
+    /// @param ipv6 specifies if the engine is IPv6 or IPv4
+    NakedAllocEngine(AllocEngine::AllocType engine_type,
+                     unsigned int attempts, bool ipv6 = true)
+        :AllocEngine(engine_type, attempts, ipv6) {
+    }
+
+    // Expose internal classes for testing purposes
+    using AllocEngine::Allocator;
+    using AllocEngine::IterativeAllocator;
+    using AllocEngine::getAllocator;
+
+    /// @brief IterativeAllocator with internal methods exposed
+    class NakedIterativeAllocator: public AllocEngine::IterativeAllocator {
+    public:
+
+        /// @brief constructor
+        /// @param type pool types that will be interated
+        NakedIterativeAllocator(Lease::Type type)
+            :IterativeAllocator(type) {
+        }
+
+        using AllocEngine::IterativeAllocator::increaseAddress;
+        using AllocEngine::IterativeAllocator::increasePrefix;
+    };
+};
+
+/// @brief Used in Allocation Engine tests for IPv6
+class AllocEngine6Test : public ::testing::Test {
+public:
+
+    /// @brief Default constructor
+    ///
+    /// Sets duid_, iaid_, subnet_, pool_ fields to example values used
+    /// in many tests, initializes cfg_mgr configuration and creates
+    /// lease database.
+    AllocEngine6Test();
+
+    /// @brief Configures a subnet and adds one pool to it.
+    ///
+    /// This function removes existing v6 subnets before configuring
+    /// a new one.
+    ///
+    /// @param subnet Address of a subnet to be configured.
+    /// @param pool_start First address in the address pool.
+    /// @param pool_end Last address in the address pool.
+    void initSubnet(const asiolink::IOAddress& subnet,
+                    const asiolink::IOAddress& pool_start,
+                    const asiolink::IOAddress& pool_end);
+
+    /// @brief Initializes FQDN data for a test.
+    ///
+    /// The initialized values are used by the test fixture class members to
+    /// verify the correctness of a lease.
+    ///
+    /// @param hostname Hostname to be assigned to a lease.
+    /// @param fqdn_fwd Indicates whether or not to perform forward DNS update
+    /// for a lease.
+    /// @param fqdn_fwd Indicates whether or not to perform reverse DNS update
+    /// for a lease.
+    void initFqdn(const std::string& hostname, const bool fqdn_fwd,
+                  const bool fqdn_rev) {
+        hostname_ = hostname;
+        fqdn_fwd_ = fqdn_fwd;
+        fqdn_rev_ = fqdn_rev;
+    }
+
+    /// @brief attempts to convert leases collection to a single lease
+    ///
+    /// This operation makes sense if there is at most one lease in the
+    /// collection. Otherwise it will throw.
+    ///
+    /// @param col collection of leases (zero or one leases allowed)
+    /// @throw MultipleRecords if there is more than one lease
+    /// @return Lease6 pointer (or NULL if collection was empty)
+    Lease6Ptr expectOneLease(const Lease6Collection& col) {
+        if (col.size() > 1) {
+            isc_throw(MultipleRecords, "More than one lease found in collection");
+        }
+        if (col.empty()) {
+            return (Lease6Ptr());
+        }
+        return (*col.begin());
+    }
+
+    /// @brief checks if Lease6 matches expected configuration
+    ///
+    /// @param lease lease to be checked
+    /// @param exp_type expected lease type
+    /// @param exp_pd_len expected prefix length
+    /// @param expected_in_subnet whether the lease is expected to be in subnet
+    /// @param expected_in_pool whether the lease is expected to be in dynamic
+    void checkLease6(const Lease6Ptr& lease, Lease::Type exp_type,
+                     uint8_t exp_pd_len = 128, bool expected_in_subnet = true,
+                     bool expected_in_pool = true) {
+
+        // that is belongs to the right subnet
+        EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+
+        if (expected_in_subnet) {
+            EXPECT_TRUE(subnet_->inRange(lease->addr_));
+        } else {
+            EXPECT_FALSE(subnet_->inRange(lease->addr_));
+        }
+
+        if (expected_in_pool) {
+            EXPECT_TRUE(subnet_->inPool(exp_type, lease->addr_));
+        } else {
+            EXPECT_FALSE(subnet_->inPool(exp_type, lease->addr_));
+        }
+
+        // that it have proper parameters
+        EXPECT_EQ(exp_type, lease->type_);
+        EXPECT_EQ(iaid_, lease->iaid_);
+        EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+        EXPECT_EQ(subnet_->getPreferred(), lease->preferred_lft_);
+        EXPECT_EQ(subnet_->getT1(), lease->t1_);
+        EXPECT_EQ(subnet_->getT2(), lease->t2_);
+        EXPECT_EQ(exp_pd_len, lease->prefixlen_);
+        EXPECT_EQ(fqdn_fwd_, lease->fqdn_fwd_);
+        EXPECT_EQ(fqdn_rev_, lease->fqdn_rev_);
+        EXPECT_EQ(hostname_, lease->hostname_);
+        EXPECT_TRUE(*lease->duid_ == *duid_);
+        /// @todo: check cltt
+    }
+
+    /// @brief Checks if specified address is increased properly
+    ///
+    /// Method uses gtest macros to mark check failure.
+    ///
+    /// @param alloc IterativeAllocator that is tested
+    /// @param input address to be increased
+    /// @param exp_output expected address after increase
+    void
+    checkAddrIncrease(NakedAllocEngine::NakedIterativeAllocator& alloc,
+                      std::string input, std::string exp_output) {
+        EXPECT_EQ(exp_output, alloc.increaseAddress(asiolink::IOAddress(input))
+                  .toText());
+    }
+
+    /// @brief Checks if increasePrefix() works as expected
+    ///
+    /// Method uses gtest macros to mark check failure.
+    ///
+    /// @param alloc allocator to be tested
+    /// @param input IPv6 prefix (as a string)
+    /// @param prefix_len prefix len
+    /// @param exp_output expected output (string)
+    void
+    checkPrefixIncrease(NakedAllocEngine::NakedIterativeAllocator& alloc,
+                        std::string input, uint8_t prefix_len,
+                        std::string exp_output) {
+        EXPECT_EQ(exp_output, alloc.increasePrefix(asiolink::IOAddress(input),
+                                                   prefix_len).toText());
+    }
+
+    /// @brief Checks if the simple allocation can succeed
+    ///
+    /// The type of lease is determined by pool type (pool->getType()
+    ///
+    /// @param pool pool from which the lease will be allocated from
+    /// @param hint address to be used as a hint
+    /// @param fake true - this is fake allocation (SOLICIT)
+    /// @param in_pool specifies whether the lease is expected to be in pool
+    /// @return allocated lease (or NULL)
+    Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
+                               const asiolink::IOAddress& hint,
+                               bool fake, bool in_pool = true);
+
+    /// @brief Checks if the allocation can succeed.
+    ///
+    /// The type of lease is determined by pool type (pool->getType()).
+    /// This test is particularly useful in connection with @ref renewTest.
+    ///
+    /// @param engine a reference to Allocation Engine
+    /// @param pool pool from which the lease will be allocated from
+    /// @param hint address to be used as a hint
+    /// @param fake true - this is fake allocation (SOLICIT)
+    /// @param in_pool specifies whether the lease is expected to be in pool
+    /// @return allocated lease(s) (may be empty)
+    Lease6Collection allocateTest(AllocEngine& engine, const Pool6Ptr& pool,
+                                  const asiolink::IOAddress& hint, bool fake,
+                                  bool in_pool = true);
+
+    /// @brief Checks if the allocation can be renewed.
+    ///
+    /// The type of lease is determined by pool type (pool->getType()).
+    /// This test is particularly useful as a follow up to @ref allocateTest.
+    ///
+    /// @param engine a reference to Allocation Engine
+    /// @param pool pool from which the lease will be allocated from
+    /// @param hints address to be used as a hint
+    /// @param allow_new_leases_in_renewal - specifies how to set the
+    ///        allow_new_leases_in_renewal flag in ClientContext6
+    /// @param in_pool specifies whether the lease is expected to be in pool
+    /// @return allocated lease(s) (may be empty)
+    Lease6Collection renewTest(AllocEngine& engine, const Pool6Ptr& pool,
+                               AllocEngine::HintContainer& hints,
+                               bool allow_new_leases_in_renewal,
+                               bool in_pool = true);
+
+    /// @brief Checks if the address allocation with a hint that is in range,
+    ///        in pool, but is currently used, can succeed
+    ///
+    /// Method uses gtest macros to mark check failure.
+    ///
+    /// @param type lease type
+    /// @param used_addr address should be preallocated (simulates prior
+    ///        allocation by some other user)
+    /// @param requested address requested by the client
+    /// @param expected_pd_len expected PD len (128 for addresses)
+    void allocWithUsedHintTest(Lease::Type type, asiolink::IOAddress used_addr,
+                               asiolink::IOAddress requested,
+                               uint8_t expected_pd_len);
+
+    /// @brief checks if bogus hint can be ignored and the allocation succeeds
+    ///
+    /// This test checks if the allocation with a hing that is out of the blue
+    /// can succeed. The invalid hint should be ignored completely.
+    ///
+    /// @param type Lease type
+    /// @param hint hint (as send by a client)
+    /// @param expected_pd_len (used in validation)
+    void allocBogusHint6(Lease::Type type, asiolink::IOAddress hint,
+                         uint8_t expected_pd_len);
+
+    /// @brief Utility function that creates a host reservation
+    ///
+    /// @param add_to_host_mgr true if the reservation should be added
+    /// @param type specifies reservation type
+    /// @param addr specifies reserved address or prefix
+    /// @param prefix_len prefix length (should be 128 for addresses)
+    /// @return created Host object.
+    HostPtr
+    createHost6(bool add_to_host_mgr, IPv6Resrv::Type type,
+                const asiolink::IOAddress& addr, uint8_t prefix_len) {
+        HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+                              Host::IDENT_DUID, SubnetID(0), subnet_->getID(),
+                              asiolink::IOAddress("0.0.0.0")));
+        IPv6Resrv resv(type, addr, prefix_len);
+        host->addReservation(resv);
+
+        if (add_to_host_mgr) {
+            CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+            CfgMgr::instance().commit();
+        }
+        return (host);
+    }
+
+    virtual ~AllocEngine6Test() {
+        factory_.destroy();
+    }
+
+    DuidPtr duid_;            ///< client-identifier (value used in tests)
+    uint32_t iaid_;           ///< IA identifier (value used in tests)
+    Subnet6Ptr subnet_;       ///< subnet6 (used in tests)
+    Pool6Ptr pool_;           ///< NA pool belonging to subnet_
+    Pool6Ptr pd_pool_;        ///< PD pool belonging to subnet_
+    std::string hostname_;    ///< Hostname
+    bool fqdn_fwd_;           ///< Perform forward update for a lease.
+    bool fqdn_rev_;           ///< Perform reverse update for a lease.
+    LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+};
+
+/// @brief Used in Allocation Engine tests for IPv4
+class AllocEngine4Test : public ::testing::Test {
+public:
+
+    /// @brief Default constructor
+    ///
+    /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values
+    /// used in many tests, initializes cfg_mgr configuration and creates
+    /// lease database.
+    ///
+    /// It also re-initializes the Host Manager.
+    AllocEngine4Test();
+
+    /// @brief checks if Lease4 matches expected configuration
+    ///
+    /// @param lease lease to be checked
+    void checkLease4(const Lease4Ptr& lease) {
+        // Check that is belongs to the right subnet
+        EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+        EXPECT_TRUE(subnet_->inRange(lease->addr_));
+        EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+
+        // Check that it has proper parameters
+        EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+        EXPECT_EQ(subnet_->getT1(), lease->t1_);
+        EXPECT_EQ(subnet_->getT2(), lease->t2_);
+        if (lease->client_id_ && !clientid_) {
+            ADD_FAILURE() << "Lease4 has a client-id, while it should have none.";
+        } else
+        if (!lease->client_id_ && clientid_) {
+            ADD_FAILURE() << "Lease4 has no client-id, but it was expected to have one.";
+        } else
+        if (lease->client_id_ && clientid_) {
+            EXPECT_TRUE(*lease->client_id_ == *clientid_);
+        }
+        EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+        /// @todo: check cltt
+     }
+
+    /// @brief Create a subnet with a specified pool of addresses.
+    ///
+    /// @param pool_start First address in the pool.
+    /// @param pool_end Last address in the pool.
+    void initSubnet(const asiolink::IOAddress& pool_start,
+                    const asiolink::IOAddress& pool_end);
+
+    virtual ~AllocEngine4Test() {
+        factory_.destroy();
+    }
+
+    ClientIdPtr clientid_;      ///< Client-identifier (value used in tests)
+    HWAddrPtr hwaddr_;          ///< Hardware address (value used in tests)
+    HWAddrPtr hwaddr2_;         ///< Alternative hardware address.
+    Subnet4Ptr subnet_;         ///< Subnet4 (used in tests)
+    Pool4Ptr pool_;             ///< Pool belonging to subnet_
+    LeaseMgrFactory factory_;   ///< Pointer to LeaseMgr factory
+    Lease4Ptr old_lease_;       ///< Holds previous instance of the lease.
+    AllocEngine::ClientContext4 ctx_; ///< Context information passed to various
+                                     ///< allocation engine functions.
+};
+
+}; // namespace test
+}; // namespace dhcp
+}; // namespace isc
+
+#endif

+ 13 - 0
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -500,4 +500,17 @@ TEST(HostTest, addClientClasses) {
     EXPECT_TRUE(host->getClientClasses6().contains("bar"));
 }
 
+TEST(HostTest, getIdentifierAsText) {
+    Host host1("01:02:03:04:05:06", "hw-address",
+               SubnetID(1), SubnetID(2),
+               IOAddress("192.0.2.3"));
+    EXPECT_EQ("hwaddr=01:02:03:04:05:06", host1.getIdentifierAsText());
+
+    Host host2("0a:0b:0c:0d:0e:0f:ab:cd:ef", "duid",
+               SubnetID(1), SubnetID(2),
+               IOAddress("192.0.2.3"));
+    EXPECT_EQ("duid=0a:0b:0c:0d:0e:0f:ab:cd:ef",
+              host2.getIdentifierAsText());
+}
+
 } // end of anonymous namespace

+ 0 - 1
src/lib/dhcpsrv/tests/test_utils.h

@@ -16,7 +16,6 @@
 #define LIBDHCPSRV_TEST_UTILS_H
 
 #include <dhcpsrv/lease_mgr.h>
-#include <gtest/gtest.h>
 #include <vector>
 
 namespace isc {