alloc_engine.cc 37.2 KB
Newer Older
1
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15
#include <dhcpsrv/alloc_engine.h>
16
#include <dhcpsrv/dhcpsrv_log.h>
17
#include <dhcpsrv/lease_mgr_factory.h>
18

19 20 21
#include <hooks/server_hooks.h>
#include <hooks/hooks_manager.h>

22
#include <cstring>
23
#include <vector>
24
#include <string.h>
25

26
using namespace isc::asiolink;
27
using namespace isc::hooks;
28

29 30 31
namespace {

/// Structure that holds registered hook indexes
32 33
struct AllocEngineHooks {
    int hook_index_lease4_select_; ///< index for "lease4_receive" hook point
34
    int hook_index_lease4_renew_;  ///< index for "lease4_renew" hook point
35 36 37
    int hook_index_lease6_select_; ///< index for "lease6_receive" hook point

    /// Constructor that registers hook points for AllocationEngine
38
    AllocEngineHooks() {
39
        hook_index_lease4_select_ = HooksManager::registerHook("lease4_select");
40
        hook_index_lease4_renew_  = HooksManager::registerHook("lease4_renew");
41 42 43 44 45 46 47 48
        hook_index_lease6_select_ = HooksManager::registerHook("lease6_select");
    }
};

// Declare a Hooks object. As this is outside any function or method, it
// will be instantiated (and the constructor run) when the module is loaded.
// As a result, the hook indexes will be defined before any method in this
// module is called.
49
AllocEngineHooks Hooks;
50 51

}; // anonymous namespace
52 53 54 55 56 57 58 59

namespace isc {
namespace dhcp {

AllocEngine::IterativeAllocator::IterativeAllocator()
    :Allocator() {
}

60 61
isc::asiolink::IOAddress
AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& addr) {
62 63 64 65 66 67 68 69 70
    // Get a buffer holding an address.
    const std::vector<uint8_t>& vec = addr.toBytes();
    // Get the address length.
    const int len = vec.size();

    // Since the same array will be used to hold the IPv4 and IPv6
    // address we have to make sure that the size of the array
    // we allocate will work for both types of address.
    BOOST_STATIC_ASSERT(V4ADDRESS_LEN <= V6ADDRESS_LEN);
71 72
    uint8_t packed[V6ADDRESS_LEN];

73 74 75
    // Copy the address. It can be either V4 or V6.
    std::memcpy(packed, &vec[0], len);

76
    // Start increasing the least significant byte
77
    for (int i = len - 1; i >= 0; --i) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
78
        ++packed[i];
79
        // if we haven't overflowed (0xff -> 0x0), than we are done
80 81 82 83 84
        if (packed[i] != 0) {
            break;
        }
    }

85
    return (IOAddress::fromBytes(addr.getFamily(), packed));
86 87 88
}


89
isc::asiolink::IOAddress
90
AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
91 92
                                             const DuidPtr&,
                                             const IOAddress&) {
93

94
    // Let's get the last allocated address. It is usually set correctly,
95
    // but there are times when it won't be (like after removing a pool or
96
    // perhaps restarting the server).
97 98
    IOAddress last = subnet->getLastAllocated();

99
    const PoolCollection& pools = subnet->getPools();
100

101
    if (pools.empty()) {
102 103 104 105
        isc_throw(AllocFailed, "No pools defined in selected subnet");
    }

    // first we need to find a pool the last address belongs to.
106
    PoolCollection::const_iterator it;
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
    for (it = pools.begin(); it != pools.end(); ++it) {
        if ((*it)->inRange(last)) {
            break;
        }
    }

    // last one was bogus for one of several reasons:
    // - we just booted up and that's the first address we're allocating
    // - a subnet was removed or other reconfiguration just completed
    // - perhaps allocation algorithm was changed
    if (it == pools.end()) {
        // ok to access first element directly. We checked that pools is non-empty
        IOAddress next = pools[0]->getFirstAddress();
        subnet->setLastAllocated(next);
        return (next);
    }

    // Ok, we have a pool that the last address belonged to, let's use it.

    IOAddress next = increaseAddress(last); // basically addr++
    if ((*it)->inRange(next)) {
128
        // the next one is in the pool as well, so we haven't hit pool boundary yet
129 130 131 132 133 134 135 136 137 138 139 140 141 142
        subnet->setLastAllocated(next);
        return (next);
    }

    // We hit pool boundary, let's try to jump to the next pool and try again
    ++it;
    if (it == pools.end()) {
        // Really out of luck today. That was the last pool. Let's rewind
        // to the beginning.
        next = pools[0]->getFirstAddress();
        subnet->setLastAllocated(next);
        return (next);
    }

143
    // there is a next pool, let's try first address from it
144 145 146 147 148
    next = (*it)->getFirstAddress();
    subnet->setLastAllocated(next);
    return (next);
}

149 150 151 152 153 154 155
AllocEngine::HashedAllocator::HashedAllocator()
    :Allocator() {
    isc_throw(NotImplemented, "Hashed allocator is not implemented");
}


isc::asiolink::IOAddress
156 157 158
AllocEngine::HashedAllocator::pickAddress(const SubnetPtr&,
                                          const DuidPtr&,
                                          const IOAddress&) {
159 160 161 162 163 164 165 166 167 168
    isc_throw(NotImplemented, "Hashed allocator is not implemented");
}

AllocEngine::RandomAllocator::RandomAllocator()
    :Allocator() {
    isc_throw(NotImplemented, "Random allocator is not implemented");
}


isc::asiolink::IOAddress
169 170 171
AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&,
                                          const DuidPtr&,
                                          const IOAddress&) {
172 173 174
    isc_throw(NotImplemented, "Random allocator is not implemented");
}

175 176 177 178 179

AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts)
    :attempts_(attempts) {
    switch (engine_type) {
    case ALLOC_ITERATIVE:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
180
        allocator_ = boost::shared_ptr<Allocator>(new IterativeAllocator());
181 182
        break;
    case ALLOC_HASHED:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
183
        allocator_ = boost::shared_ptr<Allocator>(new HashedAllocator());
184 185
        break;
    case ALLOC_RANDOM:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
186
        allocator_ = boost::shared_ptr<Allocator>(new RandomAllocator());
187 188 189 190 191
        break;

    default:
        isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
    }
192 193

    // Register hook points
194
    hook_index_lease4_select_ = Hooks.hook_index_lease4_select_;
195
    hook_index_lease6_select_ = Hooks.hook_index_lease6_select_;
196 197
}

198
Lease6Collection
199 200 201
AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                              const DuidPtr& duid,
                              uint32_t iaid,
202
                              const IOAddress& hint,
203 204 205
                              const bool fwd_dns_update,
                              const bool rev_dns_update,
                              const std::string& hostname,
206 207
                              bool fake_allocation,
                              const isc::hooks::CalloutHandlePtr& callout_handle) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
208

209 210 211 212 213 214
    try {
        // That check is not necessary. We create allocator in AllocEngine
        // constructor
        if (!allocator_) {
            isc_throw(InvalidOperation, "No allocator selected");
        }
215

216 217 218 219 220 221 222 223
        if (!subnet) {
            isc_throw(InvalidOperation, "Subnet is required for allocation");
        }

        if (!duid) {
            isc_throw(InvalidOperation, "DUID is mandatory for allocation");
        }

224
        // check if there's existing lease for that subnet/duid/iaid combination.
225 226 227 228 229 230 231
        /// @todo: Make this generic (cover temp. addrs and prefixes)
        Lease6Collection existing = LeaseMgrFactory::instance().getLeases6(
            Lease6::LEASE_IA_NA, *duid, iaid, subnet->getID());

        if (!existing.empty()) {
            // we have at least one lease already. This is a returning client,
            // probably after his reboot.
232 233
            return (existing);
        }
234

235 236
        // check if the hint is in pool and is available
        if (subnet->inPool(hint)) {
237 238 239 240 241

            /// @todo: We support only one hint for now
            Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(
                Lease6::LEASE_IA_NA, hint);
            if (!lease) {
242 243
                /// @todo: check if the hint is reserved once we have host support
                /// implemented
244

245
                // the hint is valid and not currently used, let's create a lease for it
246 247 248 249
                /// @todo: We support only one lease per ia for now
                lease = createLease6(subnet, duid, iaid, hint, fwd_dns_update,
                                     rev_dns_update, hostname, callout_handle,
                                     fake_allocation);
250

251 252 253 254
                // It can happen that the lease allocation failed (we could have lost
                // the race condition. That means that the hint is lo longer usable and
                // we need to continue the regular allocation path.
                if (lease) {
255 256 257 258
                    /// @todo: We support only one lease per ia for now
                    Lease6Collection collection;
                    collection.push_back(lease);
                    return (collection);
259 260
                }
            } else {
261 262 263 264
                if (lease->expired()) {
                    /// We found a lease and it is expired, so we can reuse it
                    /// @todo: We support only one lease per ia for now
                    lease = reuseExpiredLease(lease, subnet, duid, iaid,
265
                                              fwd_dns_update, rev_dns_update,
266
                                              hostname, callout_handle,
267 268 269 270
                                              fake_allocation);
                    Lease6Collection collection;
                    collection.push_back(lease);
                    return (collection);
271
                }
272

273
            }
274
        }
275

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
        // Hint is in the pool but is not available. Search the pool until first of
        // the following occurs:
        // - we find a free address
        // - we find an address for which the lease has expired
        // - we exhaust number of tries
        //
        // @todo: Current code does not handle pool exhaustion well. It will be
        // improved. Current problems:
        // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
        // 10 addresses), we will iterate over it 100 times before giving up
        // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
        // 3. the whole concept of infinite attempts is just asking for infinite loop
        // We may consider some form or reference counting (this pool has X addresses
        // left), but this has one major problem. We exactly control allocation
        // moment, but we currently do not control expiration time at all
Tomek Mrugalski's avatar
Tomek Mrugalski committed
291

292 293 294
        unsigned int i = attempts_;
        do {
            IOAddress candidate = allocator_->pickAddress(subnet, duid, hint);
295

296 297
            /// @todo: check if the address is reserved once we have host support
            /// implemented
298

299 300
            Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(
                                 Lease6::LEASE_IA_NA, candidate);
301 302 303 304
            if (!existing) {
                // there's no existing lease for selected candidate, so it is
                // free. Let's allocate it.
                Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
305
                                               fwd_dns_update, rev_dns_update,
306
                                               hostname,
307
                                               callout_handle, fake_allocation);
308
                if (lease) {
309 310 311
                    Lease6Collection collection;
                    collection.push_back(lease);
                    return (collection);
312 313 314 315 316 317 318
                }

                // Although the address was free just microseconds ago, it may have
                // been taken just now. If the lease insertion fails, we continue
                // allocation attempts.
            } else {
                if (existing->expired()) {
319 320 321 322 323 324 325
                    existing = reuseExpiredLease(existing, subnet, duid, iaid,
                                                 fwd_dns_update, rev_dns_update,
                                                 hostname, callout_handle,
                                                 fake_allocation);
                    Lease6Collection collection;
                    collection.push_back(existing);
                    return (collection);
326
                }
327 328
            }

329 330 331 332
            // Continue trying allocation until we run out of attempts
            // (or attempts are set to 0, which means infinite)
            --i;
        } while ((i > 0) || !attempts_);
333

334 335 336 337 338 339 340 341
        // Unable to allocate an address, return an empty lease.
        LOG_WARN(dhcpsrv_logger, DHCPSRV_ADDRESS6_ALLOC_FAIL).arg(attempts_);

    } catch (const isc::Exception& e) {

        // Some other error, return an empty lease.
        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS6_ALLOC_ERROR).arg(e.what());
    }
342

343
    return (Lease6Collection());
344 345
}

346 347 348 349 350
Lease4Ptr
AllocEngine::allocateAddress4(const SubnetPtr& subnet,
                              const ClientIdPtr& clientid,
                              const HWAddrPtr& hwaddr,
                              const IOAddress& hint,
351 352 353
                              const bool fwd_dns_update,
                              const bool rev_dns_update,
                              const std::string& hostname,
354
                              bool fake_allocation,
355 356 357
                              const isc::hooks::CalloutHandlePtr& callout_handle,
                              Lease4Ptr& old_lease) {

358 359 360
    // 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.
361
    old_lease.reset();
362

363 364 365 366 367
    try {
        // Allocator is always created in AllocEngine constructor and there is
        // currently no other way to set it, so that check is not really necessary.
        if (!allocator_) {
            isc_throw(InvalidOperation, "No allocator selected");
368 369
        }

370 371 372 373 374 375 376 377
        if (!subnet) {
            isc_throw(InvalidOperation, "Can't allocate IPv4 address without subnet");
        }

        if (!hwaddr) {
            isc_throw(InvalidOperation, "HWAddr must be defined");
        }

378
        // Check if there's existing lease for that subnet/clientid/hwaddr combination.
379
        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
380
        if (existing) {
381
            // Save the old lease, before renewal.
382
            old_lease.reset(new Lease4(*existing));
383
            // We have a lease already. This is a returning client, probably after
Tomek Mrugalski's avatar
Tomek Mrugalski committed
384
            // its reboot.
385 386
            existing = renewLease4(subnet, clientid, hwaddr,
                                   fwd_dns_update, rev_dns_update, hostname,
387
                                   existing, callout_handle, fake_allocation);
388 389 390
            if (existing) {
                return (existing);
            }
391 392 393

            // If renewal failed (e.g. the lease no longer matches current configuration)
            // let's continue the allocation process
394
        }
395

396 397 398
        if (clientid) {
            existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
            if (existing) {
399
                // Save the old lease before renewal.
400
                old_lease.reset(new Lease4(*existing));
401 402
                // we have a lease already. This is a returning client, probably after
                // its reboot.
403 404
                existing = renewLease4(subnet, clientid, hwaddr,
                                       fwd_dns_update, rev_dns_update,
405
                                       hostname, existing, callout_handle,
406
                                       fake_allocation);
407 408 409 410 411 412 413 414 415 416 417 418 419 420
                // @todo: produce a warning. We haven't found him using MAC address, but
                // we found him using client-id
                if (existing) {
                    return (existing);
                }
            }
        }

        // check if the hint is in pool and is available
        if (subnet->inPool(hint)) {
            existing = LeaseMgrFactory::instance().getLease4(hint);
            if (!existing) {
                /// @todo: Check if the hint is reserved once we have host support
                /// implemented
421

422
                // The hint is valid and not currently used, let's create a lease for it
423
                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint,
424 425 426
                                               fwd_dns_update, rev_dns_update,
                                               hostname, callout_handle,
                                               fake_allocation);
427

428 429 430 431 432 433 434 435
                // It can happen that the lease allocation failed (we could have lost
                // the race condition. That means that the hint is lo longer usable and
                // we need to continue the regular allocation path.
                if (lease) {
                    return (lease);
                }
            } else {
                if (existing->expired()) {
436
                    // Save the old lease, before reusing it.
437
                    old_lease.reset(new Lease4(*existing));
438
                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
439 440 441
                                              fwd_dns_update, rev_dns_update,
                                              hostname, callout_handle,
                                              fake_allocation));
442
                }
443

444
            }
445 446
        }

447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
        // Hint is in the pool but is not available. Search the pool until first of
        // the following occurs:
        // - we find a free address
        // - we find an address for which the lease has expired
        // - we exhaust the number of tries
        //
        // @todo: Current code does not handle pool exhaustion well. It will be
        // improved. Current problems:
        // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
        // 10 addresses), we will iterate over it 100 times before giving up
        // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
        // 3. the whole concept of infinite attempts is just asking for infinite loop
        // We may consider some form or reference counting (this pool has X addresses
        // left), but this has one major problem. We exactly control allocation
        // moment, but we currently do not control expiration time at all
462

463 464 465
        unsigned int i = attempts_;
        do {
            IOAddress candidate = allocator_->pickAddress(subnet, clientid, hint);
466

467 468
            /// @todo: check if the address is reserved once we have host support
            /// implemented
469

470 471 472 473
            Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(candidate);
            if (!existing) {
                // there's no existing lease for selected candidate, so it is
                // free. Let's allocate it.
474 475 476
                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr,
                                               candidate, fwd_dns_update,
                                               rev_dns_update, hostname,
477
                                               callout_handle, fake_allocation);
478 479 480 481 482 483 484 485 486
                if (lease) {
                    return (lease);
                }

                // Although the address was free just microseconds ago, it may have
                // been taken just now. If the lease insertion fails, we continue
                // allocation attempts.
            } else {
                if (existing->expired()) {
487 488
                    // Save old lease before reusing it.
                    old_lease.reset(new Lease4(*existing));
489
                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
490 491 492
                                              fwd_dns_update, rev_dns_update,
                                              hostname, callout_handle,
                                              fake_allocation));
493
                }
494 495
            }

496 497 498 499 500 501 502
            // Continue trying allocation until we run out of attempts
            // (or attempts are set to 0, which means infinite)
            --i;
        } while ((i > 0) || !attempts_);

        // Unable to allocate an address, return an empty lease.
        LOG_WARN(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_FAIL).arg(attempts_);
503

504
    } catch (const isc::Exception& e) {
505

506 507 508 509
        // Some other error, return an empty lease.
        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_ERROR).arg(e.what());
    }
    return (Lease4Ptr());
510 511
}

512 513 514
Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
                                   const ClientIdPtr& clientid,
                                   const HWAddrPtr& hwaddr,
515 516 517
                                   const bool fwd_dns_update,
                                   const bool rev_dns_update,
                                   const std::string& hostname,
518
                                   const Lease4Ptr& lease,
519
                                   const isc::hooks::CalloutHandlePtr& callout_handle,
520 521
                                   bool fake_allocation /* = false */) {

522 523 524 525 526 527 528
    if (!lease) {
        isc_throw(InvalidOperation, "Lease4 must be specified");
    }

    // 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)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
529
    /// @todo: remove this once #3083 is implemented
530 531
    Lease4 old_values = *lease;

532 533 534 535 536 537 538
    lease->subnet_id_ = subnet->getID();
    lease->hwaddr_ = hwaddr->hwaddr_;
    lease->client_id_ = clientid;
    lease->cltt_ = time(NULL);
    lease->t1_ = subnet->getT1();
    lease->t2_ = subnet->getT2();
    lease->valid_lft_ = subnet->getValid();
539 540 541
    lease->fqdn_fwd_ = fwd_dns_update;
    lease->fqdn_rev_ = rev_dns_update;
    lease->hostname_ = hostname;
542

543 544 545 546 547 548 549
    bool skip = false;
    // Execute all callouts registered for packet6_send
    if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease4_renew_)) {

        // Delete all previous arguments
        callout_handle->deleteAllArguments();

550 551 552 553 554 555
        // 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>(subnet);

556
        // Pass the parameters
557
        callout_handle->setArgument("subnet4", subnet4);
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
        callout_handle->setArgument("clientid", clientid);
        callout_handle->setArgument("hwaddr", hwaddr);

        // Pass the lease to be updated
        callout_handle->setArgument("lease4", lease);

        // Call all installed callouts
        HooksManager::callCallouts(Hooks.hook_index_lease4_renew_, *callout_handle);

        // Callouts decided to skip the next processing step. The next
        // processing step would to actually renew the lease, so skip at this
        // stage means "keep the old lease as it is".
        if (callout_handle->getSkip()) {
            skip = true;
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_RENEW_SKIP);
        }
    }

    if (!fake_allocation && !skip) {
577 578 579
        // for REQUEST we do update the lease
        LeaseMgrFactory::instance().updateLease4(lease);
    }
580 581
    if (skip) {
        // Rollback changes (really useful only for memfile)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
582
        /// @todo: remove this once #3083 is implemented
583 584
        *lease = old_values;
    }
585 586 587 588

    return (lease);
}

589 590 591 592
Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
                                         const Subnet6Ptr& subnet,
                                         const DuidPtr& duid,
                                         uint32_t iaid,
593 594 595
                                         const bool fwd_dns_update,
                                         const bool rev_dns_update,
                                         const std::string& hostname,
596
                                         const isc::hooks::CalloutHandlePtr& callout_handle,
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
                                         bool fake_allocation /*= false */ ) {

    if (!expired->expired()) {
        isc_throw(BadValue, "Attempt to recycle lease that is still valid");
    }

    // address, lease type and prefixlen (0) stay the same
    expired->iaid_ = iaid;
    expired->duid_ = duid;
    expired->preferred_lft_ = subnet->getPreferred();
    expired->valid_lft_ = subnet->getValid();
    expired->t1_ = subnet->getT1();
    expired->t2_ = subnet->getT2();
    expired->cltt_ = time(NULL);
    expired->subnet_id_ = subnet->getID();
    expired->fixed_ = false;
613 614 615
    expired->hostname_ = hostname;
    expired->fqdn_fwd_ = fwd_dns_update;
    expired->fqdn_rev_ = rev_dns_update;
616 617 618 619

    /// @todo: log here that the lease was reused (there's ticket #2524 for
    /// logging in libdhcpsrv)

Tomek Mrugalski's avatar
Tomek Mrugalski committed
620
    // Let's execute all callouts registered for lease6_select
621 622 623 624 625 626 627 628 629 630 631 632
    if (callout_handle &&
        HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {

        // Delete all previous arguments
        callout_handle->deleteAllArguments();

        // Pass necessary arguments
        // Subnet from which we do the allocation
        callout_handle->setArgument("subnet6", subnet);

        // Is this solicit (fake = true) or request (fake = false)
        callout_handle->setArgument("fake_allocation", fake_allocation);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
633 634

        // The lease that will be assigned to a client
635 636
        callout_handle->setArgument("lease6", expired);

Tomek Mrugalski's avatar
Tomek Mrugalski committed
637
        // Call the callouts
Tomek Mrugalski's avatar
Tomek Mrugalski committed
638
        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
639 640 641

        // 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
642
        // won't be inserted into the database.
643
        if (callout_handle->getSkip()) {
644
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
645 646 647 648 649 650 651 652
            return (Lease6Ptr());
        }

        // Let's use whatever callout returned. Hopefully it is the same lease
        // we handled to it.
        callout_handle->getArgument("lease6", expired);
    }

653 654 655
    if (!fake_allocation) {
        // for REQUEST we do update the lease
        LeaseMgrFactory::instance().updateLease6(expired);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
656
    }
657 658 659 660 661 662 663 664 665

    // 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);
}

666 667 668 669
Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
                                         const SubnetPtr& subnet,
                                         const ClientIdPtr& clientid,
                                         const HWAddrPtr& hwaddr,
670 671 672
                                         const bool fwd_dns_update,
                                         const bool rev_dns_update,
                                         const std::string& hostname,
673
                                         const isc::hooks::CalloutHandlePtr& callout_handle,
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
                                         bool fake_allocation /*= false */ ) {

    if (!expired->expired()) {
        isc_throw(BadValue, "Attempt to recycle lease that is still valid");
    }

    // address, lease type and prefixlen (0) stay the same
    expired->client_id_ = clientid;
    expired->hwaddr_ = hwaddr->hwaddr_;
    expired->valid_lft_ = subnet->getValid();
    expired->t1_ = subnet->getT1();
    expired->t2_ = subnet->getT2();
    expired->cltt_ = time(NULL);
    expired->subnet_id_ = subnet->getID();
    expired->fixed_ = false;
689 690 691
    expired->hostname_ = hostname;
    expired->fqdn_fwd_ = fwd_dns_update;
    expired->fqdn_rev_ = rev_dns_update;
692 693 694 695

    /// @todo: log here that the lease was reused (there's ticket #2524 for
    /// logging in libdhcpsrv)

696 697 698 699 700 701 702 703
    // Let's execute all callouts registered for lease4_select
    if (callout_handle &&
        HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {

        // Delete all previous arguments
        callout_handle->deleteAllArguments();

        // Pass necessary arguments
704

Tomek Mrugalski's avatar
Tomek Mrugalski committed
705 706 707 708
        // 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.
709 710
        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
        callout_handle->setArgument("subnet4", subnet4);
711 712 713 714 715 716 717 718 719 720 721 722

        // Is this solicit (fake = true) or request (fake = false)
        callout_handle->setArgument("fake_allocation", fake_allocation);

        // The lease that will be assigned to a client
        callout_handle->setArgument("lease4", expired);

        // Call the callouts
        HooksManager::callCallouts(hook_index_lease6_select_, *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
723
        // won't be inserted into the database.
724 725 726 727 728 729 730 731 732 733
        if (callout_handle->getSkip()) {
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
            return (Lease4Ptr());
        }

        // Let's use whatever callout returned. Hopefully it is the same lease
        // we handled to it.
        callout_handle->getArgument("lease4", expired);
    }

734 735 736 737 738 739 740 741 742 743 744 745 746
    if (!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);
}

747 748 749 750
Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
                                    const DuidPtr& duid,
                                    uint32_t iaid,
                                    const IOAddress& addr,
751 752 753
                                    const bool fwd_dns_update,
                                    const bool rev_dns_update,
                                    const std::string& hostname,
754
                                    const isc::hooks::CalloutHandlePtr& callout_handle,
755
                                    bool fake_allocation /*= false */ ) {
756

757 758 759
    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
                               subnet->getPreferred(), subnet->getValid(),
                               subnet->getT1(), subnet->getT2(), subnet->getID()));
760

761 762 763 764
    lease->fqdn_fwd_ = fwd_dns_update;
    lease->fqdn_rev_ = rev_dns_update;
    lease->hostname_ = hostname;

765
    // Let's execute all callouts registered for lease6_select
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
    if (callout_handle &&
        HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {

        // Delete all previous arguments
        callout_handle->deleteAllArguments();

        // Pass necessary arguments

        // Subnet from which we do the allocation
        callout_handle->setArgument("subnet6", subnet);

        // Is this solicit (fake = true) or request (fake = false)
        callout_handle->setArgument("fake_allocation", fake_allocation);
        callout_handle->setArgument("lease6", lease);

        // This is the first callout, so no need to clear any arguments
Tomek Mrugalski's avatar
Tomek Mrugalski committed
782
        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
783 784 785

        // 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
786
        // won't be inserted into the database.
787
        if (callout_handle->getSkip()) {
788
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
789 790 791 792 793 794 795 796
            return (Lease6Ptr());
        }

        // Let's use whatever callout returned. Hopefully it is the same lease
        // we handled to it.
        callout_handle->getArgument("lease6", lease);
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
797
    if (!fake_allocation) {
798
        // That is a real (REQUEST) allocation
799
        bool status = LeaseMgrFactory::instance().addLease(lease);
800

801 802 803 804 805 806 807 808 809 810
        if (status) {

            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());
        }
811
    } else {
812 813 814 815
        // That is only fake (SOLICIT without rapid-commit) allocation

        // It is for advertise only. We should not insert the lease into LeaseMgr,
        // but rather check that we could have inserted it.
816 817
        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(
                             Lease6::LEASE_IA_NA, addr);
818 819 820 821 822
        if (!existing) {
            return (lease);
        } else {
            return (Lease6Ptr());
        }
823 824 825
    }
}

826 827 828 829
Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
                                    const DuidPtr& clientid,
                                    const HWAddrPtr& hwaddr,
                                    const IOAddress& addr,
830 831 832
                                    const bool fwd_dns_update,
                                    const bool rev_dns_update,
                                    const std::string& hostname,
833
                                    const isc::hooks::CalloutHandlePtr& callout_handle,
834 835 836 837 838
                                    bool fake_allocation /*= false */ ) {
    if (!hwaddr) {
        isc_throw(BadValue, "Can't create a lease with NULL HW address");
    }
    time_t now = time(NULL);
839 840 841 842 843 844 845

    // @todo: remove this kludge after ticket #2590 is implemented
    std::vector<uint8_t> local_copy;
    if (clientid) {
        local_copy = clientid->getDuid();
    }

846
    Lease4Ptr lease(new Lease4(addr, &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
847 848 849
                               &local_copy[0], local_copy.size(), subnet->getValid(),
                               subnet->getT1(), subnet->getT2(), now,
                               subnet->getID()));
850

851 852 853 854 855
    // Set FQDN specific lease parameters.
    lease->fqdn_fwd_ = fwd_dns_update;
    lease->fqdn_rev_ = rev_dns_update;
    lease->hostname_ = hostname;

856 857 858 859 860 861 862 863 864
    // Let's execute all callouts registered for lease4_select
    if (callout_handle &&
        HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {

        // Delete all previous arguments
        callout_handle->deleteAllArguments();

        // Pass necessary arguments

865 866 867 868 869 870
        // 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);
871 872 873

        // Is this solicit (fake = true) or request (fake = false)
        callout_handle->setArgument("fake_allocation", fake_allocation);
874 875

        // Pass the intended lease as well
876 877 878 879 880 881 882
        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);

        // 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
883
        // won't be inserted into the database.
884 885 886 887 888 889 890
        if (callout_handle->getSkip()) {
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
            return (Lease4Ptr());
        }

        // Let's use whatever callout returned. Hopefully it is the same lease
        // we handled to it.
891
        callout_handle->getArgument("lease4", lease);
892 893
    }

894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
    if (!fake_allocation) {
        // That is a real (REQUEST) allocation
        bool status = LeaseMgrFactory::instance().addLease(lease);
        if (status) {
            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 (Lease4Ptr());
        }
    } else {
        // That is only fake (DISCOVER) allocation

        // It is for OFFER only. We should not insert the lease into LeaseMgr,
        // but rather check that we could have inserted it.
        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(addr);
        if (!existing) {
            return (lease);
        } else {
            return (Lease4Ptr());
        }
    }
}

920
AllocEngine::~AllocEngine() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
921
    // no need to delete allocator. smart_ptr will do the trick for us
922 923 924 925
}

}; // end of isc::dhcp namespace
}; // end of isc namespace