]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[5425a] Rebased and known client removal: first phase done
authorFrancis Dupont <fdupont@isc.org>
Thu, 18 Jan 2018 23:54:40 +0000 (00:54 +0100)
committerFrancis Dupont <fdupont@isc.org>
Thu, 18 Jan 2018 23:54:40 +0000 (00:54 +0100)
15 files changed:
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/dhcp_parsers.h
src/lib/dhcpsrv/pool.cc
src/lib/dhcpsrv/pool.h
src/lib/dhcpsrv/subnet.cc
src/lib/dhcpsrv/subnet.h
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
src/lib/dhcpsrv/tests/alloc_engine_utils.h
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
src/lib/dhcpsrv/tests/pool_unittest.cc
src/lib/dhcpsrv/tests/subnet_unittest.cc

index 28e35668f77e7f90e766cd23f0f9fac1eca4691c..5347cfc5847e0cf9acb2c045413b9b8f7d94f48d 100644 (file)
@@ -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<Pool6>(*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<Pool6>(*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<Pool6>
-            (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<Pool6>(
-                    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<int64_t>(-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)) {
index e33b2f7056ac2245851f833bd31c400add7bb301..af5c44da217b2d0b8b5c49ee5d8b0d06e512043f 100644 (file)
@@ -8,6 +8,7 @@
 #define ALLOC_ENGINE_H
 
 #include <asiolink/io_address.h>
+#include <dhcp/classify.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/pkt4.h>
@@ -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
index 4f5c5f8fdd4bf2c634dc41ed1be8515f6fb62679..b10d7e7edaabab48f339aed5c6369888b803f8b2 100644 (file)
@@ -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_);
 }
index 0941e70b46cf7cad66f5c5fae72d84e611a6c2e8..e195cd4bad33a934fef07c21fe8efa36df84007b 100644 (file)
@@ -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.
index 1542b8ca1876388e3b062f716873bc032ef6c098..dc169b0baebbc2ff42d20fa9e040a1074484e81e 100644 (file)
@@ -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);
 }
 
index 5bdefceccb5713eb82f7ee7041f1a6e0197e3bd6..c7c4aa05db9185f86839c699a7479e77de0cb8f3 100644 (file)
@@ -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 <asiolink/io_address.h>
+#include <dhcp/classify.h>
 #include <dhcp/option6_pdexclude.h>
 #include <boost/shared_ptr.hpp>
 #include <cc/data.h>
@@ -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
index 945256b7b036de7f9eaf57d6b0e683be36e016b0..3b27412280167372c7c8bdc1514b46b4962b5b64 100644 (file)
@@ -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<int>(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<uint64_t>::max() - sum) {
+            return (std::numeric_limits<uint64_t>::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);
     }
index ed556988990be082ab61e06b29bd692a91f39c65..5ac4b908c467daf061de44e8f492d00b276ae179 100644 (file)
@@ -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<SubnetID, IOAddress> somewhere in the
+    /// @todo: Define map<SubnetID, ClientClass, IOAddress> 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<SubnetID, IOAddress> somewhere in the
+    /// @todo: Define map<SubnetID, ClientClass, IOAddress> 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.
index ff216a6a77957e609cde4127bbebebd6cecfc1de..3e1a37058056cfd6407e393ee4c7d9fe56c1f9c5 100644 (file)
@@ -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<NakedAllocEngine::Allocator>
+        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<IOAddress> 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).
index 1002958cfb44a1b86a608483d5fc65c98fb8f48e..e96ebbd37bbe276c9465c5846554d03179e4b9c5 100644 (file)
@@ -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<NakedAllocEngine::Allocator>
+        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<IOAddress> 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.
index 426189eaaaab92efa7903b64d8dcb729f506eb2b..eeed469755caeab813b2f9ebce8438b5fa4b37cd 100644 (file)
@@ -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.
 };
 
index cfe0363bdab1befb2a94b9bb30626f932770a05a..fb2f22a554c23768b213ca697a1cc289a42f1c49 100644 (file)
@@ -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"
index f8200fad0652a2a8a0dcf72cae02964791e39aa6..5565387d262052d652f5140c932b37ea796887fc 100644 (file)
@@ -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"
index b78cf1fbcd2d591698f650b39f85d987ec8eba06..5662f6a5ce158281bd24f4d000f63809dab82753 100644 (file)
@@ -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
index a969b9ccab0a0108ee9ac58ae178bbb5a8275c07..4226bc2eeac4f9ac77a5370eb097cfbddab337ea 100644 (file)
@@ -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<uint64_t>::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