alloc_engine.cc 131 KB
Newer Older
Francis Dupont's avatar
Francis Dupont committed
1
// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6

7 8
#include <config.h>

9 10 11
#include <dhcp/dhcp6.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
12
#include <dhcp_ddns/ncr_msg.h>
13
#include <dhcpsrv/alloc_engine.h>
14
#include <dhcpsrv/alloc_engine_log.h>
15
#include <dhcpsrv/cfgmgr.h>
16
#include <dhcpsrv/dhcpsrv_log.h>
17
#include <dhcpsrv/host_mgr.h>
18
#include <dhcpsrv/host.h>
19
#include <dhcpsrv/lease_mgr_factory.h>
20
#include <dhcpsrv/ncr_generator.h>
21
#include <dhcpsrv/network.h>
22
#include <dhcpsrv/shared_network.h>
23 24
#include <hooks/callout_handle.h>
#include <hooks/hooks_manager.h>
25
#include <dhcpsrv/callout_handle_store.h>
26
#include <stats/stats_mgr.h>
27
#include <util/stopwatch.h>
28 29 30
#include <hooks/server_hooks.h>
#include <hooks/hooks_manager.h>

31 32
#include <boost/foreach.hpp>

33
#include <algorithm>
34
#include <cstring>
35
#include <sstream>
36
#include <limits>
37
#include <vector>
38
#include <stdint.h>
39
#include <string.h>
40
#include <utility>
41

42
using namespace isc::asiolink;
43
using namespace isc::dhcp;
44
using namespace isc::dhcp_ddns;
45
using namespace isc::hooks;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
46
using namespace isc::stats;
47

48 49 50
namespace {

/// Structure that holds registered hook indexes
51 52
struct AllocEngineHooks {
    int hook_index_lease4_select_; ///< index for "lease4_receive" hook point
53
    int hook_index_lease4_renew_;  ///< index for "lease4_renew" hook point
54
    int hook_index_lease4_expire_; ///< index for "lease4_expire" hook point
55
    int hook_index_lease4_recover_;///< index for "lease4_recover" hook point
56
    int hook_index_lease6_select_; ///< index for "lease6_receive" hook point
57 58
    int hook_index_lease6_renew_;  ///< index for "lease6_renew" hook point
    int hook_index_lease6_rebind_; ///< index for "lease6_rebind" hook point
59
    int hook_index_lease6_expire_; ///< index for "lease6_expire" hook point
60
    int hook_index_lease6_recover_;///< index for "lease6_recover" hook point
61 62

    /// Constructor that registers hook points for AllocationEngine
63
    AllocEngineHooks() {
64
        hook_index_lease4_select_ = HooksManager::registerHook("lease4_select");
65
        hook_index_lease4_renew_  = HooksManager::registerHook("lease4_renew");
66
        hook_index_lease4_expire_ = HooksManager::registerHook("lease4_expire");
67
        hook_index_lease4_recover_= HooksManager::registerHook("lease4_recover");
68
        hook_index_lease6_select_ = HooksManager::registerHook("lease6_select");
69 70 71
        hook_index_lease6_renew_  = HooksManager::registerHook("lease6_renew");
        hook_index_lease6_rebind_ = HooksManager::registerHook("lease6_rebind");
        hook_index_lease6_expire_ = HooksManager::registerHook("lease6_expire");
72
        hook_index_lease6_recover_= HooksManager::registerHook("lease6_recover");
73 74 75 76 77 78 79
    }
};

// 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.
80
AllocEngineHooks Hooks;
81 82

}; // anonymous namespace
83 84 85 86

namespace isc {
namespace dhcp {

87
AllocEngine::IterativeAllocator::IterativeAllocator(Lease::Type lease_type)
88
    :Allocator(lease_type) {
89 90
}

91 92
isc::asiolink::IOAddress
AllocEngine::IterativeAllocator::increasePrefix(const isc::asiolink::IOAddress& prefix,
Tomek Mrugalski's avatar
Tomek Mrugalski committed
93
                                                const uint8_t prefix_len) {
94
    if (!prefix.isV6()) {
95
        isc_throw(BadValue, "Prefix operations are for IPv6 only (attempted to "
96
                  "increase prefix " << prefix << ")");
97 98 99 100 101 102 103 104 105 106
    }

    // Get a buffer holding an address.
    const std::vector<uint8_t>& vec = prefix.toBytes();

    if (prefix_len < 1 || prefix_len > 128) {
        isc_throw(BadValue, "Cannot increase prefix: invalid prefix length: "
                  << prefix_len);
    }

107 108 109
    // Brief explanation what happens here:
    // http://www.youtube.com/watch?v=NFQCYpIHLNQ

110 111 112 113
    uint8_t n_bytes = (prefix_len - 1)/8;
    uint8_t n_bits = 8 - (prefix_len - n_bytes*8);
    uint8_t mask = 1 << n_bits;

114 115 116 117 118 119 120
    // Longer explanation: n_bytes specifies number of full bytes that are
    // in-prefix. They can also be used as an offset for the first byte that
    // is not in prefix. n_bits specifies number of bits on the last byte that
    // is (often partially) in prefix. For example for a /125 prefix, the values
    // are 15 and 3, respectively. Mask is a bitmask that has the least
    // significant bit from the prefix set.

121 122 123 124 125
    uint8_t packed[V6ADDRESS_LEN];

    // Copy the address. It must be V6, but we already checked that.
    std::memcpy(packed, &vec[0], V6ADDRESS_LEN);

126
    // Can we safely increase only the last byte in prefix without overflow?
127 128 129 130 131 132 133 134
    if (packed[n_bytes] + uint16_t(mask) < 256u) {
        packed[n_bytes] += mask;
        return (IOAddress::fromBytes(AF_INET6, packed));
    }

    // Overflow (done on uint8_t, but the sum is greater than 255)
    packed[n_bytes] += mask;

135
    // Deal with the overflow. Start increasing the least significant byte
136 137 138 139 140 141 142 143 144 145 146
    for (int i = n_bytes - 1; i >= 0; --i) {
        ++packed[i];
        // If we haven't overflowed (0xff->0x0) the next byte, then we are done
        if (packed[i] != 0) {
            break;
        }
    }

    return (IOAddress::fromBytes(AF_INET6, packed));
}

147

148
isc::asiolink::IOAddress
149
AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
150 151
                                             const DuidPtr&,
                                             const IOAddress&) {
152

153 154 155
    // Is this prefix allocation?
    bool prefix = pool_type_ == Lease::TYPE_PD;

156
    // Let's get the last allocated address. It is usually set correctly,
157
    // but there are times when it won't be (like after removing a pool or
158
    // perhaps restarting the server).
Tomek Mrugalski's avatar
Tomek Mrugalski committed
159
    IOAddress last = subnet->getLastAllocated(pool_type_);
160

Tomek Mrugalski's avatar
Tomek Mrugalski committed
161
    const PoolCollection& pools = subnet->getPools(pool_type_);
162

163
    if (pools.empty()) {
164 165 166 167
        isc_throw(AllocFailed, "No pools defined in selected subnet");
    }

    // first we need to find a pool the last address belongs to.
168
    PoolCollection::const_iterator it;
169 170 171 172 173 174 175 176 177 178 179 180 181
    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
182
        subnet->setLastAllocated(pool_type_, next);
183 184 185 186 187
        return (next);
    }

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

188 189
    IOAddress next("::");
    if (!prefix) {
190
        next = IOAddress::increase(last); // basically addr++
191 192 193 194
    } else {
        Pool6Ptr pool6 = boost::dynamic_pointer_cast<Pool6>(*it);
        if (!pool6) {
            // Something is gravely wrong here
195 196
            isc_throw(Unexpected, "Wrong type of pool: " << (*it)->toText()
                      << " is not Pool6");
197 198
        }
        // Get the next prefix
Tomek Mrugalski's avatar
Tomek Mrugalski committed
199
        next = increasePrefix(last, pool6->getLength());
200
    }
201
    if ((*it)->inRange(next)) {
202
        // the next one is in the pool as well, so we haven't hit pool boundary yet
Tomek Mrugalski's avatar
Tomek Mrugalski committed
203
        subnet->setLastAllocated(pool_type_, next);
204 205 206 207 208 209 210 211 212
        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
213
        subnet->setLastAllocated(pool_type_, next);
214 215 216
        return (next);
    }

217
    // there is a next pool, let's try first address from it
218
    next = (*it)->getFirstAddress();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
219
    subnet->setLastAllocated(pool_type_, next);
220 221 222
    return (next);
}

223
AllocEngine::HashedAllocator::HashedAllocator(Lease::Type lease_type)
224
    :Allocator(lease_type) {
225 226 227 228 229
    isc_throw(NotImplemented, "Hashed allocator is not implemented");
}


isc::asiolink::IOAddress
230 231 232
AllocEngine::HashedAllocator::pickAddress(const SubnetPtr&,
                                          const DuidPtr&,
                                          const IOAddress&) {
233 234 235
    isc_throw(NotImplemented, "Hashed allocator is not implemented");
}

236
AllocEngine::RandomAllocator::RandomAllocator(Lease::Type lease_type)
237
    :Allocator(lease_type) {
238 239 240 241 242
    isc_throw(NotImplemented, "Random allocator is not implemented");
}


isc::asiolink::IOAddress
243 244 245
AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&,
                                          const DuidPtr&,
                                          const IOAddress&) {
246 247 248
    isc_throw(NotImplemented, "Random allocator is not implemented");
}

249

250
AllocEngine::AllocEngine(AllocType engine_type, uint64_t attempts,
251
                         bool ipv6)
252 253
    : attempts_(attempts), incomplete_v4_reclamations_(0),
      incomplete_v6_reclamations_(0) {
254

255 256
    // Choose the basic (normal address) lease type
    Lease::Type basic_type = ipv6 ? Lease::TYPE_NA : Lease::TYPE_V4;
257

Francis Dupont's avatar
Francis Dupont committed
258
    // Initialize normal address allocators
259 260
    switch (engine_type) {
    case ALLOC_ITERATIVE:
261
        allocators_[basic_type] = AllocatorPtr(new IterativeAllocator(basic_type));
262 263
        break;
    case ALLOC_HASHED:
264
        allocators_[basic_type] = AllocatorPtr(new HashedAllocator(basic_type));
265 266
        break;
    case ALLOC_RANDOM:
267
        allocators_[basic_type] = AllocatorPtr(new RandomAllocator(basic_type));
268 269 270 271
        break;
    default:
        isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
    }
272

Francis Dupont's avatar
Francis Dupont committed
273
    // If this is IPv6 allocation engine, initialize also temporary addrs
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
    // and prefixes
    if (ipv6) {
        switch (engine_type) {
        case ALLOC_ITERATIVE:
            allocators_[Lease::TYPE_TA] = AllocatorPtr(new IterativeAllocator(Lease::TYPE_TA));
            allocators_[Lease::TYPE_PD] = AllocatorPtr(new IterativeAllocator(Lease::TYPE_PD));
            break;
        case ALLOC_HASHED:
            allocators_[Lease::TYPE_TA] = AllocatorPtr(new HashedAllocator(Lease::TYPE_TA));
            allocators_[Lease::TYPE_PD] = AllocatorPtr(new HashedAllocator(Lease::TYPE_PD));
            break;
        case ALLOC_RANDOM:
            allocators_[Lease::TYPE_TA] = AllocatorPtr(new RandomAllocator(Lease::TYPE_TA));
            allocators_[Lease::TYPE_PD] = AllocatorPtr(new RandomAllocator(Lease::TYPE_PD));
            break;
        default:
            isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
        }
    }

294
    // Register hook points
295
    hook_index_lease4_select_ = Hooks.hook_index_lease4_select_;
296
    hook_index_lease6_select_ = Hooks.hook_index_lease6_select_;
297 298
}

299 300 301 302 303 304 305 306 307 308
AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) {
    std::map<Lease::Type, AllocatorPtr>::const_iterator alloc = allocators_.find(type);

    if (alloc == allocators_.end()) {
        isc_throw(BadValue, "No allocator initialized for pool type "
                  << Lease::typeToText(type));
    }
    return (alloc->second);
}

309 310 311
template<typename ContextType>
void
AllocEngine::findReservationInternal(ContextType& ctx,
312 313
                                     const AllocEngine::HostGetFunc& host_get,
                                     const bool ipv6_only) {
314 315 316
    ctx.hosts_.clear();

    auto subnet = ctx.subnet_;
317 318

    // We can only search for the reservation if a subnet has been selected.
319 320
    while (subnet) {

321 322 323 324 325 326 327 328 329 330 331 332 333 334
        // Only makes sense to get reservations if the client has access
        // to the class.
        if (subnet->clientSupported(ctx.query_->getClasses())) {
            // Iterate over configured identifiers in the order of preference
            // and try to use each of them to search for the reservations.
            BOOST_FOREACH(const IdentifierPair& id_pair, ctx.host_identifiers_) {
                // Attempt to find a host using a specified identifier.
                ConstHostPtr host = host_get(subnet->getID(), id_pair.first,
                                             &id_pair.second[0], id_pair.second.size());
                // If we found matching host for this subnet.
                if (host && (!ipv6_only || host->hasIPv6Reservation())) {
                    ctx.hosts_[subnet->getID()] = host;
                    break;
                }
335
            }
336 337 338 339 340 341 342

        } else {
            LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                      ALLOC_ENGINE_RESERVATIONS_SKIPPED)
                .arg(ctx.query_->getLabel())
                .arg(subnet->toText());

343
        }
344

Tomek Mrugalski's avatar
Tomek Mrugalski committed
345 346 347
        // We need to get to the next subnet if this is a shared network. If it
        // is not (a plain subnet), getNextSubnet will return NULL and we're
        // done here.
348
        subnet = subnet->getNextSubnet(ctx.subnet_, ctx.query_->getClasses());
349
    }
350 351
}

352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
} // end of namespace isc::dhcp
} // end of namespace isc

namespace {

/// @brief Checks if the specified address belongs to one of the subnets
/// within a shared network.
///
/// @param ctx Client context. Current subnet may be modified by this
/// function when it belongs to a shared network.
/// @param lease_type Type of the lease.
/// @param address IPv6 address or prefix to be checked.
///
/// @return true if address belongs to a pool in a selected subnet or in
/// a pool within any of the subnets belonging to the current shared network.
bool
inAllowedPool(AllocEngine::ClientContext6& ctx, const Lease::Type& lease_type,
              const IOAddress& address) {
    // If the subnet belongs to a shared network we will be iterating
    // over the subnets that belong to this shared network.
    Subnet6Ptr current_subnet = ctx.subnet_;
    while (current_subnet) {

        if (current_subnet->clientSupported(ctx.query_->getClasses())) {
            if (current_subnet->inPool(lease_type, address)) {
                return (true);
            }
        }

        current_subnet = current_subnet->getNextSubnet(ctx.subnet_);
    }

    return (false);
}

}

389

390 391 392 393
// ##########################################################################
// #    DHCPv6 lease allocation code starts here.
// ##########################################################################

394 395 396
namespace isc {
namespace dhcp {

397
AllocEngine::ClientContext6::ClientContext6()
398
    : query_(), fake_allocation_(false), subnet_(), host_subnet_(), duid_(),
399
      hwaddr_(), host_identifiers_(), hosts_(), fwd_dns_update_(false),
400 401
      rev_dns_update_(false), hostname_(), callout_handle_(),
      ias_() {
402 403
}

404 405 406
AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet,
                                            const DuidPtr& duid,
                                            const bool fwd_dns,
407 408
                                            const bool rev_dns,
                                            const std::string& hostname,
409 410 411 412
                                            const bool fake_allocation,
                                            const Pkt6Ptr& query,
                                            const CalloutHandlePtr& callout_handle)
    : query_(query), fake_allocation_(fake_allocation), subnet_(subnet),
413
      duid_(duid), hwaddr_(), host_identifiers_(), hosts_(),
414
      fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns),
415
      hostname_(hostname), callout_handle_(callout_handle),
416
      allocated_resources_(), ias_() {
417 418 419

    // Initialize host identifiers.
    if (duid) {
420
        addHostIdentifier(Host::IDENT_DUID, duid->getDuid());
421
    }
422 423
}

424
AllocEngine::ClientContext6::IAContext::IAContext()
425 426
    : iaid_(0), type_(Lease::TYPE_NA), hints_(), old_leases_(),
      changed_leases_(), ia_rsp_() {
427 428 429 430 431 432 433 434
}

void
AllocEngine::ClientContext6::
IAContext::addHint(const asiolink::IOAddress& prefix,
                   const uint8_t prefix_len) {
    hints_.push_back(std::make_pair(prefix, prefix_len));
}
435

436 437
void
AllocEngine::ClientContext6::
438 439 440 441 442 443 444 445 446 447 448
addAllocatedResource(const asiolink::IOAddress& prefix,
                     const uint8_t prefix_len) {
    static_cast<void>(allocated_resources_.insert(std::make_pair(prefix,
                                                                 prefix_len)));
}

bool
AllocEngine::ClientContext6::
isAllocated(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const {
    return (static_cast<bool>
            (allocated_resources_.count(std::make_pair(prefix, prefix_len))));
449 450
}

451 452
ConstHostPtr
AllocEngine::ClientContext6::currentHost() const {
453 454 455
    Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_;
    if (subnet) {
        auto host = hosts_.find(subnet->getID());
456 457 458 459 460 461
        if (host != hosts_.cend()) {
            return (host->second);
        }
    }
    return (ConstHostPtr());
}
462

463
void AllocEngine::findReservation(ClientContext6& ctx) {
464 465 466
    findReservationInternal(ctx, boost::bind(&HostMgr::get6,
                                             &HostMgr::instance(),
                                             _1, _2, _3, _4));
467
}
468

469
Lease6Collection
470
AllocEngine::allocateLeases6(ClientContext6& ctx) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
471

472
    try {
473
        if (!ctx.subnet_) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
474 475
            isc_throw(InvalidOperation, "Subnet is required for IPv6 lease allocation");
        } else
476
        if (!ctx.duid_) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
477
            isc_throw(InvalidOperation, "DUID is mandatory for IPv6 lease allocation");
478
        }
479

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
        // Check if there are existing leases for that shared network and
        // DUID/IAID.
        Subnet6Ptr subnet = ctx.subnet_;
        Lease6Collection leases;
        while (subnet) {
            Lease6Collection leases_subnet =
                LeaseMgrFactory::instance().getLeases6(ctx.currentIA().type_,
                                                       *ctx.duid_,
                                                       ctx.currentIA().iaid_,
                                                       subnet->getID());
            leases.insert(leases.end(), leases_subnet.begin(), leases_subnet.end());

            subnet = subnet->getNextSubnet(ctx.subnet_);
        }

495 496 497 498 499 500 501 502 503 504 505 506 507
        // Now do the checks:
        // Case 1. if there are no leases, and there are reservations...
        //   1.1. are the reserved addresses are used by someone else?
        //       yes: we have a problem
        //       no: assign them => done
        // Case 2. if there are leases and there are no reservations...
        //   2.1 are the leases reserved for someone else?
        //       yes: release them, assign something else
        //       no: renew them => done
        // Case 3. if there are leases and there are reservations...
        //   3.1 are the leases matching reservations?
        //       yes: renew them => done
        //       no: release existing leases, assign new ones based on reservations
Tomek Mrugalski's avatar
Tomek Mrugalski committed
508
        // Case 4/catch-all. if there are no leases and no reservations...
509
        //       assign new leases
510

511
        // Case 1: There are no leases and there's a reservation for this host.
512
        if (leases.empty() && !ctx.hosts_.empty()) {
513

514 515 516 517
            LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                      ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR)
                .arg(ctx.query_->getLabel());

518 519 520 521 522 523 524 525 526 527 528 529
            // Try to allocate leases that match reservations. Typically this will
            // succeed, except cases where the reserved addresses are used by
            // someone else.
            allocateReservedLeases6(ctx, leases);

            // If not, we'll need to continue and will eventually fall into case 4:
            // getting a regular lease. That could happen when we're processing
            // request from client X, there's a reserved address A for X, but
            // A is currently used by client Y. We can't immediately reassign A
            // from X to Y, because Y keeps using it, so X would send Decline right
            // away. Need to wait till Y renews, then we can release A, so it
            // will become available for X.
530

531 532 533 534 535
        // Case 2: There are existing leases and there are no reservations.
        //
        // There is at least one lease for this client and there are no reservations.
        // We will return these leases for the client, but we may need to update
        // FQDN information.
536
        } else if (!leases.empty() && ctx.hosts_.empty()) {
537

538 539 540 541
            LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                      ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR)
                .arg(ctx.query_->getLabel());

542 543 544 545
            // Check if the existing leases are reserved for someone else.
            // If they're not, we're ok to keep using them.
            removeNonmatchingReservedLeases6(ctx, leases);

546
            leases = updateLeaseData(ctx, leases);
547

Tomek Mrugalski's avatar
Tomek Mrugalski committed
548
            // If leases are empty at this stage, it means that we used to have
549 550 551 552
            // leases for this client, but we checked and those leases are reserved
            // for someone else, so we lost them. We will need to continue and
            // will finally end up in case 4 (no leases, no reservations), so we'll
            // assign something new.
553

554
        // Case 3: There are leases and there are reservations.
555
        } else if (!leases.empty() && !ctx.hosts_.empty()) {
556

557 558 559 560
            LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                      ALLOC_ENGINE_V6_ALLOC_LEASES_HR)
                .arg(ctx.query_->getLabel());

561 562 563 564 565 566 567 568
            // First, check if have leases matching reservations, and add new
            // leases if we don't have them.
            allocateReservedLeases6(ctx, leases);

            // leases now contain both existing and new leases that were created
            // from reservations.

            // Second, let's remove leases that are reserved for someone else.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
569 570 571 572 573 574
            // This applies to any existing leases. This will not happen frequently,
            // but it may happen with the following chain of events:
            // 1. client A gets address X;
            // 2. reservation for client B for address X is made by a administrator;
            // 3. client A reboots
            // 4. client A requests the address (X) he got previously
575 576 577 578 579 580 581 582 583 584 585
            removeNonmatchingReservedLeases6(ctx, leases);

            // leases now contain existing and new leases, but we removed those
            // leases that are reserved for someone else (non-matching reserved).

            // There's one more check to do. Let's remove leases that are not
            // matching reservations, i.e. if client X has address A, but there's
            // a reservation for address B, we should release A and reassign B.
            // Caveat: do this only if we have at least one reserved address.
            removeNonreservedLeases6(ctx, leases);

Tomek Mrugalski's avatar
Tomek Mrugalski committed
586
            // All checks are done. Let's hope we have some leases left.
587 588 589 590 591 592 593 594 595 596

            // If we don't have any leases at this stage, it means that we hit
            // one of the following cases:
            // - we have a reservation, but it's not for this IAID/ia-type and
            //   we had to return the address we were using
            // - we have a reservation for this iaid/ia-type, but the reserved
            //   address is currently used by someone else. We can't assign it
            //   yet.
            // - we had an address, but we just discovered that it's reserved for
            //   someone else, so we released it.
597
        }
598

599 600 601 602 603 604 605 606 607 608 609
        if (leases.empty()) {
            // Case 4/catch-all: One of the following is true:
            // - we don't have leases and there are no reservations
            // - we used to have leases, but we lost them, because they are now
            //   reserved for someone else
            // - we have a reservation, but it is not usable yet, because the address
            //   is still used by someone else
            //
            // In any case, we need to go through normal lease assignment process
            // for now. This is also a catch-all or last resort approach, when we
            // couldn't find any reservations (or couldn't use them).
610

611 612 613
            LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                      ALLOC_ENGINE_V6_ALLOC_UNRESERVED)
                .arg(ctx.query_->getLabel());
614

615 616
            leases = allocateUnreservedLeases6(ctx);
        }
617

Tomek Mrugalski's avatar
Tomek Mrugalski committed
618
        if (!leases.empty()) {
619 620 621 622
            // If there are any leases allocated, let's store in them in the
            // IA context so as they are available when we process subsequent
            // IAs.
            BOOST_FOREACH(Lease6Ptr lease, leases) {
623
                ctx.addAllocatedResource(lease->addr_, lease->prefixlen_);
624
            }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
625 626
            return (leases);
        }
627

628 629 630 631

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

        // Some other error, return an empty lease.
632 633 634
        LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_ERROR)
            .arg(ctx.query_->getLabel())
            .arg(e.what());
635
    }
636

637
    return (Lease6Collection());
638 639
}

640 641 642
Lease6Collection
AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {

643
    AllocatorPtr allocator = getAllocator(ctx.currentIA().type_);
644 645 646

    if (!allocator) {
        isc_throw(InvalidOperation, "No allocator specified for "
647
                  << Lease6::typeToText(ctx.currentIA().type_));
648
    }
649

650
    // Check which host reservation mode is supported in this subnet.
651
    Network::HRMode hr_mode = ctx.subnet_->getHostReservationMode();
652

653 654
    Lease6Collection leases;

655
    IOAddress hint = IOAddress::IPV6_ZERO_ADDRESS();
656
    if (!ctx.currentIA().hints_.empty()) {
657
        /// @todo: We support only one hint for now
658
        hint = ctx.currentIA().hints_[0].first;
659 660
    }

661 662 663 664
    Subnet6Ptr original_subnet = ctx.subnet_;
    Subnet6Ptr subnet = ctx.subnet_;
    SharedNetwork6Ptr network;
    subnet->getSharedNetwork(network);
665

666
    Pool6Ptr pool;
667

668
    while (subnet) {
669

670 671 672 673
        if (!subnet->clientSupported(ctx.query_->getClasses())) {
            subnet = subnet->getNextSubnet(original_subnet);
            continue;
        }
674

675
        ctx.subnet_ = subnet;
676

677 678 679 680
        // check if the hint is in pool and is available
        // This is equivalent of subnet->inPool(hint), but returns the pool
        pool = boost::dynamic_pointer_cast<Pool6>
            (subnet->getPool(ctx.currentIA().type_, hint, false));
681

682
        if (pool) {
683

684 685 686 687
            /// @todo: We support only one hint for now
            Lease6Ptr lease =
                LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, hint);
            if (!lease) {
688

689 690 691
                // In-pool reservations: Check if this address is reserved for someone
                // else. There is no need to check for whom it is reserved, because if
                // it has been reserved for us we would have already allocated a lease.
692

693
                ConstHostPtr host;
694
                if (hr_mode != Network::HR_DISABLED) {
695
                    host = HostMgr::instance().get6(subnet->getID(), hint);
696 697 698
                }

                if (!host) {
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
                    // If the in-pool reservations are disabled, or there is no
                    // reservation for a given hint, we're good to go.

                    // The hint is valid and not currently used, let's create a
                    // lease for it
                    lease = createLease6(ctx, hint, pool->getLength());

                    // It can happen that the lease allocation failed (we could
                    // have lost the race condition. That means that the hint is
                    // no longer usable and we need to continue the regular
                    // allocation path.
                    if (lease) {

                        /// @todo: We support only one lease per ia for now
                        Lease6Collection collection;
                        collection.push_back(lease);
                        return (collection);
                    }
717 718
                } else {
                    LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
719
                              ALLOC_ENGINE_V6_HINT_RESERVED)
720 721
                        .arg(ctx.query_->getLabel())
                        .arg(hint.toText());
722
                }
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755

            } else {

                // If the lease is expired, we may likely reuse it, but...
                if (lease->expired()) {

                    ConstHostPtr host;
                    if (hr_mode != Network::HR_DISABLED) {
                        host = HostMgr::instance().get6(subnet->getID(), hint);
                    }

                    // Let's check if there is a reservation for this address.
                    if (!host) {

                        // Copy an existing, expired lease so as it can be returned
                        // to the caller.
                        Lease6Ptr old_lease(new Lease6(*lease));
                        ctx.currentIA().old_leases_.push_back(old_lease);

                        /// We found a lease and it is expired, so we can reuse it
                        lease = reuseExpiredLease(lease, ctx, pool->getLength());

                        /// @todo: We support only one lease per ia for now
                        leases.push_back(lease);
                        return (leases);

                    } else {
                        LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                                  ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED)
                            .arg(ctx.query_->getLabel())
                            .arg(hint.toText());
                    }
                }
756
            }
757
        }
758 759

        subnet = subnet->getNextSubnet(original_subnet);
760
    }
761

762 763 764 765 766 767 768
    uint64_t total_attempts = 0;
    subnet = original_subnet;

    while (subnet) {

        if (!subnet->clientSupported(ctx.query_->getClasses())) {
            subnet = subnet->getNextSubnet(original_subnet);
769 770
            continue;
        }
771

772 773 774 775 776 777 778 779 780 781
        // The hint was useless (it was not provided at all, was used by someone else,
        // was out of pool or reserved for someone else). 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
        uint64_t max_attempts = (attempts_ > 0 ? attempts_  :
                                 subnet->getPoolCapacity(ctx.currentIA().type_));

        for (uint64_t i = 0; i < max_attempts; ++i) {
782 783 784

            ++total_attempts;

785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
            IOAddress candidate = allocator->pickAddress(subnet, ctx.duid_,
                                                         hint);

            /// In-pool reservations: Check if this address is reserved for someone
            /// else. There is no need to check for whom it is reserved, because if
            /// it has been reserved for us we would have already allocated a lease.
            if (hr_mode == Network::HR_ALL &&
                HostMgr::instance().get6(subnet->getID(), candidate)) {

                // Don't allocate.
                continue;
            }

            // The first step is to find out prefix length. It is 128 for
            // non-PD leases.
            uint8_t prefix_len = 128;
            if (ctx.currentIA().type_ == Lease::TYPE_PD) {
                pool = boost::dynamic_pointer_cast<Pool6>(
                    subnet->getPool(ctx.currentIA().type_, candidate, false));
                if (pool) {
                    prefix_len = pool->getLength();
                }
807
            }
808

809
            Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
810
                                                                   candidate);
811
            if (!existing) {
812

813 814
                // there's no existing lease for selected candidate, so it is
                // free. Let's allocate it.
815

816
                ctx.subnet_ = subnet;
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
                Lease6Ptr lease = createLease6(ctx, candidate, prefix_len);
                if (lease) {
                    // We are allocating a new lease (not renewing). So, the
                    // old lease should be NULL.
                    ctx.currentIA().old_leases_.clear();

                    leases.push_back(lease);
                    return (leases);
                } else if (ctx.callout_handle_ &&
                           (ctx.callout_handle_->getStatus() !=
                            CalloutHandle::NEXT_STEP_CONTINUE)) {
                    // Don't retry when the callout status is not continue.
                    break;
                }

                // 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()) {
                    // Copy an existing, expired lease so as it can be returned
                    // to the caller.
                    Lease6Ptr old_lease(new Lease6(*existing));
                    ctx.currentIA().old_leases_.push_back(old_lease);

842
                    ctx.subnet_ = subnet;
843 844 845 846 847
                    existing = reuseExpiredLease(existing, ctx, prefix_len);

                    leases.push_back(existing);
                    return (leases);
                }
848
            }
849
        }
850 851

        subnet = subnet->getNextSubnet(original_subnet);
852
    }
853

854 855 856
    // Unable to allocate an address, return an empty lease.
    LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL)
        .arg(ctx.query_->getLabel())
857
        .arg(total_attempts);
858

859 860 861
    // We failed to allocate anything. Let's return empty collection.
    return (Lease6Collection());
}
862

863
void
864 865
AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
                                     Lease6Collection& existing_leases) {
866

867

868
    // If there are no reservations or the reservation is v4, there's nothing to do.
869
    if (ctx.hosts_.empty()) {
870 871 872
        LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                  ALLOC_ENGINE_V6_ALLOC_NO_V6_HR)
            .arg(ctx.query_->getLabel());
873
        return;
874
    }
875

876
    // Let's convert this from Lease::Type to IPv6Reserv::Type
877 878
    IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ?
        IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD;
879

880 881 882 883 884
    // We want to avoid allocating new lease for an IA if there is already
    // a valid lease for which client has reservation. So, we first check if
    // we already have a lease for a reserved address or prefix.
    BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
        if ((lease->valid_lft_ != 0)) {
885 886 887
            if ((ctx.hosts_.count(lease->subnet_id_) > 0) &&
                ctx.hosts_[lease->subnet_id_]->hasReservation(IPv6Resrv(type, lease->addr_,
                                                                        lease->prefixlen_))) {
888 889
                // We found existing lease for a reserved address or prefix.
                // We'll simply extend the lifetime of the lease.
890 891 892
                LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
                          ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS)
                    .arg(ctx.query_->getLabel())
893 894
                    .arg(lease->typeToText(lease->type_))
                    .arg(lease->addr_.toText());
895

896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926
                // Besides IP reservations we're also going to return other reserved
                // parameters, such as hostname. We want to hand out the hostname value
                // from the same reservation entry as IP addresses. Thus, let's see if
                // there is any hostname reservation.
                if (!ctx.host_subnet_) {
                    SharedNetwork6Ptr network;
                    ctx.subnet_->getSharedNetwork(network);
                    if (network) {
                        // Remember the subnet that holds this preferred host
                        // reservation. The server will use it to return appropriate
                        // FQDN, classes etc.
                        ctx.host_subnet_ = network->getSubnet(lease->subnet_id_);
                        ConstHostPtr host = ctx.hosts_[lease->subnet_id_];
                        // If there is a hostname reservation here we should stick
                        // to this reservation. By updating the hostname in the
                        // context we make sure that the database is updated with
                        // this new value and the server doesn't need to do it and
                        // its processing performance is not impacted by the hostname
                        // updates.
                        if (host && !host->getHostname().empty()) {
                            // We have to determine whether the hostname is generated
                            // in response to client's FQDN or not. If yes, we will
                            // need to qualify the hostname. Otherwise, we just use
                            // the hostname as it is specified for the reservation.
                            OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
                            ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
                                qualifyName(host->getHostname(), static_cast<bool>(fqdn));
                        }
                    }
                }

927 928
                // If this is a real allocation, we may need to extend the lease
                // lifetime.
929 930
                if (!ctx.fake_allocation_ && conditionalExtendLifetime(*lease)) {
                    LeaseMgrFactory::instance().updateLease6(lease);
931
                }
932 933 934
                return;
            }
        }
935 936 937 938 939
    }

    // There is no lease for a reservation in this IA. So, let's now iterate
    // over reservations specified and try to allocate one of them for the IA.

940 941 942
    Subnet6Ptr subnet = ctx.subnet_;

    while (subnet) {
943

944 945 946 947 948 949
        SubnetID subnet_id = subnet->getID();

        // No hosts for this subnet or the subnet not supported.
        if (!subnet->clientSupported(ctx.query_->getClasses()) ||
            ctx.hosts_.count(subnet_id) == 0) {
            subnet = subnet->getNextSubnet(ctx.subnet_);
950 951
            continue;
        }
952

953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
        ConstHostPtr host = ctx.hosts_[subnet_id];

        // Get the IPv6 reservations of specified type.
        const IPv6ResrvRange& reservs = host->getIPv6Reservations(type);
        BOOST_FOREACH(IPv6ResrvTuple type_lease_tuple, reservs) {
            // We do have a reservation for address or prefix.
            const IOAddress& addr = type_lease_tuple.second.getPrefix();
            uint8_t prefix_len = type_lease_tuple.second.getPrefixLen();

            // We have allocated this address/prefix while processing one of the
            // previous IAs, so let's try another reservation.
            if (ctx.isAllocated(addr, prefix_len)) {
                continue;
            }

            // If there's a lease for this address, let's not create it.
            // It doesn't matter whether it is for this client or for someone else.
            if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
971
                                                   addr)) {
972

973 974 975
                // Let's remember the subnet from which the reserved address has been
                // allocated. We'll use this subnet for allocating other reserved
                // resources.
976 977
                ctx.subnet_ = subnet;

978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997
                if (!ctx.host_subnet_) {
                    ctx.host_subnet_ = subnet;
                    if (!host->getHostname().empty()) {
                        // If there is a hostname reservation here we should stick
                        // to this reservation. By updating the hostname in the
                        // context we make sure that the database is updated with
                        // this new value and the server doesn't need to do it and
                        // its processing performance is not impacted by the hostname
                        // updates.

                        // We have to determine whether the hostname is generated
                        // in response to client's FQDN or not. If yes, we will
                        // need to qualify the hostname. Otherwise, we just use
                        // the hostname as it is specified for the reservation.
                        OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
                        ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
                            qualifyName(host->getHostname(), static_cast<bool>(fqdn));
                    }
                }

998
                // Ok, let's create a new lease...
999 1000 1001 1002 1003
                Lease6Ptr lease = createLease6(ctx, addr, prefix_len);

                // ... and add it to the existing leases list.
                existing_leases.push_back(lease);

1004

1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025
                if (ctx.currentIA().type_ == Lease::TYPE_NA) {
                    LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_ADDR_GRANTED)
                        .arg(addr.toText())
                        .arg(ctx.query_->getLabel());
                } else {
                    LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_PREFIX_GRANTED)
                        .arg(addr.toText())
                        .arg(static_cast<int>(prefix_len))
                        .arg(ctx.query_->getLabel());
                }

                // We found a lease for this client and this IA. Let's return.
                // Returning after the first lease was assigned is useful if we
                // have multiple reservations for the same client. If the client
                // sends 2 IAs, the first time we call allocateReservedLeases6 will
                // use the first reservation and return. The second time, we'll
                // go over the first reservation, but will discover that there's
                // a lease corresponding to it and will skip it and then pick
                // the second reservation and turn it into the lease. This approach
                // would work for any number of reservations.
                return;
1026
            }
1027

1028
        }
1029 1030

        subnet = subnet->getNextSubnet(ctx.subnet_);
1031
    }
1032 1033 1034 1035 1036
}

void
AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
                                              Lease6Collection& existing_leases) {
1037 1038 1039 1040
    // If there are no leases (so nothing to remove) or
    // host reservation is disabled (so there are no reserved leases),
    // just return.
    if (existing_leases.empty() || !ctx.subnet_ ||
1041
        (ctx.subnet_->getHostReservationMode() == Network::HR_DISABLED) ) {
1042 1043 1044 1045 1046 1047 1048 1049
        return;
    }

    // We need a copy, so we won't be iterating over a container and
    // removing from it at the same time. It's only a copy of pointers,
    // so the operation shouldn't be that expensive.
    Lease6Collection copy = existing_leases;

1050 1051 1052
    BOOST_FOREACH(const Lease6Ptr& candidate, copy) {
        // If we have reservation we should check if the reservation is for
        // the candidate lease. If so, we simply accept the lease.
1053
        if (ctx.hosts_.count(candidate->subnet_id_) > 0) {
1054
            if (candidate->type_ == Lease6::TYPE_NA) {
1055 1056
                if (ctx.hosts_[candidate->subnet_id_]->hasReservation(
                        IPv6Resrv(IPv6Resrv::TYPE_NA, candidate->addr_))) {
1057 1058 1059
                    continue;
                }
            } else {