alloc_engine.cc 36.4 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
57
AllocEngine::IterativeAllocator::IterativeAllocator(Pool::PoolType lease_type)
    :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).
97
    IOAddress last = subnet->getLastAllocated(lease_type_);
98

99
    const PoolCollection& pools = subnet->getPools(lease_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();
120
        subnet->setLastAllocated(next, lease_type_);
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
129
        subnet->setLastAllocated(next, lease_type_);
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();
139
        subnet->setLastAllocated(next, lease_type_);
140
141
142
        return (next);
    }

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

149
150
AllocEngine::HashedAllocator::HashedAllocator(Pool::PoolType lease_type)
    :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
163
AllocEngine::RandomAllocator::RandomAllocator(Pool::PoolType lease_type)
    :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
178
179

AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts)
    :attempts_(attempts) {
    switch (engine_type) {
    case ALLOC_ITERATIVE:
180
        allocator_.reset(new IterativeAllocator(Pool6::TYPE_IA));
181
182
        break;
    case ALLOC_HASHED:
183
        allocator_.reset(new HashedAllocator(Pool6::TYPE_IA));
184
185
        break;
    case ALLOC_RANDOM:
186
        allocator_.reset(new RandomAllocator(Pool6::TYPE_IA));
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
199
200
201
}

Lease6Ptr
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
225
226
227
228
229
230
        // check if there's existing lease for that subnet/duid/iaid combination.
        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(*duid, iaid, subnet->getID());
        if (existing) {
            // we have a lease already. This is a returning client, probably after
            // his reboot.
            return (existing);
        }
231

232
233
234
235
236
237
        // check if the hint is in pool and is available
        if (subnet->inPool(hint)) {
            existing = LeaseMgrFactory::instance().getLease6(hint);
            if (!existing) {
                /// @todo: check if the hint is reserved once we have host support
                /// implemented
238

239
                // the hint is valid and not currently used, let's create a lease for it
240
                Lease6Ptr lease = createLease6(subnet, duid, iaid,
241
242
                                               hint,
                                               fwd_dns_update,
243
                                               rev_dns_update, hostname,
244
                                               callout_handle,
245
                                               fake_allocation);
246

247
248
249
250
251
252
253
254
255
                // 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()) {
                    return (reuseExpiredLease(existing, subnet, duid, iaid,
256
                                              fwd_dns_update, rev_dns_update,
257
258
                                              hostname, callout_handle,
                                              fake_allocation));
259
                }
260

261
            }
262
        }
263

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
        // 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
279

280
281
282
        unsigned int i = attempts_;
        do {
            IOAddress candidate = allocator_->pickAddress(subnet, duid, hint);
283

284
285
            /// @todo: check if the address is reserved once we have host support
            /// implemented
286

287
288
289
290
291
            Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(candidate);
            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,
292
                                               fwd_dns_update, rev_dns_update,
293
                                               hostname,
294
                                               callout_handle, fake_allocation);
295
296
297
298
299
300
301
302
303
304
                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()) {
                    return (reuseExpiredLease(existing, subnet, duid, iaid,
305
                                              fwd_dns_update, rev_dns_update,
306
307
                                              hostname, callout_handle,
                                              fake_allocation));
308
                }
309
310
            }

311
312
313
314
            // Continue trying allocation until we run out of attempts
            // (or attempts are set to 0, which means infinite)
            --i;
        } while ((i > 0) || !attempts_);
315

316
317
318
319
320
321
322
323
        // 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());
    }
324

325
    return (Lease6Ptr());
326
327
}

328
329
330
331
332
Lease4Ptr
AllocEngine::allocateAddress4(const SubnetPtr& subnet,
                              const ClientIdPtr& clientid,
                              const HWAddrPtr& hwaddr,
                              const IOAddress& hint,
333
334
335
                              const bool fwd_dns_update,
                              const bool rev_dns_update,
                              const std::string& hostname,
336
                              bool fake_allocation,
337
338
339
                              const isc::hooks::CalloutHandlePtr& callout_handle,
                              Lease4Ptr& old_lease) {

340
341
342
    // 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.
343
    old_lease.reset();
344

345
346
347
348
349
    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");
350
351
        }

352
353
354
355
356
357
358
359
        if (!subnet) {
            isc_throw(InvalidOperation, "Can't allocate IPv4 address without subnet");
        }

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

360
        // Check if there's existing lease for that subnet/clientid/hwaddr combination.
361
        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
362
        if (existing) {
363
            // Save the old lease, before renewal.
364
            old_lease.reset(new Lease4(*existing));
365
            // We have a lease already. This is a returning client, probably after
Tomek Mrugalski's avatar
Tomek Mrugalski committed
366
            // its reboot.
367
368
            existing = renewLease4(subnet, clientid, hwaddr,
                                   fwd_dns_update, rev_dns_update, hostname,
369
                                   existing, callout_handle, fake_allocation);
370
371
372
            if (existing) {
                return (existing);
            }
373
374
375

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

378
379
380
        if (clientid) {
            existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
            if (existing) {
381
                // Save the old lease before renewal.
382
                old_lease.reset(new Lease4(*existing));
383
384
                // we have a lease already. This is a returning client, probably after
                // its reboot.
385
386
                existing = renewLease4(subnet, clientid, hwaddr,
                                       fwd_dns_update, rev_dns_update,
387
                                       hostname, existing, callout_handle,
388
                                       fake_allocation);
389
390
391
392
393
394
395
396
397
398
399
400
401
402
                // @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
403

404
                // The hint is valid and not currently used, let's create a lease for it
405
                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint,
406
407
408
                                               fwd_dns_update, rev_dns_update,
                                               hostname, callout_handle,
                                               fake_allocation);
409

410
411
412
413
414
415
416
417
                // 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()) {
418
                    // Save the old lease, before reusing it.
419
                    old_lease.reset(new Lease4(*existing));
420
                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
421
422
423
                                              fwd_dns_update, rev_dns_update,
                                              hostname, callout_handle,
                                              fake_allocation));
424
                }
425

426
            }
427
428
        }

429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
        // 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
444

445
446
447
        unsigned int i = attempts_;
        do {
            IOAddress candidate = allocator_->pickAddress(subnet, clientid, hint);
448

449
450
            /// @todo: check if the address is reserved once we have host support
            /// implemented
451

452
453
454
455
            Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(candidate);
            if (!existing) {
                // there's no existing lease for selected candidate, so it is
                // free. Let's allocate it.
456
457
458
                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr,
                                               candidate, fwd_dns_update,
                                               rev_dns_update, hostname,
459
                                               callout_handle, fake_allocation);
460
461
462
463
464
465
466
467
468
                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()) {
469
470
                    // Save old lease before reusing it.
                    old_lease.reset(new Lease4(*existing));
471
                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
472
473
474
                                              fwd_dns_update, rev_dns_update,
                                              hostname, callout_handle,
                                              fake_allocation));
475
                }
476
477
            }

478
479
480
481
482
483
484
            // 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_);
485

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

488
489
490
491
        // Some other error, return an empty lease.
        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_ERROR).arg(e.what());
    }
    return (Lease4Ptr());
492
493
}

494
495
496
Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
                                   const ClientIdPtr& clientid,
                                   const HWAddrPtr& hwaddr,
497
498
499
                                   const bool fwd_dns_update,
                                   const bool rev_dns_update,
                                   const std::string& hostname,
500
                                   const Lease4Ptr& lease,
501
                                   const isc::hooks::CalloutHandlePtr& callout_handle,
502
503
                                   bool fake_allocation /* = false */) {

504
505
506
507
508
509
510
    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
511
    /// @todo: remove this once #3083 is implemented
512
513
    Lease4 old_values = *lease;

514
515
516
517
518
519
520
    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();
521
522
523
    lease->fqdn_fwd_ = fwd_dns_update;
    lease->fqdn_rev_ = rev_dns_update;
    lease->hostname_ = hostname;
524

525
526
527
528
529
530
531
    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();

532
533
534
535
536
537
        // 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);

538
        // Pass the parameters
539
        callout_handle->setArgument("subnet4", subnet4);
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
        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) {
559
560
561
        // for REQUEST we do update the lease
        LeaseMgrFactory::instance().updateLease4(lease);
    }
562
563
    if (skip) {
        // Rollback changes (really useful only for memfile)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
564
        /// @todo: remove this once #3083 is implemented
565
566
        *lease = old_values;
    }
567
568
569
570

    return (lease);
}

571
572
573
574
Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
                                         const Subnet6Ptr& subnet,
                                         const DuidPtr& duid,
                                         uint32_t iaid,
575
576
577
                                         const bool fwd_dns_update,
                                         const bool rev_dns_update,
                                         const std::string& hostname,
578
                                         const isc::hooks::CalloutHandlePtr& callout_handle,
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
                                         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;
595
596
597
    expired->hostname_ = hostname;
    expired->fqdn_fwd_ = fwd_dns_update;
    expired->fqdn_rev_ = rev_dns_update;
598
599
600
601

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
602
    // Let's execute all callouts registered for lease6_select
603
604
605
606
607
608
609
610
611
612
613
614
    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
615
616

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
619
        // Call the callouts
Tomek Mrugalski's avatar
Tomek Mrugalski committed
620
        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
621
622
623

        // 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
624
        // won't be inserted into the database.
625
        if (callout_handle->getSkip()) {
626
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
627
628
629
630
631
632
633
634
            return (Lease6Ptr());
        }

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

635
636
637
    if (!fake_allocation) {
        // for REQUEST we do update the lease
        LeaseMgrFactory::instance().updateLease6(expired);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
638
    }
639
640
641
642
643
644
645
646
647

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

648
649
650
651
Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
                                         const SubnetPtr& subnet,
                                         const ClientIdPtr& clientid,
                                         const HWAddrPtr& hwaddr,
652
653
654
                                         const bool fwd_dns_update,
                                         const bool rev_dns_update,
                                         const std::string& hostname,
655
                                         const isc::hooks::CalloutHandlePtr& callout_handle,
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
                                         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;
671
672
673
    expired->hostname_ = hostname;
    expired->fqdn_fwd_ = fwd_dns_update;
    expired->fqdn_rev_ = rev_dns_update;
674
675
676
677

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

678
679
680
681
682
683
684
685
    // 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
686

Tomek Mrugalski's avatar
Tomek Mrugalski committed
687
688
689
690
        // 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.
691
692
        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
        callout_handle->setArgument("subnet4", subnet4);
693
694
695
696
697
698
699
700
701
702
703
704

        // 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
705
        // won't be inserted into the database.
706
707
708
709
710
711
712
713
714
715
        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);
    }

716
717
718
719
720
721
722
723
724
725
726
727
728
    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);
}

729
730
731
732
Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
                                    const DuidPtr& duid,
                                    uint32_t iaid,
                                    const IOAddress& addr,
733
734
735
                                    const bool fwd_dns_update,
                                    const bool rev_dns_update,
                                    const std::string& hostname,
736
                                    const isc::hooks::CalloutHandlePtr& callout_handle,
737
                                    bool fake_allocation /*= false */ ) {
738

739
740
741
    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
                               subnet->getPreferred(), subnet->getValid(),
                               subnet->getT1(), subnet->getT2(), subnet->getID()));
742

743
744
745
746
    lease->fqdn_fwd_ = fwd_dns_update;
    lease->fqdn_rev_ = rev_dns_update;
    lease->hostname_ = hostname;

747
    // Let's execute all callouts registered for lease6_select
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
    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
764
        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
765
766
767

        // 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
768
        // won't be inserted into the database.
769
        if (callout_handle->getSkip()) {
770
            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
771
772
773
774
775
776
777
778
            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
779
    if (!fake_allocation) {
780
        // That is a real (REQUEST) allocation
781
        bool status = LeaseMgrFactory::instance().addLease(lease);
782

783
784
785
786
787
788
789
790
791
792
        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());
        }
793
    } else {
794
795
796
797
        // 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.
798
        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(addr);
799
800
801
802
803
        if (!existing) {
            return (lease);
        } else {
            return (Lease6Ptr());
        }
804
805
806
    }
}

807
808
809
810
Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
                                    const DuidPtr& clientid,
                                    const HWAddrPtr& hwaddr,
                                    const IOAddress& addr,
811
812
813
                                    const bool fwd_dns_update,
                                    const bool rev_dns_update,
                                    const std::string& hostname,
814
                                    const isc::hooks::CalloutHandlePtr& callout_handle,
815
816
817
818
819
                                    bool fake_allocation /*= false */ ) {
    if (!hwaddr) {
        isc_throw(BadValue, "Can't create a lease with NULL HW address");
    }
    time_t now = time(NULL);
820
821
822
823
824
825
826

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

827
    Lease4Ptr lease(new Lease4(addr, &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
828
829
830
                               &local_copy[0], local_copy.size(), subnet->getValid(),
                               subnet->getT1(), subnet->getT2(), now,
                               subnet->getID()));
831

832
833
834
835
836
    // Set FQDN specific lease parameters.
    lease->fqdn_fwd_ = fwd_dns_update;
    lease->fqdn_rev_ = rev_dns_update;
    lease->hostname_ = hostname;

837
838
839
840
841
842
843
844
845
    // 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

846
847
848
849
850
851
        // 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);
852
853
854

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

        // Pass the intended lease as well
857
858
859
860
861
862
863
        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
864
        // won't be inserted into the database.
865
866
867
868
869
870
871
        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.
872
        callout_handle->getArgument("lease4", lease);
873
874
    }

875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
    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());
        }
    }
}

901
AllocEngine::~AllocEngine() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
902
    // no need to delete allocator. smart_ptr will do the trick for us
903
904
905
906
}

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