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