From: Francis Dupont Date: Thu, 18 Jan 2018 23:54:40 +0000 (+0100) Subject: [5425a] Rebased and known client removal: first phase done X-Git-Tag: trac5524_base~19^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5c5c3c5272c6de0a519385beebdbb3e2bd0a1a07;p=thirdparty%2Fkea.git [5425a] Rebased and known client removal: first phase done --- diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 28e35668f7..5347cfc584 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -144,19 +144,33 @@ AllocEngine::IterativeAllocator::increasePrefix(const isc::asiolink::IOAddress& return (IOAddress::fromBytes(AF_INET6, packed)); } +isc::asiolink::IOAddress +AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& address, + bool prefix, + const uint8_t prefix_len) { + if (!prefix) { + return (IOAddress::increase(address)); + } else { + return (increasePrefix(address, prefix_len)); + } +} isc::asiolink::IOAddress AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet, + const ClientClasses& client_classes, const DuidPtr&, const IOAddress&) { // Is this prefix allocation? bool prefix = pool_type_ == Lease::TYPE_PD; + uint8_t prefix_len = 0; // 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 restarting the server). IOAddress last = subnet->getLastAllocated(pool_type_); + bool valid = true; + bool retrying = false; const PoolCollection& pools = subnet->getPools(pool_type_); @@ -166,58 +180,108 @@ AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet, // first we need to find a pool the last address belongs to. PoolCollection::const_iterator it; + PoolCollection::const_iterator first = pools.end(); + PoolPtr first_pool; for (it = pools.begin(); it != pools.end(); ++it) { + if (!(*it)->clientSupported(client_classes)) { + continue; + } + if (first == pools.end()) { + first = it; + } if ((*it)->inRange(last)) { break; } } + // Caller checked this cannot happen + if (first == pools.end()) { + isc_throw(AllocFailed, "No allowed pools defined in selected subnet"); + } + // 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 + // - last pool does not allow this client if (it == pools.end()) { - // ok to access first element directly. We checked that pools is non-empty - IOAddress next = pools[0]->getFirstAddress(); - subnet->setLastAllocated(pool_type_, next); - return (next); + it = first; } - // Ok, we have a pool that the last address belonged to, let's use it. + for (;;) { + // Trying next pool + if (retrying) { + for (; it != pools.end(); ++it) { + if ((*it)->clientSupported(client_classes)) { + break; + } + } + if (it == pools.end()) { + // Really out of luck today. That was the last pool. + break; + } + } - IOAddress next("::"); - if (!prefix) { - next = IOAddress::increase(last); // basically addr++ - } else { - Pool6Ptr pool6 = boost::dynamic_pointer_cast(*it); - if (!pool6) { - // Something is gravely wrong here - isc_throw(Unexpected, "Wrong type of pool: " << (*it)->toText() - << " is not Pool6"); + last = (*it)->getLastAllocated(); + valid = (*it)->isLastAllocatedValid(); + if (!valid && (last == (*it)->getFirstAddress())) { + // Pool was (re)initialized + (*it)->setLastAllocated(last); + subnet->setLastAllocated(pool_type_, last); + return (last); } - // Get the next prefix - next = increasePrefix(last, pool6->getLength()); - } - if ((*it)->inRange(next)) { - // the next one is in the pool as well, so we haven't hit pool boundary yet - subnet->setLastAllocated(pool_type_, next); - return (next); + // still can be bogus + if (valid && !(*it)->inRange(last)) { + valid = false; + (*it)->resetLastAllocated(); + (*it)->setLastAllocated((*it)->getFirstAddress()); + } + + if (valid) { + // Ok, we have a pool that the last address belonged to, let's use it. + if (prefix) { + Pool6Ptr pool6 = boost::dynamic_pointer_cast(*it); + + if (!pool6) { + // Something is gravely wrong here + isc_throw(Unexpected, "Wrong type of pool: " + << (*it)->toText() + << " is not Pool6"); + } + // Get the prefix length + prefix_len = pool6->getLength(); + } + + IOAddress next = increaseAddress(last, prefix, prefix_len); + if ((*it)->inRange(next)) { + // the next one is in the pool as well, so we haven't hit + // pool boundary yet + (*it)->setLastAllocated(next); + subnet->setLastAllocated(pool_type_, next); + return (next); + } + + valid = false; + (*it)->resetLastAllocated(); + } + // We hit pool boundary, let's try to jump to the next pool and try again + ++it; + retrying = true; } - // 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(pool_type_, next); - return (next); + // Let's rewind to the beginning. + for (it = first; it != pools.end(); ++it) { + if ((*it)->clientSupported(client_classes)) { + (*it)->setLastAllocated((*it)->getFirstAddress()); + (*it)->resetLastAllocated(); + } } - // there is a next pool, let's try first address from it - next = (*it)->getFirstAddress(); - subnet->setLastAllocated(pool_type_, next); - return (next); + // ok to access first element directly. We checked that pools is non-empty + last = (*first)->getLastAllocated(); + (*first)->setLastAllocated(last); + subnet->setLastAllocated(pool_type_, last); + return (last); } AllocEngine::HashedAllocator::HashedAllocator(Lease::Type lease_type) @@ -228,6 +292,7 @@ AllocEngine::HashedAllocator::HashedAllocator(Lease::Type lease_type) isc::asiolink::IOAddress AllocEngine::HashedAllocator::pickAddress(const SubnetPtr&, + const ClientClasses&, const DuidPtr&, const IOAddress&) { isc_throw(NotImplemented, "Hashed allocator is not implemented"); @@ -241,6 +306,7 @@ AllocEngine::RandomAllocator::RandomAllocator(Lease::Type lease_type) isc::asiolink::IOAddress AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&, + const ClientClasses&, const DuidPtr&, const IOAddress&) { isc_throw(NotImplemented, "Random allocator is not implemented"); @@ -361,20 +427,29 @@ namespace { /// function when it belongs to a shared network. /// @param lease_type Type of the lease. /// @param address IPv6 address or prefix to be checked. +/// @param check_subnet if true only subnets are checked else both subnets +/// and pools are 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) { + const IOAddress& address, bool check_subnet) { // 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); + if (check_subnet) { + if (current_subnet->inPool(lease_type, address)) { + return (true); + } + } else { + if (current_subnet->inPool(lease_type, address, + ctx.query_->getClasses())) { + return (true); + } } } @@ -677,7 +752,12 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { // 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 - (subnet->getPool(ctx.currentIA().type_, hint, false)); + (subnet->getPool(ctx.currentIA().type_, ctx.query_->getClasses(), hint)); + + // check if the pool is allowed + if (pool && !pool->clientSupported(ctx.query_->getClasses())) { + pool.reset(); + } if (pool) { @@ -775,14 +855,23 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { // - 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_)); + uint64_t possible_attempts = + subnet->getPoolCapacity(ctx.currentIA().type_, + ctx.query_->getClasses()); + // Try next subnet if there is no chance to get something + if (possible_attempts == 0) { + subnet = subnet->getNextSubnet(original_subnet); + continue; + } + uint64_t max_attempts = (attempts_ > 0 ? attempts_ : possible_attempts); for (uint64_t i = 0; i < max_attempts; ++i) { ++total_attempts; - IOAddress candidate = allocator->pickAddress(subnet, ctx.duid_, + IOAddress candidate = allocator->pickAddress(subnet, + ctx.query_->getClasses(), + ctx.duid_, hint); /// In-pool reservations: Check if this address is reserved for someone @@ -800,7 +889,7 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) { uint8_t prefix_len = 128; if (ctx.currentIA().type_ == Lease::TYPE_PD) { pool = boost::dynamic_pointer_cast( - subnet->getPool(ctx.currentIA().type_, candidate, false)); + subnet->getPool(ctx.currentIA().type_, ctx.query_->getClasses(), candidate)); if (pool) { prefix_len = pool->getLength(); } @@ -1034,11 +1123,14 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx, void AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases) { - // 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_ || - (ctx.subnet_->getHostReservationMode() == Network::HR_DISABLED) ) { + // If there are no leases (so nothing to remove) just return. + if (existing_leases.empty() || !ctx.subnet_) { + return; + } + // If host reservation is disabled (so there are no reserved leases) + // use the simplified version. + if (ctx.subnet_->getHostReservationMode() == Network::HR_DISABLED) { + removeNonmatchingReservedNoHostLeases6(ctx, existing_leases); return; } @@ -1075,7 +1167,8 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, // be allocated to us from a dynamic pool, but we must check if // this lease belongs to any pool. If it does, we can proceed to // checking the next lease. - if (!host && inAllowedPool(ctx, candidate->type_, candidate->addr_)) { + if (!host && inAllowedPool(ctx, candidate->type_, + candidate->addr_, false)) { continue; } @@ -1122,6 +1215,44 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, } } +void +AllocEngine::removeNonmatchingReservedNoHostLeases6(ClientContext6& ctx, + Lease6Collection& existing_leases) { + // 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; + + BOOST_FOREACH(const Lease6Ptr& candidate, copy) { + // Lease can be allocated to us from a dynamic pool, but we must + // check if this lease belongs to any allowed pool. If it does, + // we can proceed to checking the next lease. + if (inAllowedPool(ctx, candidate->type_, + candidate->addr_, false)) { + continue; + } + + // Remove this lease from LeaseMgr as it doesn't belong to a pool. + LeaseMgrFactory::instance().deleteLease(candidate->addr_); + + // Update DNS if needed. + queueNCR(CHG_REMOVE, candidate); + + // Need to decrease statistic for assigned addresses. + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", candidate->subnet_id_, + ctx.currentIA().type_ == Lease::TYPE_NA ? + "assigned-nas" : "assigned-pds"), + static_cast(-1)); + + // Add this to the list of removed leases. + ctx.currentIA().old_leases_.push_back(candidate); + + // Let's remove this candidate from existing leases + removeLeases(existing_leases, candidate->addr_); + } +} + bool AllocEngine::removeLeases(Lease6Collection& container, const asiolink::IOAddress& addr) { @@ -1675,7 +1806,8 @@ AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases // If the lease is in the current subnet we need to account // for the re-assignment of The lease. - if (inAllowedPool(ctx, ctx.currentIA().type_, lease->addr_)) { + if (inAllowedPool(ctx, ctx.currentIA().type_, + lease->addr_, true)) { StatsMgr::instance().addValue( StatsMgr::generateName("subnet", lease->subnet_id_, ctx.currentIA().type_ == Lease::TYPE_NA ? @@ -2478,6 +2610,7 @@ void findClientLease(AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) /// within a shared network. /// /// @todo Update this function to take client classification into account. +/// @note client classification in pools (vs subnets) is checked /// /// @param ctx Client context. Current subnet may be modified by this /// function when it belongs to a shared network. @@ -2492,7 +2625,8 @@ inAllowedPool(AllocEngine::ClientContext4& ctx, const IOAddress& address) { Subnet4Ptr current_subnet = ctx.subnet_; while (current_subnet) { - if (current_subnet->inPool(Lease::TYPE_V4, address)) { + if (current_subnet->inPool(Lease::TYPE_V4, address, + ctx.query_->getClasses())) { // We found a subnet that this address belongs to, so it // seems that this subnet is the good candidate for allocation. // Let's update the selected subnet. @@ -2816,9 +2950,13 @@ AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) { // If the client is requesting an address which is assigned to the client // let's just renew this address. Also, renew this address if the client // doesn't request any specific address. + // Added extra checks: the address is reserved or belongs to the dynamic + // pool for the case the pool class has changed before the request. if (client_lease) { - if ((client_lease->addr_ == ctx.requested_address_) || - ctx.requested_address_.isV4Zero()) { + if (((client_lease->addr_ == ctx.requested_address_) || + ctx.requested_address_.isV4Zero()) && + (hasAddressReservation(ctx) || + inAllowedPool(ctx, client_lease->addr_))) { LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, ALLOC_ENGINE_V4_REQUEST_EXTEND_LEASE) @@ -3214,10 +3352,19 @@ AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) { client_id = ctx.clientid_; } - const uint64_t max_attempts = (attempts_ > 0 ? attempts_ : - subnet->getPoolCapacity(Lease::TYPE_V4)); + uint64_t possible_attempts = + subnet->getPoolCapacity(Lease::TYPE_V4, + ctx.query_->getClasses()); + uint64_t max_attempts = (attempts_ > 0 ? attempts_ : possible_attempts); + // Skip trying if there is no chance to get something + if (possible_attempts == 0) { + max_attempts = 0; + } + for (uint64_t i = 0; i < max_attempts; ++i) { - IOAddress candidate = allocator->pickAddress(subnet, client_id, + IOAddress candidate = allocator->pickAddress(subnet, + ctx.query_->getClasses(), + client_id, ctx.requested_address_); // If address is not reserved for another client, try to allocate it. if (!addressReserved(candidate, ctx)) { diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index e33b2f7056..af5c44da21 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -8,6 +8,7 @@ #define ALLOC_ENGINE_H #include +#include #include #include #include @@ -78,13 +79,19 @@ protected: /// than pickResource(), because nobody would immediately know what the /// resource means in this context. /// + /// Pools which are not allowed for client classes are skipped. + /// /// @param subnet next address will be returned from pool of that subnet + /// @param client_classes list of classes client belongs to + /// @param duid Client's DUID /// @param hint client's hint /// /// @return the next address virtual isc::asiolink::IOAddress - pickAddress(const SubnetPtr& subnet, const DuidPtr& duid, + pickAddress(const SubnetPtr& subnet, + const ClientClasses& client_classes, + const DuidPtr& duid, const isc::asiolink::IOAddress& hint) = 0; /// @brief Default constructor. @@ -125,11 +132,13 @@ protected: /// @brief returns the next address from pools in a subnet /// /// @param subnet next address will be returned from pool of that subnet + /// @param client_classes list of classes client belongs to /// @param duid Client's DUID (ignored) /// @param hint client's hint (ignored) /// @return the next address virtual isc::asiolink::IOAddress pickAddress(const SubnetPtr& subnet, + const ClientClasses& client_classes, const DuidPtr& duid, const isc::asiolink::IOAddress& hint); protected: @@ -147,6 +156,20 @@ protected: static isc::asiolink::IOAddress increasePrefix(const isc::asiolink::IOAddress& prefix, const uint8_t prefix_len); + + /// @brief Returns the next address or prefix + /// + /// This method works for IPv4 addresses, IPv6 addresses and + /// IPv6 prefixes. + /// + /// @param address address or prefix to be increased + /// @param prefix true when the previous argument is a prefix + /// @param prefix_len length of the prefix + /// @return result address or prefix + static isc::asiolink::IOAddress + increaseAddress(const isc::asiolink::IOAddress& address, + bool prefix, const uint8_t prefix_len); + }; /// @brief Address/prefix allocator that gets an address based on a hash @@ -164,12 +187,15 @@ protected: /// @todo: Implement this method /// /// @param subnet an address will be picked from pool of that subnet + /// @param client_classes list of classes client belongs to /// @param duid Client's DUID /// @param hint a hint (last address that was picked) /// @return selected address - virtual isc::asiolink::IOAddress pickAddress(const SubnetPtr& subnet, - const DuidPtr& duid, - const isc::asiolink::IOAddress& hint); + virtual isc::asiolink::IOAddress + pickAddress(const SubnetPtr& subnet, + const ClientClasses& client_classes, + const DuidPtr& duid, + const isc::asiolink::IOAddress& hint); }; /// @brief Random allocator that picks address randomly @@ -191,7 +217,9 @@ protected: /// @param hint the last address that was picked (ignored) /// @return a random address from the pool virtual isc::asiolink::IOAddress - pickAddress(const SubnetPtr& subnet, const DuidPtr& duid, + pickAddress(const SubnetPtr& subnet, + const ClientClasses& client_classes, + const DuidPtr& duid, const isc::asiolink::IOAddress& hint); }; @@ -803,8 +831,8 @@ private: /// @brief Removes leases that are reserved for someone else. /// /// Goes through the list specified in existing_leases and removes those that - /// are reserved by someone else. The removed leases are added to the - /// ctx.removed_leases_ collection. + /// are reserved by someone else or do not belong to an allowed pool. + /// The removed leases are added to the ctx.removed_leases_ collection. /// /// @param ctx client context that contains all details (subnet, client-id, etc.) /// @param existing_leases [in/out] leases that should be checked @@ -812,6 +840,17 @@ private: removeNonmatchingReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases); + /// @brief Removes leases that are reserved for someone else. + /// + /// Simplified version of removeNonmatchingReservedLeases6 to be + /// used when host reservations are disabled. + /// + /// @param ctx client context that contains all details (subnet, client-id, etc.) + /// @param existing_leases [in/out] leases that should be checked + void + removeNonmatchingReservedNoHostLeases6(ClientContext6& ctx, + Lease6Collection& existing_leases); + /// @brief Removed leases that are not reserved for this client /// /// This method iterates over existing_leases and will remove leases that are diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index 4f5c5f8fdd..b10d7e7eda 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -377,6 +377,15 @@ PoolParser::parse(PoolStoragePtr pools, << " (" << option_data->getPosition() << ")"); } } + + // Client-class. + ConstElementPtr client_class = pool_structure->get("client-class"); + if (client_class) { + string cclass = client_class->stringValue(); + if (!cclass.empty()) { + pool->allowClientClass(cclass); + } + } } //****************************** Pool4Parser ************************* @@ -846,6 +855,11 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { user_context_ = user_context; } + ConstElementPtr client_class = pd_pool_->get("client-class"); + if (client_class) { + client_class_ = client_class; + } + // Check the pool parameters. It will throw an exception if any // of the required parameters are invalid. try { @@ -869,6 +883,14 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { pool_->setContext(user_context_); } + + if (client_class_) { + string cclass = client_class_->stringValue(); + if (!cclass.empty()) { + pool_->allowClientClass(cclass); + } + } + // Add the local pool to the external storage ptr. pools->push_back(pool_); } diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index 0941e70b46..e195cd4bad 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -652,6 +652,9 @@ private: CfgOptionPtr options_; isc::data::ConstElementPtr user_context_; + + isc::data::ConstElementPtr client_class_; + }; /// @brief Parser for a list of prefix delegation pools. diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc index 1542b8ca18..dc169b0bae 100644 --- a/src/lib/dhcpsrv/pool.cc +++ b/src/lib/dhcpsrv/pool.cc @@ -18,13 +18,35 @@ namespace dhcp { Pool::Pool(Lease::Type type, const isc::asiolink::IOAddress& first, const isc::asiolink::IOAddress& last) :id_(getNextID()), first_(first), last_(last), type_(type), - capacity_(0), cfg_option_(new CfgOption()) { + capacity_(0), cfg_option_(new CfgOption()), white_list_(), + last_allocated_(first), last_allocated_valid_(false) { } bool Pool::inRange(const isc::asiolink::IOAddress& addr) const { return (first_.smallerEqual(addr) && addr.smallerEqual(last_)); } +bool Pool::clientSupported(const ClientClasses& classes) const { + if (white_list_.empty()) { + // There is no class defined for this pool, so we do + // support everyone. + return (true); + } + + for (ClientClasses::const_iterator it = white_list_.begin(); + it != white_list_.end(); ++it) { + if (classes.contains(*it)) { + return (true); + } + } + + return (false); +} + +void Pool::allowClientClass(const ClientClass& class_name) { + white_list_.insert(class_name); +} + std::string Pool::toText() const { std::stringstream tmp; @@ -86,6 +108,16 @@ Pool::toElement() const { // Set pool options ConstCfgOptionPtr opts = getCfgOption(); map->set("option-data", opts->toElement()); + + // Set client-class + const ClientClasses& cclasses = getClientClasses(); + if (cclasses.size() > 1) { + isc_throw(ToElementError, "client-class has too many items: " + << cclasses.size()); + } else if (!cclasses.empty()) { + map->set("client-class", Element::create(*cclasses.cbegin())); + } + return (map); } diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h index 5bdefceccb..c7c4aa05db 100644 --- a/src/lib/dhcpsrv/pool.h +++ b/src/lib/dhcpsrv/pool.h @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC") // // 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 @@ -8,6 +8,7 @@ #define POOL_H #include +#include #include #include #include @@ -96,6 +97,58 @@ public: return (cfg_option_); } + /// @Checks whether this pool supports client that belongs to + /// specified classes. + /// + /// @todo: currently doing the same than network which + /// is known to be improved. + /// + /// @param client_classes list of all classes the client belongs to + /// @return true if client can be supported, false otherwise + bool clientSupported(const ClientClasses& client_classes) const; + + /// @brief Adds class class_name to the list of supported classes + /// + /// @param class_name client class to be supported by this pool + void allowClientClass(const ClientClass& class_name); + + /// @brief returns the client class white list + /// + /// @note Currently white list is empty or has one element + /// @note The returned reference is only valid as long as the object + /// returned is valid. + /// + /// @return client classes @ref white_list_ + const ClientClasses& getClientClasses() const { + return (white_list_); + } + + /// @brief returns the last address that was tried from this pool + /// + /// @return address/prefix that was last tried from this pool + isc::asiolink::IOAddress getLastAllocated() const { + return last_allocated_; + } + + /// @brief checks if the last address is valid + /// @return true if the last address is valid + bool isLastAllocatedValid() const { + return last_allocated_valid_; + } + + /// @brief sets the last address that was tried from this pool + /// + /// @param addr address/prefix to that was tried last + void setLastAllocated(const isc::asiolink::IOAddress& addr) { + last_allocated_ = addr; + last_allocated_valid_ = true; + } + + /// @brief resets the last address to invalid + void resetLastAllocated() { + last_allocated_valid_ = false; + } + /// @brief Unparse a pool object. /// /// @return A pointer to unparsed pool configuration. @@ -148,6 +201,19 @@ protected: /// @brief Pointer to the option data configuration for this pool. CfgOptionPtr cfg_option_; + + /// @brief Optional definition of a client class + /// + /// @ref Network::white_list_ + ClientClasses white_list_; + + /// @brief Last allocated address + /// See @ref isc::dhcp::subnet::last_allocated_ia_ + /// Initialized and reset to first + isc::asiolink::IOAddress last_allocated_; + + /// @brief Status of last allocated address + bool last_allocated_valid_; }; /// @brief Pool information for IPv4 addresses diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index 945256b7b0..3b27412280 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -134,6 +134,23 @@ Subnet::getPoolCapacity(Lease::Type type) const { } } +uint64_t +Subnet::getPoolCapacity(Lease::Type type, + const ClientClasses& client_classes) const { + switch (type) { + case Lease::TYPE_V4: + case Lease::TYPE_NA: + return sumPoolCapacity(pools_, client_classes); + case Lease::TYPE_TA: + return sumPoolCapacity(pools_ta_, client_classes); + case Lease::TYPE_PD: + return sumPoolCapacity(pools_pd_, client_classes); + default: + isc_throw(BadValue, "Unsupported pool type: " + << static_cast(type)); + } +} + uint64_t Subnet::sumPoolCapacity(const PoolCollection& pools) const { uint64_t sum = 0; @@ -152,6 +169,28 @@ Subnet::sumPoolCapacity(const PoolCollection& pools) const { return (sum); } +uint64_t +Subnet::sumPoolCapacity(const PoolCollection& pools, + const ClientClasses& client_classes) const { + uint64_t sum = 0; + for (PoolCollection::const_iterator p = pools.begin(); p != pools.end(); ++p) { + if (!(*p)->clientSupported(client_classes)) { + continue; + } + uint64_t x = (*p)->getCapacity(); + + // Check if we can add it. If sum + x > uint64::max, then we would have + // overflown if we tried to add it. + if (x > std::numeric_limits::max() - sum) { + return (std::numeric_limits::max()); + } + + sum += x; + } + + return (sum); +} + void Subnet4::checkType(Lease::Type type) const { if (type != Lease::TYPE_V4) { isc_throw(BadValue, "Only TYPE_V4 is allowed for Subnet4"); @@ -329,6 +368,33 @@ const PoolPtr Subnet::getPool(Lease::Type type, const isc::asiolink::IOAddress& return (candidate); } +const PoolPtr Subnet::getPool(Lease::Type type, + const ClientClasses& client_classes, + const isc::asiolink::IOAddress& hint) const { + // check if the type is valid (and throw if it isn't) + checkType(type); + + const PoolCollection& pools = getPools(type); + + PoolPtr candidate; + + if (!pools.empty()) { + PoolCollection::const_iterator ub = + std::upper_bound(pools.begin(), pools.end(), hint, + prefixLessThanFirstAddress); + + if (ub != pools.begin()) { + --ub; + if ((*ub)->inRange(hint) && (*ub)->clientSupported(client_classes)) { + candidate = *ub; + } + } + } + + // Return a pool or NULL if no match found. + return (candidate); +} + void Subnet::addPool(const PoolPtr& pool) { // check if the type is valid (and throw if it isn't) @@ -409,6 +475,31 @@ Subnet::inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const { return (false); } +bool +Subnet::inPool(Lease::Type type, + const isc::asiolink::IOAddress& addr, + const ClientClasses& client_classes) const { + + // Let's start with checking if it even belongs to that subnet. + if ((type != Lease::TYPE_PD) && !inRange(addr)) { + return (false); + } + + const PoolCollection& pools = getPools(type); + + for (PoolCollection::const_iterator pool = pools.begin(); + pool != pools.end(); ++pool) { + if (!(*pool)->clientSupported(client_classes)) { + continue; + } + if ((*pool)->inRange(addr)) { + return (true); + } + } + // There's no pool that address belongs to + return (false); +} + bool Subnet::poolOverlaps(const Lease::Type& pool_type, const PoolPtr& pool) const { const PoolCollection& pools = getPools(pool_type); @@ -637,6 +728,14 @@ Subnet6::toElement() const { // Set pool options ConstCfgOptionPtr opts = (*pool)->getCfgOption(); pool_map->set("option-data", opts->toElement()); + // Set client-class + const ClientClasses& cclasses = (*pool)->getClientClasses(); + if (cclasses.size() > 1) { + isc_throw(ToElementError, "client-class has too many items: " + << cclasses.size()); + } else if (!cclasses.empty()) { + pool_map->set("client-class", Element::create(*cclasses.cbegin())); + } // Push on the pool list pool_list->add(pool_map); } @@ -688,6 +787,14 @@ Subnet6::toElement() const { // Set pool options ConstCfgOptionPtr opts = pdpool->getCfgOption(); pool_map->set("option-data", opts->toElement()); + // Set client-class + const ClientClasses& cclasses = pdpool->getClientClasses(); + if (cclasses.size() > 1) { + isc_throw(ToElementError, "client-class has too many items: " + << cclasses.size()); + } else if (!cclasses.empty()) { + pool_map->set("client-class", Element::create(*cclasses.cbegin())); + } // Push on the pool list pdpool_list->add(pool_map); } diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index ed55698899..5ac4b908c4 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -55,26 +55,39 @@ public: /// @return true if the address is in any of the pools bool inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const; - /// @brief returns the last address that was tried from this pool + /// @brief checks if the specified address is in allowed pools + /// + /// This takes also into account client classes + /// + /// @param type type of pools to iterate over + /// @param addr this address will be checked if it belongs to any pools in + /// that subnet + /// @param client_classes client class list which must be allowed + /// @return true if the address is in any of the allowed pools + bool inPool(Lease::Type type, + const isc::asiolink::IOAddress& addr, + const ClientClasses& client_classes) const; + + /// @brief returns the last address that was tried from this subnet /// /// This method returns the last address that was attempted to be allocated /// from this subnet. This is used as helper information for the next /// iteration of the allocation algorithm. /// - /// @todo: Define map somewhere in the + /// @todo: Define map somewhere in the /// AllocEngine::IterativeAllocator and keep the data there /// /// @param type lease type to be returned - /// @return address/prefix that was last tried from this pool + /// @return address/prefix that was last tried from this subnet isc::asiolink::IOAddress getLastAllocated(Lease::Type type) const; - /// @brief sets the last address that was tried from this pool + /// @brief sets the last address that was tried from this subnet /// /// This method sets the last address that was attempted to be allocated /// from this subnet. This is used as helper information for the next /// iteration of the allocation algorithm. /// - /// @todo: Define map somewhere in the + /// @todo: Define map somewhere in the /// AllocEngine::IterativeAllocator and keep the data there /// @param addr address/prefix to that was tried last /// @param type lease type to be set @@ -143,6 +156,17 @@ public: const PoolPtr getPool(Lease::Type type, const isc::asiolink::IOAddress& addr, bool anypool = true) const; + /// @brief Returns a pool that specified address belongs to with classes + /// + /// Variant using only pools allowing given classes + /// + /// @param type pool type that the pool is looked for + /// @param client_classes client class list which must be allowed + /// @param addr address that the returned pool should cover (optional) + const PoolPtr getPool(Lease::Type type, + const ClientClasses& client_classes, + const isc::asiolink::IOAddress& addr) const; + /// @brief Returns a pool without any address specified /// /// @param type pool type that the pool is looked for @@ -170,6 +194,14 @@ public: /// @param type type of the lease uint64_t getPoolCapacity(Lease::Type type) const; + /// @brief Returns the number of possible leases for specified lease type + /// allowed for a client which belongs to classes. + /// + /// @param type type of the lease + /// @param client_classes List of classes the client belongs to. + uint64_t getPoolCapacity(Lease::Type type, + const ClientClasses& client_classes) const; + /// @brief Returns textual representation of the subnet (e.g. /// "2001:db8::/64") /// @@ -288,6 +320,13 @@ protected: /// @return sum of possible leases uint64_t sumPoolCapacity(const PoolCollection& pools) const; + /// @brief returns a sum of possible leases in all pools allowing classes + /// @param pools list of pools + /// @param client_classes list of classes + /// @return sum of possible/allowed leases + uint64_t sumPoolCapacity(const PoolCollection& pools, + const ClientClasses& client_classes) const; + /// @brief Checks if the specified pool overlaps with an existing pool. /// /// @param pool_type Pool type. diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc index ff216a6a77..3e1a370580 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc @@ -326,12 +326,35 @@ TEST_F(AllocEngine4Test, IterativeAllocator) { alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_V4)); for (int i = 0; i < 1000; ++i) { - IOAddress candidate = alloc->pickAddress(subnet_, clientid_, + IOAddress candidate = alloc->pickAddress(subnet_, cc_, clientid_, IOAddress("0.0.0.0")); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate)); } } +// This test verifies that the allocator picks addresses that belong to the +// pool using classification +TEST_F(AllocEngine4Test, IterativeAllocator_class) { + boost::scoped_ptr + alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_V4)); + + // Restrict pool_ to the foo class. Add a second pool with bar class. + pool_->allowClientClass("foo"); + Pool4Ptr pool(new Pool4(IOAddress("192.0.2.200"), + IOAddress("192.0.2.209"))); + pool->allowClientClass("bar"); + subnet_->addPool(pool); + + // Clients are in bar + cc_.insert("bar"); + + for (int i = 0; i < 1000; ++i) { + IOAddress candidate = alloc->pickAddress(subnet_, cc_, clientid_, + IOAddress("0.0.0.0")); + EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate)); + EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_)); + } +} // This test verifies that the iterative allocator really walks over all addresses // in all pools in specified subnet. It also must not pick the same address twice @@ -359,7 +382,7 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) { std::set generated_addrs; int cnt = 0; while (++cnt) { - IOAddress candidate = alloc.pickAddress(subnet_, clientid_, IOAddress("0.0.0.0")); + IOAddress candidate = alloc.pickAddress(subnet_, cc_, clientid_, IOAddress("0.0.0.0")); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate)); // One way to easily verify that the iterative allocator really works is @@ -690,6 +713,44 @@ TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkClassification) { EXPECT_EQ("192.0.2.17", lease->addr_.toText()); } +// This test verifies that the server can offer an address from a +// different subnet than orginally selected, when the address pool in +// the first subnet requires another class. +TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkPoolClassification) { + + // Try to offer address from subnet1. There is one address available + // so it should be offerred. + AllocEngine::ClientContext4 + ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), + false, false, "host.example.com.", true); + ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); + Lease4Ptr lease = engine_.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_)); + + // Apply restrictions on the pool1. This should be only assigned + // to clients belonging to cable-modem class. + pool1_->allowClientClass("cable-modem"); + + // The allocation engine should determine that the pool1 is not + // available for the client not belonging to the cable-modem class. + // Instead, it should offer an address from subnet2 that belongs + // to the same shared network. + ctx.subnet_ = subnet1_; + lease = engine_.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); + + // Assign cable-modem class and try again. This time, we should + // offer an address from the pool1. + ctx.query_->addClass(ClientClass("cable-modem")); + + ctx.subnet_ = subnet1_; + lease = engine_.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_EQ("192.0.2.17", lease->addr_.toText()); +} + // Test that reservations within shared network take precedence over the // existing leases regardless in which subnet belonging to a shared network // reservations belong. @@ -888,6 +949,59 @@ TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkClassification) { EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); } +// This test verifies that the server can assign an address from a +// different subnet than orginally selected, when the address pool in +// the first subnet requires another class. +TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkPoolClassification) { + // Try to offer address from subnet1. There is one address available + // so it should be offerred. + AllocEngine::ClientContext4 + ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), + false, false, "host.example.com.", false); + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + Lease4Ptr lease = engine_.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_)); + + // Remove the lease so as we can start over. + LeaseMgrFactory::instance().deleteLease(lease->addr_); + + // Apply restrictions on the pool1. This should be only assigned + // to clients belonging to cable-modem class. + pool1_->allowClientClass("cable-modem"); + + // The allocation engine should determine that the pool1 is not + // available for the client not belonging to the cable-modem class. + // Instead, it should assign an address from subnet2 that belongs + // to the same shared network. + ctx.subnet_ = subnet1_; + lease = engine_.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); + + // Remove the lease so as we can start over. + LeaseMgrFactory::instance().deleteLease(lease->addr_); + + // Assign cable-modem class and try again. This time, we should + // offer an address from the pool1. + ctx.query_->addClass(ClientClass("cable-modem")); + + ctx.subnet_ = subnet1_; + lease = engine_.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_)); + + // Let's now remove the client from the cable-modem class and try + // to renew the address. The engine should determine that the + // client doesn't have access to the pool1 anymore and + // assign an address from unrestricted pool. + ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); + ctx.subnet_ = subnet1_; + lease = engine_.allocateLease4(ctx); + ASSERT_TRUE(lease); + EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); +} + // Test that reservations within shared network take precedence over the // existing leases regardless in which subnet belonging to a shared network // reservations belong (DHCPREQUEST case). diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc index 1002958cfb..e96ebbd37b 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc @@ -189,11 +189,34 @@ TEST_F(AllocEngine6Test, IterativeAllocator) { alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_NA)); for (int i = 0; i < 1000; ++i) { - IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::")); + IOAddress candidate = alloc->pickAddress(subnet_, cc_, duid_, IOAddress("::")); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate)); } } +// This test verifies that the allocator picks addresses that belong to the +// pool using classification +TEST_F(AllocEngine6Test, IterativeAllocator_class) { + boost::scoped_ptr + alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_NA)); + + // Restrict pool_ to the foo class. Add a second pool with bar class. + pool_->allowClientClass("foo"); + Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"), + IOAddress("2001:db8:1::109"))); + pool->allowClientClass("bar"); + subnet_->addPool(pool); + + // Clients are in bar + cc_.insert("bar"); + + for (int i = 0; i < 1000; ++i) { + IOAddress candidate = alloc->pickAddress(subnet_, cc_, duid_, IOAddress("::")); + EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate)); + EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_)); + } +} + TEST_F(AllocEngine6Test, IterativeAllocatorAddrStep) { NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA); @@ -210,23 +233,100 @@ TEST_F(AllocEngine6Test, IterativeAllocatorAddrStep) { subnet_->addPool(pool3); // Let's check the first pool (5 addresses here) - EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:1::3", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:1::4", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:1::5", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::3", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::4", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::5", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); // The second pool is easy - only one address here - EXPECT_EQ("2001:db8:1::100", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::100", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); // This is the third and last pool, with 2 addresses in it - EXPECT_EQ("2001:db8:1::105", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:1::106", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::105", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::106", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); // We iterated over all addresses and reached to the end of the last pool. // Let's wrap around and start from the beginning - EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepInClass) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA); + + subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool + + Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), + IOAddress("2001:db8:1::5"))); + Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"), + IOAddress("2001:db8:1::100"))); + Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"), + IOAddress("2001:db8:1::106"))); + // Set pool1 and pool3 but not pool2 in foo class + pool1->allowClientClass("foo"); + pool3->allowClientClass("foo"); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // Clients are in foo + cc_.insert("foo"); + + // Let's check the first pool (5 addresses here) + EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::3", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::4", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::5", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // The second pool is easy - only one address here + EXPECT_EQ("2001:db8:1::100", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // This is the third and last pool, with 2 addresses in it + EXPECT_EQ("2001:db8:1::105", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::106", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // We iterated over all addresses and reached to the end of the last pool. + // Let's wrap around and start from the beginning + EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepOutClass) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA); + + subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool + + Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), + IOAddress("2001:db8:1::5"))); + Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"), + IOAddress("2001:db8:1::100"))); + Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"), + IOAddress("2001:db8:1::106"))); + // Set pool2 in foo + pool2->allowClientClass("foo"); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // Let's check the first pool (5 addresses here) + EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::3", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::4", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::5", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // The second pool is skipped + + // This is the third and last pool, with 2 addresses in it + EXPECT_EQ("2001:db8:1::105", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::106", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // We iterated over all addresses and reached to the end of the last pool. + // Let's wrap around and start from the beginning + EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); } TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStep) { @@ -247,41 +347,158 @@ TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStep) { // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::) // First pool check (Let's check over all 16 leases) - EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:20::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:30::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:40::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:50::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:60::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:70::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:80::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:90::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:a0::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:b0::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:c0::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:d0::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:e0::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:f0::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:20::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:30::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:40::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:50::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:60::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:70::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:80::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:90::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:a0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:b0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:c0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:d0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:e0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:f0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); // Second pool (just one lease here) - EXPECT_EQ("2001:db8:1::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); // Third pool (256 leases, let's check first and last explicitly and the // rest over in a pool - EXPECT_EQ("2001:db8:2::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:2::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); for (int i = 1; i < 255; i++) { stringstream exp; exp << "2001:db8:2:" << hex << i << dec << "::"; - EXPECT_EQ(exp.str(), alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ(exp.str(), alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); } - EXPECT_EQ("2001:db8:2:ff::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:2:ff::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); // Ok, we've iterated over all prefixes in all pools. We now wrap around. // We're looping over now (iterating over first pool again) - EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); - EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepInClass) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD); + + subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4)); + + Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60)); + Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48)); + Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64)); + // Set pool1 and pool3 but not pool2 in foo class + pool1->allowClientClass("foo"); + pool3->allowClientClass("foo"); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // Clients are in foo + cc_.insert("foo"); + + // We have a 2001:db8::/48 subnet that has 3 pools defined in it: + // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::) + // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease) + // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::) + + // First pool check (Let's check over all 16 leases) + EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:20::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:30::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:40::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:50::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:60::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:70::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:80::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:90::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:a0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:b0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:c0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:d0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:e0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:f0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Second pool (just one lease here) + EXPECT_EQ("2001:db8:1::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Third pool (256 leases, let's check first and last explicitly and the + // rest over in a pool + EXPECT_EQ("2001:db8:2::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + for (int i = 1; i < 255; i++) { + stringstream exp; + exp << "2001:db8:2:" << hex << i << dec << "::"; + EXPECT_EQ(exp.str(), alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + } + EXPECT_EQ("2001:db8:2:ff::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Ok, we've iterated over all prefixes in all pools. We now wrap around. + // We're looping over now (iterating over first pool again) + EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepOutClass) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD); + + subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4)); + + Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60)); + Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48)); + Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64)); + // Set pool2 in foo + pool2->allowClientClass("foo"); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // We have a 2001:db8::/48 subnet that has 3 pools defined in it: + // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::) + // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease) + // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::) + + // First pool check (Let's check over all 16 leases) + EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:20::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:30::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:40::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:50::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:60::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:70::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:80::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:90::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:a0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:b0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:c0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:d0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:e0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:f0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // The second pool is skipped + + // Third pool (256 leases, let's check first and last explicitly and the + // rest over in a pool + EXPECT_EQ("2001:db8:2::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + for (int i = 1; i < 255; i++) { + stringstream exp; + exp << "2001:db8:2:" << hex << i << dec << "::"; + EXPECT_EQ(exp.str(), alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + } + EXPECT_EQ("2001:db8:2:ff::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Ok, we've iterated over all prefixes in all pools. We now wrap around. + // We're looping over now (iterating over first pool again) + EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); } // This test verifies that the iterative allocator can step over addresses @@ -289,7 +506,7 @@ TEST_F(AllocEngine6Test, IterativeAllocatorAddressIncrease) { NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA); // Let's pick the first address - IOAddress addr1 = alloc.pickAddress(subnet_, duid_, IOAddress("2001:db8:1::10")); + IOAddress addr1 = alloc.pickAddress(subnet_, cc_, duid_, IOAddress("2001:db8:1::10")); // Check that we can indeed pick the first address from the pool EXPECT_EQ("2001:db8:1::10", addr1.toText()); @@ -379,7 +596,7 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) { std::set generated_addrs; int cnt = 0; while (++cnt) { - IOAddress candidate = alloc.pickAddress(subnet_, duid_, IOAddress("::")); + IOAddress candidate = alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate)); // One way to easily verify that the iterative allocator really works is @@ -2185,6 +2402,58 @@ TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkClassification) { EXPECT_EQ("2001:db8:1::1", lease->addr_.toText()); } +// This test verifies that the server can offer an address from a +// different subnet than orginally selected, when the address pool in +// the first subnet requires another class. +TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkPoolClassification) { + // Try to offer address from subnet1. There is an address available so + // it should be offerred. + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); + AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true, + query); + ctx.currentIA().iaid_ = iaid_; + + Lease6Ptr lease; + ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx))); + ASSERT_TRUE(lease); + ASSERT_TRUE(subnet1_->inRange(lease->addr_)); + + // Apply restrictions on the pool1. This should be only assigned + // to clients belonging to cable-modem class. + pool1_->allowClientClass("cable-modem"); + + // The allocation engine should determine that the pool1 is not + // available for the client not belonging to the cable-modem class. + // Instead, it should offer an address from subnet2 that belongs + // to the same shared network. + AllocEngine::ClientContext6 ctx2(subnet1_, duid_, false, false, "", true, + query); + ctx2.currentIA().iaid_ = iaid_; + ctx2.query_ = query; + ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx2))); + ASSERT_TRUE(lease); + ASSERT_TRUE(subnet2_->inRange(lease->addr_)); + + AllocEngine::ClientContext6 ctx3(subnet1_, duid_, false, false, "", true, + query); + ctx3.currentIA().iaid_ = iaid_; + ctx3.query_ = query; + + AllocEngine::ClientContext6 ctx4(subnet1_, duid_, false, false, "", true, + query); + ctx4.currentIA().iaid_ = iaid_; + ctx4.query_ = query; + + // Assign cable-modem class and try again. This time, we should + // offer an address from the pool1_. + ctx4.query_->addClass(ClientClass("cable-modem")); + + AllocEngine::findReservation(ctx4); + ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx4))); + ASSERT_TRUE(lease); + EXPECT_EQ("2001:db8:1::1", lease->addr_.toText()); +} + // This test verifies that the client is offerred a reserved address // even if this address belongs to another subnet within the same // shared network. diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.h b/src/lib/dhcpsrv/tests/alloc_engine_utils.h index 426189eaaa..eeed469755 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine_utils.h +++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.h @@ -74,6 +74,7 @@ public: :IterativeAllocator(type) { } + using AllocEngine::IterativeAllocator::increaseAddress; using AllocEngine::IterativeAllocator::increasePrefix; }; }; @@ -221,10 +222,10 @@ public: /// @param input address to be increased /// @param exp_output expected address after increase void - checkAddrIncrease(NakedAllocEngine::NakedIterativeAllocator&, + checkAddrIncrease(NakedAllocEngine::NakedIterativeAllocator& alloc, std::string input, std::string exp_output) { - EXPECT_EQ(exp_output, asiolink::IOAddress::increase( - asiolink::IOAddress(input)).toText()); + EXPECT_EQ(exp_output, alloc.increaseAddress(asiolink::IOAddress(input), + false, 0).toText()); } /// @brief Checks if increasePrefix() works as expected @@ -409,6 +410,7 @@ public: bool fqdn_fwd_; ///< Perform forward update for a lease. bool fqdn_rev_; ///< Perform reverse update for a lease. LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory + ClientClasses cc_; ///< client classes }; /// @brief Used in Allocation Engine tests for IPv4 @@ -510,6 +512,7 @@ public: Pool4Ptr pool_; ///< Pool belonging to subnet_ LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory AllocEngine::ClientContext4 ctx_; ///< Context information passed to various + ClientClasses cc_; ///< Client classes ///< allocation engine functions. }; diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc index cfe0363bda..fb2f22a554 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC") // // 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 @@ -819,6 +819,7 @@ TEST(CfgSubnets4Test, unparsePool) { Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 123)); Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.1"), IOAddress("192.0.2.10"))); Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.64"), 26)); + pool2->allowClientClass("bar"); std::string json1 = "{ \"comment\": \"foo\", \"version\": 1 }"; data::ElementPtr ctx1 = data::Element::fromJSON(json1); @@ -857,6 +858,7 @@ TEST(CfgSubnets4Test, unparsePool) { " },{\n" " \"option-data\": [ ],\n" " \"pool\": \"192.0.2.64/26\"\n," + " \"client-class\": \"bar\",\n" " \"user-context\": { \"foo\": \"bar\" }\n" " }\n" " ]\n" diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc index f8200fad06..5565387d26 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC") // // 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 @@ -510,6 +510,7 @@ TEST(CfgSubnets6Test, unparsePool) { IOAddress("2001:db8:1::100"), IOAddress("2001:db8:1::199"))); Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64)); + pool2->allowClientClass("bar"); std::string json1 = "{ \"comment\": \"foo\", \"version\": 1 }"; data::ElementPtr ctx1 = data::Element::fromJSON(json1); @@ -541,6 +542,7 @@ TEST(CfgSubnets6Test, unparsePool) { " \"option-data\": [ ]\n" " },{\n" " \"pool\": \"2001:db8:1:1::/64\",\n" + " \"client-class\": \"bar\",\n" " \"user-context\": { \"foo\": \"bar\" },\n" " \"option-data\": [ ]\n" " }\n" @@ -563,6 +565,7 @@ TEST(CfgSubnets6Test, unparsePdPool) { IOAddress("2001:db8:2::"), 48, 64)); Pool6Ptr pdpool2(new Pool6(IOAddress("2001:db8:3::"), 48, 56, IOAddress("2001:db8:3::"), 64)); + pdpool2->allowClientClass("bar"); data::ElementPtr ctx1 = data::Element::fromJSON("{ \"foo\": [ \"bar\" ] }"); pdpool1->setContext(ctx1); @@ -597,7 +600,8 @@ TEST(CfgSubnets6Test, unparsePdPool) { " \"delegated-len\": 56,\n" " \"excluded-prefix\": \"2001:db8:3::\",\n" " \"excluded-prefix-len\": 64,\n" - " \"option-data\": [ ]\n" + " \"option-data\": [ ],\n" + " \"client-class\": \"bar\"\n" " }\n" " ],\n" " \"option-data\": [ ]\n" diff --git a/src/lib/dhcpsrv/tests/pool_unittest.cc b/src/lib/dhcpsrv/tests/pool_unittest.cc index b78cf1fbcd..5662f6a5ce 100644 --- a/src/lib/dhcpsrv/tests/pool_unittest.cc +++ b/src/lib/dhcpsrv/tests/pool_unittest.cc @@ -190,6 +190,106 @@ TEST(Pool4Test, userContext) { EXPECT_EQ(ctx->str(), pool->getContext()->str()); } +// This test checks that handling for client-class is valid. +TEST(Pool4Test, clientClass) { + // Create a pool. + Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"), + IOAddress("192.0.2.255"))); + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + + // No class restrictions defined, any client should be supported + EXPECT_EQ(0, pool->getClientClasses().size()); + EXPECT_TRUE(pool->clientSupported(no_class)); + EXPECT_TRUE(pool->clientSupported(foo_class)); + EXPECT_TRUE(pool->clientSupported(bar_class)); + EXPECT_TRUE(pool->clientSupported(three_classes)); + + // Let's allow only clients belonging to "bar" class. + pool->allowClientClass("bar"); + EXPECT_EQ(1, pool->getClientClasses().size()); + + EXPECT_FALSE(pool->clientSupported(no_class)); + EXPECT_FALSE(pool->clientSupported(foo_class)); + EXPECT_TRUE(pool->clientSupported(bar_class)); + EXPECT_TRUE(pool->clientSupported(three_classes)); +} + +// This test checks that handling for multiple client-classes is valid. +TEST(Pool4Test, clientClasses) { + // Create a pool. + Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"), + IOAddress("192.0.2.255"))); + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // No class restrictions defined, any client should be supported + EXPECT_EQ(0, pool->getClientClasses().size()); + EXPECT_TRUE(pool->clientSupported(no_class)); + EXPECT_TRUE(pool->clientSupported(foo_class)); + EXPECT_TRUE(pool->clientSupported(bar_class)); + + // Let's allow clients belonging to "bar" or "foo" class. + pool->allowClientClass("bar"); + pool->allowClientClass("foo"); + EXPECT_EQ(2, pool->getClientClasses().size()); + + // Class-less clients are to be rejected. + EXPECT_FALSE(pool->clientSupported(no_class)); + + // Clients in foo class should be accepted. + EXPECT_TRUE(pool->clientSupported(foo_class)); + + // Clients in bar class should be accepted as well. + EXPECT_TRUE(pool->clientSupported(bar_class)); +} + +// This test checks that handling for last allocated address/prefix is valid. +TEST(Pool4Test, lastAllocated) { + // Create a pool. + IOAddress first("192.0.2.0"); + Pool4Ptr pool(new Pool4(first, IOAddress("192.0.2.255"))); + + // Initial values are first invalid. + EXPECT_EQ(first.toText(), pool->getLastAllocated().toText()); + EXPECT_FALSE(pool->isLastAllocatedValid()); + + // Now set last allocated + IOAddress addr("192.0.2.100"); + EXPECT_NO_THROW(pool->setLastAllocated(addr)); + EXPECT_EQ(addr.toText(), pool->getLastAllocated().toText()); + EXPECT_TRUE(pool->isLastAllocatedValid()); + + // Reset makes it invalid and does not touch address + pool->resetLastAllocated(); + EXPECT_EQ(addr.toText(), pool->getLastAllocated().toText()); + EXPECT_FALSE(pool->isLastAllocatedValid()); +} + TEST(Pool6Test, constructor_first_last) { // let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool @@ -494,4 +594,104 @@ TEST(Pool6Test, userContext) { EXPECT_EQ(ctx->str(), pool.getContext()->str()); } +// This test checks that handling for client-class is valid. +TEST(Pool6Test, clientClass) { + // Create a pool. + Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"), + IOAddress("2001:db8::2")); + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + + // No class restrictions defined, any client should be supported + EXPECT_EQ(0, pool.getClientClasses().size()); + EXPECT_TRUE(pool.clientSupported(no_class)); + EXPECT_TRUE(pool.clientSupported(foo_class)); + EXPECT_TRUE(pool.clientSupported(bar_class)); + EXPECT_TRUE(pool.clientSupported(three_classes)); + + // Let's allow only clients belonging to "bar" class. + pool.allowClientClass("bar"); + EXPECT_EQ(1, pool.getClientClasses().size()); + + EXPECT_FALSE(pool.clientSupported(no_class)); + EXPECT_FALSE(pool.clientSupported(foo_class)); + EXPECT_TRUE(pool.clientSupported(bar_class)); + EXPECT_TRUE(pool.clientSupported(three_classes)); +} + +// This test checks that handling for multiple client-classes is valid. +TEST(Pool6Test, clientClasses) { + // Create a pool. + Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"), + IOAddress("2001:db8::2")); + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // No class restrictions defined, any client should be supported + EXPECT_EQ(0, pool.getClientClasses().size()); + EXPECT_TRUE(pool.clientSupported(no_class)); + EXPECT_TRUE(pool.clientSupported(foo_class)); + EXPECT_TRUE(pool.clientSupported(bar_class)); + + // Let's allow clients belonging to "bar" or "foo" class. + pool.allowClientClass("bar"); + pool.allowClientClass("foo"); + EXPECT_EQ(2, pool.getClientClasses().size()); + + // Class-less clients are to be rejected. + EXPECT_FALSE(pool.clientSupported(no_class)); + + // Clients in foo class should be accepted. + EXPECT_TRUE(pool.clientSupported(foo_class)); + + // Clients in bar class should be accepted as well. + EXPECT_TRUE(pool.clientSupported(bar_class)); +} + +// This test checks that handling for last allocated address/prefix is valid. +TEST(Pool6Test, lastAllocated) { + // Create a pool. + IOAddress first("2001:db8::1"); + Pool6 pool(Lease::TYPE_NA, first, IOAddress("2001:db8::200")); + + // Initial values are first invalid. + EXPECT_EQ(first.toText(), pool.getLastAllocated().toText()); + EXPECT_FALSE(pool.isLastAllocatedValid()); + + // Now set last allocated + IOAddress addr("2001:db8::100"); + EXPECT_NO_THROW(pool.setLastAllocated(addr)); + EXPECT_EQ(addr.toText(), pool.getLastAllocated().toText()); + EXPECT_TRUE(pool.isLastAllocatedValid()); + + // Reset makes it invalid and does not touch address + pool.resetLastAllocated(); + EXPECT_EQ(addr.toText(), pool.getLastAllocated().toText()); + EXPECT_FALSE(pool.isLastAllocatedValid()); +} + }; // end of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc index a969b9ccab..4226bc2eea 100644 --- a/src/lib/dhcpsrv/tests/subnet_unittest.cc +++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc @@ -168,15 +168,18 @@ TEST(Subnet4Test, pool4InSubnet4) { PoolPtr pool1(new Pool4(IOAddress("192.1.2.0"), 25)); PoolPtr pool2(new Pool4(IOAddress("192.1.2.128"), 26)); PoolPtr pool3(new Pool4(IOAddress("192.1.2.192"), 30)); + pool3->allowClientClass("bar"); + PoolPtr pool4(new Pool4(IOAddress("192.1.2.200"), 30)); // Add pools in reverse order to make sure that they get ordered by // first address. - EXPECT_NO_THROW(subnet->addPool(pool3)); + EXPECT_NO_THROW(subnet->addPool(pool4)); // If there's only one pool, get that pool PoolPtr mypool = subnet->getAnyPool(Lease::TYPE_V4); - EXPECT_EQ(mypool, pool3); + EXPECT_EQ(mypool, pool4); + EXPECT_NO_THROW(subnet->addPool(pool3)); EXPECT_NO_THROW(subnet->addPool(pool2)); EXPECT_NO_THROW(subnet->addPool(pool1)); @@ -188,8 +191,8 @@ TEST(Subnet4Test, pool4InSubnet4) { // If we provide a hint, we should get a pool that this hint belongs to ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, - IOAddress("192.1.2.195"))); - EXPECT_EQ(mypool, pool3); + IOAddress("192.1.2.201"))); + EXPECT_EQ(mypool, pool4); ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, IOAddress("192.1.2.129"))); @@ -203,7 +206,7 @@ TEST(Subnet4Test, pool4InSubnet4) { // third parameter prevents it from returning "any" available // pool if a good match is not found. ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, - IOAddress("192.1.2.200"), + IOAddress("192.1.2.210"), false)); EXPECT_FALSE(mypool); @@ -211,6 +214,57 @@ TEST(Subnet4Test, pool4InSubnet4) { IOAddress("192.1.1.254"), false)); EXPECT_FALSE(mypool); + + // Now play with classes + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + + // If we provide a hint, we should get a pool that this hint belongs to + ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, no_class, + IOAddress("192.1.2.201"))); + EXPECT_EQ(mypool, pool4); + + ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, no_class, + IOAddress("192.1.2.129"))); + EXPECT_EQ(mypool, pool2); + + ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, no_class, + IOAddress("192.1.2.64"))); + EXPECT_EQ(mypool, pool1); + + // Specify addresses which don't belong to any existing pools. + ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, three_classes, + IOAddress("192.1.2.210"))); + EXPECT_FALSE(mypool); + + // Pool3 requires a member of bar + ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, no_class, + IOAddress("192.1.2.195"))); + EXPECT_FALSE(mypool); + ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, foo_class, + IOAddress("192.1.2.195"))); + EXPECT_FALSE(mypool); + ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, bar_class, + IOAddress("192.1.2.195"))); + EXPECT_EQ(mypool, pool3); + ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, three_classes, + IOAddress("192.1.2.195"))); + EXPECT_EQ(mypool, pool3); } // Check if it's possible to get specified number of possible leases for @@ -237,6 +291,38 @@ TEST(Subnet4Test, getCapacity) { PoolPtr pool3(new Pool4(IOAddress("192.1.2.192"), 30)); subnet->addPool(pool3); EXPECT_EQ(196, subnet->getPoolCapacity(Lease::TYPE_V4)); + + // Let's add a forth pool /30. This one has 4 addresses. + PoolPtr pool4(new Pool4(IOAddress("192.1.2.200"), 30)); + subnet->addPool(pool4); + EXPECT_EQ(200, subnet->getPoolCapacity(Lease::TYPE_V4)); + + // Now play with classes + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + + pool3->allowClientClass("bar"); + + // Pool3 requires a member of bar + EXPECT_EQ(196, subnet->getPoolCapacity(Lease::TYPE_V4, no_class)); + EXPECT_EQ(196, subnet->getPoolCapacity(Lease::TYPE_V4, foo_class)); + EXPECT_EQ(200, subnet->getPoolCapacity(Lease::TYPE_V4, bar_class)); + EXPECT_EQ(200, subnet->getPoolCapacity(Lease::TYPE_V4, three_classes)); } // Checks that it is not allowed to add invalid pools. @@ -435,19 +521,44 @@ TEST(Subnet4Test, inRangeinPool) { // the first address that is in range, in pool EXPECT_TRUE(subnet->inRange(IOAddress("192.2.0.0"))); - EXPECT_TRUE (subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.0.0"))); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.0.0"))); // let's try something in the middle as well EXPECT_TRUE(subnet->inRange(IOAddress("192.2.3.4"))); - EXPECT_TRUE (subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"))); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"))); // the last address that is in range, in pool EXPECT_TRUE(subnet->inRange(IOAddress("192.2.255.255"))); - EXPECT_TRUE (subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.255.255"))); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.255.255"))); // the first address that is in range, but out of pool EXPECT_TRUE(subnet->inRange(IOAddress("192.3.0.0"))); EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.3.0.0"))); + + // Add with classes + pool1->allowClientClass("bar"); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"))); + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), no_class)); + + // This client belongs to foo only + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), foo_class)); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), bar_class)); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), three_classes)); } // This test checks if the toText() method returns text representation @@ -647,6 +758,37 @@ TEST(Subnet6Test, Pool6getCapacity) { EXPECT_EQ(uint64_t(4294967296ull + 4294967296ull + 65536), subnet->getPoolCapacity(Lease::TYPE_NA)); + // Now play with classes + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + + pool3->allowClientClass("bar"); + + // Pool3 requires a member of bar + EXPECT_EQ(uint64_t(4294967296ull + 65536), + subnet->getPoolCapacity(Lease::TYPE_NA, no_class)); + EXPECT_EQ(uint64_t(4294967296ull + 65536), + subnet->getPoolCapacity(Lease::TYPE_NA, foo_class)); + EXPECT_EQ(uint64_t(4294967296ull + 4294967296ull + 65536), + subnet->getPoolCapacity(Lease::TYPE_NA, bar_class)); + EXPECT_EQ(uint64_t(4294967296ull + 4294967296ull + 65536), + subnet->getPoolCapacity(Lease::TYPE_NA, three_classes)); + // This is 2^64 prefixes. We're overflown uint64_t. PoolPtr pool4(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:4::"), 64)); subnet->addPool(pool4); @@ -657,6 +799,7 @@ TEST(Subnet6Test, Pool6getCapacity) { subnet->addPool(pool5); EXPECT_EQ(std::numeric_limits::max(), subnet->getPoolCapacity(Lease::TYPE_NA)); + } // Test checks whether the number of prefixes available in the pools are @@ -725,6 +868,41 @@ TEST(Subnet6Test, Pool6InSubnet6) { mypool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1:3::dead:beef")); EXPECT_EQ(mypool, pool3); + + // Now play with classes + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + + // This client belongs to foo only. + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + + pool3->allowClientClass("bar"); + + // Pool3 requires a member of bar + mypool = subnet->getPool(Lease::TYPE_NA, no_class, + IOAddress("2001:db8:1:3::dead:beef")); + EXPECT_FALSE(mypool); + mypool = subnet->getPool(Lease::TYPE_NA, foo_class, + IOAddress("2001:db8:1:3::dead:beef")); + EXPECT_FALSE(mypool); + mypool = subnet->getPool(Lease::TYPE_NA, bar_class, + IOAddress("2001:db8:1:3::dead:beef")); + EXPECT_EQ(mypool, pool3); + mypool = subnet->getPool(Lease::TYPE_NA, three_classes, + IOAddress("2001:db8:1:3::dead:beef")); + EXPECT_EQ(mypool, pool3); } // Check if Subnet6 supports different types of pools properly. @@ -1198,11 +1376,11 @@ TEST(Subnet6Test, inRangeinPool) { // the first address that is in range, in pool EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::10"))); - EXPECT_TRUE (subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::10"))); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::10"))); // let's try something in the middle as well EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::18"))); - EXPECT_TRUE (subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"))); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"))); // the last address that is in range, in pool EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::20"))); @@ -1211,6 +1389,31 @@ TEST(Subnet6Test, inRangeinPool) { // the first address that is in range, but out of pool EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::21"))); EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::21"))); + + // Add with classes + pool1->allowClientClass("bar"); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"))); + + // This client does not belong to any class. + isc::dhcp::ClientClasses no_class; + EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), no_class)); + + // This client belongs to foo only + isc::dhcp::ClientClasses foo_class; + foo_class.insert("foo"); + EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), foo_class)); + + // This client belongs to bar only. I like that client. + isc::dhcp::ClientClasses bar_class; + bar_class.insert("bar"); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), bar_class)); + + // This client belongs to foo, bar and baz classes. + isc::dhcp::ClientClasses three_classes; + three_classes.insert("foo"); + three_classes.insert("bar"); + three_classes.insert("baz"); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), three_classes)); } // This test verifies that inRange() and inPool() methods work properly