From: Marcin Siodelski Date: Thu, 4 Dec 2014 18:07:16 +0000 (+0100) Subject: [3564] Updated allocateLease4 in allocation engine to use reservations. X-Git-Tag: trac3504_base~25^2~9 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=bf3ea66a3e291fd1ec56e03531b4d42398ccfa09;p=thirdparty%2Fkea.git [3564] Updated allocateLease4 in allocation engine to use reservations. --- diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 5009cfcf78..a40b45d4e9 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -509,70 +510,94 @@ AllocEngine::allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid isc_throw(InvalidOperation, "HWAddr must be defined"); } - // Check if there's existing lease for that subnet/clientid/hwaddr combination. - Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID()); - if (existing) { - // Save the old lease, before renewal. - old_lease.reset(new Lease4(*existing)); - // We have a lease already. This is a returning client, probably after - // its reboot. - existing = renewLease4(subnet, clientid, hwaddr, - fwd_dns_update, rev_dns_update, hostname, - existing, callout_handle, fake_allocation); - if (existing) { - return (existing); + // Build the processing context. + Context4 ctx; + ctx.subnet_ = subnet; + ctx.clientid_ = clientid; + ctx.hwaddr_ = hwaddr; + ctx.hint_ = 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; + ctx.host_ = HostMgr::instance().get4(subnet->getID(), hwaddr); + + if (ctx.host_) { + if (ctx.hint_ == IOAddress("0.0.0.0")) { + ctx.hint_ = ctx.host_->getIPv4Reservation(); + + } else if (!ctx.fake_allocation_ && + (ctx.hint_ != ctx.host_->getIPv4Reservation())) { + return (Lease4Ptr()); } + } - // If renewal failed (e.g. the lease no longer matches current configuration) - // let's continue the allocation process + LeaseMgr& lease_mgr = LeaseMgrFactory::instance(); + Lease4Ptr existing = lease_mgr.getLease4(*hwaddr, ctx.subnet_->getID()); + if (!existing && clientid) { + existing = lease_mgr.getLease4(*clientid, ctx.subnet_->getID()); } - if (clientid) { - existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID()); - if (existing) { - // Save the old lease before renewal. - old_lease.reset(new Lease4(*existing)); - // we have a lease already. This is a returning client, probably after - // its reboot. - existing = renewLease4(subnet, clientid, hwaddr, - fwd_dns_update, rev_dns_update, - hostname, existing, callout_handle, - fake_allocation); - // @todo: produce a warning. We haven't found him using MAC address, but - // we found him using client-id - if (existing) { - return (existing); - } + if (existing) { + existing = reallocateClientLease(existing, ctx); + if (ctx.host_ || existing) { + old_lease = ctx.old_lease_; + return (existing); } } - // check if the hint is in pool and is available - if (subnet->inPool(Lease::TYPE_V4, hint)) { - existing = LeaseMgrFactory::instance().getLease4(hint); - if (!existing) { - /// @todo: Check if the hint is reserved once we have host support - /// implemented + // Check if we have a specific address we would like to allocate for the + // client. It may either be an address which the client is currently + // using, or it may be a administratively reserved address. + if (ctx.host_ || subnet->inPool(Lease::TYPE_V4, hint)) { + // If a client is requesting specific IP address, but the + // reservation was made for a different address the server returns + // NAK to the client. By returning NULL lease here we indicate to + // the server that we're not able to fulfil client's request for the + // particular IP address. + if (!ctx.fake_allocation_ && ctx.host_ && + (hint != IOAddress("0.0.0.0")) && + (ctx.host_->getIPv4Reservation() != hint)) { + return (Lease4Ptr()); + } - // The hint is valid and not currently used, let's create a lease for it - Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint, - fwd_dns_update, rev_dns_update, + // The reserved address always takes precedence over hints. But, if + // there is no reservation, try to respect the client's hint. + const IOAddress& candidate = ctx.host_ ? + ctx.host_->getIPv4Reservation() : hint; + + existing = LeaseMgrFactory::instance().getLease4(candidate); + if (!existing) { + // The candidate address is currently unused. Let's create a + // lease for it. + Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, + candidate, fwd_dns_update, + rev_dns_update, hostname, callout_handle, fake_allocation); - // It can happen that the lease allocation failed (we could have lost - // the race condition. That means that the hint is lo longer usable and - // we need to continue the regular allocation path. - if (lease) { + // If we have allocated the lease let's return it. Also, + // always return when tried to allocate reserved address, + // regardless if allocation was successful or not. If it + // was not successful, we will return a NULL pointer which + // indicates to the server that it should send NAK to the + // client. + if (lease || ctx.host_) { return (lease); } + } else { if (existing->expired()) { // Save the old lease, before reusing it. old_lease.reset(new Lease4(*existing)); - return (reuseExpiredLease(existing, subnet, clientid, hwaddr, - fwd_dns_update, rev_dns_update, - hostname, callout_handle, - fake_allocation)); + return (reuseExpiredLease(existing, ctx)); + + } else if (ctx.host_ && (ctx.host_->getIPv4Reservation() != + IOAddress("0.0.0.0"))) { + return (Lease4Ptr()); + } } @@ -620,10 +645,7 @@ AllocEngine::allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid if (existing->expired()) { // Save old lease before reusing it. old_lease.reset(new Lease4(*existing)); - return (reuseExpiredLease(existing, subnet, clientid, hwaddr, - fwd_dns_update, rev_dns_update, - hostname, callout_handle, - fake_allocation)); + return (reuseExpiredLease(existing, ctx)); } } @@ -643,18 +665,11 @@ AllocEngine::allocateLease4(const SubnetPtr& subnet, const ClientIdPtr& clientid return (Lease4Ptr()); } -Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet, - const ClientIdPtr& clientid, - const HWAddrPtr& hwaddr, - const bool fwd_dns_update, - const bool rev_dns_update, - const std::string& hostname, - const Lease4Ptr& lease, - const isc::hooks::CalloutHandlePtr& callout_handle, - bool fake_allocation /* = false */) { - +Lease4Ptr +AllocEngine::renewLease4(const Lease4Ptr& lease, + const AllocEngine::Context4& ctx) { if (!lease) { - isc_throw(InvalidOperation, "Lease4 must be specified"); + isc_throw(BadValue, "null lease specified for renewLease4"); } // Let's keep the old data. This is essential if we are using memfile @@ -663,51 +678,46 @@ Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet, /// @todo: remove this once #3083 is implemented Lease4 old_values = *lease; - lease->subnet_id_ = subnet->getID(); - lease->hwaddr_ = hwaddr; - lease->client_id_ = clientid; - lease->cltt_ = time(NULL); - lease->t1_ = subnet->getT1(); - lease->t2_ = subnet->getT2(); - lease->valid_lft_ = subnet->getValid(); - lease->fqdn_fwd_ = fwd_dns_update; - lease->fqdn_rev_ = rev_dns_update; - lease->hostname_ = hostname; + // Update the lease with the information from the context. + updateLease4Information(lease, ctx); bool skip = false; - // Execute all callouts registered for packet6_send - if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease4_renew_)) { + // Execute all callouts registered for lease4_renew. + if (HooksManager::getHooksManager(). + calloutsPresent(Hooks.hook_index_lease4_renew_)) { // Delete all previous arguments - callout_handle->deleteAllArguments(); + ctx.callout_handle_->deleteAllArguments(); // 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(subnet); + Subnet4Ptr subnet4 = boost::dynamic_pointer_cast(ctx.subnet_); // Pass the parameters - callout_handle->setArgument("subnet4", subnet4); - callout_handle->setArgument("clientid", clientid); - callout_handle->setArgument("hwaddr", hwaddr); + ctx.callout_handle_->setArgument("subnet4", subnet4); + ctx.callout_handle_->setArgument("clientid", ctx.clientid_); + ctx.callout_handle_->setArgument("hwaddr", ctx.hwaddr_); // Pass the lease to be updated - callout_handle->setArgument("lease4", lease); + ctx.callout_handle_->setArgument("lease4", lease); // Call all installed callouts - HooksManager::callCallouts(Hooks.hook_index_lease4_renew_, *callout_handle); + HooksManager::callCallouts(Hooks.hook_index_lease4_renew_, + *ctx.callout_handle_); // Callouts decided to skip the next processing step. The next // processing step would to actually renew the lease, so skip at this // stage means "keep the old lease as it is". - if (callout_handle->getSkip()) { + if (ctx.callout_handle_->getSkip()) { skip = true; - LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_RENEW_SKIP); + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, + DHCPSRV_HOOK_LEASE4_RENEW_SKIP); } } - if (!fake_allocation && !skip) { + if (!ctx.fake_allocation_ && !skip) { // for REQUEST we do update the lease LeaseMgrFactory::instance().updateLease4(lease); } @@ -803,42 +813,29 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired, return (expired); } -Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired, - const SubnetPtr& subnet, - const ClientIdPtr& clientid, - const HWAddrPtr& hwaddr, - const bool fwd_dns_update, - const bool rev_dns_update, - const std::string& hostname, - const isc::hooks::CalloutHandlePtr& callout_handle, - bool fake_allocation /*= false */ ) { +Lease4Ptr +AllocEngine::reuseExpiredLease(Lease4Ptr& expired, + const AllocEngine::Context4& ctx) { + if (!expired) { + isc_throw(BadValue, "null lease specified for reuseExpiredLease"); + } - if (!expired->expired()) { - isc_throw(BadValue, "Attempt to recycle lease that is still valid"); + if (!ctx.subnet_) { + isc_throw(BadValue, "null subnet specified for the reuseExpiredLease"); } - // address, lease type and prefixlen (0) stay the same - expired->client_id_ = clientid; - expired->hwaddr_ = hwaddr; - expired->valid_lft_ = subnet->getValid(); - expired->t1_ = subnet->getT1(); - expired->t2_ = subnet->getT2(); - expired->cltt_ = time(NULL); - expired->subnet_id_ = subnet->getID(); + updateLease4Information(expired, ctx); expired->fixed_ = false; - expired->hostname_ = hostname; - expired->fqdn_fwd_ = fwd_dns_update; - expired->fqdn_rev_ = rev_dns_update; /// @todo: log here that the lease was reused (there's ticket #2524 for /// logging in libdhcpsrv) // Let's execute all callouts registered for lease4_select - if (callout_handle && - HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) { + 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 @@ -846,32 +843,34 @@ Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired, // 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(subnet); - callout_handle->setArgument("subnet4", subnet4); + Subnet4Ptr subnet4 = boost::dynamic_pointer_cast(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_); // The lease that will be assigned to a client - callout_handle->setArgument("lease4", expired); + ctx.callout_handle_->setArgument("lease4", expired); // Call the callouts - HooksManager::callCallouts(hook_index_lease6_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 // won't be inserted into the database. - if (callout_handle->getSkip()) { - LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP); + if (ctx.callout_handle_->getSkip()) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, + DHCPSRV_HOOK_LEASE4_SELECT_SKIP); return (Lease4Ptr()); } // Let's use whatever callout returned. Hopefully it is the same lease // we handled to it. - callout_handle->getArgument("lease4", expired); + ctx.callout_handle_->getArgument("lease4", expired); } - if (!fake_allocation) { + if (!ctx.fake_allocation_) { // for REQUEST we do update the lease LeaseMgrFactory::instance().updateLease4(expired); } @@ -884,6 +883,87 @@ Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired, return (expired); } +Lease4Ptr +AllocEngine::replaceClientLease(Lease4Ptr& lease, const Context4& ctx) { + if (!lease) { + isc_throw(BadValue, "null lease specified for replaceClientLease"); + } + + if (!ctx.subnet_) { + isc_throw(BadValue, "null subnet specified for replaceClientLease"); + } + + if (!ctx.host_) { + isc_throw(BadValue, "null host specified for replaceClientLease"); + } + + if (ctx.hint_ == IOAddress("0.0.0.0")) { + isc_throw(BadValue, "zero address specified for the" + " replaceClientLease"); + } + + IOAddress prev_address = lease->addr_; + updateLease4Information(lease, ctx); + lease->addr_ = ctx.host_->getIPv4Reservation(); + + // Execute callouts registered for lease4_select. + if (ctx.callout_handle_ && HooksManager::getHooksManager() + .calloutsPresent(hook_index_lease4_select_)) { + + // Delete all previous arguments. + ctx.callout_handle_->deleteAllArguments(); + + // Pass arguments. + Subnet4Ptr subnet4 = boost::dynamic_pointer_cast(ctx.subnet_); + ctx.callout_handle_->setArgument("subnet4", subnet4); + + ctx.callout_handle_->setArgument("fake_allocation", + ctx.fake_allocation_); + + ctx.callout_handle_->setArgument("lease4", lease); + + HooksManager::callCallouts(hook_index_lease4_select_, + *ctx.callout_handle_); + + if (ctx.callout_handle_->getSkip()) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, + DHCPSRV_HOOK_LEASE4_SELECT_SKIP); + return (Lease4Ptr()); + } + + // Let's use whatever callout returned. + ctx.callout_handle_->getArgument("lease4", lease); + } + + if (!ctx.fake_allocation_) { + LeaseMgrFactory::instance().deleteLease(prev_address); + LeaseMgrFactory::instance().addLease(lease); + } + + return (lease); +} + +Lease4Ptr +AllocEngine::reallocateClientLease(Lease4Ptr& lease, + AllocEngine::Context4& ctx) { + // Save the old lease, before renewal. + ctx.old_lease_.reset(new Lease4(*lease)); + + if (ctx.host_ && ctx.host_->getIPv4Reservation() != lease->addr_) { + lease = replaceClientLease(lease, ctx); + return (lease); + + } else { + lease = renewLease4(lease, ctx); + if (lease) { + return (lease); + } + } + + return (Lease4Ptr()); +} + + Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid, const uint32_t iaid, @@ -1064,6 +1144,31 @@ Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet, } } +void +AllocEngine::updateLease4Information(const Lease4Ptr& lease, + const AllocEngine::Context4& ctx) const { + // This should not happen in theory. + if (!lease) { + isc_throw(BadValue, "null lease specified for updateLease4Information"); + } + + if (!ctx.subnet_) { + isc_throw(BadValue, "null subnet specified for" + " updateLease4Information"); + } + + 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_; +} + Lease6Collection AllocEngine::updateFqdnData(const Lease6Collection& leases, const bool fwd_dns_update, diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 68492d4852..220219ce1a 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -214,6 +215,72 @@ protected: ALLOC_RANDOM // random - an address is randomly selected } AllocType; + /// @brief Context information for the DHCPv4 lease allocation. + /// + /// 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 Context4 { + /// @brief Subnet selected for the client by the server. + SubnetPtr subnet_; + + /// @brief Client identifier from the DHCP message. + ClientIdPtr clientid_; + + /// @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 hint_; + + /// @brief Perform forward DNS update. + bool fwd_dns_update_; + + /// @brief Perform reverse DNS update. + bool rev_dns_update_; + + /// @brief Hostname. + 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 Default constructor. + Context4() + : subnet_(), clientid_(), hwaddr_(), hint_("0.0.0.0"), + fwd_dns_update_(false), rev_dns_update_(false), + hostname_(""), callout_handle_(), fake_allocation_(false), + old_lease_(), host_() { + } + }; /// @brief Default constructor. /// @@ -283,38 +350,20 @@ protected: const isc::hooks::CalloutHandlePtr& callout_handle, Lease4Ptr& old_lease); - /// @brief Renews a IPv4 lease + /// @brief Renews an DHCPv4 lease. /// - /// Since both request and renew are implemented in DHCPv4 as the sending of - /// a REQUEST packet, it is difficult to easily distinguish between those - /// cases. Therefore renew for DHCPv4 is done in the allocation engine. - /// This method is also used when client crashed/rebooted and tries - /// to get a new lease. It thinks that it gets a new lease, but in fact - /// we are only renewing the still valid lease for that client. + /// This method updates the lease with the information from the provided + /// context and invokes the lease4_renew callout. /// - /// @param subnet A subnet the client is attached to - /// @param clientid Client identifier - /// @param hwaddr Client's hardware address - /// @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 lease A lease to be renewed - /// @param callout_handle a callout handle (used in hooks). A lease callouts - /// will be executed if this parameter is passed. - /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking - /// an address for DISCOVER that is not really allocated (true) + /// The address of the lease being renewed is NOT updated. + /// + /// @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 DHCPDISCOVER message). Lease4Ptr - renewLease4(const SubnetPtr& subnet, - const ClientIdPtr& clientid, - const HWAddrPtr& hwaddr, - const bool fwd_dns_update, - const bool rev_dns_update, - const std::string& hostname, - const Lease4Ptr& lease, - const isc::hooks::CalloutHandlePtr& callout_handle, - bool fake_allocation /* = false */); + renewLease4(const Lease4Ptr& lease, const Context4& ctx); /// @brief Allocates an IPv6 lease /// @@ -396,6 +445,25 @@ private: const isc::hooks::CalloutHandlePtr& callout_handle, bool fake_allocation = false); + /// @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 a fresh information from the context to create a + /// lease to be held for the client. + /// + /// Note that this doesn't update the lease address. + /// + /// @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, + const Context4& ctx) const; + /// @brief creates a lease and inserts it in LeaseMgr if necessary /// /// Creates a lease based on specified parameters and tries to insert it @@ -433,36 +501,57 @@ private: const isc::hooks::CalloutHandlePtr& callout_handle, bool fake_allocation = false); - /// @brief Reuses expired IPv4 lease + /// @brief Reuses expired DHCPv4 lease. /// - /// Updates existing expired lease with new information. Lease database - /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not - /// dummy allocation request (i.e. DISCOVER, fake_allocation = true). + /// Makes new allocation using an expired lease. The lease is updated with + /// the information from the provided context. Typically, an expired lease + /// lease which belonged to one client may be assigned to another client + /// which asked for the specific address. /// - /// @param expired Old, expired lease - /// @param subnet Subnet the lease is allocated from - /// @param clientid Client identifier - /// @param hwaddr Client's hardware address - /// @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. - /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking - /// an address for DISCOVER that is not really allocated (true) - /// @return refreshed lease - /// @throw BadValue if trying to recycle lease that is still valid - Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, - const SubnetPtr& subnet, - const ClientIdPtr& clientid, - const HWAddrPtr& hwaddr, - const bool fwd_dns_update, - const bool rev_dns_update, - const std::string& hostname, - const isc::hooks::CalloutHandlePtr& callout_handle, - bool fake_allocation = false); + /// @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 reuseExpiredLease(Lease4Ptr& expired, const Context4& ctx); + + /// @brief Updates the existing, non expired lease with a information from + /// the context. + /// + /// This method is invoked when the client requests allocation of the + /// (reserved) lease but there is a lease for this client with a different + /// address in the database already. In this case the existing lease must + /// be updated in the database with a new information. In particular, + /// with a new address. + /// + /// This method invokes the lease4_release and lease4_select callouts. + /// + /// @param lease A pointer to the lease to be updated. + /// @param ctx A context to be used to update the lease. + /// + /// @return Pointer to the updated lease. + /// @throw BadValue if the provided parameters are invalid. + Lease4Ptr replaceClientLease(Lease4Ptr& lease, const Context4& ctx); + + /// @brief Replace or renew client's lease. + /// + /// This method is ivoked by the @c AllocEngine::allocateLease4 when it + /// finds that the lease for the particular client already exists in the + /// database. If the existing lease has the same IP address as the one + /// that the client should be allocated the existing lease is renewed. + /// If the client should be allocated a different address, e.g. there + /// is a static reservation for the client, the existing lease is replaced + /// with a new one. This method handles both cases. + /// + /// @param lease Existing lease. + /// @param ctx Context holding parameters to be used for the lease + /// allocation. + /// + /// @return Updated lease, or NULL if allocation was unsucessful. + /// @throw BadValue if specified parameters are invalid. + Lease4Ptr reallocateClientLease(Lease4Ptr& lease, Context4& ctx); /// @brief Reuses expired IPv6 lease /// diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h index cb8ded8dfc..09847f4979 100644 --- a/src/lib/dhcpsrv/host_mgr.h +++ b/src/lib/dhcpsrv/host_mgr.h @@ -15,8 +15,14 @@ #ifndef HOST_MGR_H #define HOST_MGR_H +#include +#include +#include +#include +#include #include #include +#include namespace isc { namespace dhcp { diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc index 0388cbada9..ae5ecdd0ba 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -410,14 +411,25 @@ public: /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values /// used in many tests, initializes cfg_mgr configuration and creates /// lease database. + /// + /// It also re-initializes the Host Manager. AllocEngine4Test() { + // Create fresh instance of the HostMgr, and drop any previous HostMgr + // state. + HostMgr::instance().create(); + clientid_ = ClientIdPtr(new ClientId(vector(8, 0x44))); - static uint8_t mac[] = { 0, 1, 22, 33, 44, 55}; + uint8_t mac[] = { 0, 1, 22, 33, 44, 55}; // Let's use odd hardware type to check if there is no Ethernet // hardcoded anywhere. hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI)); + // Allocate different MAC address for the tests that require two + // different MAC addresses. + ++mac[sizeof(mac) - 1]; + hwaddr2_ = HWAddrPtr(new HWAddr(mac, sizeof (mac), HTYPE_FDDI)); + // instantiate cfg_mgr CfgMgr& cfg_mgr = CfgMgr::instance(); @@ -429,6 +441,13 @@ public: cfg_mgr.commit(); factory_.create("type=memfile universe=4 persist=false"); + + // Create a default context. Note that remaining parameters must be + // assigned when needed. + ctx_.subnet_ = subnet_; + ctx_.clientid_ = clientid_; + ctx_.hwaddr_ = hwaddr_; + ctx_.callout_handle_ = HooksManager::createCalloutHandle(); } /// @brief checks if Lease4 matches expected configuration @@ -461,12 +480,15 @@ public: factory_.destroy(); } - ClientIdPtr clientid_; ///< Client-identifier (value used in tests) - HWAddrPtr hwaddr_; ///< Hardware address (value used in tests) - Subnet4Ptr subnet_; ///< Subnet4 (used in tests) - Pool4Ptr pool_; ///< Pool belonging to subnet_ - LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory - Lease4Ptr old_lease_; ///< Holds previous instance of the lease. + ClientIdPtr clientid_; ///< Client-identifier (value used in tests) + HWAddrPtr hwaddr_; ///< Hardware address (value used in tests) + HWAddrPtr hwaddr2_; ///< Alternative hardware address. + Subnet4Ptr subnet_; ///< Subnet4 (used in tests) + Pool4Ptr pool_; ///< Pool belonging to subnet_ + LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory + Lease4Ptr old_lease_; ///< Holds previous instance of the lease. + AllocEngine::Context4 ctx_; ///< Context information passed to various + ///< allocation engine functions. }; // This test checks if the v6 Allocation Engine can be instantiated, parses @@ -1532,7 +1554,6 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) { // called TEST_F(AllocEngine4Test, renewLease4) { boost::scoped_ptr engine; - CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100, false))); @@ -1556,9 +1577,12 @@ TEST_F(AllocEngine4Test, renewLease4) { // Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's // renew it. ASSERT_FALSE(lease->expired()); - lease = engine->renewLease4(subnet_, clientid_, hwaddr_, true, - true, "host.example.com.", lease, - callout_handle, false); + 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_); @@ -1574,6 +1598,539 @@ TEST_F(AllocEngine4Test, renewLease4) { detailCompareLease(lease, from_mgr); } +// This test checks the behavior of the allocation engine in the following +// scenario: +// - Client has no lease in the database. +// - Client has a reservation. +// - Client sends DHCPREQUEST without requested IP Address, nor ciaddr. +// - Client is allocated a reserved address. +// +// Note that client must normally include a requested IP address or ciaddr +// in its message. But, we still want to provision clients that don't do that. +// The server simply picks reserved address or any other available one if there +// is no reservation. +TEST_F(AllocEngine4Test, reservedAddressNoHint) { + // Create reservation for the client. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Try to allocate a lease without specifying a hint. This is actually + // incorrect behavior of the client to not send an address it wants to + // obtain but the server should handle this gracefully. + Lease4Ptr lease = engine.allocateLease4(subnet_, clientid_, hwaddr_, + IOAddress("0.0.0.0"), + false, false, "", + false, CalloutHandlePtr(), + old_lease_); + ASSERT_TRUE(lease); + EXPECT_EQ("192.0.2.123", lease->addr_.toText()); + + // Make sure that the lease has been committed to the lease database. + // And that the committed lease is equal to the one returned. + Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); + ASSERT_TRUE(from_mgr); + detailCompareLease(lease, from_mgr); + + // Initially, there was no lease for this client, so the returned old + // lease should be NULL. + EXPECT_FALSE(old_lease_); +} + +// This test checks behavior of the allocation engine in the following scenario: +// - Client has no lease in the database. +// - Client has a reservation. +// - Client sends DHCPDISCOVER without requested IP Address. +// - Server returns DHCPOFFER with the reserved address. +TEST_F(AllocEngine4Test,reservedAddressNoHintFakeAllocation) { + // Create reservation for the client. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Query allocation engine for the lease to be assigned to this + // client without specifying the address to be assigned. + Lease4Ptr lease = engine.allocateLease4(subnet_, clientid_, hwaddr_, + IOAddress("0.0.0.0"), + false, false, "", + true, CalloutHandlePtr(), + old_lease_); + ASSERT_TRUE(lease); + // The allocation engine should return a reserved address. + EXPECT_EQ("192.0.2.123", lease->addr_.toText()); + + // This is a "fake" allocation so the returned lease should not be committed + // to the lease database. + EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); + + // Client had no lease in the database, so the old lease returned should + // be NULL. + EXPECT_FALSE(old_lease_); +} + +// This test checks the behavior of the allocation engine in the following +// scenario: +// - Client has no lease in the database. +// - Client has a reservation. +// - Client sends DHCPREQUEST with a requested IP address +// - Server returns DHCPNAK when requested IP address is different than +// the reserved address. +// - Server allocates a reserved address to the client when the client requests +// this address using requested IP address option. +TEST_F(AllocEngine4Test, reservedAddressHint) { + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + Lease4Ptr lease = engine.allocateLease4(subnet_, clientid_, hwaddr_, + IOAddress("192.0.2.234"), + false, false, "", + false, CalloutHandlePtr(), + old_lease_); + ASSERT_FALSE(lease); + ASSERT_FALSE(old_lease_); + + lease = engine.allocateLease4(subnet_, clientid_, hwaddr_, + IOAddress("192.0.2.123"), + false, false, "", + false, CalloutHandlePtr(), + old_lease_); + ASSERT_TRUE(lease); + EXPECT_EQ("192.0.2.123", lease->addr_.toText()); + + // Make sure that the lease has been committed to the lease database. + // And that the committed lease is equal to the one returned. + Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); + ASSERT_TRUE(from_mgr); + detailCompareLease(lease, from_mgr); + + EXPECT_FALSE(old_lease_); +} + +// This test checks the behavior of the allocation engine in the following +// scenario: +// - Client has no lease in the database. +// - Client has a reservation. +// - Client sends DHCPDISCOVER with a requested IP address as a hint. +// - Server offers a reserved address, even though it is different than the +// requested address. +TEST_F(AllocEngine4Test, reservedAddressHintFakeAllocation) { + // Create a reservation for the client. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Query the allocation engine for the lease to be assigned to the client + // and specify a hint being a different address than the reserved one. + Lease4Ptr lease = engine.allocateLease4(subnet_, clientid_, hwaddr_, + IOAddress("192.0.2.234"), + false, false, "", + true, CalloutHandlePtr(), + old_lease_); + ASSERT_TRUE(lease); + // Allocation engine should return reserved address. + EXPECT_EQ("192.0.2.123", lease->addr_.toText()); + + // This is a "fake" allocation so the returned lease should not be committed + // to the lease database. + EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); + + EXPECT_FALSE(old_lease_); +} + +// This test checks that the behavior of the allocation engine in the following +// scenario: +// - Client has a lease for the address from the dynamic pool in the database. +// - Client has a reservation for a different address than the one for which +// the client has a lease. +// - Client sends DHCPREQUEST, asking for the reserved address (as it has been +// offered to it when it sent DHCPDISCOVER). +// - Server allocates a reserved address and removes the lease for the address +// previously allocated to the client. +TEST_F(AllocEngine4Test, reservedAddressExistingLease) { + // Create the reservation for the client. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + // Create a lease for the client with a different address than the reserved + // one. + Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0, + 100, 30, 60, time(NULL), subnet_->getID(), + false, false, "")); + LeaseMgrFactory::instance().addLease(lease); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Request allocation of the reserved address. + Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_, + hwaddr_, + IOAddress("192.0.2.123"), + false, false, "", + false, CalloutHandlePtr(), + old_lease_); + ASSERT_TRUE(allocated_lease); + // The engine should have allocated the reserved address. + EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); + + // Make sure that the lease has been committed to the lease database. + Lease4Ptr from_mgr = + LeaseMgrFactory::instance().getLease4(allocated_lease->addr_); + ASSERT_TRUE(from_mgr); + detailCompareLease(allocated_lease, from_mgr); + + // The previous lease should have been replaced by a new one. The previous + // lease should be returned by the allocation engine to the caller. + ASSERT_TRUE(old_lease_); + EXPECT_EQ("192.0.2.101", old_lease_->addr_.toText()); + detailCompareLease(old_lease_, lease); +} + +// This test checks that the behavior of the allocation engine in the following +// scenario: +// - Client A has a lease in the database. +// - Client B has a reservation for the address in use by client A. +// - Client B sends a DHCPREQUEST requesting the allocation of the reserved +// lease (in use by client A). +// - Server determines that the reserved address is in use by a different client +// and returns DHCPNAK to client B. +TEST_F(AllocEngine4Test, reservedAddressHijacked) { + // Create host reservation for the client B. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + // Allocate a lease for the client A for the same address as reserved + // for the client B. + Lease4Ptr lease(new Lease4(IOAddress("192.0.2.123"), hwaddr2_, 0, 0, + 100, 30, 60, time(NULL), subnet_->getID(), + false, false, "")); + LeaseMgrFactory::instance().addLease(lease); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Try to allocate the reserved lease to client B. + Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_, + hwaddr_, + IOAddress("192.0.2.123"), + false, false, "", + false, CalloutHandlePtr(), + old_lease_); + // The lease is allocated to someone else, so the allocation should not + // succeed. + ASSERT_FALSE(allocated_lease); + EXPECT_FALSE(old_lease_); + + // Make sure that the allocation engine didn't modify the lease of the + // client A. + Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); + ASSERT_TRUE(from_mgr); + detailCompareLease(lease, from_mgr); + + // Try doing the same thing, but this time do not request any specific + // address. It should have the same effect. + allocated_lease = engine.allocateLease4(subnet_, clientid_, + hwaddr_, + IOAddress("0.0.0.0"), + false, false, "", + false, CalloutHandlePtr(), + old_lease_); + ASSERT_FALSE(allocated_lease); + EXPECT_FALSE(old_lease_); + + from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); + ASSERT_TRUE(from_mgr); + detailCompareLease(lease, from_mgr); +} + +// This test checks that the behavior of the allocation engine in the following +// scenario: +// - Client A has a lease in the database. +// - Client B has a reservation for the address in use by client A. +// - Client B sends a DHCPDISCOVER. +// - Server determines that the reserved address is in use by a different client +// and that it can't allocate a lease to the client B. +// +// In the scenario presented here, the allocation engine should return a +// NULL lease to the server. When the server receives NULL pointer from the +// allocation engine the proper action for the server will be to not +// respond to the client. Instead it should report to the administrator +// that it was unable to allocate the (reserved) lease. +TEST_F(AllocEngine4Test, reservedAddressHijackedFakeAllocation) { + // Create a reservation for the client B. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + // Create a lease for the client A. + Lease4Ptr lease(new Lease4(IOAddress("192.0.2.123"), hwaddr2_, 0, 0, + 100, 30, 60, time(NULL), subnet_->getID(), + false, false, "")); + LeaseMgrFactory::instance().addLease(lease); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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_); + // The allocation engine should return no lease. + ASSERT_FALSE(allocated_lease); + 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_); + EXPECT_FALSE(allocated_lease); + EXPECT_FALSE(old_lease_); +} + +// This test checks that the behavior of the allocation engine in the following +// scenario: +// - Client has a reservation. +// - Client has a lease in the database for a different address than reserved. +// - Client sends a DHCPREQUEST and asks for a different address than reserved, +// and different than it has in a database. +// - Server doesn't allocate the reserved address to the client because the +// client asked for the different address. +// +// Note that in this case the client should get the DHCPNAK and should fall back +// to the DHCPDISCOVER. +TEST_F(AllocEngine4Test, reservedAddressExistingLeaseInvalidHint) { + // Create a reservation for the client. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + // Create a lease for the client for a different address than reserved. + Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0, + 100, 30, 60, time(NULL), subnet_->getID(), + false, false, "")); + LeaseMgrFactory::instance().addLease(lease); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Try to allocate a lease and specify a different address than reserved + // and different from the one that client is currently using. + Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_, + hwaddr_, + IOAddress("192.0.2.102"), + false, false, "", + false, CalloutHandlePtr(), + old_lease_); + ASSERT_FALSE(allocated_lease); + ASSERT_FALSE(old_lease_); + + // Repeat the test, but this time ask for the address that the client + // has allocated. + allocated_lease = engine.allocateLease4(subnet_, clientid_, + hwaddr_, + IOAddress("192.0.2.101"), + false, false, "", + false, CalloutHandlePtr(), + old_lease_); + // The client has reservation so the server wants to allocate a + // reserved address and doesn't want to renew the address that the + // client is currently using. This is equivalent of the case when + // the client tries to renew the lease but there is a new reservation + // for this client. The server doesn't allow for the renewal and + // responds with DHCPNAK to force the client to return to the + // DHCP server discovery. + EXPECT_FALSE(allocated_lease); + EXPECT_FALSE(old_lease_); +} + +// This test checks that the behavior of the allocation engine in the following +// scenario: +// - Client has a lease in the database. +// - Client has a reservation for a different address than the one for which it +// has a lease. +// - Client sends a DHCPDISCOVER and asks for a different address than reserved +// and different from which it has a lease for. +// - Server ignores the client's hint and offers a reserved address. +TEST_F(AllocEngine4Test, reservedAddressExistingLeaseFakeAllocation) { + // Create a reservation for the client. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + // Create a lease for a different address than reserved. + Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0, + 100, 30, 60, time(NULL), subnet_->getID(), + false, false, "")); + LeaseMgrFactory::instance().addLease(lease); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Try to allocate a lease and use a completely different address + // as a hint. + Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_, + hwaddr_, + IOAddress("192.0.2.102"), + false, false, "", + true, CalloutHandlePtr(), + old_lease_); + // Server should offer a lease for a reserved address. + ASSERT_TRUE(allocated_lease); + EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); + + // The lease should not be allocated until the client sends a DHCPREQUEST. + EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(allocated_lease->addr_)); + + // Old lease should contain the currently used lease. + ASSERT_TRUE(old_lease_); + EXPECT_EQ("192.0.2.101", old_lease_->addr_.toText()); + + // Repeat the test but this time ask for the address for which the + // client has a lease. + allocated_lease = engine.allocateLease4(subnet_, clientid_, + hwaddr_, + IOAddress("192.0.2.101"), + false, false, "", + true, CalloutHandlePtr(), + old_lease_); + // The server should offer the lease, but not for the address that + // the client requested. The server should offer a reserved address. + ASSERT_TRUE(allocated_lease); + EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); + + // Old lease should contain the currently used lease. + ASSERT_TRUE(old_lease_); + EXPECT_EQ("192.0.2.101", old_lease_->addr_.toText()); +} + +// This test checks that the behavior of the allocation engine in the following +// scenario: +// - Client has a reservation. +// - Client has a lease for a different address than reserved. +// - Client sends a DHCPREQUEST to allocate a lease. +// - The server determines that the client has a reservation for the +// different address than it is currently using and should assign +// a reserved address and remove the previous lease. +TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHint) { + // Create a reservation. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + // Create a lease for a different address than reserved. + Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0, + 100, 30, 60, time(NULL), subnet_->getID(), + false, false, "")); + LeaseMgrFactory::instance().addLease(lease); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Try to allocate a lease with providing no hint. + Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_, + hwaddr_, + IOAddress("0.0.0.0"), + false, false, "", + false, CalloutHandlePtr(), + old_lease_); + // The reserved address should be allocated. + ASSERT_TRUE(allocated_lease); + EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); + + // The previous lease should be removed. + EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); + + // Make sure that the allocated lease is committed in the lease database. + Lease4Ptr from_mgr = + LeaseMgrFactory::instance().getLease4(allocated_lease->addr_); + ASSERT_TRUE(from_mgr); + detailCompareLease(allocated_lease, from_mgr); + + // Old lease should be returned. + ASSERT_TRUE(old_lease_); + detailCompareLease(lease, old_lease_); +} + +// This test checks that the behavior of the allocation engine in the following +// scenario: +// - Client has a reservation. +// - Client has a lease for a different address than reserved. +// - Client sends a DHCPDISCOVER with no hint. +// - Server determines that there is a reservation for the client and that +// the current lease should be removed and the reserved address should be +// allocated. +TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHintFakeAllocation) { + // Create a reservation. + HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), + Host::IDENT_HWADDR, subnet_->getID(), + SubnetID(0), IOAddress("192.0.2.123"))); + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + // Create a lease for a different address than reserved. + Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, 0, 0, + 100, 30, 60, time(NULL), subnet_->getID(), + false, false, "")); + LeaseMgrFactory::instance().addLease(lease); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // Query the allocation engine for the lease to be allocated for the + // client. + Lease4Ptr allocated_lease = engine.allocateLease4(subnet_, clientid_, + hwaddr_, + IOAddress("0.0.0.0"), + false, false, "", + true, CalloutHandlePtr(), + old_lease_); + // The server should offer the reserved address. + ASSERT_TRUE(allocated_lease); + EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); + + // The lease should not be committed to the lease database until the + // client sends a DHCPREQUEST. + EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(allocated_lease->addr_)); + + // The old lease should reflect what is in the database. + ASSERT_TRUE(old_lease_); + Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); + ASSERT_TRUE(from_mgr); + detailCompareLease(lease, from_mgr); +} + /// @brief helper class used in Hooks testing in AllocEngine6 /// /// It features a couple of callout functions and buffers to store