Browse Source

[3625] Implemented the CfgSubnets6 class to hold IPv6 subnets configuration

Marcin Siodelski 10 years ago
parent
commit
452efc3208

+ 2 - 0
src/lib/dhcpsrv/Makefile.am

@@ -49,6 +49,7 @@ libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h
+libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
 libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
 libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h
 libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
@@ -79,6 +80,7 @@ libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
 libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h
 libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libkea_dhcpsrv_la_SOURCES += subnet_id.h
+libkea_dhcpsrv_la_SOURCES += subnet_selector.h
 libkea_dhcpsrv_la_SOURCES += triplet.h
 libkea_dhcpsrv_la_SOURCES += utils.h
 

+ 182 - 0
src/lib/dhcpsrv/cfg_subnets6.cc

@@ -0,0 +1,182 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/subnet_id.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+void
+CfgSubnets6::add(const Subnet6Ptr& subnet) {
+    /// @todo: Check that this new subnet does not cross boundaries of any
+    /// other already defined subnet.
+    if (isDuplicate(*subnet)) {
+        isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv6 subnet '"
+                  << subnet->getID() << "' is already in use");
+    }
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
+              .arg(subnet->toText());
+    subnets_.push_back(subnet);
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const SubnetSelector& selector) const {
+    Subnet6Ptr subnet;
+
+    // If relay agent link address is set to zero it means that we're dealing
+    // with a directly connected client.
+    if (selector.first_relay_linkaddr_ == IOAddress("::")) {
+        // If interface name is known try to match it with interface names
+        // specified for configured subnets.
+        if (!selector.iface_name_.empty()) {
+            subnet = selectSubnet(selector.iface_name_,
+                                  selector.client_classes_);
+        }
+
+        // If interface name didn't match, try the client's address.
+        if (!subnet && selector.remote_address_.isSpecified()) {
+            subnet = selectSubnet(selector.remote_address_,
+                                  selector.client_classes_);
+        }
+
+    // If relay agent link address is set, we're dealing with a relayed message.
+    } else {
+
+        // Find the subnet using the Interface Id option, if present.
+        subnet = selectSubnet(selector.interface_id_, selector.client_classes_);
+
+        // If Interface ID option could not be matched for any subnet, try
+        // the relay agent link address.
+        if (!subnet) {
+            subnet = selectSubnet(selector.first_relay_linkaddr_,
+                                  selector.client_classes_,
+                                  true);
+        }
+    }
+
+    // Return subnet found, or NULL if not found.
+    return (subnet);
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const asiolink::IOAddress& address,
+                          const ClientClasses& client_classes,
+                          const bool is_relay_address) const {
+
+    // If the specified address is a relay address we first need to match
+    // it with the relay addresses specified for all subnets.
+    if (is_relay_address) {
+        for (Subnet6Collection::const_iterator subnet = subnets_.begin();
+             subnet != subnets_.end(); ++subnet) {
+
+            // If the specified address matches the relay address, return this
+            // subnet.
+            if (is_relay_address &&
+                ((*subnet)->getRelayInfo().addr_ == address) &&
+                (*subnet)->clientSupported(client_classes)) {
+                LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                          DHCPSRV_CFGMGR_SUBNET6_RELAY)
+                    .arg((*subnet)->toText()).arg(address.toText());
+                return (*subnet);
+            }
+        }
+    }
+
+    // No success so far. Check if the specified address is in range
+    // with any subnet.
+    for (Subnet6Collection::const_iterator subnet = subnets_.begin();
+         subnet != subnets_.end(); ++subnet) {
+        if ((*subnet)->inRange(address) &&
+            (*subnet)->clientSupported(client_classes)) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_SUBNET6)
+                      .arg((*subnet)->toText()).arg(address.toText());
+            return (*subnet);
+        }
+    }
+
+    // Nothing found.
+    return (Subnet6Ptr());
+}
+
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const std::string& iface_name,
+                          const ClientClasses& client_classes) const {
+
+    // If empty interface specified, we can't select subnet by interface.
+    if (!iface_name.empty()) {
+        for (Subnet6Collection::const_iterator subnet = subnets_.begin();
+             subnet != subnets_.end(); ++subnet) {
+
+            // If interface name matches with the one specified for the subnet
+            // and the client is not rejected based on the classification,
+            // return the subnet.
+            if ((iface_name == (*subnet)->getIface()) &&
+                (*subnet)->clientSupported(client_classes)) {
+
+                LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                          DHCPSRV_CFGMGR_SUBNET6_IFACE)
+                    .arg((*subnet)->toText()).arg(iface_name);
+                return (*subnet);
+            }
+        }
+    }
+
+    // No subnet found for this interface name.
+    return (Subnet6Ptr());
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const OptionPtr& interface_id,
+                          const ClientClasses& client_classes) const {
+    // We can only select subnet using an interface id, if the interface
+    // id is known.
+    if (interface_id) {
+        for (Subnet6Collection::const_iterator subnet = subnets_.begin();
+             subnet != subnets_.end(); ++subnet) {
+
+            // If interface id matches for the subnet and the subnet is not
+            // rejected based on the classification.
+            if ((*subnet)->getInterfaceId() &&
+                (*subnet)->getInterfaceId()->equals(interface_id) &&
+                (*subnet)->clientSupported(client_classes)) {
+
+                LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
+                    .arg((*subnet)->toText());
+                return (*subnet);
+            }
+        }
+    }
+    // No subnet found.
+    return (Subnet6Ptr());
+}
+
+bool
+CfgSubnets6::isDuplicate(const Subnet6& subnet) const {
+    for (Subnet6Collection::const_iterator subnet_it = subnets_.begin();
+         subnet_it != subnets_.end(); ++subnet_it) {
+        if ((*subnet_it)->getID() == subnet.getID()) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 174 - 0
src/lib/dhcpsrv/cfg_subnets6.h

@@ -0,0 +1,174 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CFG_SUBNETS6_H
+#define CFG_SUBNETS6_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <util/optional_value.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds subnets configured for the DHCPv6 server.
+///
+/// This class holds a collection of subnets configured for the DHCPv6 server.
+/// It allows for retrieving a subnet for the particular client using various
+/// parameters extracted from the DHCPv6 message. These parameters must be
+/// assigned to the appropriate members of the @c SubnetSelector structure.
+///
+/// See @c CfgSubnets6::selectSubnet documentation for more details on how the subnet
+/// is selected for the client.
+class CfgSubnets6 {
+public:
+
+    /// @brief Adds new subnet to the configuration.
+    ///
+    /// @param subnet Pointer to the subnet being added.
+    ///
+    /// @throw isc::DuplicateSubnetID If the subnet id for the new subnet
+    /// duplicates id of an existing subnet.
+    void add(const Subnet6Ptr& subnet);
+
+    /// @brief Returns pointer to the collection of all IPv6 subnets.
+    ///
+    /// This is used in a hook (subnet6_select), where the hook is able
+    /// to choose a different subnet. Server code has to offer a list
+    /// of possible choices (i.e. all subnets).
+    ///
+    /// @return A pointer to const Subnet6 collection
+    const Subnet6Collection* getAll() const {
+        return (&subnets_);
+    }
+
+    /// @brief Selects a subnet using parameters specified in the selector.
+    ///
+    /// This method tries to retrieve the subnet for the client using various
+    /// parameters extracted from the client's message using the following
+    /// logic.
+    ///
+    /// If the relay agent link address is set to zero it is assumed that
+    /// the subnet is selected for the directly connected client.
+    /// In this case it is checked if there is any subnet associated with the
+    /// interface over which the message has been received. If there is no
+    /// subnet explicitly associated with this interface the client's address
+    /// will be used to check if the address is in range with any of the
+    /// subnets.
+    ///
+    /// If the message was relayed it is possible that the relay agent has
+    /// appended an Interface ID option. If this option is present, the method
+    /// will check if it matches with any explicitly specified interface id
+    /// for any subnet. If it does, the subnet is returned. Otherwise, the
+    /// relay agents link address is used to select the subnet. In this case,
+    /// the method will first check if this link address is explicitly
+    /// associated with any subnet. If not, it is checked if the link address
+    /// is in range with any of the subnets.
+    ///
+    /// @param selector Const reference to the selector structure which holds
+    /// various information extracted from the client's packet which are used
+    /// to find appropriate subnet.
+    ///
+    /// @return Pointer to the selected subnet or NULL if no subnet found.
+    Subnet6Ptr selectSubnet(const SubnetSelector& selector) const;
+
+    /// @brief Selects the subnet using a specified address.
+    ///
+    /// This method searches for the subnet using the specified address. If
+    /// the specified address is a link address on the relay agent (which is
+    /// indicated by the 3rd argument) the method will first try to match the
+    /// specified address with the relay addresses explicitly specified for
+    /// existing subnets. If no match is found, the method will check if the
+    /// address is in range with any of the subnets.
+    ///
+    /// If the address is not a relay agent link address (@c is_relay_address
+    /// is set to false), the method will simply check if the address is in
+    /// range with any of the subnets.
+    ///
+    /// @param address Address for which the subnet is searched.
+    /// @param client_classes Optional parameter specifying the classes that
+    /// the client belongs to.
+    /// @param is_relay_address Specifies if the provided address is an
+    /// address of the relay agent (true) or not (false).
+    ///
+    /// @return Pointer to the selected subnet or NULL if no subnet found.
+    Subnet6Ptr
+    selectSubnet(const asiolink::IOAddress& address,
+                 const ClientClasses& client_classes = ClientClasses(),
+                 const bool is_relay_address = false) const;
+
+private:
+
+    /// @brief Selects a subnet using the interface name.
+    ///
+    /// This method searches for the subnet using the name of the interface.
+    /// If any of the subnets is explicitly associated with the interface
+    /// name, the subnet is returned.
+    ///
+    /// @param iface_name Interface name.
+    /// @param client_classes Optional parameter specifying the classes that
+    /// the client belongs to.
+    ///
+    /// @return Pointer to the selected subnet or NULL if no subnet found.
+    Subnet6Ptr
+    selectSubnet(const std::string& iface_name,
+                 const ClientClasses& client_classes) const;
+
+    /// @brief Selects a subnet using Interface ID option.
+    ///
+    /// This method searches for the subnet using the Interface ID option
+    /// inserted by the relay agent to the message from a client. If any 
+    /// of the subnets is explicitly associated with that interface id, the
+    /// subnet is returned.
+    ///
+    /// @param interface_id An instance of the Interface ID option received
+    /// from the client.
+    /// @param client_classes Optional parameter specifying the classes that
+    /// the client belongs to.
+    ///
+    /// @return Pointer to the selected subnet or NULL if no subnet found.
+    Subnet6Ptr
+    selectSubnet(const OptionPtr& interface_id,
+                 const ClientClasses& client_classes) const;
+
+    /// @brief Checks that the IPv6 subnet with the given id already exists.
+    ///
+    /// @param subnet Subnet for which this function will check if the other
+    /// subnet with equal id already exists.
+    ///
+    /// @return true if the duplicate subnet exists.
+    bool isDuplicate(const Subnet6& subnet) const;
+
+    /// @brief A container for IPv6 subnets.
+    Subnet6Collection subnets_;
+
+};
+
+/// @name Pointer to the @c CfgSubnets6 objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgSubnets6> CfgSubnets6Ptr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgSubnets6> ConstCfgSubnets6Ptr;
+
+//@}
+
+}
+}
+
+#endif // CFG_SUBNETS6_H

+ 76 - 0
src/lib/dhcpsrv/subnet_selector.h

@@ -0,0 +1,76 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef SUBNET_SELECTOR_H
+#define SUBNET_SELECTOR_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/classify.h>
+#include <dhcp/option.h>
+#include <util/optional_value.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Subnet selector used to specify parameters used to select a subnet.
+///
+/// This structure holds various parameters extracted from a packet sent
+/// by a DHCP client used to select the subnet for the client. This selector
+/// is common for IPv4 and IPv6 subnets.
+struct SubnetSelector {
+    /// @name DHCPv4 specific parameters.
+    //@{
+    /// @brief ciaddr from the client's message.
+    asiolink::IOAddress ciaddr_;
+    /// @brief giaddr from the client's message.
+    asiolink::IOAddress giaddr_;
+    //@}
+
+    /// @name DHCPv6 specific parameters.
+    //@{
+    /// @brief Interface id option.
+    OptionPtr interface_id_;
+    /// @brief First relay link address.
+    asiolink::IOAddress first_relay_linkaddr_;
+    //@}
+
+    /// @brief Address on which the message was received.
+    util::OptionalValue<asiolink::IOAddress> local_address_;
+    /// @brief Source address of the message.
+    util::OptionalValue<asiolink::IOAddress> remote_address_;
+    /// @brief Classes that the client belongs to.
+    ClientClasses client_classes_;
+    /// @brief Name of the interface on which the message was received.
+    std::string iface_name_;
+
+    /// @brief Default constructor.
+    ///
+    /// Sets the default values for the @c Selector.
+    SubnetSelector()
+        : ciaddr_(asiolink::IOAddress("0.0.0.0")),
+          giaddr_(asiolink::IOAddress("0.0.0.0")),
+          interface_id_(),
+          first_relay_linkaddr_(asiolink::IOAddress("::")),
+          local_address_(asiolink::IOAddress("0.0.0.0"), false),
+          remote_address_(asiolink::IOAddress("0.0.0.0"), false),
+          client_classes_(), iface_name_(std::string()) {
+    }
+};
+
+
+}
+}
+
+#endif // SUBNET_SELECTOR_H

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -59,6 +59,7 @@ libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_subnets4_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_subnets6_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc

+ 353 - 0
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc

@@ -0,0 +1,353 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/classify.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Generates interface id option.
+///
+/// @param text Interface id in a textual format.
+OptionPtr
+generateInterfaceId(const std::string& text) {
+    OptionBuffer buffer(text.begin(), text.end());
+    return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
+}
+
+// This test checks that the subnet can be selected using a relay agent's
+// link address.
+TEST(CfgSubnets6Test, selectSubnetByRelayAddress) {
+    CfgSubnets6 cfg;
+
+    // Let's configure 3 subnets
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Make sure that none of the subnets is selected when there is no relay
+    // information configured for them.
+    SubnetSelector selector;
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Now specify relay information.
+    subnet1->setRelayInfo(IOAddress("2001:db8:ff::1"));
+    subnet2->setRelayInfo(IOAddress("2001:db8:ff::2"));
+    subnet3->setRelayInfo(IOAddress("2001:db8:ff::3"));
+
+    // And try again. This time relay-info is there and should match.
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1");
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3");
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+}
+
+// This test checks that the subnet can be selected using an interface
+// name associated with a asubnet.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceName) {
+    CfgSubnets6 cfg;
+
+    // Let's create 3 subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    subnet1->setIface("foo");
+    subnet2->setIface("bar");
+    subnet3->setIface("foobar");
+
+    // Until subnets are added to the configuration, there should be nothing
+    // returned.
+    SubnetSelector selector;
+    selector.iface_name_ = "foo";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Add one of the subnets.
+    cfg.add(subnet1);
+
+    // The subnet should be now selected for the interface name "foo".
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+
+    // Check that the interface name is checked even when there is
+    // only one subnet defined: there should be nothing returned when
+    // other interface name is specified.
+    selector.iface_name_ = "bar";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Add other subnets.
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // When we specify correct interface names, the subnets should be returned.
+    selector.iface_name_ = "foobar";
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+    selector.iface_name_ = "bar";
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+
+    // When specifying a non-existing interface the subnet should not be
+    // returned.
+    selector.iface_name_ = "xyzzy";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// This test checks that the subnet can be selected using an Interface ID
+// option inserted by a relay.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceId) {
+    CfgSubnets6 cfg;
+
+    // Create 3 subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+    // Create Interface-id options used in subnets 1,2, and 3
+    OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+    OptionPtr ifaceid2 = generateInterfaceId("VL32");
+    // That's a strange interface-id, but this is a real life example
+    OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+    // Bogus interface-id.
+    OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+    // Assign interface ids to the respective subnets.
+    subnet1->setInterfaceId(ifaceid1);
+    subnet2->setInterfaceId(ifaceid2);
+    subnet3->setInterfaceId(ifaceid3);
+
+    // There shouldn't be any subnet configured at this stage.
+    SubnetSelector selector;
+    selector.interface_id_ = ifaceid1;
+    // Note that some link address must be specified to indicate that it is
+    // a relayed message!
+    selector.first_relay_linkaddr_ = IOAddress("5000::1");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Add one of the subnets.
+    cfg.add(subnet1);
+
+    // If only one subnet has been specified, it should be returned when the
+    // interface id matches. But, for a different interface id there should be
+    // no match.
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid2;
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Add other subnets.
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Now that we have all subnets added. we should be able to retrieve them
+    // using appropriate interface ids.
+    selector.interface_id_ = ifaceid3;
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid2;
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+
+    // For invalid interface id, there should be nothing returned.
+    selector.interface_id_ = ifaceid_bogus;
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Test that the client classes are considered when the subnet is selected by
+// the relay link address.
+TEST(CfgSubnets6Test, selectSubnetByRelayAddressAndClassify) {
+    CfgSubnets6 cfg;
+
+    // Let's configure 3 subnets
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Let's sanity check that we can use that configuration.
+    SubnetSelector selector;
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+    // Client now belongs to bar class.
+    selector.client_classes_.insert("bar");
+
+    // There are no class restrictions defined, so everything should work
+    // as before.
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+    // Now let's add client class restrictions.
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    // The same check as above should result in client being served only in
+    // bar class, i.e. subnet2
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Now let's check that client with wrong class is not supported
+    selector.client_classes_.clear();
+    selector.client_classes_.insert("some_other_class");
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Finally, let's check that client without any classes is not supported
+    selector.client_classes_.clear();
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Test that client classes are considered when the subnet is selcted by the
+// interface name.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceNameAndClaassify) {
+    CfgSubnets6 cfg;
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    subnet1->setIface("foo");
+    subnet2->setIface("bar");
+    subnet3->setIface("foobar");
+
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Now we have only one subnet, any request will be served from it
+    SubnetSelector selector;
+    selector.client_classes_.insert("bar");
+    selector.iface_name_ = "foo";
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.iface_name_ = "bar";
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.iface_name_ = "foobar";
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    selector.iface_name_ = "foo";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.iface_name_ = "bar";
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.iface_name_ = "foobar";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Test that client classes are considered when the interface is selected by
+// the interface id.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceIdAndClassify) {
+    CfgSubnets6 cfg;
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+    // interface-id options used in subnets 1,2, and 3
+    OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+    OptionPtr ifaceid2 = generateInterfaceId("VL32");
+    // That's a strange interface-id, but this is a real life example
+    OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+    // bogus interface-id
+    OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+    subnet1->setInterfaceId(ifaceid1);
+    subnet2->setInterfaceId(ifaceid2);
+    subnet3->setInterfaceId(ifaceid3);
+
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // If we have only a single subnet and the request came from a local
+    // address, let's use that subnet
+    SubnetSelector selector;
+    selector.first_relay_linkaddr_ = IOAddress("5000::1");
+    selector.client_classes_.insert("bar");
+    selector.interface_id_ = ifaceid1;
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid2;
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid3;
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid2;
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid3;
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Checks that detection of duplicated subnet IDs works as expected. It should
+// not be possible to add two IPv6 subnets holding the same ID.
+TEST(CfgSubnets6, duplication) {
+    CfgSubnets6 cfg;
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4, 123));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4, 124));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4, 123));
+
+    ASSERT_NO_THROW(cfg.add(subnet1));
+    EXPECT_NO_THROW(cfg.add(subnet2));
+    // Subnet 3 has the same ID as subnet 1. It shouldn't be able to add it.
+    EXPECT_THROW(cfg.add(subnet3), isc::dhcp::DuplicateSubnetID);
+}
+
+} // end of anonymous namespace