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
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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
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
--- /dev/null
+// 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