Parcourir la source

[3587] Created CfgSubnets4 class to hold subnets configuration.

Marcin Siodelski il y a 10 ans
Parent
commit
69f66e61ff

+ 1 - 0
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

+ 153 - 0
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 <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

+ 149 - 0
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 <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

+ 1 - 1
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

+ 7 - 3
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<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.

+ 1 - 0
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

+ 287 - 0
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 <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

+ 17 - 4
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.

+ 26 - 0
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<int> value(-10, true);
+    ASSERT_EQ(-10, value.get());
+    ASSERT_TRUE(value.isSpecified());
 
+    int actual = value;
+    EXPECT_EQ(-10, actual);
+}
 
 } // end of anonymous namespace