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
--- /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 <dhcp/iface_mgr.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+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
--- /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_SUBNETS4_H
+#define CFG_SUBNETS4_H
+
+#include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/subnet.h>
+#include <util/optional_value.h>
+
+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<asiolink::IOAddress> ciaddr_;
+ /// @brief giaddr from the client's message.
+ util::OptionalValue<asiolink::IOAddress> giaddr_;
+ /// @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.
+ util::OptionalValue<ClientClasses> client_classes_;
+ /// @brief Name of the interface on which the message was received.
+ util::OptionalValue<std::string> 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
/// 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
isc::asiolink::IOAddress siaddr_;
};
-/// @brief A pointer to a Subnet4 object
+/// @brief A pointer to a @c Subnet4 object
typedef boost::shared_ptr<Subnet4> 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<Subnet4Ptr> Subnet4Collection;
-
/// @brief A configuration holder for IPv6 subnet.
///
/// This class represents an IPv6 subnet.
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
--- /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/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/subnet.h>
+#include <gtest/gtest.h>
+
+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
/// @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.
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<int> value(-10, true);
+ ASSERT_EQ(-10, value.get());
+ ASSERT_TRUE(value.isSpecified());
+ int actual = value;
+ EXPECT_EQ(-10, actual);
+}
} // end of anonymous namespace