]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[master] Merge branch 'trac3694'
authorMarcin Siodelski <marcin@isc.org>
Fri, 6 Mar 2015 10:20:09 +0000 (11:20 +0100)
committerMarcin Siodelski <marcin@isc.org>
Fri, 6 Mar 2015 10:20:09 +0000 (11:20 +0100)
Conflicts:
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc

1  2 
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc

index b9ee2d334145bdf55a800c19cc5d34c10b4f6a1a,781694aaa252a7fb9af525719d201245e602aa06..1b96d90a99960f44b30f32968e94e21a8750a466
@@@ -1238,267 -972,613 +972,595 @@@ Lease6Ptr AllocEngine::createLease6(Cli
      }
  
      if (!ctx.fake_allocation_) {
-         // for REQUEST we do update the lease
-         LeaseMgrFactory::instance().updateLease4(expired);
-     }
-     // We do nothing for SOLICIT. We'll just update database when
-     // the client gets back to us with REQUEST message.
-     // it's not really expired at this stage anymore - let's return it as
-     // an updated lease
-     return (expired);
- }
+         // That is a real (REQUEST) allocation
+         bool status = LeaseMgrFactory::instance().addLease(lease);
  
- Lease4Ptr
- AllocEngine::replaceClientLease(Lease4Ptr& lease, ClientContext4& ctx) {
+         if (status) {
  
-     if (!lease) {
-         isc_throw(BadValue, "null lease specified for replaceClientLease");
-     }
+             return (lease);
+         } else {
+             // One of many failures with LeaseMgr (e.g. lost connection to the
+             // database, database failed etc.). One notable case for that
+             // is that we are working in multi-process mode and we lost a race
+             // (some other process got that address first)
+             return (Lease6Ptr());
+         }
+     } else {
+         // That is only fake (SOLICIT without rapid-commit) allocation
  
-     if (!ctx.subnet_) {
-         isc_throw(BadValue, "null subnet specified for replaceClientLease");
+         // It is for advertise only. We should not insert the lease into LeaseMgr,
+         // but rather check that we could have inserted it.
+         Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(
+                              Lease::TYPE_NA, addr);
+         if (!existing) {
+             return (lease);
+         } else {
+             return (Lease6Ptr());
+         }
      }
+ }
  
-     if (ctx.requested_address_ == IOAddress("0.0.0.0")) {
-         isc_throw(BadValue, "zero address specified for the"
-                   " replaceClientLease");
-     }
+ Lease6Collection
+ AllocEngine::renewLeases6(ClientContext6& ctx) {
+     try {
+         if (!ctx.subnet_) {
+             isc_throw(InvalidOperation, "Subnet is required for allocation");
+         }
  
-     // Remember the previous address for this lease.
-     IOAddress prev_address = lease->addr_;
+         if (!ctx.duid_) {
+             isc_throw(InvalidOperation, "DUID is mandatory for allocation");
+         }
  
-     if (!ctx.host_) {
-         ConstHostPtr host = HostMgr::instance().get4(ctx.subnet_->getID(),
-                                                      ctx.requested_address_);
-         // If there is a reservation for the new address and the reservation
-         // is made for another client, do not use this address.
-         if (host && ctx.hwaddr_ && (*host->getHWAddress() != *ctx.hwaddr_)) {
-             ctx.interrupt_processing_ = true;
-             return (Lease4Ptr());
-         }
-         lease->addr_ = ctx.requested_address_;
-     } else {
-         lease->addr_ = ctx.host_->getIPv4Reservation();
-     }
-     updateLease4Information(lease, ctx);
-     bool skip = false;
-     // Execute callouts registered for lease4_select.
-     if (ctx.callout_handle_ && HooksManager::getHooksManager()
-         .calloutsPresent(hook_index_lease4_select_)) {
+         // Check which host reservation mode is supported in this subnet.
+         Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
  
-         // Delete all previous arguments.
-         ctx.callout_handle_->deleteAllArguments();
+         // 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) {
  
-         // Pass arguments.
-         Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
-         ctx.callout_handle_->setArgument("subnet4", subnet4);
+             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();
+         }
  
-         ctx.callout_handle_->setArgument("fake_allocation",
-                                          ctx.fake_allocation_);
+         // Check if there are any leases for this client.
+         Lease6Collection leases = LeaseMgrFactory::instance()
+             .getLeases6(ctx.type_, *ctx.duid_, ctx.iaid_, ctx.subnet_->getID());
  
-         ctx.callout_handle_->setArgument("lease4", lease);
+         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);
+         }
  
-         HooksManager::callCallouts(hook_index_lease4_select_,
-                                    *ctx.callout_handle_);
+         if (ctx.host_) {
+             // If we have host reservation, allocate those leases.
+             allocateReservedLeases6(ctx, leases);
  
-         if (ctx.callout_handle_->getSkip()) {
-             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
-                       DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
-             return (Lease4Ptr());
+             // 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);
          }
  
-         // Let's use whatever callout returned.
-         ctx.callout_handle_->getArgument("lease4", lease);
+         // 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);
+         }
  
-         // 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 (ctx.callout_handle_->getSkip()) {
-             skip = true;
-             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
-                       DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+         // Extend all existing leases that passed all checks.
+         for (Lease6Collection::iterator l = leases.begin(); l != leases.end(); ++l) {
+             extendLease6(ctx, *l);
          }
-     }
  
-     /// @todo There should be a callout for a deletion of an old lease.
-     /// The lease4_release callout is in appropriate, because by definition
-     /// it is invoked when DHCPRELEASE packet is received.
+         return (leases);
  
-     if (!ctx.fake_allocation_ && !skip) {
-         // We can't use LeaseMgr::updateLease because it identifies the
-         // lease by an IP address. Instead, we have to delete an old
-         // lease and add a new one.
-         LeaseMgrFactory::instance().deleteLease(prev_address);
-         LeaseMgrFactory::instance().addLease(lease);
+     } catch (const isc::Exception& e) {
+         // Some other error, return an empty lease.
+         LOG_ERROR(dhcpsrv_logger, DHCPSRV_RENEW6_ERROR).arg(e.what());
      }
  
-     return (lease);
+     return (Lease6Collection());
  }
  
- Lease4Ptr
- AllocEngine::reallocateClientLease(Lease4Ptr& lease,
-                                    AllocEngine::ClientContext4& ctx) {
-     // Save the old lease, before renewal.
-     ctx.old_lease_.reset(new Lease4(*lease));
-     /// The client's address will need to be modified in case if:
-     /// - There is a reservation for the client (likely new one) and
-     ///   the currently used address is different.
-     /// - Client requested some IP address and the requested address
-     ///   is different than the currently used one. Note that if this
-     ///   is a DHCPDISCOVER the requested IP address is ignored when
-     ///   it doesn't match the one in use.
-     if ((ctx.host_ && (ctx.host_->getIPv4Reservation() != lease->addr_)) ||
-         (!ctx.fake_allocation_ &&
-          (ctx.requested_address_ != IOAddress("0.0.0.0")) &&
-          (lease->addr_ != ctx.requested_address_))) {
-         lease = replaceClientLease(lease, ctx);
-         return (lease);
+ void
+ AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
  
-     } else {
-         lease = renewLease4(lease, ctx);
-         if (lease) {
-             return (lease);
-         }
+     if (!lease || !ctx.subnet_) {
+         return;
      }
  
-     return (Lease4Ptr());
- }
+     // 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_);
  
- Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
-                                     const IOAddress& addr,
-                                     uint8_t prefix_len) {
+         // Add it to the removed leases list.
+         ctx.old_leases_.push_back(lease);
  
-     if (ctx.type_ != Lease::TYPE_PD) {
-         prefix_len = 128; // non-PD lease types must be always /128
+         return;
      }
  
-     Lease6Ptr lease(new Lease6(ctx.type_, addr, ctx.duid_, ctx.iaid_,
-                                ctx.subnet_->getPreferred(), ctx.subnet_->getValid(),
-                                ctx.subnet_->getT1(), ctx.subnet_->getT2(),
-                                ctx.subnet_->getID(), ctx.hwaddr_, prefix_len));
+     // 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->hostname_ = ctx.hostname_;
+     lease->hwaddr_ = ctx.hwaddr_;
  
-     // Let's execute all callouts registered for lease6_select
-     if (ctx.callout_handle_ &&
-         HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {
+     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
-         ctx.callout_handle_->deleteAllArguments();
+         callout_handle->deleteAllArguments();
  
-         // Pass necessary arguments
+         // Pass the original packet
+         callout_handle->setArgument("query6", ctx.query_);
  
-         // Subnet from which we do the allocation
-         ctx.callout_handle_->setArgument("subnet6", ctx.subnet_);
+         // Pass the lease to be updated
+         callout_handle->setArgument("lease6", lease);
  
-         // Is this solicit (fake = true) or request (fake = false)
-         ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
-         ctx.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_);
+         }
  
-         // This is the first callout, so no need to clear any arguments
-         HooksManager::callCallouts(hook_index_lease6_select_, *ctx.callout_handle_);
+         // Call all installed callouts
+         HooksManager::callCallouts(hook_point, *callout_handle);
  
-         // Callouts decided to skip the action. This means that the lease is not
-         // assigned, so the client will get NoAddrAvail as a result. The lease
-         // won't be inserted into the database.
-         if (ctx.callout_handle_->getSkip()) {
-             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
-             return (Lease6Ptr());
+         // Callouts decided to skip the next processing step. The next
+         // processing step would 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());
          }
+     }
  
-         // Let's use whatever callout returned. Hopefully it is the same lease
-         // we handled to it.
-         ctx.callout_handle_->getArgument("lease6", lease);
+     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;
      }
+ }
  
-     if (!ctx.fake_allocation_) {
-         // That is a real (REQUEST) allocation
-         bool status = LeaseMgrFactory::instance().addLease(lease);
+ Lease6Collection
+ 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_ = 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);
+     }
+     return (updated_leases);
+ }
  
-         if (status) {
+ } // end of isc::dhcp namespace
+ } // end of isc namespace
  
-             return (lease);
-         } else {
-             // One of many failures with LeaseMgr (e.g. lost connection to the
-             // database, database failed etc.). One notable case for that
-             // is that we are working in multi-process mode and we lost a race
-             // (some other process got that address first)
-             return (Lease6Ptr());
-         }
-     } else {
-         // That is only fake (SOLICIT without rapid-commit) allocation
+ // ##########################################################################
+ // #    DHCPv4 lease allocation code starts here.
+ // ##########################################################################
+ namespace {
+ /// @brief Check if the specific address is reserved for another client.
+ ///
+ /// This function uses the HW address from the context to check if the
+ /// requested address (specified as first parameter) is reserved for
+ /// another client, i.e. client using a different HW address.
+ ///
+ /// @param address An address for which the function should check if
+ /// there is a reservation for the different client.
+ /// @param ctx Client context holding the data extracted from the
+ /// client's message.
+ ///
+ /// @return true if the address is reserved for another client.
+ bool
+ addressReserved(const IOAddress& address, const AllocEngine::ClientContext4& ctx) {
+     ConstHostPtr host = HostMgr::instance().get4(ctx.subnet_->getID(), address);
+     HWAddrPtr host_hwaddr;
+     if (host) {
+         host_hwaddr = host->getHWAddress();
+         if (ctx.hwaddr_ && host_hwaddr) {
+             /// @todo Use the equality operators for HWAddr class.
+             /// Currently, this is impossible because the HostMgr uses the
+             /// HTYPE_ETHER type, whereas the unit tests may use other types
+             /// which HostMgr doesn't support yet.
+             return (host_hwaddr->hwaddr_ != ctx.hwaddr_->hwaddr_);
  
-         // It is for advertise only. We should not insert the lease into LeaseMgr,
-         // but rather check that we could have inserted it.
-         Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(
-                              Lease::TYPE_NA, addr);
-         if (!existing) {
-             return (lease);
          } else {
-             return (Lease6Ptr());
+             return (false);
          }
      }
+     return (false);
  }
  
- Lease4Ptr AllocEngine::createLease4(const ClientContext4& ctx,
-                                     const IOAddress& addr) {
-     if (!ctx.hwaddr_) {
-         isc_throw(BadValue, "Can't create a lease with NULL HW address");
-     }
-     if (!ctx.subnet_) {
-         isc_throw(BadValue, "Can't create a lease without a subnet");
-     }
+ } // end of anonymous namespace
  
-     time_t now = time(NULL);
+ namespace isc {
+ namespace dhcp {
  
-     // @todo: remove this kludge after ticket #2590 is implemented
-     std::vector<uint8_t> local_copy;
-     if (ctx.clientid_) {
-         local_copy = ctx.clientid_->getDuid();
-     }
+ AllocEngine::ClientContext4::ClientContext4()
+     : subnet_(), clientid_(), hwaddr_(),
+       requested_address_(IOAddress::IPV4_ZERO_ADDRESS()),
+       fwd_dns_update_(false), rev_dns_update_(false),
+       hostname_(""), callout_handle_(), fake_allocation_(false),
+       old_lease_(), host_(), conflicting_lease_() {
+ }
  
-     Lease4Ptr lease(new Lease4(addr, ctx.hwaddr_, &local_copy[0], local_copy.size(),
-                                ctx.subnet_->getValid(), ctx.subnet_->getT1(),
-                                ctx.subnet_->getT2(),
-                                now, ctx.subnet_->getID()));
++AllocEngine::ClientContext4::ClientContext4(const SubnetPtr& subnet,
++                                            const ClientIdPtr& clientid,
++                                            const HWAddrPtr& hwaddr,
++                                            const asiolink::IOAddress& requested_addr,
++                                            const bool fwd_dns_update,
++                                            const bool rev_dns_update,
++                                            const std::string& hostname,
++                                            const bool fake_allocation)
++    : subnet_(subnet), clientid_(clientid), hwaddr_(hwaddr),
++      requested_address_(requested_addr),
++      fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update),
++      hostname_(hostname), callout_handle_(),
++      fake_allocation_(fake_allocation), old_lease_(), host_() {
++}
 +
-     // Set FQDN specific lease parameters.
-     lease->fqdn_fwd_ = ctx.fwd_dns_update_;
-     lease->fqdn_rev_ = ctx.rev_dns_update_;
-     lease->hostname_ = ctx.hostname_;
 +
-     // Let's execute all callouts registered for lease4_select
-     if (ctx.callout_handle_ &&
-         HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {
 +
-         // Delete all previous arguments
-         ctx.callout_handle_->deleteAllArguments();
+ bool
+ AllocEngine::ClientContext4::myLease(const Lease4& lease) const {
+     if ((!hwaddr_ && lease.hwaddr_) || (hwaddr_ && !lease.hwaddr_)) {
+         return (false);
+     }
  
-         // Pass necessary arguments
+     if ((hwaddr_ && lease.hwaddr_) && (hwaddr_->hwaddr_ != lease.hwaddr_->hwaddr_)) {
+         return (false);
+     }
  
-         // Subnet from which we do the allocation (That's as far as we can go
-         // with using SubnetPtr to point to Subnet4 object. Users should not
-         // be confused with dynamic_pointer_casts. They should get a concrete
-         // pointer (Subnet4Ptr) pointing to a Subnet4 object.
-         Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
-         ctx.callout_handle_->setArgument("subnet4", subnet4);
+     if ((!clientid_ && lease.client_id_) || (clientid_ && !lease.client_id_)) {
+         return (false);
+     }
  
-         // Is this solicit (fake = true) or request (fake = false)
+     if ((clientid_ && lease.client_id_) && (*clientid_ != *lease.client_id_)) {
+         return (false);
+     }
+     return (true);
+ }
+ Lease4Ptr
 -AllocEngine::allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
 -                            const HWAddrPtr& hwaddr, const IOAddress& hint,
 -                            const bool fwd_dns_update, const bool rev_dns_update,
 -                            const std::string& hostname, bool fake_allocation,
 -                            const isc::hooks::CalloutHandlePtr& callout_handle,
 -                            Lease4Ptr& old_lease) {
 -
++AllocEngine::allocateLease4(ClientContext4& ctx) {
+     // The NULL pointer indicates that the old lease didn't exist. It may
+     // be later set to non NULL value if existing lease is found in the
+     // database.
 -    old_lease.reset();
++    ctx.old_lease_.reset();
+     Lease4Ptr new_lease;
 -    /// @todo The context for lease allocation should really be created
 -    /// by the DHCPv4 server and passed to this function. The reason for
 -    /// this is that the server should retrieve the Host object for the
 -    /// client because the Host object contains the data not only useful
 -    /// for the address allocation but also hostname and DHCP options
 -    /// for the client. The Host object should be passed in the context.
 -    /// Making this change would require a change to the allocateLease4
 -    /// API which would in turn require lots of changes in unit tests.
 -    /// The ticket introducing a context and host reservation in the
 -    /// allocation engine is complex enough by itself to warrant that
 -    /// the API change is done with a separate ticket (#3709).
 -    ClientContext4 ctx;
 -    ctx.subnet_ = subnet;
 -    ctx.clientid_ = clientid;
 -    ctx.hwaddr_ = hwaddr;
 -    ctx.requested_address_ = hint;
 -    ctx.fwd_dns_update_ = fwd_dns_update;
 -    ctx.rev_dns_update_ = rev_dns_update;
 -    ctx.hostname_ = hostname;
 -    ctx.fake_allocation_ = fake_allocation;
 -    ctx.callout_handle_ = callout_handle;
 -    ctx.old_lease_ = old_lease;
 -
+     try {
 -        if (!subnet) {
++        if (!ctx.subnet_) {
+             isc_throw(BadValue, "Can't allocate IPv4 address without subnet");
+         }
 -        if (!hwaddr) {
++        if (!ctx.hwaddr_) {
+             isc_throw(BadValue, "HWAddr must be defined");
+         }
 -        ctx.host_ = HostMgr::instance().get4(subnet->getID(), hwaddr);
++        ctx.host_ = HostMgr::instance().get4(ctx.subnet_->getID(), ctx.hwaddr_);
+         new_lease = ctx.fake_allocation_ ? discoverLease4(ctx) : requestLease4(ctx);
+         if (!new_lease) {
+             // Unable to allocate an address, return an empty lease.
+             LOG_WARN(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_FAIL).arg(attempts_);
+         }
+     } catch (const isc::Exception& e) {
+         // Some other error, return an empty lease.
+         LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_ERROR).arg(e.what());
+     }
 -    if (ctx.old_lease_) {
 -        old_lease.reset(new Lease4(*ctx.old_lease_));
 -    }
 -
+     return (new_lease);
+ }
+ Lease4Ptr
+ AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
+     // Obtain the sole instance of the LeaseMgr.
+     LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+     // Check if the client has any lease already. This information is needed
+     // to either return this lease to the client or to return it as an old
+     // (existing) lease if a different one is offered.
+     Lease4Ptr client_lease = lease_mgr.getLease4(*ctx.hwaddr_, ctx.subnet_->getID());
+     if (!client_lease && ctx.clientid_) {
+         client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
+     }
+     // new_lease will hold the pointer to the lease that we will offer to the
+     // caller.
+     Lease4Ptr new_lease;
+     // Check if there is a reservation for the client. If there is, we want to
+     // assign the reserved address, rather than any other one.
+     if (ctx.host_) {
+         // If the client doesn't have a lease or the leased address is different
+         // than the reserved one then let's try to allocate the reserved address.
+         // Otherwise the address that the client has is the one for which it
+         // has a reservation, so just renew it.
+         if (!client_lease || (client_lease->addr_ != ctx.host_->getIPv4Reservation())) {
+             // The call below will return a pointer to the lease for the address
+             // reserved to this client, if the lease is available, i.e. is not
+             // currently assigned to any other client.
+             // Note that we don't remove the existing client's lease at this point
+             // because this is not a real allocation, we just offer what we can
+             // allocate in the DHCPREQUEST time.
+             new_lease = allocateOrReuseLease4(ctx.host_->getIPv4Reservation(), ctx);
+             if (!new_lease) {
+                 LOG_WARN(dhcpsrv_logger, DHCPSRV_DISCOVER_ADDRESS_CONFLICT)
+                     .arg(ctx.host_->getIPv4Reservation().toText())
+                     .arg(ctx.conflicting_lease_ ? ctx.conflicting_lease_->toText() :
+                          "(no lease info)");
+             }
+         } else {
+             new_lease = renewLease4(client_lease, ctx);
+         }
+     }
+     // Client does not have a reservation or the allocation of the reserved
+     // address has failed, probably because the reserved address is in use
+     // by another client. If the client has a lease, we will check if we can
+     // offer this lease to the client. The lease can't be offered in the
+     // situation when it is reserved for another client or when the address
+     // is not in the dynamic pool. The former may be the result of adding the
+     // new reservation for the address used by this client. The latter may
+     // be due to the client using the reserved out-of-the pool address, for
+     // which the reservation has just been removed.
+     if (!new_lease && client_lease &&
+         ctx.subnet_->inPool(Lease::TYPE_V4, client_lease->addr_) &&
+         !addressReserved(client_lease->addr_, ctx)) {
+         new_lease = renewLease4(client_lease, ctx);
+     }
+     // The client doesn't have any lease or the lease can't be offered
+     // because it is either reserved for some other client or the
+     // address is not in the dynamic pool.
+     // Let's use the client's hint (requested IP address), if the client
+     // has provided it, and try to offer it. This address must not be
+     // reserved for another client, and must be in the range of the
+     // dynamic pool.
+     if (!new_lease && !ctx.requested_address_.isV4Zero() &&
+         ctx.subnet_->inPool(Lease::TYPE_V4, ctx.requested_address_) &&
+         !addressReserved(ctx.requested_address_, ctx)) {
+         new_lease = allocateOrReuseLease4(ctx.requested_address_, ctx);
+     }
+     // The allocation engine failed to allocate all of the candidate
+     // addresses. We will now use the allocator to pick the address
+     // from the dynamic pool.
+     if (!new_lease) {
+         new_lease = allocateUnreservedLease4(ctx);
+     }
+     // Some of the methods like reuseExpiredLease4 may set the old lease to point
+     // to the lease which they remove/override. If it is not set, but we have
+     // found that the client has the lease the client's lease is the one
+     // to return as an old lease.
+     if (!ctx.old_lease_ && client_lease) {
+         ctx.old_lease_ = client_lease;
+     }
+     return (new_lease);
+ }
+ Lease4Ptr
+ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
+     // Obtain the sole instance of the LeaseMgr.
+     LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+     // Check if the client has any lease already. This information is needed
+     // to either return this lease to the client or to delete this lease if
+     // the new lease is allocated.
+     Lease4Ptr client_lease = lease_mgr.getLease4(*ctx.hwaddr_, ctx.subnet_->getID());
+     if (!client_lease && ctx.clientid_) {
+         client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
+     }
+     // When the client sends the DHCPREQUEST, it should always specify the
+     // address which it is requesting or renewing. That is, the client should
+     // either use the requested IP address option or set the ciaddr. However,
+     // we try to be liberal and allow the clients to not specify an address
+     // in which case the allocation engine will pick a suitable address
+     // for the client.
+     if (!ctx.requested_address_.isV4Zero()) {
+         // If the client has specified an address, make sure this address
+         // is not reserved for another client. If it is, stop here because
+         // we can't allocate this address.
+         if (addressReserved(ctx.requested_address_, ctx)) {
+             return (Lease4Ptr());
+         }
+     } else if (ctx.host_) {
+         // The client hasn't specified an address to allocate, so the
+         // allocation engine needs to find an appropriate address.
+         // If there is a reservation for the client, let's try to
+         // allocate the reserved address.
+         ctx.requested_address_ = ctx.host_->getIPv4Reservation();
+     }
+     if (!ctx.requested_address_.isV4Zero()) {
+         // There is a specific address to be allocated. Let's find out if
+         // the address is in use.
+         Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(ctx.requested_address_);
+         // If the address is in use (allocated and not expired), we check
+         // if the address is in use by our client or another client.
+         // If it is in use by another client, the address can't be
+         // allocated.
+         if (existing && !existing->expired() && !ctx.myLease(*existing)) {
+             return (Lease4Ptr());
+         }
+         // If the client has a reservation but it is requesting a different
+         // address it is possible that the client was offered this different
+         // address because the reserved address is in use. We will have to
+         // check if the address is in use.
+         if (ctx.host_ && (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) {
+             existing = LeaseMgrFactory::instance().getLease4(ctx.host_->getIPv4Reservation());
+             // If the reserved address is not in use, i.e. the lease doesn't
+             // exist or is expired, and the client is requesting a different
+             // address, return NULL. The client should go back to the
+             // DHCPDISCOVER and the reserved address will be offered.
+             if (!existing || existing->expired()) {
+                 return (Lease4Ptr());
+             }
+         }
+         // The use of the out-of-pool addresses is only allowed when the requested
+         // address is reserved for the client. If the address is not reserved one
+         // and it doesn't belong to the dynamic pool, do not allocate it.
+         if ((!ctx.host_ || (ctx.host_->getIPv4Reservation() != ctx.requested_address_)) &&
+             !ctx.subnet_->inPool(Lease4::TYPE_V4, ctx.requested_address_)) {
+             return (Lease4Ptr());
+         }
+     }
+     // We have gone through all the checks, so we can now allocate the address
+     // for the client.
+     // If the client is requesting an address which is assigned to the client
+     // let's just renew this address. Also, renew this address if the client
+     // doesn't request any specific address.
+     if (client_lease) {
+         if ((client_lease->addr_ == ctx.requested_address_) ||
+             ctx.requested_address_.isV4Zero()) {
+             return (renewLease4(client_lease, ctx));
+         }
+     }
+     // new_lease will hold the pointer to the allocated lease if we allocate
+     // successfully.
+     Lease4Ptr new_lease;
+     // The client doesn't have the lease or it is requesting an address
+     // which it doesn't have. Let's try to allocate the requested address.
+     if (!ctx.requested_address_.isV4Zero()) {
+         // The call below will return a pointer to the lease allocated
+         // for the client if there is no lease for the requested address,
+         // or the existing lease has expired. If the allocation fails,
+         // e.g. because the lease is in use, we will return NULL to
+         // indicate that we were unable to allocate the lease.
+         new_lease = allocateOrReuseLease4(ctx.requested_address_, ctx);
+     } else {
+         // We will only get here if the client didn't specify which
+         // address it wanted to be allocated. The allocation engine will
+         // to pick the address from the dynamic pool.
+         new_lease = allocateUnreservedLease4(ctx);
+     }
+     // If we allocated the lease for the client, but the client already had a
+     // lease, we will need to return the pointer to the previous lease and
+     // the previous lease needs to be removed from the lease database.
+     if (new_lease && client_lease) {
+         ctx.old_lease_ = Lease4Ptr(new Lease4(*client_lease));
+         lease_mgr.deleteLease(client_lease->addr_);
+     }
+     // Return the allocated lease or NULL pointer if allocation was
+     // unsuccessful.
+     return (new_lease);
+ }
 -Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
 -                                    const DuidPtr& clientid,
 -                                    const HWAddrPtr& hwaddr,
 -                                    const IOAddress& addr,
 -                                    const bool fwd_dns_update,
 -                                    const bool rev_dns_update,
 -                                    const std::string& hostname,
 -                                    const isc::hooks::CalloutHandlePtr& callout_handle,
 -                                    bool fake_allocation /*= false */ ) {
 -    if (!hwaddr) {
++Lease4Ptr
++AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr) {
++    if (!ctx.hwaddr_) {
+         isc_throw(BadValue, "Can't create a lease with NULL HW address");
+     }
++    if (!ctx.subnet_) {
++        isc_throw(BadValue, "Can't create a lease without a subnet");
++    }
++
+     time_t now = time(NULL);
+     // @todo: remove this kludge after ticket #2590 is implemented
+     std::vector<uint8_t> local_copy;
 -    if (clientid) {
 -        local_copy = clientid->getDuid();
++    if (ctx.clientid_) {
++        local_copy = ctx.clientid_->getDuid();
+     }
 -    Lease4Ptr lease(new Lease4(addr, hwaddr, &local_copy[0], local_copy.size(),
 -                               subnet->getValid(), subnet->getT1(), subnet->getT2(),
 -                               now, subnet->getID()));
++    Lease4Ptr lease(new Lease4(addr, ctx.hwaddr_, &local_copy[0], local_copy.size(),
++                               ctx.subnet_->getValid(), ctx.subnet_->getT1(),
++                               ctx.subnet_->getT2(),
++                               now, ctx.subnet_->getID()));
+     // Set FQDN specific lease parameters.
 -    lease->fqdn_fwd_ = fwd_dns_update;
 -    lease->fqdn_rev_ = rev_dns_update;
 -    lease->hostname_ = hostname;
++    lease->fqdn_fwd_ = ctx.fwd_dns_update_;
++    lease->fqdn_rev_ = ctx.rev_dns_update_;
++    lease->hostname_ = ctx.hostname_;
+     // Let's execute all callouts registered for lease4_select
 -    if (callout_handle &&
++    if (ctx.callout_handle_ &&
+         HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {
+         // Delete all previous arguments
 -        callout_handle->deleteAllArguments();
++        ctx.callout_handle_->deleteAllArguments();
+         // Pass necessary arguments
+         // Subnet from which we do the allocation (That's as far as we can go
+         // with using SubnetPtr to point to Subnet4 object. Users should not
+         // be confused with dynamic_pointer_casts. They should get a concrete
+         // pointer (Subnet4Ptr) pointing to a Subnet4 object.
 -        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
 -        callout_handle->setArgument("subnet4", subnet4);
++        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
++        ctx.callout_handle_->setArgument("subnet4", subnet4);
+         // Is this solicit (fake = true) or request (fake = false)
 -        callout_handle->setArgument("fake_allocation", fake_allocation);
 +        ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
  
          // Pass the intended lease as well
 -        callout_handle->setArgument("lease4", lease);
 +        ctx.callout_handle_->setArgument("lease4", lease);
  
          // This is the first callout, so no need to clear any arguments
 -        HooksManager::callCallouts(hook_index_lease4_select_, *callout_handle);
 +        HooksManager::callCallouts(hook_index_lease4_select_, *ctx.callout_handle_);
  
          // Callouts decided to skip the action. This means that the lease is not
          // assigned, so the client will get NoAddrAvail as a result. The lease
      }
  }
  
- void
- AllocEngine::updateLease4Information(const Lease4Ptr& lease,
-                                      AllocEngine::ClientContext4& ctx) const {
-     // This should not happen in theory.
+ Lease4Ptr
+ AllocEngine::renewLease4(const Lease4Ptr& lease,
+                          AllocEngine::ClientContext4& ctx) {
      if (!lease) {
-         isc_throw(BadValue, "null lease specified for updateLease4Information");
-     }
-     if (!ctx.subnet_) {
-         isc_throw(BadValue, "null subnet specified for"
-                   " updateLease4Information");
+         isc_throw(BadValue, "null lease specified for renewLease4");
      }
  
-     lease->subnet_id_ = ctx.subnet_->getID();
-     lease->hwaddr_ = ctx.hwaddr_;
-     lease->client_id_ = ctx.clientid_;
-     lease->cltt_ = time(NULL);
-     lease->t1_ = ctx.subnet_->getT1();
-     lease->t2_ = ctx.subnet_->getT2();
-     lease->valid_lft_ = ctx.subnet_->getValid();
-     lease->fqdn_fwd_ = ctx.fwd_dns_update_;
-     lease->fqdn_rev_ = ctx.rev_dns_update_;
-     lease->hostname_ = ctx.hostname_;
- }
+     // Let's keep the old data. This is essential if we are using memfile
+     // (the lease returned points directly to the lease4 object in the database)
+     // We'll need it if we want to skip update (i.e. roll back renewal)
+     /// @todo: remove this once #3083 is implemented
+     Lease4 old_values = *lease;
+     ctx.old_lease_.reset(new Lease4(old_values));
  
- Lease6Collection
- 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_ = 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);
-     }
-     return (updated_leases);
- }
+     // Update the lease with the information from the context.
+     updateLease4Information(lease, ctx);
  
- AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
-     std::map<Lease::Type, AllocatorPtr>::const_iterator alloc = allocators_.find(type);
+     bool skip = false;
+     // Execute all callouts registered for lease4_renew.
+     if (HooksManager::getHooksManager().
+         calloutsPresent(Hooks.hook_index_lease4_renew_)) {
  
-     if (alloc == allocators_.end()) {
-         isc_throw(BadValue, "No allocator initialized for pool type "
-                   << Lease::typeToText(type));
-     }
-     return (alloc->second);
- }
+         // Delete all previous arguments
+         ctx.callout_handle_->deleteAllArguments();
  
- Lease6Collection
- AllocEngine::renewLeases6(ClientContext6& ctx) {
-     try {
-         if (!ctx.subnet_) {
-             isc_throw(InvalidOperation, "Subnet is required for allocation");
-         }
+         // Subnet from which we do the allocation. Convert the general subnet
+         // pointer to a pointer to a Subnet4.  Note that because we are using
+         // boost smart pointers here, we need to do the cast using the boost
+         // version of dynamic_pointer_cast.
+         Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
  
-         if (!ctx.duid_) {
-             isc_throw(InvalidOperation, "DUID is mandatory for allocation");
-         }
+         // Pass the parameters
+         ctx.callout_handle_->setArgument("subnet4", subnet4);
+         ctx.callout_handle_->setArgument("clientid", ctx.clientid_);
+         ctx.callout_handle_->setArgument("hwaddr", ctx.hwaddr_);
  
-         // Check which host reservation mode is supported in this subnet.
-         Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
+         // Pass the lease to be updated
+         ctx.callout_handle_->setArgument("lease4", lease);
  
-         // 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) {
+         // Call all installed callouts
+         HooksManager::callCallouts(Hooks.hook_index_lease4_renew_,
+                                    *ctx.callout_handle_);
  
-             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();
+         // Callouts decided to skip the next processing step. The next
+         // processing step would actually renew the lease, so skip at this
+         // stage means "keep the old lease as it is".
+         if (ctx.callout_handle_->getSkip()) {
+             skip = true;
+             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+                       DHCPSRV_HOOK_LEASE4_RENEW_SKIP);
          }
+     }
  
-         // 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.fake_allocation_ && !skip) {
+         // for REQUEST we do update the lease
+         LeaseMgrFactory::instance().updateLease4(lease);
+     }
+     if (skip) {
+         // Rollback changes (really useful only for memfile)
+         /// @todo: remove this once #3083 is implemented
+         *lease = old_values;
+     }
  
-         if (ctx.host_) {
-             // If we have host reservation, allocate those leases.
-             allocateReservedLeases6(ctx, leases);
+     return (lease);
+ }
  
-             // 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);
-         }
+ Lease4Ptr
+ AllocEngine::reuseExpiredLease4(Lease4Ptr& expired,
+                                 AllocEngine::ClientContext4& ctx) {
+     if (!expired) {
+         isc_throw(BadValue, "null lease specified for reuseExpiredLease");
+     }
  
-         // 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);
-         }
+     if (!ctx.subnet_) {
+         isc_throw(BadValue, "null subnet specified for the reuseExpiredLease");
+     }
  
-         // Extend all existing leases that passed all checks.
-         for (Lease6Collection::iterator l = leases.begin(); l != leases.end(); ++l) {
-             extendLease6(ctx, *l);
-         }
+     updateLease4Information(expired, ctx);
+     expired->fixed_ = false;
  
-         return (leases);
+     /// @todo: log here that the lease was reused (there's ticket #2524 for
+     /// logging in libdhcpsrv)
  
-     } catch (const isc::Exception& e) {
+     // Let's execute all callouts registered for lease4_select
+     if (ctx.callout_handle_ &&  HooksManager::getHooksManager()
+         .calloutsPresent(hook_index_lease4_select_)) {
  
-         // Some other error, return an empty lease.
-         LOG_ERROR(dhcpsrv_logger, DHCPSRV_RENEW6_ERROR).arg(e.what());
-     }
+         // Delete all previous arguments
+         ctx.callout_handle_->deleteAllArguments();
  
-     return (Lease6Collection());
- }
+         // Pass necessary arguments
  
- void
- AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
+         // Subnet from which we do the allocation. Convert the general subnet
+         // pointer to a pointer to a Subnet4.  Note that because we are using
+         // boost smart pointers here, we need to do the cast using the boost
+         // version of dynamic_pointer_cast.
+         Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
+         ctx.callout_handle_->setArgument("subnet4", subnet4);
  
-     if (!lease || !ctx.subnet_) {
-         return;
-     }
+         // Is this solicit (fake = true) or request (fake = false)
+         ctx.callout_handle_->setArgument("fake_allocation",
+                                          ctx.fake_allocation_);
  
-     // 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.
+         // The lease that will be assigned to a client
+         ctx.callout_handle_->setArgument("lease4", expired);
  
-         // Remove this lease from LeaseMgr
-         LeaseMgrFactory::instance().deleteLease(lease->addr_);
+         // Call the callouts
+         HooksManager::callCallouts(hook_index_lease4_select_, *ctx.callout_handle_);
  
-         // Add it to the removed leases list.
-         ctx.old_leases_.push_back(lease);
+         // Callouts decided to skip the action. This means that the lease is not
+         // assigned, so the client will get NoAddrAvail as a result. The lease
+         // won't be inserted into the database.
+         if (ctx.callout_handle_->getSkip()) {
+             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+                       DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+             return (Lease4Ptr());
+         }
  
-         return;
+         // Let's use whatever callout returned. Hopefully it is the same lease
+         // we handed to it.
+         ctx.callout_handle_->getArgument("lease4", expired);
      }
  
-     // 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_;
+     if (!ctx.fake_allocation_) {
+         // for REQUEST we do update the lease
+         LeaseMgrFactory::instance().updateLease4(expired);
+     }
  
-     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_;
+     // We do nothing for SOLICIT. We'll just update database when
+     // the client gets back to us with REQUEST message.
  
-         // Delete all previous arguments
-         callout_handle->deleteAllArguments();
+     // it's not really expired at this stage anymore - let's return it as
+     // an updated lease
+     return (expired);
+ }
  
-         // Pass the original packet
-         callout_handle->setArgument("query6", ctx.query_);
+ Lease4Ptr
+ AllocEngine::allocateOrReuseLease4(const IOAddress& candidate, ClientContext4& ctx) {
+     ctx.conflicting_lease_.reset();
  
-         // Pass the lease to be updated
-         callout_handle->setArgument("lease6", lease);
+     Lease4Ptr exist_lease = LeaseMgrFactory::instance().getLease4(candidate);
+     if (exist_lease) {
+         if (exist_lease->expired()) {
+             ctx.old_lease_ = Lease4Ptr(new Lease4(*exist_lease));
+             return (reuseExpiredLease4(exist_lease, ctx));
  
-         // 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_);
+             // If there is a lease and it is not expired, pass this lease back
+             // to the caller in the context. The caller may need to know
+             // which lease we're conflicting with.
+             ctx.conflicting_lease_ = exist_lease;
          }
  
-         // Call all installed callouts
-         HooksManager::callCallouts(hook_point, *callout_handle);
+     } else {
 -        return (createLease4(ctx.subnet_, ctx.clientid_,
 -                             ctx.hwaddr_, candidate, ctx.fwd_dns_update_,
 -                             ctx.rev_dns_update_, ctx.hostname_, ctx.callout_handle_,
 -                             ctx.fake_allocation_));
++        return (createLease4(ctx, candidate));
+     }
+     return (Lease4Ptr());
+ }
  
-         // 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());
+ Lease4Ptr
+ AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) {
+     Lease4Ptr new_lease;
+     AllocatorPtr allocator = getAllocator(Lease::TYPE_V4);
+     const uint64_t max_attempts = ctx.subnet_->getPoolCapacity(Lease::TYPE_V4);
+     for (uint64_t i = 0; i < max_attempts; ++i) {
+         IOAddress candidate = allocator->pickAddress(ctx.subnet_, ctx.clientid_,
+                                                      ctx.requested_address_);
+         // If address is not reserved for another client, try to allocate it.
+         if (!addressReserved(candidate, ctx)) {
+             // The call below will return the non-NULL pointer if we
+             // successfully allocate this lease. This means that the
+             // address is not in use by another client.
+             new_lease = allocateOrReuseLease4(candidate, ctx);
+             if (new_lease) {
+                 return (new_lease);
+             }
          }
      }
  
index 3651800f95bf65074807f3185603124e7204cf65,af6a59703f8f12a719f60a57d20fe940ad96cede..a92a837aae491e5e21cc5756d6037592956cce48
@@@ -993,18 -661,395 +661,412 @@@ private
      /// @param lease IPv6 lease to be extended.
      void extendLease6(ClientContext6& ctx, Lease6Ptr lease);
  
-     /// @brief a pointer to currently used allocator
+ public:
+     /// @brief Context information for the DHCPv4 lease allocation.
      ///
-     /// For IPv4, there will be only one allocator: TYPE_V4
-     /// For IPv6, there will be 3 allocators: TYPE_NA, TYPE_TA, TYPE_PD
-     std::map<Lease::Type, AllocatorPtr> allocators_;
+     /// This structure holds a set of information provided by the DHCPv4
+     /// server to the allocation engine. In particular, it holds the
+     /// client identifying information, such as HW address or client
+     /// identifier. It also holds the information about the subnet that
+     /// the client is connected to.
+     ///
+     /// This structure is also used to pass  some information from
+     /// the allocation engine back to the server, i.e. the old lease
+     /// which the client had before the allocation.
+     ///
+     /// This structure is meant to be extended in the future, if more
+     /// information should be passed to the allocation engine. Note
+     /// that the big advantage of using the context structure to pass
+     /// information to the allocation engine methods is that adding
+     /// new information doesn't modify the API of the allocation engine.
+     struct ClientContext4 {
+         /// @brief Subnet selected for the client by the server.
+         SubnetPtr subnet_;
  
-     /// @brief number of attempts before we give up lease allocation (0=unlimited)
-     unsigned int attempts_;
+         /// @brief Client identifier from the DHCP message.
+         ClientIdPtr clientid_;
  
-     // hook name indexes (used in hooks callouts)
-     int hook_index_lease4_select_; ///< index for lease4_select hook
-     int hook_index_lease6_select_; ///< index for lease6_select hook
+         /// @brief HW address from the DHCP message.
+         HWAddrPtr hwaddr_;
+         /// @brief An address that the client desires.
+         ///
+         /// If this address is set to 0 it indicates that this address
+         /// is unspecified.
+         asiolink::IOAddress requested_address_;
+         /// @brief Perform forward DNS update.
+         bool fwd_dns_update_;
+         /// @brief Perform reverse DNS update.
+         bool rev_dns_update_;
+         /// @brief Hostname.
+         ///
+         /// The server retrieves the hostname from the Client FQDN option,
+         /// Hostname option or the host reservation record for the client.
+         std::string hostname_;
+         /// @brief Callout handle associated with the client's message.
+         hooks::CalloutHandlePtr callout_handle_;
+         /// @brief Indicates if this is a real or fake allocation.
+         ///
+         /// The real allocation is when the allocation engine is supposed
+         /// to make an update in a lease database: create new lease, or
+         /// update existing lease.
+         bool fake_allocation_;
+         /// @brief A pointer to an old lease that the client had before update.
+         Lease4Ptr old_lease_;
+         /// @brief A pointer to the object identifying host reservations.
+         ConstHostPtr host_;
+         /// @brief A pointer to the object representing a lease in conflict.
+         ///
+         /// This pointer is set by some of the allocation methods when
+         /// the lease can't be allocated because there is another lease
+         /// which is in conflict with this allocation.
+         Lease4Ptr conflicting_lease_;
+         /// @brief Default constructor.
+         ClientContext4();
++        /// @brief Constructor with parameters
++        ///
++        /// @param subnet subnet the allocation should come from (mandatory)
++        /// @param clientid Client identifier (optional)
++        /// @param hwaddr Client's hardware address info (mandatory)
++        /// @param requested_addr A hint that the client provided (may be 0.0.0.0)
++        /// @param fwd_dns_update Indicates whether forward DNS
++        ///      update will be performed for the client (true) or not (false).
++        /// @param rev_dns_update Indicates whether reverse DNS
++        ///      update will be performed for the client (true) or not (false).
++        /// @param hostname A string carrying hostname to be used for DNS updates.
++        /// @param fake_allocation Is this real i.e. REQUEST (false)
++        ///      or just picking an address for DISCOVER that is not really
++        ///      allocated (true)
++        ClientContext4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
++                       const HWAddrPtr& hwaddr,
++                       const asiolink::IOAddress& requested_addr,
++                       const bool fwd_dns_update, const bool rev_dns_update,
++                       const std::string& hostname, const bool fake_allocation);
++
+         /// @brief Check if the specified lease belongs to the client.
+         ///
+         /// This method compares the hardware address and the client id
+         /// in the lease with the relevant values in the context. That
+         /// way the method determines whether the lease belongs to the
+         /// client which message the server is processing.
+         ///
+         /// @return true if the lease belongs to the client for which
+         /// the context has been created, false otherwise.
+         bool myLease(const Lease4& lease) const;
+     };
+     /// @brief Returns IPv4 lease.
+     ///
+     /// This method finds a lease for a client using the following algorithm:
+     /// - If a lease exists for the combination of the HW address or client id
+     ///   and a subnet, try to use this lease for the client. If the client
+     ///   has a reservation for an address for which the lease was created or
+     ///   the client desires to renew the lease for this address (ciaddr or
+     ///   requested IP address option), the server renews the lease for the
+     ///   client. If the client desires a different address or the server has
+     ///   a (potentially new) reservation for a different address for this
+     ///   client, the existing lease is replaced with a new lease.
+     /// - If the client has no lease in the lease database the server will try
+     ///   to allocate a new lease. If the client has a reservation for the
+     ///   particular address or if it has specified a desired address the
+     ///   server will check if the particular address is not allocated to
+     ///   another client. If the address is available, the server will allocate
+     ///   this address for the client.
+     /// - If the desired address is unavailable the server checks if the
+     ///   lease for this address has expired. If the lease is expired, the
+     ///   server will allocate this lease to the client. The relevant
+     ///   information will be updated, e.g. new client HW address, host name
+     ///   etc.
+     /// - If the desired address is in use by another client, the server will
+     ///   try to allocate a different address. The server picks addresses from
+     ///   a dynamic pool and checks if the address is available and that
+     ///   it is not reserved for another client. If it is in use by another
+     ///   client or if it is reserved for another client, the address is not
+     ///   allocated. The server picks the next address and repeats this check.
+     ///   Note that the server ceases allocation after the configured number
+     ///   of unsuccessful attempts.
+     ///
+     /// The lease allocation process is slightly different for the
+     /// DHCPDISCOVER and DHCPREQUEST messages. In the former case, the client
+     /// may specify the requested IP address option with a desired address and
+     /// the server treats this address as a hint. This means that the server may
+     /// allocate a different address at its discretion and send it to the
+     /// client in the DHCPOFFER. If the client accepts this offer it specifies
+     /// this address in the requested IP address option in the DHCPREQUEST.
+     /// At this point, the allocation engine will use the requested IP address
+     /// as a hard requirement and if this address can't be allocated for
+     /// any reason, the allocation engine returns NULL lease. As a result,
+     /// the DHCP server sends a DHCPNAK to the client and the client
+     /// falls back to the DHCP server discovery.
+     ///
+     /// The only exception from this rule is when the client doesn't specify
+     /// a requested IP address option (invalid behavior) in which case the
+     /// allocation engine will try to allocate any address.
+     ///
+     /// If there is an address reservation specified for the particular client
+     /// the reserved address always takes precedence over addresses from the
+     /// dynamic pool or even an address currently allocated for this client.
+     ///
+     /// It is possible that the address reserved for the particular client
+     /// is in use by another client, e.g. as a result of pools reconfiguration.
+     /// In this case, when the client requests allocation of the reserved
+     /// address and the server determines that it is leased to someone else,
+     /// the allocation engine allocates a different address for this client.
+     ///
+     /// When the client having a lease returns to renew, the allocation engine
+     /// doesn't extend the lease for it and returns a NULL pointer. The client
+     /// falls back to the 4-way exchange and a different lease is allocated.
+     /// At this point, the reserved address is freed and can be allocated to
+     /// the client which holds this reservation. However, this client has a
+     /// lease for a different address at this time. When the client renews its
+     /// lease it receives the DHCPNAK and falls back to the DHCP server
+     /// discovery and obtains the lease for the reserved address.
+     ///
+     /// When a server should do DNS updates, it is required that allocation
+     /// returns the information about how the lease was obtained by the allocation
+     /// engine. In particular, the DHCP server should be able to check whether
+     /// an existing lease was returned, or a new lease was allocated. When an
+     /// existing lease was returned, the server should check whether the FQDN has
+     /// changed between the allocation of the old and new lease. If so, the server
+     /// should perform the appropriate DNS update. If not, the server may choose
+     /// to not perform the update. The information about the old lease is returned via
+     /// @c old_lease parameter. If NULL value is returned, it is an indication
+     /// that a new lease was allocated for the client. If non-NULL value is
+     /// returned, it is an indication that allocation engine reused/renewed an
+     /// existing lease.
+     ///
 -    /// @todo Replace parameters with a single parameter of a
 -    /// @c ClientContext4 type.
 -    ///
 -    /// @param subnet subnet the allocation should come from
 -    /// @param clientid Client identifier
 -    /// @param hwaddr Client's hardware address info
 -    /// @param hint A hint that the client provided
 -    /// @param fwd_dns_update Indicates whether forward DNS update will be
 -    ///        performed for the client (true) or not (false).
 -    /// @param rev_dns_update Indicates whether reverse DNS update will be
 -    ///        performed for the client (true) or not (false).
 -    /// @param hostname A string carrying hostname to be used for DNS updates.
 -    /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
 -    ///        an address for DISCOVER that is not really allocated (true)
 -    /// @param callout_handle A callout handle (used in hooks). A lease callouts
 -    ///        will be executed if this parameter is passed.
 -    /// @param [out] old_lease Holds the pointer to a previous instance of a
 -    ///        lease. The NULL pointer indicates that lease didn't exist prior
 -    ///        to calling this function (e.g. new lease has been allocated).
++    /// @param ctx client context that passes all necessary information. See
++    ///        @ref ClientContext4 for details.
++    ///
++    /// The following fields of @ref ClientContext4 are used:
++    ///
++    /// - @ref ClientContext4::subnet_ subnet the allocation should come from
++    /// - @ref ClientContext4::clientid_ Client identifier
++    /// - @ref ClientContext4::hwaddr_ Client's hardware address info
++    /// - @ref ClientContext4::requested_address_ A hint that the client provided
++    /// - @ref ClientContext4::fwd_dns_update_ Indicates whether forward DNS
++    ///      update will be performed for the client (true) or not (false).
++    /// - @ref ClientContext4::rev_dns_update_ Indicates whether reverse DNS
++    ///      update will be performed for the client (true) or not (false).
++    /// - @ref ClientContext4::hostname_ A string carrying hostname to be used for
++    ///      DNS updates.
++    /// - @ref ClientContext4::fake_allocation_ Is this real i.e. REQUEST (false)
++    ///      or just picking an address for DISCOVER that is not really
++    ///      allocated (true)
++    /// - @ref ClientContext4::callout_handle_ A callout handle (used in hooks).
++    ///      A lease callouts will be executed if this parameter is passed.
++    /// - @ref ClientContext4::old_lease_ [out] Holds the pointer to a previous
++    ///      instance of a lease. The NULL pointer indicates that lease didn't
++    ///      exist prior to calling this function (e.g. new lease has been allocated).
+     ///
+     /// @return Allocated IPv4 lease (or NULL if allocation failed).
 -    Lease4Ptr allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
 -                             const HWAddrPtr& hwaddr,
 -                             const isc::asiolink::IOAddress& hint,
 -                             const bool fwd_dns_update, const bool rev_dns_update,
 -                             const std::string& hostname, bool fake_allocation,
 -                             const isc::hooks::CalloutHandlePtr& callout_handle,
 -                             Lease4Ptr& old_lease);
++    Lease4Ptr allocateLease4(ClientContext4& ctx);
+ private:
+     /// @brief Offers the lease.
+     ///
+     /// This method is called by the @c AllocEngine::allocateLease4 when
+     /// the server is processing a DHCPDISCOVER message, i.e. the fake
+     /// allocation case.
+     ///
+     /// This method doesn't modify leases in the lease database. It finds
+     /// the most suitable lease for the client and returns it to the caller.
+     /// The server uses this lease when it sends the DHCPOFFER to the
+     /// client from which it has received a DHCPDISCOVER message.
+     ///
+     /// The lease is found using the following algorithm:
+     /// -# If there is a reservation for the client, try to use the reserved
+     ///    address. This may fail if the particular address is in use by
+     ///    another client. In such case:
+     /// -# If the client has a lease, try to offer this lease. This may fail
+     ///    if it turns out that this address is reserved for another client
+     ///    or the address doesn't belong to the address pool. In such case:
+     /// -# Try to allocate the address provided by the client as a hint.
+     ///    This may fail if the address is in use or is reserved by some
+     ///    other client. In such case:
+     /// -# Try to offer an address from the dynamic pool.
+     ///
+     /// @throw various exceptions if the allocation goes wrong.
+     ///
+     /// @param ctx Client context holding the data extracted from the
+     /// client's message.
+     ///
+     /// @return A pointer to the offered lease, or NULL if no suitable lease
+     /// has been found.
+     Lease4Ptr discoverLease4(ClientContext4& ctx);
+     /// @brief Allocates the lease.
+     ///
+     /// This method is called by the @c AllocEngine::allocateLease4 when
+     /// the server is processing a DHCPREQUEST message, i.e. the real
+     /// allocation case.
+     ///
+     /// This method modifies the lease information in the lease database.
+     /// It adds new leases, modifies existing leases or deletes them.
+     ///
+     /// The method returns NULL to indicate that the lease allocation
+     /// has failed when any of the following occur:
+     /// -# The requested address is specified but is reserved for another
+     ///    client.
+     /// -# The requested address is in use by another client.
+     /// -# There is a reservation for the particular client, the
+     ///    reserved address is not in use by another client and the
+     ///    requested address is different than the reserved address.
+     /// -# There is no reservation for the client and the requested address
+     ///    is not in the dynamic pool.
+     ///
+     /// If none of the above occurs, the method will try to allocate the
+     /// lease for the client using the following algorithm:
+     /// -# If the client has a lease and the client is requesting the
+     ///    address for which it has a lease, renew its lease.
+     /// -# If the client is requesting a different address than that for
+     ///    which it has a lease, try to allocate the requested address.
+     ///    This may fail if the address is in use by another client.
+     /// -# If the client is not requesting any specific address, allocate
+     ///    the address from the dynamic pool.
+     ///
+     /// @throws various exceptions if the allocation goes wrong.
+     ///
+     /// @param ctx Client context holding the data extracted from the
+     /// client's message.
+     ///
+     /// @return A pointer to the allocated lease, or NULL if no suitable
+     /// lease could be allocated.
+     Lease4Ptr requestLease4(ClientContext4& ctx);
+     /// @brief Creates a lease and inserts it in LeaseMgr if necessary
+     ///
+     /// Creates a lease based on specified parameters and tries to insert it
+     /// into the database. That may fail in some cases, e.g. when there is another
+     /// allocation process and we lost a race to a specific lease.
+     ///
 -    /// @param subnet Subnet the lease is allocated from
 -    /// @param clientid Client identifier
 -    /// @param hwaddr Client's hardware address
++    /// @param ctx client context that contains additional parameters.
+     /// @param addr An address that was selected and is confirmed to be available
 -    /// @param fwd_dns_update Indicates whether forward DNS update will be
 -    ///        performed for the client (true) or not (false).
 -    /// @param rev_dns_update Indicates whether reverse DNS update will be
 -    ///        performed for the client (true) or not (false).
 -    /// @param hostname A string carrying hostname to be used for DNS updates.
 -    /// @param callout_handle a callout handle (used in hooks). A lease callouts
 -    ///        will be executed if this parameter is passed (and there are callouts
 -    ///        registered)
 -    /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
 -    ///        an address for DISCOVER that is not really allocated (true)
++    ///
++    /// In particular, the following fields from Client context are used:
++    /// - @ref ClientContext4::subnet_ Subnet the lease is allocated from
++    /// - @ref ClientContext4::clientid_ Client identifier
++    /// - @ref ClientContext4::hwaddr_ Client's hardware address
++    /// - @ref ClientContext4::fwd_dns_update_ Indicates whether forward DNS update
++    ///        will be performed for the client (true) or not (false).
++    /// - @ref ClientContext4::rev_dns_update_ Indicates whether reverse DNS update
++    ///        will be performed for the client (true) or not (false).
++    /// - @ref ClientContext4::hostname_ A string carrying hostname to be used for
++    ///        DNS updates.
++    /// - @ref ClientContext4::callout_handle_ a callout handle (used in hooks).
++    ///        A lease callouts will be executed if this parameter is passed
++    ///        (and there are callouts registered)
++    /// - @ref ClientContext4::fake_allocation_ Is this real i.e. REQUEST (false)
++    ///        or just picking an address for DISCOVER that is not really
++    ///        allocated (true)
+     /// @return allocated lease (or NULL in the unlikely case of the lease just
 -    ///        became unavailable)
 -    Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid,
 -                           const HWAddrPtr& hwaddr,
 -                           const isc::asiolink::IOAddress& addr,
 -                           const bool fwd_dns_update,
 -                           const bool rev_dns_update,
 -                           const std::string& hostname,
 -                           const isc::hooks::CalloutHandlePtr& callout_handle,
 -                           bool fake_allocation = false);
++    ///        becomed unavailable)
++    Lease4Ptr createLease4(const ClientContext4& ctx,
++                           const isc::asiolink::IOAddress& addr);
+     /// @brief Renews a DHCPv4 lease.
+     ///
+     /// This method updates the lease with the information from the provided
+     /// context and invokes the lease4_renew callout.
+     ///
+     /// The address of the lease being renewed is NOT updated.
+     ///
+     /// @param lease A lease to be renewed.
+     /// @param ctx Message processing context. It holds various information
+     /// extracted from the client's message and required to allocate a lease.
+     ///
+     /// @return Returns renewed lease. Note that the lease is only updated when
+     /// it is an actual allocation (not processing a DHCPDISCOVER message).
+     Lease4Ptr renewLease4(const Lease4Ptr& lease, ClientContext4& ctx);
+     /// @brief Reuses expired DHCPv4 lease.
+     ///
+     /// Makes a new allocation using an expired lease. The lease is updated with
+     /// the information from the provided context. Typically, an expired lease
+     /// which belonged to one client may be assigned to another client
+     /// which asked for the specific address.
+     ///
+     /// @param expired An old, expired lease.
+     /// @param ctx Message processing context. It holds various information
+     /// extracted from the client's message and required to allocate a lease.
+     ///
+     /// @return Updated lease instance.
+     /// @throw BadValue if trying to reuse a lease which is still valid or
+     /// when the provided parameters are invalid.
+     Lease4Ptr reuseExpiredLease4(Lease4Ptr& expired, ClientContext4& ctx);
+     /// @brief Allocates the lease by replacing an existing lease.
+     ///
+     /// This method checks if the lease database contains the lease for
+     /// the specified address. If the lease exists and has expired, it
+     /// reuses the expired lease. If the lease doesn't exist, it creates
+     /// the new lease.
+     ///
+     /// @param address Requested address for which the lease should be
+     /// allocted.
+     /// @param ctx Client context holding the data extracted from the
+     /// client's message.
+     ///
+     /// @return A pointer to the allocated lease or NULL if the allocation
+     /// was not successful.
+     Lease4Ptr allocateOrReuseLease4(const asiolink::IOAddress& address,
+                                     ClientContext4& ctx);
+     /// @brief Allocates the lease from the dynamic pool.
+     ///
+     /// This method allocates the lease from the dynamic pool. It uses
+     /// one of the allocators to pick addresses from the pool and if the
+     /// address appears to be available, it allocates the new lease
+     /// using this address. The number of attempts depends on the size
+     /// of the dynamic pool. If all of the addresses in the pool have
+     /// been tried and all of them appeared to be in use, the allocation
+     /// fails. This is the case when the pool is exhausted.
+     ///
+     /// The time required to find a suitable lease depends on the current
+     /// pool utilization.
+     ///
+     /// @param ctx Client context holding the data extracted from the
+     /// client's message.
+     ///
+     /// @return A pointer to the allocated lease or NULL if the allocation
+     /// was not successful.
+     Lease4Ptr allocateUnreservedLease4(ClientContext4& ctx);
+     /// @brief Updates the specified lease with the information from a context.
+     ///
+     /// The context, specified as an argument to this method, holds various
+     /// information gathered from the client's message and passed to the
+     /// allocation engine. The allocation engine uses this information to make
+     /// lease allocation decisions. Some public methods of the allocation engine
+     /// requires updating the lease information with the data gathered from the
+     /// context, e.g. @c AllocEngine::reuseExpiredLease requires updating the
+     /// expired lease with fresh information from the context to create a
+     /// lease to be held for the client.
+     ///
+     /// Note that this doesn't update the lease address.
+     ///
+     /// @warning This method doesn't check if the pointer to the lease is
+     /// valid nor if the subnet to the pointer in the @c ctx is valid.
+     /// The caller is responsible for making sure that they are valid.
+     ///
+     /// @param [out] lease A pointer to the lease to be updated.
+     /// @param ctx A context containing information from the server about the
+     /// client and its message.
+     void updateLease4Information(const Lease4Ptr& lease,
+                                  ClientContext4& ctx) const;
  };
  
  }; // namespace isc::dhcp
index 1351ae66c380c7fcbc51676732c787d2d25cc2c8,b6f7202620fb7c39d8f2fa48a7e925f2c07eabf9..3d5706ce3e21f9f4c1ed8a69dafb75b7f4b2d8d8
@@@ -160,13 -162,14 +160,13 @@@ TEST_F(AllocEngine4Test, allocWithUsedH
      // 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.
 -    Lease4Ptr lease = engine->allocateLease4(subnet_, clientid_, hwaddr_,
 -                                             IOAddress("192.0.2.106"),
 -                                             false, false, "",
 -                                             true, CalloutHandlePtr(),
 -                                             old_lease_);
 +    AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
 +                                    IOAddress("192.0.2.106"), false, false,
-                                     "", false);
++                                    "", true);
 +    Lease4Ptr lease = engine->allocateLease4(ctx);
  
      // New lease has been allocated, so the old lease should not exist.
 -    EXPECT_FALSE(old_lease_);
 +    EXPECT_FALSE(ctx.old_lease_);
  
      // Check that we got a lease
      ASSERT_TRUE(lease);
@@@ -197,13 -197,14 +194,13 @@@ TEST_F(AllocEngine4Test, allocBogusHint
                                                   100, false)));
      ASSERT_TRUE(engine);
  
-     // Client would like to get a 3000::abc lease, which does not belong to any
+     // Client would like to get a 10.1.1.1 lease, which does not belong to any
      // supported lease. Allocation engine should ignore it and carry on
      // with the normal allocation
 -    Lease4Ptr lease = engine->allocateLease4(subnet_, clientid_, hwaddr_,
 -                                             IOAddress("10.1.1.1"),
 -                                             false, false, "",
 -                                             true, CalloutHandlePtr(),
 -                                             old_lease_);
 +    AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
 +                                    IOAddress("10.1.1.1"), false, false,
-                                     "", false);
++                                    "", true);
 +    Lease4Ptr lease = engine->allocateLease4(ctx);
      // Check that we got a lease
      ASSERT_TRUE(lease);
  
@@@ -539,58 -542,48 +532,43 @@@ TEST_F(AllocEngine4Test, requestReuseEx
  
      // The allocation engine should return a copy of the old lease. This
      // lease should be equal to the original lease.
 -    ASSERT_TRUE(old_lease_);
 -    EXPECT_TRUE(*old_lease_ == original_lease);
 +    ASSERT_TRUE(ctx.old_lease_);
 +    EXPECT_TRUE(*ctx.old_lease_ == original_lease);
  }
  
- /// @todo write renewLease6
- // This test checks if a lease is really renewed when renewLease4 method is
- // called
- TEST_F(AllocEngine4Test, renewLease4) {
-     boost::scoped_ptr<AllocEngine> engine;
-     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE,
-                                                  100, false)));
-     ASSERT_TRUE(engine);
-     IOAddress addr("192.0.2.102");
-     const uint32_t old_lifetime = 100;
-     const uint32_t old_t1 = 50;
-     const uint32_t old_t2 = 75;
-     const time_t old_timestamp = time(NULL) - 45; // Allocated 45 seconds ago
-     // Just a different hw/client-id for the second client
-     const uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
-     HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
-     const uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
-     Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2),
-                                old_lifetime, old_t1, old_t2,
-                                old_timestamp, subnet_->getID()));
-     ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
-     // Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's
-     // renew it.
-     ASSERT_FALSE(lease->expired());
-     ctx_.fwd_dns_update_ = true;
-     ctx_.rev_dns_update_ = true;
-     ctx_.hostname_ = "host.example.com.";
-     ctx_.fake_allocation_ = false;
-     lease = engine->renewLease4(lease, ctx_);
-     // Check that he got that single lease
-     ASSERT_TRUE(lease);
-     EXPECT_EQ(addr, lease->addr_);
-     // Check that the lease matches subnet_, hwaddr_,clientid_ parameters
-     checkLease4(lease);
+ // This test checks that when the client requests the address which belongs
+ // to another client, the allocation engine returns NULL (for the
+ // DHCPREQUEST case) or a lease for the address which belongs to this
+ // client (DHCPDISCOVER case).
+ TEST_F(AllocEngine4Test, requestOtherClientLease) {
+     // Create the first lease.
+     Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0,
+                                100, 30, 60, time(NULL), subnet_->getID(),
+                                false, false, ""));
+     // Create the second lease.
+     Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.102"), hwaddr2_, 0, 0,
+                                100, 30, 60, time(NULL), subnet_->getID(),
+                                false, false, ""));
+     // Add leases for both clients to the Lease Manager.
+     LeaseMgrFactory::instance().addLease(lease);
+     LeaseMgrFactory::instance().addLease(lease2);
  
-     // Check that the lease is indeed updated in LeaseMgr
-     Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
-     ASSERT_TRUE(from_mgr);
+     AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false);
  
-     // Now check that the lease in LeaseMgr has the same parameters
-     detailCompareLease(lease, from_mgr);
+     // First client requests the lease which belongs to the second client.
 -    Lease4Ptr new_lease = engine.allocateLease4(subnet_, clientid_, hwaddr_,
 -                                                IOAddress("192.0.2.102"),
 -                                                false, false, "",
 -                                                false, CalloutHandlePtr(),
 -                                                old_lease_);
++    AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.102"),
++                                    false, false, "", false);
++    Lease4Ptr new_lease = engine.allocateLease4(ctx);
+     // Allocation engine should return NULL.
+     ASSERT_FALSE(new_lease);
+     // Now simulate the DHCPDISCOVER case when the provided address is
+     // treated as a hint. The engine should return a lease for a
+     // different address than requested.
 -    new_lease = engine.allocateLease4(subnet_, clientid_, hwaddr_,
 -                                      IOAddress("192.0.2.102"),
 -                                      false, false, "",
 -                                      true, CalloutHandlePtr(),
 -                                      old_lease_);
++    ctx.fake_allocation_ = true;
++    new_lease = engine.allocateLease4(ctx);
+     ASSERT_TRUE(new_lease);
+     EXPECT_EQ("192.0.2.101", new_lease->addr_.toText());
  }
  
  // This test checks the behavior of the allocation engine in the following
@@@ -889,24 -881,32 +860,30 @@@ TEST_F(AllocEngine4Test, reservedAddres
      // Query allocation engine for the lease to be allocated to the client B.
      // The allocation engine is not able to allocate the lease to the client
      // B, because the address is in use by client A.
 -    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_,
 -                                                      hwaddr_,
 -                                                      IOAddress("192.0.2.123"),
 -                                                      false, false, "",
 -                                                      true, CalloutHandlePtr(),
 -                                                      old_lease_);
 +    AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
 +                                    IOAddress("192.0.2.123"), false, false,
 +                                    "", true);
 +    Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
 +
-     // The allocation engine should return no lease.
-     ASSERT_FALSE(allocated_lease);
+     // The allocation engine should return a lease but for a different address
+     // than requested because this address is in use.
+     ASSERT_TRUE(allocated_lease);
 +    EXPECT_FALSE(ctx1.old_lease_);
+     EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.123");
+     EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, allocated_lease->addr_));
 -    EXPECT_FALSE(old_lease_);
  
      // Do the same test. But, this time do not specify any address to be
      // allocated.
 -    allocated_lease = engine.allocateLease4(subnet_, clientid_,
 -                                            hwaddr_,
 -                                            IOAddress("0.0.0.0"),
 -                                            false, false, "",
 -                                            true, CalloutHandlePtr(),
 -                                            old_lease_);
 +    AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
 +                                    IOAddress("0.0.0.0"), false, false,
 +                                    "", true);
 +    allocated_lease = engine.allocateLease4(ctx2);
 +
-     EXPECT_FALSE(allocated_lease);
+     ASSERT_TRUE(allocated_lease);
+     EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.123");
+     EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, allocated_lease->addr_));
 -    EXPECT_FALSE(old_lease_);
 +    EXPECT_FALSE(ctx2.old_lease_);
  }
  
  // This test checks that the behavior of the allocation engine in the following
@@@ -1153,42 -1163,46 +1132,42 @@@ TEST_F(AllocEngine4Test, reservedAddres
  
  
      // Client B sends a DHCPREQUEST to allocate a reserved lease. The
-     // allocation engine declines allocation of the address for the
-     // client because Client A has a lease for it.
+     // allocation engine can't allocate a reserved lease for this client
+     // because this specific address is in use by the Client A.
 -    Lease4Ptr offered_lease = engine.allocateLease4(subnet_, ClientIdPtr(),
 -                                                    hwaddr2_,
 -                                                    IOAddress("192.0.2.101"),
 -                                                    false, false, "", false,
 -                                                    CalloutHandlePtr(),
 -                                                    old_lease_);
 +    AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr2_,
 +                                    IOAddress("192.0.2.101"), false, false,
 +                                    "", false);
-     ASSERT_FALSE(engine.allocateLease4(ctx1));
++    Lease4Ptr offered_lease = engine.allocateLease4(ctx1);
+     ASSERT_FALSE(offered_lease);
  
 -    // Client A trys to renew the lease. The renewal should fail because
 +    // Client A tries to renew the lease. The renewal should fail because
      // server detects that Client A doesn't have reservation for this
      // address.
 -    ASSERT_FALSE(engine.allocateLease4(subnet_, clientid_, hwaddr_,
 -                                       IOAddress("192.0.2.101"), false, false,
 -                                       "", false, CalloutHandlePtr(),
 -                                       old_lease_));
 -    ASSERT_FALSE(old_lease_);
 +    AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
 +                                    IOAddress("192.0.2.101"), false, false,
 +                                    "", false);
 +    ASSERT_FALSE(engine.allocateLease4(ctx2));
 +
-     ASSERT_TRUE(ctx2.old_lease_);
-     EXPECT_EQ("192.0.2.101", ctx2.old_lease_->addr_.toText());
++    ASSERT_FALSE(ctx2.old_lease_);
  
      // Client A returns to DHCPDISCOVER and should be offered a lease.
      // The offered lease address must be different than the one the
      // Client B has reservation for.
 -    offered_lease = engine.allocateLease4(subnet_, clientid_,
 -                                          hwaddr_,
 -                                          IOAddress("192.0.2.101"),
 -                                          false, false, "", true,
 -                                          CalloutHandlePtr(),
 -                                          old_lease_);
 +    AllocEngine::ClientContext4 ctx3(subnet_, clientid_, hwaddr_,
 +                                    IOAddress("192.0.2.101"), false, false,
 +                                    "", true);
-     Lease4Ptr offered_lease = engine.allocateLease4(ctx3);
++    offered_lease = engine.allocateLease4(ctx3);
      ASSERT_TRUE(offered_lease);
      EXPECT_NE(offered_lease->addr_.toText(), "192.0.2.101");
  
-     // Client A tried to acquire the lease. It should succeed. At this point
 -    // Client A trys to acquire the lease. It should succeed. At this point
++    // Client A tries to acquire the lease. It should succeed. At this point
      // the previous lease should be released and become available for the
      // Client B.
 -    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_,
 -                                                      hwaddr_,
 -                                                      offered_lease->addr_,
 -                                                      false, false, "", false,
 -                                                      CalloutHandlePtr(),
 -                                                      old_lease_);
 +    AllocEngine::ClientContext4 ctx4(subnet_, clientid_, hwaddr_,
 +                                    offered_lease->addr_, false, false,
 +                                    "", false);
 +    Lease4Ptr allocated_lease = engine.allocateLease4(ctx4);
 +
      ASSERT_TRUE(allocated_lease);
      EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.101");
  
@@@ -1228,13 -1244,18 +1207,17 @@@ TEST_F(AllocEngine4Test, reservedAddres
      // an iterative allocator which would pick the first address from the
      // dynamic pool, i.e. 192.0.2.100. This address is reserved so we expect
      // that a different address will be allocated.
 -    Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, ClientIdPtr(),
 -                                                      hwaddr_,
 -                                                      IOAddress("0.0.0.0"),
 -                                                      false, false, "", false,
 -                                                      CalloutHandlePtr(),
 -                                                      old_lease_);
 +    AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
 +                                     IOAddress("0.0.0.0"), false, false,
 +                                    "", false);
 +    Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
 +
      ASSERT_TRUE(allocated_lease);
      EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.100");
+     Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(allocated_lease->addr_);
+     ASSERT_TRUE(from_mgr);
+     detailCompareLease(allocated_lease, from_mgr);
  }
  
  // This test checks that the client requesting an address which is