KeaKnownIssues: alloc_engine.cc

File alloc_engine.cc, 22.3 KB (added by tomek, 5 years ago)

fixed alloc_engine.cc

Line 
1// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
8// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
10// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
12// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13// PERFORMANCE OF THIS SOFTWARE.
14
15#include <dhcpsrv/alloc_engine.h>
16#include <dhcpsrv/dhcpsrv_log.h>
17#include <dhcpsrv/lease_mgr_factory.h>
18
19#include <cstring>
20#include <vector>
21#include <string.h>
22
23using namespace isc::asiolink;
24
25namespace isc {
26namespace dhcp {
27
28AllocEngine::IterativeAllocator::IterativeAllocator()
29    :Allocator() {
30}
31
32isc::asiolink::IOAddress
33AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& addr) {
34    // Get a buffer holding an address.
35    const std::vector<uint8_t>& vec = addr.toBytes();
36    // Get the address length.
37    const int len = vec.size();
38
39    // Since the same array will be used to hold the IPv4 and IPv6
40    // address we have to make sure that the size of the array
41    // we allocate will work for both types of address.
42    BOOST_STATIC_ASSERT(V4ADDRESS_LEN <= V6ADDRESS_LEN);
43    uint8_t packed[V6ADDRESS_LEN];
44
45    // Copy the address. It can be either V4 or V6.
46    std::memcpy(packed, &vec[0], len);
47
48    // Start increasing the least significant byte
49    for (int i = len - 1; i >= 0; --i) {
50        ++packed[i];
51        // if we haven't overflowed (0xff -> 0x0), than we are done
52        if (packed[i] != 0) {
53            break;
54        }
55    }
56
57    return (IOAddress::fromBytes(addr.getFamily(), packed));
58}
59
60
61isc::asiolink::IOAddress
62AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
63                                             const DuidPtr&,
64                                             const IOAddress&) {
65
66    // Let's get the last allocated address. It is usually set correctly,
67    // but there are times when it won't be (like after removing a pool or
68    // perhaps restaring the server).
69    IOAddress last = subnet->getLastAllocated();
70
71    const PoolCollection& pools = subnet->getPools();
72
73    if (pools.empty()) {
74        isc_throw(AllocFailed, "No pools defined in selected subnet");
75    }
76
77    // first we need to find a pool the last address belongs to.
78    PoolCollection::const_iterator it;
79    for (it = pools.begin(); it != pools.end(); ++it) {
80        if ((*it)->inRange(last)) {
81            break;
82        }
83    }
84
85    // last one was bogus for one of several reasons:
86    // - we just booted up and that's the first address we're allocating
87    // - a subnet was removed or other reconfiguration just completed
88    // - perhaps allocation algorithm was changed
89    if (it == pools.end()) {
90        // ok to access first element directly. We checked that pools is non-empty
91        IOAddress next = pools[0]->getFirstAddress();
92        subnet->setLastAllocated(next);
93        return (next);
94    }
95
96    // Ok, we have a pool that the last address belonged to, let's use it.
97
98    IOAddress next = increaseAddress(last); // basically addr++
99    if ((*it)->inRange(next)) {
100        // the next one is in the pool as well, so we haven't hit pool boundary yet
101        subnet->setLastAllocated(next);
102        return (next);
103    }
104
105    // We hit pool boundary, let's try to jump to the next pool and try again
106    ++it;
107    if (it == pools.end()) {
108        // Really out of luck today. That was the last pool. Let's rewind
109        // to the beginning.
110        next = pools[0]->getFirstAddress();
111        subnet->setLastAllocated(next);
112        return (next);
113    }
114
115    // there is a next pool, let's try first adddress from it
116    next = (*it)->getFirstAddress();
117    subnet->setLastAllocated(next);
118    return (next);
119}
120
121AllocEngine::HashedAllocator::HashedAllocator()
122    :Allocator() {
123    isc_throw(NotImplemented, "Hashed allocator is not implemented");
124}
125
126
127isc::asiolink::IOAddress
128AllocEngine::HashedAllocator::pickAddress(const SubnetPtr&,
129                                          const DuidPtr&,
130                                          const IOAddress&) {
131    isc_throw(NotImplemented, "Hashed allocator is not implemented");
132}
133
134AllocEngine::RandomAllocator::RandomAllocator()
135    :Allocator() {
136    isc_throw(NotImplemented, "Random allocator is not implemented");
137}
138
139
140isc::asiolink::IOAddress
141AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&,
142                                          const DuidPtr&,
143                                          const IOAddress&) {
144    isc_throw(NotImplemented, "Random allocator is not implemented");
145}
146
147
148AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts)
149    :attempts_(attempts) {
150    switch (engine_type) {
151    case ALLOC_ITERATIVE:
152        allocator_ = boost::shared_ptr<Allocator>(new IterativeAllocator());
153        break;
154    case ALLOC_HASHED:
155        allocator_ = boost::shared_ptr<Allocator>(new HashedAllocator());
156        break;
157    case ALLOC_RANDOM:
158        allocator_ = boost::shared_ptr<Allocator>(new RandomAllocator());
159        break;
160
161    default:
162        isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
163    }
164}
165
166Lease6Ptr
167AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
168                              const DuidPtr& duid,
169                              uint32_t iaid,
170                              const IOAddress& hint,
171                              bool fake_allocation /* = false */ ) {
172
173    try {
174        // That check is not necessary. We create allocator in AllocEngine
175        // constructor
176        if (!allocator_) {
177            isc_throw(InvalidOperation, "No allocator selected");
178        }
179
180        // check if there's existing lease for that subnet/duid/iaid combination.
181        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(*duid, iaid, subnet->getID());
182        if (existing) {
183            // we have a lease already. This is a returning client, probably after
184            // his reboot.
185            return (existing);
186        }
187
188        // check if the hint is in pool and is available
189        if (subnet->inPool(hint)) {
190            existing = LeaseMgrFactory::instance().getLease6(hint);
191            if (!existing) {
192                /// @todo: check if the hint is reserved once we have host support
193                /// implemented
194
195                // the hint is valid and not currently used, let's create a lease for it
196                Lease6Ptr lease = createLease6(subnet, duid, iaid, hint, fake_allocation);
197
198                // It can happen that the lease allocation failed (we could have lost
199                // the race condition. That means that the hint is lo longer usable and
200                // we need to continue the regular allocation path.
201                if (lease) {
202                    return (lease);
203                }
204            } else {
205                if (existing->expired()) {
206                    return (reuseExpiredLease(existing, subnet, duid, iaid,
207                                              fake_allocation));
208                }
209
210            }
211        }
212
213        // Hint is in the pool but is not available. Search the pool until first of
214        // the following occurs:
215        // - we find a free address
216        // - we find an address for which the lease has expired
217        // - we exhaust number of tries
218        //
219        // @todo: Current code does not handle pool exhaustion well. It will be
220        // improved. Current problems:
221        // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
222        // 10 addresses), we will iterate over it 100 times before giving up
223        // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
224        // 3. the whole concept of infinite attempts is just asking for infinite loop
225        // We may consider some form or reference counting (this pool has X addresses
226        // left), but this has one major problem. We exactly control allocation
227        // moment, but we currently do not control expiration time at all
228
229        unsigned int i = attempts_;
230        do {
231            IOAddress candidate = allocator_->pickAddress(subnet, duid, hint);
232
233            /// @todo: check if the address is reserved once we have host support
234            /// implemented
235
236            Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(candidate);
237            if (!existing) {
238                // there's no existing lease for selected candidate, so it is
239                // free. Let's allocate it.
240                Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
241                                              fake_allocation);
242                if (lease) {
243                    return (lease);
244                }
245
246                // Although the address was free just microseconds ago, it may have
247                // been taken just now. If the lease insertion fails, we continue
248                // allocation attempts.
249            } else {
250                if (existing->expired()) {
251                    return (reuseExpiredLease(existing, subnet, duid, iaid,
252                                              fake_allocation));
253                }
254            }
255
256            // Continue trying allocation until we run out of attempts
257            // (or attempts are set to 0, which means infinite)
258            --i;
259        } while ((i > 0) || !attempts_);
260
261        // Unable to allocate an address, return an empty lease.
262        LOG_WARN(dhcpsrv_logger, DHCPSRV_ADDRESS6_ALLOC_FAIL).arg(attempts_);
263
264    } catch (const isc::Exception& e) {
265
266        // Some other error, return an empty lease.
267        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS6_ALLOC_ERROR).arg(e.what());
268    }
269
270    return (Lease6Ptr());
271}
272
273Lease4Ptr
274AllocEngine::allocateAddress4(const SubnetPtr& subnet,
275                              const ClientIdPtr& clientid,
276                              const HWAddrPtr& hwaddr,
277                              const IOAddress& hint,
278                              bool fake_allocation /* = false */ ) {
279
280    try {
281        // Allocator is always created in AllocEngine constructor and there is
282        // currently no other way to set it, so that check is not really necessary.
283        if (!allocator_) {
284            isc_throw(InvalidOperation, "No allocator selected");
285        }
286
287        // Check if there's existing lease for that subnet/clientid/hwaddr combination.
288        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
289        if (existing) {
290            // We have a lease already. This is a returning client, probably after
291            // its reboot.
292            existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
293            if (existing) {
294                return (existing);
295            }
296
297            // If renewal failed (e.g. the lease no longer matches current configuration)
298            // let's continue the allocation process
299        }
300
301        if (clientid) {
302            existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
303            if (existing) {
304                // we have a lease already. This is a returning client, probably after
305                // its reboot.
306                existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
307                // @todo: produce a warning. We haven't found him using MAC address, but
308                // we found him using client-id
309                if (existing) {
310                    return (existing);
311                }
312            }
313        }
314
315        // check if the hint is in pool and is available
316        if (subnet->inPool(hint)) {
317            existing = LeaseMgrFactory::instance().getLease4(hint);
318            if (!existing) {
319                /// @todo: Check if the hint is reserved once we have host support
320                /// implemented
321
322                // The hint is valid and not currently used, let's create a lease for it
323                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint, fake_allocation);
324
325                // It can happen that the lease allocation failed (we could have lost
326                // the race condition. That means that the hint is lo longer usable and
327                // we need to continue the regular allocation path.
328                if (lease) {
329                    return (lease);
330                }
331            } else {
332                if (existing->expired()) {
333                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
334                                              fake_allocation));
335                }
336
337            }
338        }
339
340        // Hint is in the pool but is not available. Search the pool until first of
341        // the following occurs:
342        // - we find a free address
343        // - we find an address for which the lease has expired
344        // - we exhaust the number of tries
345        //
346        // @todo: Current code does not handle pool exhaustion well. It will be
347        // improved. Current problems:
348        // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
349        // 10 addresses), we will iterate over it 100 times before giving up
350        // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
351        // 3. the whole concept of infinite attempts is just asking for infinite loop
352        // We may consider some form or reference counting (this pool has X addresses
353        // left), but this has one major problem. We exactly control allocation
354        // moment, but we currently do not control expiration time at all
355
356        unsigned int i = attempts_;
357        do {
358            IOAddress candidate = allocator_->pickAddress(subnet, clientid, hint);
359
360            /// @todo: check if the address is reserved once we have host support
361            /// implemented
362
363            Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(candidate);
364            if (!existing) {
365                // there's no existing lease for selected candidate, so it is
366                // free. Let's allocate it.
367                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, candidate,
368                                              fake_allocation);
369                if (lease) {
370                    return (lease);
371                }
372
373                // Although the address was free just microseconds ago, it may have
374                // been taken just now. If the lease insertion fails, we continue
375                // allocation attempts.
376            } else {
377                if (existing->expired()) {
378                    return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
379                                              fake_allocation));
380                }
381            }
382
383            // Continue trying allocation until we run out of attempts
384            // (or attempts are set to 0, which means infinite)
385            --i;
386        } while ((i > 0) || !attempts_);
387
388        // Unable to allocate an address, return an empty lease.
389        LOG_WARN(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_FAIL).arg(attempts_);
390
391    } catch (const isc::Exception& e) {
392
393        // Some other error, return an empty lease.
394        LOG_ERROR(dhcpsrv_logger, DHCPSRV_ADDRESS4_ALLOC_ERROR).arg(e.what());
395    }
396    return (Lease4Ptr());
397}
398
399Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
400                                   const ClientIdPtr& clientid,
401                                   const HWAddrPtr& hwaddr,
402                                   const Lease4Ptr& lease,
403                                   bool fake_allocation /* = false */) {
404
405    lease->subnet_id_ = subnet->getID();
406    lease->hwaddr_ = hwaddr->hwaddr_;
407    lease->client_id_ = clientid;
408    lease->cltt_ = time(NULL);
409    lease->t1_ = subnet->getT1();
410    lease->t2_ = subnet->getT2();
411    lease->valid_lft_ = subnet->getValid();
412
413    if (!fake_allocation) {
414        // for REQUEST we do update the lease
415        LeaseMgrFactory::instance().updateLease4(lease);
416    }
417
418    return (lease);
419}
420
421Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
422                                         const Subnet6Ptr& subnet,
423                                         const DuidPtr& duid,
424                                         uint32_t iaid,
425                                         bool fake_allocation /*= false */ ) {
426
427    if (!expired->expired()) {
428        isc_throw(BadValue, "Attempt to recycle lease that is still valid");
429    }
430
431    // address, lease type and prefixlen (0) stay the same
432    expired->iaid_ = iaid;
433    expired->duid_ = duid;
434    expired->preferred_lft_ = subnet->getPreferred();
435    expired->valid_lft_ = subnet->getValid();
436    expired->t1_ = subnet->getT1();
437    expired->t2_ = subnet->getT2();
438    expired->cltt_ = time(NULL);
439    expired->subnet_id_ = subnet->getID();
440    expired->fixed_ = false;
441    expired->hostname_ = std::string("");
442    expired->fqdn_fwd_ = false;
443    expired->fqdn_rev_ = false;
444
445    /// @todo: log here that the lease was reused (there's ticket #2524 for
446    /// logging in libdhcpsrv)
447
448    if (!fake_allocation) {
449        // for REQUEST we do update the lease
450        LeaseMgrFactory::instance().updateLease6(expired);
451    }
452
453    // We do nothing for SOLICIT. We'll just update database when
454    // the client gets back to us with REQUEST message.
455
456    // it's not really expired at this stage anymore - let's return it as
457    // an updated lease
458    return (expired);
459}
460
461Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
462                                         const SubnetPtr& subnet,
463                                         const ClientIdPtr& clientid,
464                                         const HWAddrPtr& hwaddr,
465                                         bool fake_allocation /*= false */ ) {
466
467    if (!expired->expired()) {
468        isc_throw(BadValue, "Attempt to recycle lease that is still valid");
469    }
470
471    // address, lease type and prefixlen (0) stay the same
472    expired->client_id_ = clientid;
473    expired->hwaddr_ = hwaddr->hwaddr_;
474    expired->valid_lft_ = subnet->getValid();
475    expired->t1_ = subnet->getT1();
476    expired->t2_ = subnet->getT2();
477    expired->cltt_ = time(NULL);
478    expired->subnet_id_ = subnet->getID();
479    expired->fixed_ = false;
480    expired->hostname_ = std::string("");
481    expired->fqdn_fwd_ = false;
482    expired->fqdn_rev_ = false;
483
484    /// @todo: log here that the lease was reused (there's ticket #2524 for
485    /// logging in libdhcpsrv)
486
487    if (!fake_allocation) {
488        // for REQUEST we do update the lease
489        LeaseMgrFactory::instance().updateLease4(expired);
490    }
491
492    // We do nothing for SOLICIT. We'll just update database when
493    // the client gets back to us with REQUEST message.
494
495    // it's not really expired at this stage anymore - let's return it as
496    // an updated lease
497    return (expired);
498}
499
500Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
501                                    const DuidPtr& duid,
502                                    uint32_t iaid,
503                                    const IOAddress& addr,
504                                    bool fake_allocation /*= false */ ) {
505
506    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
507                               subnet->getPreferred(), subnet->getValid(),
508                               subnet->getT1(), subnet->getT2(), subnet->getID()));
509
510    if (!fake_allocation) {
511        // That is a real (REQUEST) allocation
512        bool status = LeaseMgrFactory::instance().addLease(lease);
513
514        if (status) {
515
516            return (lease);
517        } else {
518            // One of many failures with LeaseMgr (e.g. lost connection to the
519            // database, database failed etc.). One notable case for that
520            // is that we are working in multi-process mode and we lost a race
521            // (some other process got that address first)
522            return (Lease6Ptr());
523        }
524    } else {
525        // That is only fake (SOLICIT without rapid-commit) allocation
526
527        // It is for advertise only. We should not insert the lease into LeaseMgr,
528        // but rather check that we could have inserted it.
529        Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(addr);
530        if (!existing) {
531            return (lease);
532        } else {
533            return (Lease6Ptr());
534        }
535    }
536}
537
538Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
539                                    const DuidPtr& clientid,
540                                    const HWAddrPtr& hwaddr,
541                                    const IOAddress& addr,
542                                    bool fake_allocation /*= false */ ) {
543    if (!hwaddr) {
544        isc_throw(BadValue, "Can't create a lease with NULL HW address");
545    }
546    time_t now = time(NULL);
547
548    // @todo: remove this kludge after ticket #2590 is implemented
549    std::vector<uint8_t> local_copy;
550    if (clientid) {
551        local_copy = clientid->getDuid();
552    }
553
554    Lease4Ptr lease(new Lease4(addr, &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
555                               &local_copy[0], local_copy.size(), subnet->getValid(),
556                               subnet->getT1(), subnet->getT2(), now,
557                               subnet->getID()));
558
559    if (!fake_allocation) {
560        // That is a real (REQUEST) allocation
561        bool status = LeaseMgrFactory::instance().addLease(lease);
562        if (status) {
563            return (lease);
564        } else {
565            // One of many failures with LeaseMgr (e.g. lost connection to the
566            // database, database failed etc.). One notable case for that
567            // is that we are working in multi-process mode and we lost a race
568            // (some other process got that address first)
569            return (Lease4Ptr());
570        }
571    } else {
572        // That is only fake (DISCOVER) allocation
573
574        // It is for OFFER only. We should not insert the lease into LeaseMgr,
575        // but rather check that we could have inserted it.
576        Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(addr);
577        if (!existing) {
578            return (lease);
579        } else {
580            return (Lease4Ptr());
581        }
582    }
583}
584
585AllocEngine::~AllocEngine() {
586    // no need to delete allocator. smart_ptr will do the trick for us
587}
588
589}; // end of isc::dhcp namespace
590}; // end of isc namespace