From: Marcin Siodelski Date: Thu, 30 Oct 2014 09:49:43 +0000 (+0100) Subject: [3625] Implemented the CfgSubnets6 class to hold IPv6 subnets configuration X-Git-Tag: trac3629a_base^2~6 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=452efc320847289f927e04c810ddaeaf2bf7638d;p=thirdparty%2Fkea.git [3625] Implemented the CfgSubnets6 class to hold IPv6 subnets configuration --- diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 7502945b4b..2cee4f65c5 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/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 diff --git a/src/lib/dhcpsrv/cfg_subnets6.cc b/src/lib/dhcpsrv/cfg_subnets6.cc new file mode 100644 index 0000000000..45b8627f27 --- /dev/null +++ b/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 +#include +#include + +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 diff --git a/src/lib/dhcpsrv/cfg_subnets6.h b/src/lib/dhcpsrv/cfg_subnets6.h new file mode 100644 index 0000000000..a2c1d2261d --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +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 CfgSubnets6Ptr; + +/// @brief Const pointer. +typedef boost::shared_ptr ConstCfgSubnets6Ptr; + +//@} + +} +} + +#endif // CFG_SUBNETS6_H diff --git a/src/lib/dhcpsrv/subnet_selector.h b/src/lib/dhcpsrv/subnet_selector.h new file mode 100644 index 0000000000..8fef4b6c2f --- /dev/null +++ b/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 +#include +#include +#include +#include + +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 local_address_; + /// @brief Source address of the message. + util::OptionalValue 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 diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index c0943de6b7..e30526de45 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/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 diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc new file mode 100644 index 0000000000..983f8238e8 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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