From: Marcin Siodelski Date: Fri, 6 Mar 2015 10:20:09 +0000 (+0100) Subject: [master] Merge branch 'trac3694' X-Git-Tag: trac3713_base~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=95b09ff53b941691cba172c933de0682b05a0d85;p=thirdparty%2Fkea.git [master] Merge branch 'trac3694' Conflicts: src/lib/dhcpsrv/alloc_engine.cc src/lib/dhcpsrv/alloc_engine.h src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc --- 95b09ff53b941691cba172c933de0682b05a0d85 diff --cc src/lib/dhcpsrv/alloc_engine.cc index b9ee2d3341,781694aaa2..1b96d90a99 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@@ -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(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 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(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 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(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_); // 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 @@@ -1539,200 -1619,185 +1601,182 @@@ } } - 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::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(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(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); + } } } diff --cc src/lib/dhcpsrv/alloc_engine.h index 3651800f95,af6a59703f..a92a837aae --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@@ -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 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 diff --cc src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc index 1351ae66c3,b6f7202620..3d5706ce3e --- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc @@@ -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 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