Commit 869e658f authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[master] Merge branch 'trac2324' (DHCPv6 allocation engine)

Conflicts:
	ChangeLog
	src/lib/dhcp/subnet.cc
	src/lib/dhcp/subnet.h
	src/lib/dhcp/tests/subnet_unittest.cc
parents 4e98f9c8 7e2dedc0
496. [func] tomek
DHCPv6 Allocation Engine implemented. It allows address allocation
from the configured subnets/pools. It currently features a single
allocator: IterativeAllocator, which assigns addresses iteratively.
Other allocators (hashed, random) are planned.
(Trac #2324, git 8aa188a10298e3a55b725db36502a99d2a8d638a)
495. [func] team
b10-auth now handles reconfiguration of data sources in
background using a separate thread. This means even if the new
......
......@@ -57,76 +57,4 @@
* that does not support msgq. That is useful for embedded environments.
* It may also be useful in validation.
*
* @page libdhcp libdhcp++
*
* @section libdhcpIntro Libdhcp++ Library Introduction
*
* libdhcp++ is an all-purpose DHCP-manipulation library, written in
* C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6
* options parsing and ssembly, interface detection (currently on
* Linux systems only) and socket operations. Following classes are
* implemented:
*
* - isc::dhcp::Pkt4 - represents DHCPv4 packet.
* - isc::dhcp::Pkt6 - represents DHCPv6 packet.
*
* There are two pointer types defined: Pkt4Ptr and Pkt6Ptr. They are
* smart pointer and are using boost::shared_ptr. There are not const
* versions defined, as we assume that hooks can modify any aspect of
* the packet at almost any stage of processing.
*
* Both packets use collection of Option objects to represent DHCPv4
* and DHCPv6 options. The base class -- Option -- can be used to
* represent generic option that contains collection of
* bytes. Depending on if the option is instantiated as v4 or v6
* option, it will adjust its header (DHCPv4 options use 1 octet for
* type and 1 octet for length, while DHCPv6 options use 2 bytes for
* each).
*
* There are many specialized classes that are intended to handle options with
* specific content:
* - isc::dhcp::Option4AddrLst -- DHCPv4 option, contains one or more IPv4 addresses;
* - isc::dhcp::Option6AddrLst -- DHCPv6 option, contains one or more IPv6 addresses;
* - isc::dhcp::Option6IAAddr -- DHCPv6 option, represents IAADDR_OPTION (an option that
* contains IPv6 address with extra parameters);
* - isc::dhcp::Option6IA -- DHCPv6 option used to store IA_NA and its suboptions.
*
* All options can store sub-options (i.e. options that are stored within option
* rather than in a message directly). This functionality is commonly used in
* DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
* isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
* for that purpose.
*
* @section libdhcpIfaceMgr Interface Manager
*
* Interface Manager (or IfaceMgr) is an abstraction layer about low-level
* network operations. In particlar, it provides information about existing
* network interfaces See isc::dhcp::IfaceMgr::Iface class and
* isc::dhcp::IfaceMgr::detectIfaces() and isc::dhcp::IfaceMgr::getIface().
*
* Currently there is interface detection is implemented in Linux only. There
* are plans to implement such support for other OSes, but they remain low
* priority for now.
*
* Generic parts of the code are isc::dhcp::IfaceMgr class in
* src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate
* files, e.g. iface_mgr_linux.cc. Such separation should be maintained when
* additional code will be developed.
*
* For systems that interface detection is not supported on, there is a stub
* mechanism implemented. It assumes that interface name is read from a text
* file. This is a temporary solution and will be removed as soon as proper
* interface detection is implemented. It is not going to be developed further.
* To use this feature, store interfaces.txt file. It uses a simple syntax.
* Each line represents an interface name, followed by IPv4 or IPv6 address
* that follows it. This is usually link-local IPv6 address that the server
* should bind to. In theory this mechanism also supports IPv4, but it was
* never tested. The code currently supports only a single interface defined
* that way.
*
* Another useful methods are dedicated to transmission
* (isc::dhcp::IfaceMgr::send(), 2 overloads) and reception
* (isc::dhcp::IfaceMgr::receive4() and isc::dhcp::IfaceMgr::receive6()).
* Note that receive4() and receive6() methods may return NULL, e.g.
* when timeout is reached or if dhcp daemon receives a signal.
*/
\ No newline at end of file
......@@ -29,6 +29,10 @@
* - @subpage libdhcp
* - @subpage libdhcpIntro
* - @subpage libdhcpIfaceMgr
* - @subpage libdhcpsrv
* - @subpage leasemgr
* - @subpage cfgmgr
* - @subpage allocengine
* - @subpage perfdhcpInternals
*
* @section misc Miscellaneous topics
......
......@@ -35,7 +35,7 @@
This method iterates over list of received configuration elements and creates a
list of parsers for each received entry. Parser is an object that is derived
from a \ref isc::dhcp::Dhcp6ConfigParser class. Once a parser is created
from a \ref isc::dhcp::DhcpConfigParser class. Once a parser is created
(constructor), its value is set (using build() method). Once all parsers are
build, the configuration is then applied ("commited") and commit() method is
called.
......@@ -51,7 +51,7 @@
@section dhcpv6-config-inherit DHCPv6 Configuration Inheritance
One notable useful features of DHCP configuration is its parameter inheritance.
One notable useful feature of DHCP configuration is its parameter inheritance.
For example, renew-timer value may be specified at a global scope and it then
applies to all subnets. However, some subnets may have it overwritten with more
specific values that takes precedence over global values that are considered
......@@ -64,7 +64,7 @@
phase (commit() method), appropriate parsers can use apply parameter inheritance.
Debugging configuration parser may be confusing. Therefore there is a special
class called \ref isc::dhcp::DummyParser. It does not configure anything, but just
class called \ref isc::dhcp::DebugParser. It does not configure anything, but just
accepts any parameter of any type. If requested to commit configuration, it will
print out received parameter name and its value. This class is not currently used,
but it is convenient to have it every time a new parameter is added to DHCP
......
......@@ -16,7 +16,6 @@ CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libb10-dhcp++.la libb10-dhcpsrv.la
libb10_dhcp___la_SOURCES =
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
libb10_dhcp___la_SOURCES += lease_mgr.cc lease_mgr.h
libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
......@@ -39,7 +38,9 @@ libb10_dhcpsrv_la_SOURCES = cfgmgr.cc cfgmgr.h
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
libb10_dhcpsrv_la_SOURCES += triplet.h
libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
......
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
//
// 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.
#include <alloc_engine.h>
using namespace isc::asiolink;
namespace isc {
namespace dhcp {
AllocEngine::IterativeAllocator::IterativeAllocator()
:Allocator() {
}
isc::asiolink::IOAddress
AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& addr) {
uint8_t packed[V6ADDRESS_LEN];
int len;
// First we copy the whole address as 16 bytes.
if (addr.getFamily()==AF_INET) {
// IPv4
memcpy(packed, addr.getAddress().to_v4().to_bytes().data(), 4);
len = 4;
} else {
// IPv6
memcpy(packed, addr.getAddress().to_v6().to_bytes().data(), 16);
len = 16;
}
for (int i = len - 1; i >= 0; --i) {
++packed[i];
if (packed[i] != 0) {
break;
}
}
return (IOAddress::from_bytes(addr.getFamily(), packed));
}
isc::asiolink::IOAddress
AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
const DuidPtr&,
const IOAddress&) {
// Let's get the last allocated address. It is usually set correctly,
// but there are times when it won't be (like after removing a pool or
// perhaps restaring the server).
IOAddress last = subnet->getLastAllocated();
const Pool6Collection& pools = subnet->getPools();
if (pools.size() == 0) {
isc_throw(AllocFailed, "No pools defined in selected subnet");
}
// first we need to find a pool the last address belongs to.
Pool6Collection::const_iterator it;
for (it = pools.begin(); it != pools.end(); ++it) {
if ((*it)->inRange(last)) {
break;
}
}
// last one was bogus for one of several reasons:
// - we just booted up and that's the first address we're allocating
// - a subnet was removed or other reconfiguration just completed
// - perhaps allocation algorithm was changed
if (it == pools.end()) {
// ok to access first element directly. We checked that pools is non-empty
IOAddress next = pools[0]->getFirstAddress();
subnet->setLastAllocated(next);
return (next);
}
// Ok, we have a pool that the last address belonged to, let's use it.
IOAddress next = increaseAddress(last); // basically addr++
if ((*it)->inRange(next)) {
// the next one is in the pool as well, so we haven't hit pool boundary yet
subnet->setLastAllocated(next);
return (next);
}
// We hit pool boundary, let's try to jump to the next pool and try again
++it;
if (it == pools.end()) {
// Really out of luck today. That was the last pool. Let's rewind
// to the beginning.
next = pools[0]->getFirstAddress();
subnet->setLastAllocated(next);
return (next);
}
// there is a next pool, let's try first adddress from it
next = (*it)->getFirstAddress();
subnet->setLastAllocated(next);
return (next);
}
AllocEngine::HashedAllocator::HashedAllocator()
:Allocator() {
isc_throw(NotImplemented, "Hashed allocator is not implemented");
}
isc::asiolink::IOAddress
AllocEngine::HashedAllocator::pickAddress(const Subnet6Ptr&,
const DuidPtr&,
const IOAddress&) {
isc_throw(NotImplemented, "Hashed allocator is not implemented");
}
AllocEngine::RandomAllocator::RandomAllocator()
:Allocator() {
isc_throw(NotImplemented, "Random allocator is not implemented");
}
isc::asiolink::IOAddress
AllocEngine::RandomAllocator::pickAddress(const Subnet6Ptr&,
const DuidPtr&,
const IOAddress&) {
isc_throw(NotImplemented, "Random allocator is not implemented");
}
AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts)
:attempts_(attempts) {
switch (engine_type) {
case ALLOC_ITERATIVE:
allocator_ = boost::shared_ptr<Allocator>(new IterativeAllocator());
break;
case ALLOC_HASHED:
allocator_ = boost::shared_ptr<Allocator>(new HashedAllocator());
break;
case ALLOC_RANDOM:
allocator_ = boost::shared_ptr<Allocator>(new RandomAllocator());
break;
default:
isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
}
}
Lease6Ptr
AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
const DuidPtr& duid,
uint32_t iaid,
const IOAddress& hint,
bool fake_allocation /* = false */ ) {
// That check is not necessary. We create allocator in AllocEngine
// constructor
if (!allocator_) {
isc_throw(InvalidOperation, "No allocator selected");
}
// check if there's existing lease for that subnet/duid/iaid combination.
Lease6Ptr existing = LeaseMgr::instance().getLease6(*duid, iaid, subnet->getID());
if (existing) {
// we have a lease already. This is a returning client, probably after
// his reboot.
return (existing);
}
// check if the hint is in pool and is available
if (subnet->inPool(hint)) {
existing = LeaseMgr::instance().getLease6(hint);
if (!existing) {
/// @todo: check if the hint is reserved once we have host support
/// implemented
// the hint is valid and not currently used, let's create a lease for it
Lease6Ptr lease = createLease(subnet, duid, iaid, hint, fake_allocation);
// 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);
}
}
}
unsigned int i = attempts_;
do {
IOAddress candidate = allocator_->pickAddress(subnet, duid, hint);
/// @todo: check if the address is reserved once we have host support
/// implemented
Lease6Ptr existing = LeaseMgr::instance().getLease6(candidate);
// there's no existing lease for selected candidate, so it is
// free. Let's allocate it.
if (!existing) {
Lease6Ptr lease = createLease(subnet, duid, iaid, candidate,
fake_allocation);
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.
}
// continue trying allocation until we run out of attempts
// (or attempts are set to 0, which means infinite)
--i;
} while ( i || !attempts_);
isc_throw(AllocFailed, "Failed to allocate address after " << attempts_
<< " tries");
}
Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
const DuidPtr& duid,
uint32_t iaid,
const IOAddress& addr,
bool fake_allocation /*= false */ ) {
Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
subnet->getPreferred(), subnet->getValid(),
subnet->getT1(), subnet->getT2(), subnet->getID()));
if (!fake_allocation) {
// That is a real (REQUEST) allocation
bool status = LeaseMgr::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 (Lease6Ptr());
}
} else {
// 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.
Lease6Ptr existing = LeaseMgr::instance().getLease6(addr);
if (!existing) {
return (lease);
} else {
return (Lease6Ptr());
}
}
}
AllocEngine::~AllocEngine() {
// no need to delete allocator. smart_ptr will do the trick for us
}
}; // end of isc::dhcp namespace
}; // end of isc namespace
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
//
// 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.
#ifndef ALLOC_ENGINE_H
#define ALLOC_ENGINE_H
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
#include <dhcp/duid.h>
#include <dhcp/subnet.h>
#include <asiolink/io_address.h>
#include <dhcp/lease_mgr.h>
namespace isc {
namespace dhcp {
/// An exception that is thrown when allocation module fails (e.g. due to
/// lack of available addresses)
class AllocFailed : public isc::Exception {
public:
/// @brief constructor
///
/// @param file name of the file, where exception occurred
/// @param line line of the file, where exception occurred
/// @param what text description of the issue that caused exception
AllocFailed(const char* file, size_t line, const char* what)
: isc::Exception(file, line, what) {}
};
/// @brief DHCPv4 and DHCPv6 allocation engine
///
/// This class represents DHCP allocation engine. It is responsible
/// for picking subnets, choosing and allocating a lease, extending,
/// renewing, releasing and possibly expiring leases.
///
/// @todo: Does not handle out of leases well
/// @todo: Does not handle out of allocation attempts well
class AllocEngine : public boost::noncopyable {
protected:
/// @brief base class for all address/prefix allocation algorithms
///
/// This is an abstract class that should not be used directly, but rather
/// specialized implementations should be used instead.
class Allocator {
public:
/// @brief picks one address out of available pools in a given subnet
///
/// This method returns one address from the available pools in the
/// specified subnet. It should not check if the address is used or
/// reserved - AllocEngine will check that and will call pickAddress
/// again if necessary. The number of times this method is called will
/// increase as the number of available leases will decrease.
virtual isc::asiolink::IOAddress
pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
const isc::asiolink::IOAddress& hint) = 0;
/// @brief virtual destructor
virtual ~Allocator() {
}
protected:
};
/// @brief Address/prefix allocator that iterates over all addresses
///
/// This class implements iterative algorithm that returns all addresses in
/// a pool iteratively, one after another. Once the last address is reached,
/// it starts allocating from the beginning of the first pool (i.e. it loops
/// over).
class IterativeAllocator : public Allocator {
public:
/// @brief default constructor
///
/// Does not do anything
IterativeAllocator();
/// @brief returns the next address from pools in a subnet
///
/// @param subnet next address will be returned from pool of that subnet
/// @param duid Client's DUID (ignored)
/// @param hint client's hint (ignored)
/// @return the next address
virtual isc::asiolink::IOAddress
pickAddress(const Subnet6Ptr& subnet,
const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
private:
/// @brief returns an address by one
/// @param addr address to be increased
/// @return address increased by one
isc::asiolink::IOAddress increaseAddress(const isc::asiolink::IOAddress& addr);
};
/// @brief Address/prefix allocator that gets an address based on a hash
///
/// @todo: This is a skeleton class for now and is missing implementation.
class HashedAllocator : public Allocator {
public:
/// @brief default constructor (does nothing)
HashedAllocator();
/// @brief returns an address based on hash calculated from client's DUID.
///
/// @todo: Implement this method
///
/// @param subnet an address will be picked from pool of that subnet
/// @param duid Client's DUID
/// @param hint a hint (last address that was picked)
/// @return selected address
virtual isc::asiolink::IOAddress pickAddress(const Subnet6Ptr& subnet,
const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
};
/// @brief Random allocator that picks address randomly
///
/// @todo: This is a skeleton class for now and is missing implementation.
class RandomAllocator : public Allocator {
public:
/// @brief default constructor (does nothing)
RandomAllocator();
/// @brief returns an random address from pool of specified subnet
///
/// @todo: Implement this method
///
/// @param subnet an address will be picked from pool of that subnet
/// @param duid Client's DUID (ignored)
/// @param hint the last address that was picked (ignored)
/// @return a random address from the pool
virtual isc::asiolink::IOAddress
pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
};
public:
/// @brief specifies allocation type
typedef enum {
ALLOC_ITERATIVE, // iterative - one address after another
ALLOC_HASHED, // hashed - client's DUID/client-id is hashed
ALLOC_RANDOM // random - an address is randomly selected
} AllocType;
/// @brief Default constructor.
///
/// Instantiates necessary services, required to run DHCPv6 server.
/// In particular, creates IfaceMgr that will be responsible for
/// network interaction. Will instantiate lease manager, and load
/// old or create new DUID.
///
/// @param engine_type selects allocation algorithm
/// @param attempts number of attempts for each lease allocation before
/// we give up (0 means unlimited)
AllocEngine(AllocType engine_type, unsigned int attempts);
/// @brief Allocates an IPv6 lease
///
/// This method uses currently selected allocator to pick an address from
/// specified subnet, creates a lease for that address and then inserts
/// it into LeaseMgr (if this allocation is not fake).
///
/// @param subnet subnet the allocation should come from
/// @param duid Client'd DUID
/// @param iaid iaid field from the IA_NA container that client sent
/// @param hint a hint that the client provided
/// @param fake_allocation is this real i.e. REQUEST (false) or just picking
/// an address for SOLICIT that is not really allocated (true)
/// @return Allocated IPv6 lease (or NULL if allocation failed)
Lease6Ptr
allocateAddress6(const Subnet6Ptr& subnet,
const DuidPtr& duid,
uint32_t iaid,
const isc::asiolink::IOAddress& hint,
bool fake_allocation);
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~AllocEngine();
private:
/// @brief creates a lease and inserts it in LeaseMgr if necessary
///
/// Creates a lease based on specified parameters and tries to insert it
/// into the database. That may fail in some cases, i.e. when there is another
/// allocation process and we lost a race to a specific lease.
///
/// @param subnet subnet the lease is allocated from