From: Marcin Siodelski Date: Fri, 17 Oct 2014 10:40:45 +0000 (+0200) Subject: [3587] Created CfgSubnets4 class to hold subnets configuration. X-Git-Tag: trac3629a_base~1^2~6 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=69f66e61ff03e606dbd9b3e8403fbbdd721b2298;p=thirdparty%2Fkea.git [3587] Created CfgSubnets4 class to hold subnets configuration. --- diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index dbba0bcab2..7502945b4b 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -48,6 +48,7 @@ libkea_dhcpsrv_la_SOURCES += callout_handle_store.h 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 += 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 diff --git a/src/lib/dhcpsrv/cfg_subnets4.cc b/src/lib/dhcpsrv/cfg_subnets4.cc new file mode 100644 index 0000000000..b73bca1449 --- /dev/null +++ b/src/lib/dhcpsrv/cfg_subnets4.cc @@ -0,0 +1,153 @@ +// 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 { + +/// @brief Returns @c IOAddress object set to "0.0.0.0". +const IOAddress& ZERO_ADDRESS() { + static IOAddress address("0.0.0.0"); + return (address); +} + +} // end of anonymous namespace + +namespace isc { +namespace dhcp { + +CfgSubnets4::Selector::Selector() + : ciaddr_(ZERO_ADDRESS()), giaddr_(ZERO_ADDRESS()), + local_address_(ZERO_ADDRESS()), remote_address_(ZERO_ADDRESS()), + client_classes_(ClientClasses()), iface_name_(std::string()) { +} + +void +CfgSubnets4::add(const Subnet4Ptr& subnet) { + /// @todo: Check that this new subnet does not cross boundaries of any + /// other already defined subnet. + if (isDuplicate(*subnet)) { + isc_throw(isc::dhcp::DuplicateSubnet4ID, "ID of the new IPv4 subnet '" + << subnet->getID() << "' is already in use"); + } + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4) + .arg(subnet->toText()); + subnets_.push_back(subnet); +} + +Subnet4Ptr +CfgSubnets4::get(const Selector& selector) const { + // If relayed message has been received, try to match the giaddr with the + // relay address specified for a subnet. It is also possible that the relay + // address will not match with any of the relay addresses accross all + // subnets, but we need to verify that for all subnets before we can try + // to use the giaddr to match with the subnet prefix. + if (selector.giaddr_.isSpecified() && selector.giaddr_ != ZERO_ADDRESS()) { + for (Subnet4Collection::const_iterator subnet = subnets_.begin(); + subnet != subnets_.end(); ++subnet) { + // Eliminate those subnets that do not meet client class criteria. + if (!(*subnet)->clientSupported(selector.client_classes_)) { + continue; + } + + // Check if the giaddr is equal to the one defined for the subnet. + if (selector.giaddr_ == (*subnet)->getRelayInfo().addr_) { + return (*subnet); + } + } + } + + // If we got to this point it means that we were not able to match the + // giaddr with any of the addresses specified for subnets. Let's determine + // what address from the client's packet to use to match with the + // subnets' prefixes. + + IOAddress address = ZERO_ADDRESS(); + // If there is a giaddr, use it for subnet selection. + if (selector.giaddr_.isSpecified() && + (selector.giaddr_ != ZERO_ADDRESS())) { + address = selector.giaddr_; + + // If it is a Renew or Rebind, use the ciaddr. + } else if (selector.ciaddr_.isSpecified() && + selector.ciaddr_ != ZERO_ADDRESS()) { + address = selector.ciaddr_; + + // If ciaddr is not specified, use the source address. + } else if (selector.remote_address_.isSpecified() && + selector.remote_address_ != ZERO_ADDRESS()) { + address = selector.remote_address_; + + // If local interface name is known, use the local address on this + // interface. + } else if (selector.iface_name_.isSpecified()) { + Iface* iface = IfaceMgr::instance().getIface(selector.iface_name_); + // This should never happen in the real life. Hence we throw an + // exception. + if (iface == NULL) { + isc_throw(isc::BadValue, "interface " << selector.iface_name_.get() + << " doesn't exist and therefore it is impossible" + " to find a suitable subnet for its IPv4 address"); + } + iface->getAddress4(address); + + } else { + isc_throw(isc::BadValue, "subnet selector structure does not contain" + " sufficient information"); + } + + // Unable to find a suitable address to use for subnet selection. + if (address == ZERO_ADDRESS()) { + return (Subnet4Ptr()); + } + + // We have identified an address in the client's packet that can be + // used for subnet selection. Match this packet with the subnets. + for (Subnet4Collection::const_iterator subnet = subnets_.begin(); + subnet != subnets_.end(); ++subnet) { + // Eliminate those subnets that do not meet client class criteria. + if (!(*subnet)->clientSupported(selector.client_classes_)) { + continue; + } + + // Address is in range for the subnet prefix, so return it. + if ((*subnet)->inRange(address)) { + return (*subnet); + } + } + + // Failed to find a subnet. + return (Subnet4Ptr()); +} + +bool +CfgSubnets4::isDuplicate(const Subnet4& subnet) const { + for (Subnet4Collection::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_subnets4.h b/src/lib/dhcpsrv/cfg_subnets4.h new file mode 100644 index 0000000000..8857fa7369 --- /dev/null +++ b/src/lib/dhcpsrv/cfg_subnets4.h @@ -0,0 +1,149 @@ +// 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_SUBNETS4_H +#define CFG_SUBNETS4_H + +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Exception thrown upon attempt to add subnet with an ID that belongs +/// to the subnet that already exists. +class DuplicateSubnet4ID : public Exception { +public: + DuplicateSubnet4ID(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Holds subnets configured for the DHCPv4 server. +/// +/// This class holds a collection of subnets configured for the DHCPv4 server. +/// It allows for retrieving a subnet for the particular client using various +/// parameters extracted from the DHCPv4 message. These parameters must be +/// assigned to the appropriate members of the @c CfgSubnets4::Selector +/// structure. +/// +/// See @c CfgSubnets4::get documentation for more details on how the subnet +/// is selected for the client. +class CfgSubnets4 { +public: + + /// @brief Subnet selector used in @c CfgSubnets4::getSubnet4. + /// + /// This structure holds various parameters extracted from a packet sent + /// by a DHCP client used to select the subnet for the client. Note that + /// data members are optional which means that they may be left unspecified + /// by the caller, if the caller doesn't have access to the relevant + /// information. + struct Selector { + /// @brief ciaddr from the client's message. + util::OptionalValue ciaddr_; + /// @brief giaddr from the client's message. + util::OptionalValue giaddr_; + /// @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. + util::OptionalValue client_classes_; + /// @brief Name of the interface on which the message was received. + util::OptionalValue iface_name_; + + /// @brief Default constructor. + /// + /// Sets the default values for the @c Selector. + Selector(); + }; + + /// @brief Adds new subnet to the configuration. + /// + /// @param subnet Pointer to the subnet being added. + /// + /// @throw isc::DuplicateSubnet4ID If the subnet id for the new subnet + /// duplicates id of an existing subnet. + void add(const Subnet4Ptr& subnet); + + /// @brief Returns pointer to the collection of all IPv4 subnets. + /// + /// This is used in a hook (subnet4_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 Subnet4 collection + const Subnet4Collection* getAll() const { + return (&subnets_); + } + + /// @brief Returns pointer to the selected subnet. + /// + /// 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 giaddr value is found it means that the client's message was + /// relayed. The subnet configuration allows for setting the relay address + /// for each subnet to indicate that the subnet must be assigned when the + /// packet was transmitted over the particular relay. This method first + /// tries to match the giaddr with the relay addresses specified for + /// all subnets. If the relay address for the subnet is equal to the address + /// of the relay through which the message was transmitted, the particular + /// subnet is returned. + /// + /// If the giaddr is not matched with any of the relay addresses in any + /// subnet or the message was not relayed, the method will need to try to + /// match one of the addresses in the client's message with the prefixes + /// of the existing subnets. Depending whether it is a relayed message, + /// message from the renewing client or a new allocation, the server will + /// pick one of the following addresses for this matching: + /// - giaddr - for relayed message + /// - ciaddr - for renewing or rebinding client + /// - source address - for the renewing client which didn't provide ciaddr + /// - address on the local server's interface if this is a new allocation + /// requested by the directly connected client + /// + /// If the address matches with a subnet, the subnet is returned. + /// + /// @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. + /// @throw isc::BadValue if the values in the subnet selector are invalid + /// or they are insufficient to select a subnet. + Subnet4Ptr get(const Selector& selector) const; + +private: + + /// @brief Checks that the IPv4 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 Subnet4& subnet) const; + + /// @brief A container for IPv4 subnets. + Subnet4Collection subnets_; + +}; + +} +} + +#endif // CFG_SUBNETS4_H diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h index 95eb2d70a1..44eb2c7d73 100644 --- a/src/lib/dhcpsrv/cfgmgr.h +++ b/src/lib/dhcpsrv/cfgmgr.h @@ -484,7 +484,7 @@ protected: /// a match is found. Subnet6Collection subnets6_; - /// @brief a container for IPv4 subnets. + /// @brief A container for IPv4 subnets. /// /// That is a simple vector of pointers. It does not make much sense to /// optimize access time (e.g. using a map), because typical search diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index a3ca08a7f1..c6c152859a 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -493,13 +493,17 @@ protected: isc::asiolink::IOAddress siaddr_; }; -/// @brief A pointer to a Subnet4 object +/// @brief A pointer to a @c Subnet4 object typedef boost::shared_ptr Subnet4Ptr; -/// @brief A collection of Subnet6 objects +/// @brief A collection of @c Subnet4 objects +/// +/// That is a simple vector of pointers. It does not make much sense to +/// optimize access time (e.g. using a map), because typical search +/// pattern will use calling inRange() method on each subnet until +/// a match is found. typedef std::vector Subnet4Collection; - /// @brief A configuration holder for IPv6 subnet. /// /// This class represents an IPv6 subnet. diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index 79dc040c9d..c0943de6b7 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -58,6 +58,7 @@ libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc 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 += 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_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc new file mode 100644 index 0000000000..b36b1d40fc --- /dev/null +++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc @@ -0,0 +1,287 @@ +// 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 + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; + +namespace { + +// This test verifies that it is possible to retrieve a subnet using an +// IP address. +TEST(CfgSubnets4Test, getSubnetByCiaddr) { + CfgSubnets4 cfg; + + // Create 3 subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3)); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3)); + Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3)); + + // Make sure that initially the subnets don't exist. + CfgSubnets4::Selector selector; + selector.ciaddr_ = IOAddress("192.0.2.0"); + // Set some unicast local address to simulate a Renew. + selector.local_address_ = IOAddress("10.0.0.100"); + ASSERT_FALSE(cfg.get(selector)); + + // Add one subnet and make sure it is returned. + cfg.add(subnet1); + selector.ciaddr_ = IOAddress("192.0.2.63"); + EXPECT_EQ(subnet1, cfg.get(selector)); + + // Add all other subnets. + cfg.add(subnet2); + cfg.add(subnet3); + + // Make sure they are returned for the appropriate addresses. + selector.ciaddr_ = IOAddress("192.0.2.15"); + EXPECT_EQ(subnet1, cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.85"); + EXPECT_EQ(subnet2, cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.191"); + EXPECT_EQ(subnet3, cfg.get(selector)); + + // Also, make sure that the NULL pointer is returned if the subnet + // cannot be found. + selector.ciaddr_ = IOAddress("192.0.2.192"); + EXPECT_FALSE(cfg.get(selector)); +} + + +// This test verifies that when the classification information is specified for +// subnets, the proper subnets are returned by the subnet configuration. +TEST(CfgSubnets4Test, getSubnetByClasses) { + CfgSubnets4 cfg; + + // Create 3 subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3)); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3)); + Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3)); + + // Add them to the configuration. + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + CfgSubnets4::Selector selector; + + selector.local_address_ = IOAddress("10.0.0.10"); + + selector.ciaddr_ = IOAddress("192.0.2.5"); + EXPECT_EQ(subnet1, cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.70"); + EXPECT_EQ(subnet2, cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.130"); + EXPECT_EQ(subnet3, cfg.get(selector)); + + ClientClasses client_classes; + client_classes.insert("bar"); + selector.client_classes_ = client_classes; + + // There are no class restrictions defined, so everything should work + // as before + selector.ciaddr_ = IOAddress("192.0.2.5"); + EXPECT_EQ(subnet1, cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.70"); + EXPECT_EQ(subnet2, cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.130"); + EXPECT_EQ(subnet3, cfg.get(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.ciaddr_ = IOAddress("192.0.2.5"); + EXPECT_FALSE(cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.70"); + EXPECT_EQ(subnet2, cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.130"); + EXPECT_FALSE(cfg.get(selector)); + + // Now let's check that client with wrong class is not supported. + client_classes.clear(); + client_classes.insert("some_other_class"); + selector.client_classes_ = client_classes; + selector.ciaddr_ = IOAddress("192.0.2.5"); + EXPECT_FALSE(cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.70"); + EXPECT_FALSE(cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.130"); + EXPECT_FALSE(cfg.get(selector)); + + // Finally, let's check that client without any classes is not supported. + client_classes.clear(); + selector.ciaddr_ = IOAddress("192.0.2.5"); + EXPECT_FALSE(cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.70"); + EXPECT_FALSE(cfg.get(selector)); + selector.ciaddr_ = IOAddress("192.0.2.130"); + EXPECT_FALSE(cfg.get(selector)); +} + +// This test verifies that the relay information can be used to retrieve the +// subnet. +TEST(CfgSubnetsTest, getSubnetByRelayAddress) { + CfgSubnets4 cfg; + + // Create 3 subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3)); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3)); + Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3)); + + // Add them to the configuration. + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + CfgSubnets4::Selector selector; + + // Check that without relay-info specified, subnets are not selected + selector.giaddr_ = IOAddress("10.0.0.1"); + EXPECT_FALSE(cfg.get(selector)); + selector.giaddr_ = IOAddress("10.0.0.2"); + EXPECT_FALSE(cfg.get(selector)); + selector.giaddr_ = IOAddress("10.0.0.3"); + EXPECT_FALSE(cfg.get(selector)); + + // Now specify relay info + subnet1->setRelayInfo(IOAddress("10.0.0.1")); + subnet2->setRelayInfo(IOAddress("10.0.0.2")); + subnet3->setRelayInfo(IOAddress("10.0.0.3")); + + // And try again. This time relay-info is there and should match. + selector.giaddr_ = IOAddress("10.0.0.1"); + EXPECT_EQ(subnet1, cfg.get(selector)); + selector.giaddr_ = IOAddress("10.0.0.2"); + EXPECT_EQ(subnet2, cfg.get(selector)); + selector.giaddr_ = IOAddress("10.0.0.3"); + EXPECT_EQ(subnet3, cfg.get(selector)); +} + +// This test verifies that the subnet can be selected for the client +// using a source address if the client hasn't set the ciaddr. +TEST(CfgSubnetsTest, getSubnetNoCiaddr) { + CfgSubnets4 cfg; + + // Create 3 subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3)); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3)); + Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3)); + + // Make sure that initially the subnets don't exist. + CfgSubnets4::Selector selector; + selector.remote_address_ = IOAddress("192.0.2.0"); + // Set some unicast local address to simulate a Renew. + selector.local_address_ = IOAddress("10.0.0.100"); + ASSERT_FALSE(cfg.get(selector)); + + // Add one subnet and make sure it is returned. + cfg.add(subnet1); + selector.remote_address_ = IOAddress("192.0.2.63"); + EXPECT_EQ(subnet1, cfg.get(selector)); + + // Add all other subnets. + cfg.add(subnet2); + cfg.add(subnet3); + + // Make sure they are returned for the appropriate addresses. + selector.remote_address_ = IOAddress("192.0.2.15"); + EXPECT_EQ(subnet1, cfg.get(selector)); + selector.remote_address_ = IOAddress("192.0.2.85"); + EXPECT_EQ(subnet2, cfg.get(selector)); + selector.remote_address_ = IOAddress("192.0.2.191"); + EXPECT_EQ(subnet3, cfg.get(selector)); + + // Also, make sure that the NULL pointer is returned if the subnet + // cannot be found. + selector.remote_address_ = IOAddress("192.0.2.192"); + EXPECT_FALSE(cfg.get(selector)); +} + +// This test verifies that the subnet can be selected using an address +// set on the local interface. +TEST(CfgSubnetsTest, getSubnetInterface) { + IfaceMgrTestConfig config(true); + + CfgSubnets4 cfg; + CfgSubnets4::Selector selector; + + // Initially, there are no subnets configured, so none of the IPv4 + // addresses assigned to eth0 and eth1 can match with any subnet. + selector.iface_name_ = "eth0"; + EXPECT_FALSE(cfg.get(selector)); + selector.iface_name_ = "eth1"; + EXPECT_FALSE(cfg.get(selector)); + + // Configure first subnet which address on eth0 corresponds to. + Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.1"), 24, 1, 2, 3)); + cfg.add(subnet1); + + // The address on eth0 should match the existing subnet. + selector.iface_name_ = "eth0"; + Subnet4Ptr subnet1_ret = cfg.get(selector); + ASSERT_TRUE(subnet1_ret); + EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first); + // There should still be no match for eth1. + selector.iface_name_ = "eth1"; + EXPECT_FALSE(cfg.get(selector)); + + // Configure a second subnet. + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.1"), 24, 1, 2, 3)); + cfg.add(subnet2); + + // There should be match between eth0 and subnet1 and between eth1 and + // subnet 2. + selector.iface_name_ = "eth0"; + subnet1_ret = cfg.get(selector); + ASSERT_TRUE(subnet1_ret); + EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first); + selector.iface_name_ = "eth1"; + Subnet4Ptr subnet2_ret = cfg.get(selector); + ASSERT_TRUE(subnet2_ret); + EXPECT_EQ(subnet2->get().first, subnet2_ret->get().first); + + // This function throws an exception if the name of the interface is wrong. + selector.iface_name_ = "bogus-interface"; + EXPECT_THROW(cfg.get(selector), isc::BadValue); +} + +// Checks that detection of duplicated subnet IDs works as expected. It should +// not be possible to add two IPv4 subnets holding the same ID. +TEST(CfgSubnets4, duplication) { + CfgSubnets4 cfg; + + Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 123)); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 124)); + Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 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::DuplicateSubnet4ID); +} + + +} // end of anonymous namespace diff --git a/src/lib/util/optional_value.h b/src/lib/util/optional_value.h index 0e47f891bb..3bb86fb6c1 100644 --- a/src/lib/util/optional_value.h +++ b/src/lib/util/optional_value.h @@ -101,17 +101,30 @@ public: /// @brief Equality operator. /// /// @param value Actual value to compare to. - bool operator==(const T& value) { - return (value_ == value); + /// + /// @return true if the value is specified and equals the argument. + bool operator==(const T& value) const { + return (specified_ && (value_ == value)); } /// @brief Inequality operator. /// /// @param value Actual value to compare to. - bool operator!=(const T& value) { - return (value_ != value); + /// + /// @return true if the value is unspecified or unequal. + bool operator!=(const T& value) const { + return (!specified_ || (value_ != value)); } + /// @brief Type cast operator. + /// + /// This operator converts the optional value to the actual value being + /// encapsulated. + /// + /// @return Encapsulated value. + operator T() const { + return (value_); + } private: T value_; ///< Encapsulated value. diff --git a/src/lib/util/tests/optional_value_unittest.cc b/src/lib/util/tests/optional_value_unittest.cc index 82558eeb33..7ef0a1f94b 100644 --- a/src/lib/util/tests/optional_value_unittest.cc +++ b/src/lib/util/tests/optional_value_unittest.cc @@ -105,14 +105,40 @@ TEST(OptionalValueTest, equalityOperators) { ASSERT_EQ(10, value.get()); ASSERT_FALSE(value.isSpecified()); + EXPECT_FALSE(value == 10); + EXPECT_TRUE(value != 10); + EXPECT_FALSE(value == 123); + EXPECT_TRUE(value != 123); + + value.specify(true); + EXPECT_TRUE(value == 10); EXPECT_FALSE(value != 10); + EXPECT_FALSE(value == 123); + EXPECT_TRUE(value != 123); value = 123; EXPECT_TRUE(value == 123); EXPECT_FALSE(value != 123); + EXPECT_FALSE(value == 10); + EXPECT_TRUE(value != 10); + + value.specify(false); + + EXPECT_FALSE(value == 123); + EXPECT_TRUE(value != 123); + EXPECT_FALSE(value == 10); + EXPECT_TRUE(value != 10); } +// This test checks if the type case operator returns correct value. +TEST(OptionalValueTest, typeCastOperator) { + OptionalValue value(-10, true); + ASSERT_EQ(-10, value.get()); + ASSERT_TRUE(value.isSpecified()); + int actual = value; + EXPECT_EQ(-10, actual); +} } // end of anonymous namespace