]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[master] Merge branch 'trac2324' (DHCPv6 allocation engine)
authorTomek Mrugalski <tomasz@isc.org>
Mon, 29 Oct 2012 12:18:27 +0000 (13:18 +0100)
committerTomek Mrugalski <tomasz@isc.org>
Mon, 29 Oct 2012 12:18:27 +0000 (13:18 +0100)
Conflicts:
ChangeLog
src/lib/dhcp/subnet.cc
src/lib/dhcp/subnet.h
src/lib/dhcp/tests/subnet_unittest.cc

1  2 
ChangeLog
src/lib/dhcp/Makefile.am
src/lib/dhcp/subnet.cc
src/lib/dhcp/subnet.h
src/lib/dhcp/tests/Makefile.am
src/lib/dhcp/tests/subnet_unittest.cc

diff --cc ChangeLog
index e31f44ca5558c46e865d05de5fd67334982b1e6f,6ec7aec8bb068790f8ee90c291df654aead57770..b7ed58b4a6a89798cb5d70f933c5ad3d291aeb59
+++ b/ChangeLog
@@@ -1,10 -1,9 +1,17 @@@
 -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
Simple merge
index cb82a9f12f771339e3177904feea2a79523091dd,230a1b9ebd3f7a3a9e9a67fd209f560bb0ff3563..f6ce1b154aa60cbfaa359779632212ef4c534146
@@@ -96,15 -85,23 +96,31 @@@ Pool4Ptr Subnet4::getPool4(const isc::a
      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,
@@@ -151,13 -148,21 +167,30 @@@ Pool6Ptr Subnet6::getPool6(const isc::a
      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
index aa680ce5938eca662bc5d7c6779be7032a2c979b,714ed9e502b71b7143bcd0a45bef03f31319ba4c..894d8079905e275c39110b78a77dbfbfb0e7f332
@@@ -36,174 -30,33 +36,195 @@@ namespace dhcp 
  /// 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.
@@@ -321,15 -195,15 +382,23 @@@ public
          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_;
  };
@@@ -389,15 -263,15 +458,23 @@@ public
          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_;
  
index a81647232799c5fd163ec47d74f9133a9795480f,cb9941c11f0dcf17189fa0601b3aff02c77ce366..fcbdec81366d73ea6d29d0b16ad6d7f7525b0236
@@@ -56,8 -55,8 +58,9 @@@ libdhcpsrv_unittests_CXXFLAGS = $(AM_CX
  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
  
  
index 0e2e846141b3e9f06294b8a6044608976417e784,825c354386187618cc60e823257bc969c7d67a69..be25bc1ac9b6a34392a63a471900cff9bdb453c0
@@@ -105,24 -104,41 +105,59 @@@ TEST(Subnet4Test, Subnet4_Pool4_checks
      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) {
@@@ -206,146 -221,40 +240,182 @@@ TEST(Subnet6Test, Subnet6_Pool6_checks
      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")));
+ }
  };