alloc_engine.cc 37.5 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

namespace isc {
namespace dhcp {

56
AllocEngine::IterativeAllocator::IterativeAllocator(Lease::Type lease_type)
57
    :Allocator(lease_type) {
58 59
}

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).
Tomek Mrugalski's avatar
Tomek Mrugalski committed
97
    IOAddress last = subnet->getLastAllocated(pool_type_);
98

Tomek Mrugalski's avatar
Tomek Mrugalski committed
99
    const PoolCollection& pools = subnet->getPools(pool_type_);
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
    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();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
120
        subnet->setLastAllocated(pool_type_, next);
121 122 123 124 125 126 127
        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
Tomek Mrugalski's avatar
Tomek Mrugalski committed
129
        subnet->setLastAllocated(pool_type_, next);
130 131 132 133 134 135 136 137 138
        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();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
139
        subnet->setLastAllocated(pool_type_, next);
140 141 142
        return (next);
    }

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

149
AllocEngine::HashedAllocator::HashedAllocator(Lease::Type lease_type)
150
    :Allocator(lease_type) {
151 152 153 154 155
    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
    isc_throw(NotImplemented, "Hashed allocator is not implemented");
}

162
AllocEngine::RandomAllocator::RandomAllocator(Lease::Type lease_type)
163
    :Allocator(lease_type) {
164 165 166 167 168
    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
AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts,
                         bool ipv6)
178
    :attempts_(attempts) {
179 180 181

    Pool::PoolType pool_type = ipv6?Pool::TYPE_IA:Pool::TYPE_V4;

182 183
    switch (engine_type) {
    case ALLOC_ITERATIVE:
184
        allocator_.reset(new IterativeAllocator(pool_type));
185 186
        break;
    case ALLOC_HASHED:
187
        allocator_.reset(new HashedAllocator(pool_type));
188 189
        break;
    case ALLOC_RANDOM:
190
        allocator_.reset(new RandomAllocator(pool_type));
191 192 193 194 195
        break;

    default:
        isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
    }
196 197

    // Register hook points
198
    hook_index_lease4_select_ = Hooks.hook_index_lease4_select_;
199
    hook_index_lease6_select_ = Hooks.hook_index_lease6_select_;
200 201
}

202
Lease6Collection
203 204 205
AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                              const DuidPtr& duid,
                              uint32_t iaid,
206
                              const IOAddress& hint,
207
                              Lease::Type type,
208 209 210
                              const bool fwd_dns_update,
                              const bool rev_dns_update,
                              const std::string& hostname,
211 212
                              bool fake_allocation,
                              const isc::hooks::CalloutHandlePtr& callout_handle) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
213

214 215 216 217 218 219
    try {
        // That check is not necessary. We create allocator in AllocEngine
        // constructor
        if (!allocator_) {
            isc_throw(InvalidOperation, "No allocator selected");
        }
220

221 222 223 224 225 226 227 228
        if (!subnet) {
            isc_throw(InvalidOperation, "Subnet is required for allocation");
        }

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

229
        // check if there's existing lease for that subnet/duid/iaid combination.
230
        /// @todo: Make this generic (cover temp. addrs and prefixes)
231 232
        Lease6Collection existing = LeaseMgrFactory::instance().getLeases6(type,
                                    *duid, iaid, subnet->getID());
233 234 235 236

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

240 241
        // check if the hint is in pool and is available
        if (subnet->inPool(hint)) {
242 243

            /// @todo: We support only one hint for now
244
            Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(type, hint);
245
            if (!lease) {
246 247
                /// @todo: check if the hint is reserved once we have host support
                /// implemented
248

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

255 256 257 258
                // 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) {
259 260 261 262
                    /// @todo: We support only one lease per ia for now
                    Lease6Collection collection;
                    collection.push_back(lease);
                    return (collection);
263 264
                }
            } else {
265 266 267
                if (lease->expired()) {
                    /// We found a lease and it is expired, so we can reuse it
                    lease = reuseExpiredLease(lease, subnet, duid, iaid,
268
                                              fwd_dns_update, rev_dns_update,
269
                                              hostname, callout_handle,
270
                                              fake_allocation);
271 272

                    /// @todo: We support only one lease per ia for now
273 274 275
                    Lease6Collection collection;
                    collection.push_back(lease);
                    return (collection);
276
                }
277

278
            }
279
        }
280

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
        // 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
296

297 298 299
        unsigned int i = attempts_;
        do {
            IOAddress candidate = allocator_->pickAddress(subnet, duid, hint);
300

301 302
            /// @todo: check if the address is reserved once we have host support
            /// implemented
303

304 305
            Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(type,
                                 candidate);
306 307 308 309
            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,
310 311
                                               type, fwd_dns_update,
                                               rev_dns_update, hostname,
312
                                               callout_handle, fake_allocation);
313
                if (lease) {
314 315 316
                    Lease6Collection collection;
                    collection.push_back(lease);
                    return (collection);
317 318 319 320 321 322 323
                }

                // 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()) {
324 325 326 327 328 329 330
                    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);
331
                }
332 333
            }

334 335 336 337
            // Continue trying allocation until we run out of attempts
            // (or attempts are set to 0, which means infinite)
            --i;
        } while ((i > 0) || !attempts_);
338

339 340 341 342 343 344 345 346
        // 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());
    }
347

348
    return (Lease6Collection());
349 350
}

351 352 353 354 355
Lease4Ptr
AllocEngine::allocateAddress4(const SubnetPtr& subnet,
                              const ClientIdPtr& clientid,
                              const HWAddrPtr& hwaddr,
                              const IOAddress& hint,
356 357 358
                              const bool fwd_dns_update,
                              const bool rev_dns_update,
                              const std::string& hostname,
359
                              bool fake_allocation,
360 361 362
                              const isc::hooks::CalloutHandlePtr& callout_handle,
                              Lease4Ptr& old_lease) {

363 364 365
    // 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.
366
    old_lease.reset();
367

368 369 370 371 372
    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");
373 374
        }

375 376 377 378 379 380 381 382
        if (!subnet) {
            isc_throw(InvalidOperation, "Can't allocate IPv4 address without subnet");
        }

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

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

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

401 402 403
        if (clientid) {
            existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
            if (existing) {
404
                // Save the old lease before renewal.
405
                old_lease.reset(new Lease4(*existing));
406 407
                // we have a lease already. This is a returning client, probably after
                // its reboot.
408 409
                existing = renewLease4(subnet, clientid, hwaddr,
                                       fwd_dns_update, rev_dns_update,
410
                                       hostname, existing, callout_handle,
411
                                       fake_allocation);
412 413 414 415 416 417 418 419 420 421 422 423 424 425
                // @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
426

427
                // The hint is valid and not currently used, let's create a lease for it
428
                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint,
429 430 431
                                               fwd_dns_update, rev_dns_update,
                                               hostname, callout_handle,
                                               fake_allocation);
432

433 434 435 436 437 438 439 440
                // 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()) {
441
                    // Save the old lease, before reusing it.
442
                    old_lease.reset(new Lease4(*existing));
443
                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
444 445 446
                                              fwd_dns_update, rev_dns_update,
                                              hostname, callout_handle,
                                              fake_allocation));
447
                }
448

449
            }
450 451
        }

452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
        // 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
467

468 469 470
        unsigned int i = attempts_;
        do {
            IOAddress candidate = allocator_->pickAddress(subnet, clientid, hint);
471

472 473
            /// @todo: check if the address is reserved once we have host support
            /// implemented
474

475 476 477 478
            Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(candidate);
            if (!existing) {
                // there's no existing lease for selected candidate, so it is
                // free. Let's allocate it.
479 480 481
                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr,
                                               candidate, fwd_dns_update,
                                               rev_dns_update, hostname,
482
                                               callout_handle, fake_allocation);
483 484 485 486 487 488 489 490 491
                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()) {
492 493
                    // Save old lease before reusing it.
                    old_lease.reset(new Lease4(*existing));
494
                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
495 496 497
                                              fwd_dns_update, rev_dns_update,
                                              hostname, callout_handle,
                                              fake_allocation));
498
                }
499 500
            }

501 502 503 504 505 506 507
            // 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_);
508

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

511 512 513 514
        // Some other error, return an empty lease.
        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_ERROR).arg(e.what());
    }
    return (Lease4Ptr());
515 516
}

517 518 519
Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
                                   const ClientIdPtr& clientid,
                                   const HWAddrPtr& hwaddr,
520 521 522
                                   const bool fwd_dns_update,
                                   const bool rev_dns_update,
                                   const std::string& hostname,
523
                                   const Lease4Ptr& lease,
524
                                   const isc::hooks::CalloutHandlePtr& callout_handle,
525 526
                                   bool fake_allocation /* = false */) {

527 528 529 530 531 532 533
    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
534
    /// @todo: remove this once #3083 is implemented
535 536
    Lease4 old_values = *lease;

537 538 539 540 541 542 543
    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();
544 545 546
    lease->fqdn_fwd_ = fwd_dns_update;
    lease->fqdn_rev_ = rev_dns_update;
    lease->hostname_ = hostname;
547

548 549 550 551 552 553 554
    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();

555 556 557 558 559 560
        // 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);

561
        // Pass the parameters
562
        callout_handle->setArgument("subnet4", subnet4);
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
        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) {
582 583 584
        // for REQUEST we do update the lease
        LeaseMgrFactory::instance().updateLease4(lease);
    }
585 586
    if (skip) {
        // Rollback changes (really useful only for memfile)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
587
        /// @todo: remove this once #3083 is implemented
588 589
        *lease = old_values;
    }
590 591 592 593

    return (lease);
}

594 595 596 597
Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
                                         const Subnet6Ptr& subnet,
                                         const DuidPtr& duid,
                                         uint32_t iaid,
598 599 600
                                         const bool fwd_dns_update,
                                         const bool rev_dns_update,
                                         const std::string& hostname,
601
                                         const isc::hooks::CalloutHandlePtr& callout_handle,
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
                                         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;
618 619 620
    expired->hostname_ = hostname;
    expired->fqdn_fwd_ = fwd_dns_update;
    expired->fqdn_rev_ = rev_dns_update;
621 622 623 624

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
625
    // Let's execute all callouts registered for lease6_select
626 627 628 629 630 631 632 633 634 635 636 637
    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
638 639

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
642
        // Call the callouts
Tomek Mrugalski's avatar
Tomek Mrugalski committed
643
        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
644 645 646

        // 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
647
        // won't be inserted into the database.
648
        if (callout_handle->getSkip()) {
649
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
650 651 652 653 654 655 656 657
            return (Lease6Ptr());
        }

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

658 659 660
    if (!fake_allocation) {
        // for REQUEST we do update the lease
        LeaseMgrFactory::instance().updateLease6(expired);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
661
    }
662 663 664 665 666 667 668 669 670

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

671 672 673 674
Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
                                         const SubnetPtr& subnet,
                                         const ClientIdPtr& clientid,
                                         const HWAddrPtr& hwaddr,
675 676 677
                                         const bool fwd_dns_update,
                                         const bool rev_dns_update,
                                         const std::string& hostname,
678
                                         const isc::hooks::CalloutHandlePtr& callout_handle,
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
                                         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;
694 695 696
    expired->hostname_ = hostname;
    expired->fqdn_fwd_ = fwd_dns_update;
    expired->fqdn_rev_ = rev_dns_update;
697 698 699 700

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

701 702 703 704 705 706 707 708
    // 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
709

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

        // 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
728
        // won't be inserted into the database.
729 730 731 732 733 734 735 736 737 738
        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);
    }

739 740 741 742 743 744 745 746 747 748 749 750 751
    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);
}

752 753 754 755
Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
                                    const DuidPtr& duid,
                                    uint32_t iaid,
                                    const IOAddress& addr,
756
                                    Lease::Type type,
757 758 759
                                    const bool fwd_dns_update,
                                    const bool rev_dns_update,
                                    const std::string& hostname,
760
                                    const isc::hooks::CalloutHandlePtr& callout_handle,
761
                                    bool fake_allocation /*= false */ ) {
762

763
    Lease6Ptr lease(new Lease6(type, addr, duid, iaid,
764 765
                               subnet->getPreferred(), subnet->getValid(),
                               subnet->getT1(), subnet->getT2(), subnet->getID()));
766

767 768 769 770
    lease->fqdn_fwd_ = fwd_dns_update;
    lease->fqdn_rev_ = rev_dns_update;
    lease->hostname_ = hostname;

771
    // Let's execute all callouts registered for lease6_select
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787
    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
788
        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
789 790 791

        // 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
792
        // won't be inserted into the database.
793
        if (callout_handle->getSkip()) {
794
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
795 796 797 798 799 800 801 802
            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
803
    if (!fake_allocation) {
804
        // That is a real (REQUEST) allocation
805
        bool status = LeaseMgrFactory::instance().addLease(lease);
806

807 808 809 810 811 812 813 814 815 816
        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());
        }
817
    } else {
818 819 820 821
        // 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.
822
        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(
823
                             Lease::TYPE_NA, addr);
824 825 826 827 828
        if (!existing) {
            return (lease);
        } else {
            return (Lease6Ptr());
        }
829 830 831
    }
}

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

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

852
    Lease4Ptr lease(new Lease4(addr, &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
853 854 855
                               &local_copy[0], local_copy.size(), subnet->getValid(),
                               subnet->getT1(), subnet->getT2(), now,
                               subnet->getID()));
856

857 858 859 860 861
    // Set FQDN specific lease parameters.
    lease->fqdn_fwd_ = fwd_dns_update;
    lease->fqdn_rev_ = rev_dns_update;
    lease->hostname_ = hostname;

862 863 864 865 866 867 868 869 870
    // 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

871 872 873 874 875 876
        // 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);
877 878 879

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

        // Pass the intended lease as well
882 883 884 885 886 887 888
        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
889
        // won't be inserted into the database.
890 891 892 893 894 895 896
        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.
897
        callout_handle->getArgument("lease4", lease);
898 899
    }

900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925
    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());
        }
    }
}

926
AllocEngine::~AllocEngine() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
927
    // no need to delete allocator. smart_ptr will do the trick for us
928 929 930 931
}

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