-4XX. [func] tomek
++496. [func] tomek
+ DHCPv6 Allocation Engine implemented. It allows address allocation
+ from the configured subnets/pools. It currently features a single
+ allocator: IterativeAllocator, which assigns addresses iteratively.
+ Other allocators (hashed, random) are planned.
- (Trac #2324, git TBD)
++ (Trac #2324, git 8aa188a10298e3a55b725db36502a99d2a8d638a)
++
+495. [func] team
+ b10-auth now handles reconfiguration of data sources in
+ background using a separate thread. This means even if the new
+ configuration includes a large amount of data to be loaded into
+ memory (very large zones and/or a very large number of zones),
+ the reconfiguration doesn't block query handling.
+ (Multiple Trac tickets up to #2211)
494. [bug] jinmei
Fixed a problem that shutting down BIND 10 kept some of the
return (candidate);
}
-
+void
+Subnet4::validateOption(const OptionPtr& option) const {
+ if (!option) {
+ isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
+ } else if (option->getUniverse() != Option::V4) {
+ isc_throw(isc::BadValue, "expected V4 option to be added to the subnet");
+ }
+}
+
+ bool Subnet4::inPool(const isc::asiolink::IOAddress& addr) const {
+
+ // Let's start with checking if it even belongs to that subnet.
+ if (!inRange(addr)) {
+ return (false);
+ }
+
+ for (Pool4Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+ if ((*pool)->inRange(addr)) {
+ return (true);
+ }
+ }
+ // there's no pool that address belongs to
+ return (false);
+ }
+
Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
return (candidate);
}
+void
+Subnet6::validateOption(const OptionPtr& option) const {
+ if (!option) {
+ isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
+ } else if (option->getUniverse() != Option::V6) {
+ isc_throw(isc::BadValue, "expected V6 option to be added to the subnet");
+ }
+}
++
+ bool Subnet6::inPool(const isc::asiolink::IOAddress& addr) const {
+
+ // Let's start with checking if it even belongs to that subnet.
+ if (!inRange(addr)) {
+ return (false);
+ }
+
+ for (Pool6Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+ if ((*pool)->inRange(addr)) {
+ return (true);
+ }
+ }
+ // there's no pool that address belongs to
+ return (false);
+ }
+
} // end of isc::dhcp namespace
} // end of isc namespace
/// attached to it. In most cases all devices attached to a single link can
/// share the same parameters. Therefore Subnet holds several values that are
/// typically shared by all hosts: renew timer (T1), rebind timer (T2) and
-/// leased addresses lifetime (valid-lifetime).
+/// leased addresses lifetime (valid-lifetime). It also holds the set
+/// of DHCP option instances configured for the subnet. These options are
+/// included in DHCP messages being sent to clients which are connected
+/// to the particular subnet.
+ ///
+ /// @todo: Implement support for options here
+
+
+ /// @brief Unique indentifier for a subnet (both v4 and v6)
+ typedef uint32_t SubnetID;
+
class Subnet {
public:
+
+ /// @brief Option descriptor.
+ ///
+ /// Option descriptor holds information about option configured for
+ /// a particular subnet. This information comprises the actual option
+ /// instance and information whether this option is sent to DHCP client
+ /// only on request (persistent = false) or always (persistent = true).
+ struct OptionDescriptor {
+ /// Option instance.
+ OptionPtr option;
+ /// Persistent flag, if true option is always sent to the client,
+ /// if false option is sent to the client on request.
+ bool persistent;
+
+ /// @brief Constructor.
+ ///
+ /// @param opt option
+ /// @param persist if true option is always sent.
+ OptionDescriptor(OptionPtr& opt, bool persist)
+ : option(opt), persistent(persist) {};
+ };
+
+ /// @brief Extractor class to extract key with another key.
+ ///
+ /// This class solves the problem of accessing index key values
+ /// that are stored in objects nested in other objects.
+ /// Each OptionDescriptor structure contains the OptionPtr object.
+ /// The value retured by one of its accessors (getType) is used
+ /// as an indexing value in the multi_index_container defined below.
+ /// There is no easy way to mark that value returned by Option::getType
+ /// should be an index of this multi_index_container. There are standard
+ /// key extractors such as 'member' or 'mem_fun' but they are not
+ /// sufficient here. The former can be used to mark that member of
+ /// the structure that is held in the container should be used as an
+ /// indexing value. The latter can be used if the indexing value is
+ /// a product of the class being held in the container. In this complex
+ /// scenario when the indexing value is a product of the function that
+ /// is wrapped by the structure, this new extractor template has to be
+ /// defined. The template class provides a 'chain' of two extractors
+ /// to access the value returned by nested object and to use it as
+ /// indexing value.
+ /// For some more examples of complex keys see:
+ /// http://www.cs.brown.edu/~jwicks/boost/libs/multi_index/doc/index.html
+ ///
+ /// @tparam KeyExtractor1 extractor used to access data in
+ /// OptionDescriptor::option
+ /// @tparam KeyExtractor2 extractor used to access
+ /// OptionDescriptor::option member.
+ template<typename KeyExtractor1, typename KeyExtractor2>
+ class KeyFromKey {
+ public:
+ typedef typename KeyExtractor1::result_type result_type;
+
+ /// @brief Constructor.
+ KeyFromKey()
+ : key1_(KeyExtractor1()), key2_(KeyExtractor2()) { };
+
+ /// @brief Extract key with another key.
+ ///
+ /// @param arg the key value.
+ ///
+ /// @tparam key value type.
+ template<typename T>
+ result_type operator() (T& arg) const {
+ return (key1_(key2_(arg)));
+ }
+ private:
+ KeyExtractor1 key1_; ///< key 1.
+ KeyExtractor2 key2_; ///< key 2.
+ };
+
+ /// @brief Multi index container for DHCP option descriptors.
+ ///
+ /// This container comprises three indexes to access option
+ /// descriptors:
+ /// - sequenced index: used to access elements in the order they
+ /// have been added to the container,
+ /// - option type index: used to search option descriptors containing
+ /// options with specific option code (aka option type).
+ /// - persistency flag index: used to search option descriptors with
+ /// 'persistent' flag set to true.
+ ///
+ /// This container is the equivalent of three separate STL containers:
+ /// - std::list of all options,
+ /// - std::multimap of options with option code used as a multimap key,
+ /// - std::multimap of option descriptors with option persistency flag
+ /// used as a multimap key.
+ /// The major advantage of this container over 3 separate STL containers
+ /// is automatic synchronization of all indexes when elements are added,
+ /// removed or modified in the container. With separate containers,
+ /// the synchronization would have to be guaranteed by the Subnet class
+ /// code. This would increase code complexity and presumably it would
+ /// be much harder to add new search criteria (indexes).
+ ///
+ /// @todo we may want to search for options using option spaces when
+ /// they are implemented.
+ ///
+ /// @see http://www.boost.org/doc/libs/1_51_0/libs/multi_index/doc/index.html
+ typedef boost::multi_index_container<
+ // Container comprises elements of OptionDescriptor type.
+ OptionDescriptor,
+ // Here we start enumerating various indexes.
+ boost::multi_index::indexed_by<
+ // Sequenced index allows accessing elements in the same way
+ // as elements in std::list.
+ // Sequenced is an index #0.
+ boost::multi_index::sequenced<>,
+ // Start definition of index #1.
+ boost::multi_index::hashed_non_unique<
+ // KeyFromKey is the index key extractor that allows accessing
+ // option type being held by the OptionPtr through
+ // OptionDescriptor structure.
+ KeyFromKey<
+ // Use option type as the index key. The type is held
+ // in OptionPtr object so we have to call Option::getType
+ // to retrieve this key for each element.
+ boost::multi_index::mem_fun<
+ Option,
+ uint16_t,
+ &Option::getType
+ >,
+ // Indicate that OptionPtr is a member of
+ // OptionDescriptor structure.
+ boost::multi_index::member<
+ OptionDescriptor,
+ OptionPtr,
+ &OptionDescriptor::option
+ >
+ >
+ >,
+ // Start definition of index #2.
+ // Use 'persistent' struct member as a key.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::member<
+ OptionDescriptor,
+ bool,
+ &OptionDescriptor::persistent
+ >
+ >
+ >
+ > OptionContainer;
+
+ /// Type of the index #1 - option type.
+ typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
+ /// Type of the index #2 - option persistency flag.
+ typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
+
/// @brief checks if specified address is in range
bool inRange(const isc::asiolink::IOAddress& addr) const;
+ /// @brief Add new option instance to the collection.
+ ///
+ /// @param option option instance.
+ /// @param persistent if true, send an option regardless if client
+ /// requested it or not.
+ ///
+ /// @throw isc::BadValue if invalid option provided.
+ void addOption(OptionPtr& option, bool persistent = false);
+
+ /// @brief Delete all options configured for the subnet.
+ void delOptions();
+
+ /// @brief checks if the specified address is in pools
+ ///
+ /// Note the difference between inSubnet() and inPool(). For a given
+ /// subnet (e.g. 2001::/64) there may be one or more pools defined
+ /// that may or may not cover entire subnet, e.g. pool 2001::1-2001::10).
+ /// inPool() returning true implies inSubnet(), but the reverse implication
+ /// is not always true. For the given example, 2001::1234:abcd would return
+ /// true for inSubnet(), but false for inPool() check.
+ ///
+ /// @param addr this address will be checked if it belongs to any pools in
+ /// that subnet
+ /// @return true if the address is in any of the pools
+ virtual bool inPool(const isc::asiolink::IOAddress& addr) const = 0;
+
/// @brief return valid-lifetime for addresses in that prefix
Triplet<uint32_t> getValid() const {
return (valid_);
return (t2_);
}
+ /// @brief Return a collection of options.
+ ///
+ /// @return reference to collection of options configured for a subnet.
+ /// The returned reference is valid as long as the Subnet object which
+ /// returned it still exists.
+ const OptionContainer& getOptions() {
+ return (options_);
+ }
+
+ /// @brief returns the last address that was tried from this pool
+ ///
+ /// 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
+ /// AllocEngine::IterativeAllocator and keep the data there
+ ///
+ /// @return address that was last tried from this pool
+ isc::asiolink::IOAddress getLastAllocated() const {
+ return (last_allocated_);
+ }
+
+ /// @brief sets the last address that was tried from this pool
+ ///
+ /// 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
+ /// AllocEngine::IterativeAllocator and keep the data there
+ void setLastAllocated(const isc::asiolink::IOAddress& addr) {
+ last_allocated_ = addr;
+ }
+
+ /// @brief returns unique ID for that subnet
+ /// @return unique ID for that subnet
+ SubnetID getID() const { return (id_); }
+
protected:
/// @brief protected constructor
//
/// @brief a tripet (min/default/max) holding allowed valid lifetime values
Triplet<uint32_t> valid_;
+ /// @brief a collection of DHCP options configured for a subnet.
+ OptionContainer options_;
++
+ /// @brief last allocated address
+ ///
+ /// This is the last allocated address that was previously allocated from
+ /// this particular subnet. Some allocation algorithms (e.g. iterative) use
+ /// that value, others do not. It should be noted that although the value
+ /// is usually correct, there are cases when it is invalid, e.g. after
+ /// removing a pool, restarting or changing allocation algorithms. For
+ /// that purpose it should be only considered a help that should not be
+ /// fully trusted.
+ isc::asiolink::IOAddress last_allocated_;
};
/// @brief A configuration holder for IPv4 subnet.
return pools_;
}
+ /// @brief checks if the specified address is in pools
+ ///
+ /// See the description in \ref Subnet::inPool().
+ ///
+ /// @param addr this address will be checked if it belongs to any pools in that subnet
+ /// @return true if the address is in any of the pools
+ bool inPool(const isc::asiolink::IOAddress& addr) const;
+
protected:
+
+ /// @brief Check if option is valid and can be added to a subnet.
+ ///
+ /// @param option option to be validated.
+ ///
+ /// @throw isc::BadValue if provided option is invalid.
+ virtual void validateOption(const OptionPtr& option) const;
+
/// @brief collection of pools in that list
Pool4Collection pools_;
};
return pools_;
}
+ /// @brief checks if the specified address is in pools
+ ///
+ /// See the description in \ref Subnet::inPool().
+ ///
+ /// @param addr this address will be checked if it belongs to any pools in that subnet
+ /// @return true if the address is in any of the pools
+ bool inPool(const isc::asiolink::IOAddress& addr) const;
+
protected:
+
+ /// @brief Check if option is valid and can be added to a subnet.
+ ///
+ /// @param option option to be validated.
+ ///
+ /// @throw isc::BadValue if provided option is invalid.
+ virtual void validateOption(const OptionPtr& option) const;
+
/// @brief collection of pools in that list
Pool6Collection pools_;
libdhcpsrv_unittests_LDADD = $(GTEST_LDADD)
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+ libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
EXPECT_THROW(subnet->addPool4(pool3), BadValue);
}
+TEST(Subnet4Test, addInvalidOption) {
+ // Create the V4 subnet.
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
+
+ // Some dummy option code.
+ uint16_t code = 100;
+ // Create option with invalid universe (V6 instead of V4).
+ // Attempt to add this option should result in exception.
+ OptionPtr option1(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+
+ // Create NULL pointer option. Attempt to add NULL option
+ // should result in exception.
+ OptionPtr option2;
+ ASSERT_FALSE(option2);
+ EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+}
+
+ // This test verifies that inRange() and inPool() methods work properly.
+ TEST(Subnet4Test, inRangeinPool) {
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.0.0"), 8, 1, 2, 3));
+
+ // this one is in subnet
+ Pool4Ptr pool1(new Pool4(IOAddress("192.2.0.0"), 16));
+ subnet->addPool4(pool1);
+
+ // 192.1.1.1 belongs to the subnet...
+ EXPECT_TRUE(subnet->inRange(IOAddress("192.1.1.1")));
+
+ // ... but it does not belong to any pool within
+ EXPECT_FALSE(subnet->inPool(IOAddress("192.1.1.1")));
+
+ // the last address that is in range, but out of pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("192.1.255.255")));
+ EXPECT_FALSE(subnet->inPool(IOAddress("192.1.255.255")));
+
+ // the first address that is in range, in pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("192.2.0.0")));
+ EXPECT_TRUE (subnet->inPool(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(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(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(IOAddress("192.3.0.0")));
+ }
+
// Tests for Subnet6
TEST(Subnet6Test, constructor) {
EXPECT_THROW(subnet->addPool6(pool4), BadValue);
}
-
+TEST(Subnet6Test, addOptions) {
+ // Create as subnet to add options to it.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addOption(option));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ Subnet::OptionContainer options = subnet->getOptions();
+ ASSERT_EQ(10, options.size());
+
+ // Validate codes of added options.
+ uint16_t expected_code = 100;
+ for (Subnet::OptionContainer::const_iterator option_desc = options.begin();
+ option_desc != options.end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option);
+ EXPECT_EQ(expected_code, option_desc->option->getType());
+ ++expected_code;
+ }
+
+ subnet->delOptions();
+
+ options = subnet->getOptions();
+ EXPECT_EQ(0, options.size());
+}
+
+TEST(Subnet6Test, addNonUniqueOptions) {
+ // Create as subnet to add options to it.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+ // Create a set of options with non-unique codes.
+ for (int i = 0; i < 2; ++i) {
+ // In the inner loop we create options with unique codes (100-109).
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addOption(option));
+ }
+ }
+
+ // Sanity check that all options are there.
+ Subnet::OptionContainer options = subnet->getOptions();
+ ASSERT_EQ(20, options.size());
+
+ // Use container index #1 to get the options by their codes.
+ Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ // Look for the codes 100-109.
+ for (uint16_t code = 100; code < 110; ++ code) {
+ // For each code we should get two instances of options.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(code);
+ // Distance between iterators indicates how many options
+ // have been retured for the particular code.
+ ASSERT_EQ(2, distance(range.first, range.second));
+ // Check that returned options actually have the expected option code.
+ for (Subnet::OptionContainerTypeIndex::const_iterator option_desc = range.first;
+ option_desc != range.second; ++option_desc) {
+ ASSERT_TRUE(option_desc->option);
+ EXPECT_EQ(code, option_desc->option->getType());
+ }
+ }
+
+ // Let's try to find some non-exiting option.
+ const uint16_t non_existing_code = 150;
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(non_existing_code);
+ // Empty set is expected.
+ EXPECT_EQ(0, distance(range.first, range.second));
+
+ subnet->delOptions();
+
+ options = subnet->getOptions();
+ EXPECT_EQ(0, options.size());
+}
+
+TEST(Subnet6Test, addInvalidOption) {
+ // Create as subnet to add options to it.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+ // Some dummy option code.
+ uint16_t code = 100;
+ // Create option with invalid universe (V4 instead of V6).
+ // Attempt to add this option should result in exception.
+ OptionPtr option1(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+
+ // Create NULL pointer option. Attempt to add NULL option
+ // should result in exception.
+ OptionPtr option2;
+ ASSERT_FALSE(option2);
+ EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+}
+
+TEST(Subnet6Test, addPersistentOption) {
+ // Create as subnet to add options to it.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+ // Add 10 options to the subnet with option codes 100 - 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ // We create 10 options and want some of them to be flagged
+ // persistent and some non-persistent. Persistent options are
+ // those that server sends to clients regardless if they ask
+ // for them or not. We pick 3 out of 10 options and mark them
+ // non-persistent and 7 other options persistent.
+ // Code values: 102, 105 and 108 are divisable by 3
+ // and options with these codes will be flagged non-persistent.
+ // Options with other codes will be flagged persistent.
+ bool persistent = (code % 3) ? true : false;
+ ASSERT_NO_THROW(subnet->addOption(option, persistent));
+ }
+
+ // Get added options from the subnet.
+ Subnet::OptionContainer options = subnet->getOptions();
+
+ // options.get<2> returns reference to container index #2. This
+ // index is used to access options by the 'persistent' flag.
+ Subnet::OptionContainerPersistIndex& idx = options.get<2>();
+
+ // Get all persistent options.
+ std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
+ Subnet::OptionContainerPersistIndex::const_iterator> range_persistent =
+ idx.equal_range(true);
+ // 3 out of 10 options have been flagged persistent.
+ ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
+
+ // Get all non-persistent options.
+ std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
+ Subnet::OptionContainerPersistIndex::const_iterator> range_non_persistent =
+ idx.equal_range(false);
+ // 7 out of 10 options have been flagged persistent.
+ ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
+
+ subnet->delOptions();
+
+ options = subnet->getOptions();
+ EXPECT_EQ(0, options.size());
+}
++
+ // This test verifies that inRange() and inPool() methods work properly.
+ TEST(Subnet6Test, inRangeinPool) {
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));
+
+ // this one is in subnet
+ Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::10"),
+ IOAddress("2001:db8::20")));
+ subnet->addPool6(pool1);
+
+ // 192.1.1.1 belongs to the subnet...
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
+ // ... but it does not belong to any pool within
+ EXPECT_FALSE(subnet->inPool(IOAddress("2001:db8::1")));
+
+ // the last address that is in range, but out of pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::f")));
+ EXPECT_FALSE(subnet->inPool(IOAddress("2001:db8::f")));
+
+ // the first address that is in range, in pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::10")));
+ EXPECT_TRUE (subnet->inPool(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(IOAddress("2001:db8::18")));
+
+ // the last address that is in range, in pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::20")));
+ EXPECT_TRUE (subnet->inPool(IOAddress("2001:db8::20")));
+
+ // the first address that is in range, but out of pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::21")));
+ EXPECT_FALSE(subnet->inPool(IOAddress("2001:db8::21")));
+ }
+
};