Browse Source

[master] Merge branch 'trac3242'

Conflicts:
	src/bin/dhcp4/tests/fqdn_unittest.cc
Marcin Siodelski 11 years ago
parent
commit
9e571cc217

+ 34 - 0
doc/guide/bind10-guide.xml

@@ -4475,6 +4475,40 @@ Dhcp4/subnet4	[]	list	(default)
 
     </section>
 
+    <section id="dhcp4-subnet-selection">
+      <title>How DHCPv4 server selects subnet for a client</title>
+      <para>
+        The DHCPv4 server differentiates between the directly connected clients,
+        clients trying to renew leases and clients sending their messages through
+        relays. For the directly connected clients the server will check the
+        configuration of the interface on which the message has been received, and
+        if the server configuration doesn't match any configured subnet the
+        message is discarded.</para>
+        <para>Assuming that the server's interface is configured with the 192.0.2.3
+        IPv4 address, the server will only process messages received through
+        this interface from the directly connected client, if there is a subnet
+        configured, to which this IPv4 address belongs, e.g. 192.0.2.0/24.
+        The server will use this subnet to assign IPv4 address for the client.
+      </para>
+      <para>
+        The rule above does not apply when the client unicasts its message, i.e.
+        is trying to renew its lease. Such message is accepted through any
+        interface. The renewing client sets ciaddr to the currently used IPv4
+        address. The server uses this address to select the subnet for the client
+        (in particular, to extend the lease using this address).
+      </para>
+      <para>
+        If the message is relayed it is accepted through any interface. The giaddr
+        set by the relay agent is used to select the subnet for the client.
+      </para>
+      <note>
+        <para>The subnet selection mechanism described in this section is based
+        on the assumption that client classification is not used. The classification
+        mechanism alters the way in which subnet is selected for the client,
+        depending on the clasess that the client belongs to.</para>
+      </note>
+    </section>
+
     <section id="dhcp4-std">
       <title>Supported Standards</title>
       <para>The following standards and draft standards are currently

+ 19 - 0
src/bin/dhcp4/dhcp4_messages.mes

@@ -135,6 +135,18 @@ A "libreload" command was issued to reload the hooks libraries but for
 some reason the reload failed.  Other error messages issued from the
 hooks framework will indicate the nature of the problem.
 
+% DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE received message (transaction id %1) has unrecognized type %2 in option 53
+This debug message indicates that the message type carried in DHCPv4 option
+53 is unrecognized by the server. The valid message types are listed
+on the IANA website: http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53.
+The message will not be processed by the server.
+
+% DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE received message (transaction id %1), having type %2 is not supported
+This debug message indicates that the message type carried in DHCPv4 option
+53 is valid but the message will not be processed by the server. This includes
+messages being normally sent by the server to the client, such as Offer, ACK,
+NAK etc.
+
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 This debug message indicates that the server successfully advertised
 a lease. It is up to the client to choose one server out of othe advertised
@@ -175,6 +187,13 @@ This warning message is issued when current server configuration specifies
 no interfaces that server should listen on, or specified interfaces are not
 configured to receive the traffic.
 
+% DHCP4_NO_SUBNET_FOR_DIRECT_CLIENT no suitable subnet configured for a direct client sending packet with transaction id %1, on interface %2, received message is dropped
+This info messsage is logged when received a message from a directly connected
+client but there is no suitable subnet configured for the interface on
+which this message has been received. The IPv4 address assigned on this
+interface must belong to one of the configured subnets. Otherwise
+received message is dropped.
+
 % DHCP4_NOT_RUNNING IPv4 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
 IPv4 DHCP server but it is not running.

+ 121 - 32
src/bin/dhcp4/dhcp4_srv.cc

@@ -230,26 +230,15 @@ Dhcpv4Srv::run() {
             }
         }
 
-        // Check if the DHCPv4 packet has been sent to us or to someone else.
-        // If it hasn't been sent to us, drop it!
-        if (!acceptServerId(query)) {
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US)
-                .arg(query->getTransid())
-                .arg(query->getIface());
-            continue;
-        }
-
-        // When receiving a packet without message type option, getType() will
-        // throw. Let's set type to -1 as default error indicator.
-        int type = -1;
-        try {
-            type = query->getType();
-        } catch (...) {
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_DROP_NO_TYPE)
-                .arg(query->getIface());
+        // Check whether the message should be further processed or discarded.
+        // There is no need to log anything here. This function logs by itself.
+        if (!accept(query)) {
             continue;
         }
 
+        // We have sanity checked (in accept() that the Message Type option
+        // exists, so we can safely get it here.
+        int type = query->getType();
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_RECEIVED)
             .arg(serverReceivedPacketName(type))
             .arg(type)
@@ -1413,24 +1402,38 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
 }
 
 Subnet4Ptr
-Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
 
     Subnet4Ptr subnet;
-    // Is this relayed message?
-    IOAddress relay = question->getGiaddr();
     static const IOAddress notset("0.0.0.0");
+    static const IOAddress bcast("255.255.255.255");
 
-    if (relay != notset) {
-        // Yes: Use relay address to select subnet
-        subnet = CfgMgr::instance().getSubnet4(relay);
+    // If a message is relayed, use the relay (giaddr) address to select subnet
+    // for the client. Note that this may result in exception if the value
+    // of hops does not correspond with the Giaddr. Such message is considered
+    // to be malformed anyway and the message will be dropped by the higher
+    // level functions.
+    if (question->isRelayed()) {
+        subnet = CfgMgr::instance().getSubnet4(question->getGiaddr());
+
+    // The message is not relayed so it is sent directly by a client. But
+    // the client may be renewing its lease and in such case it unicasts
+    // its message to the server. Note that the unicast Request bypasses
+    // relays and the client may be in a different network, so we can't
+    // use IP address on the local interface to get the subnet. Instead,
+    // we rely on the client's address to get the subnet.
+    } else if ((question->getLocalAddr() != bcast) &&
+               (question->getCiaddr() != notset)) {
+        subnet = CfgMgr::instance().getSubnet4(question->getCiaddr());
+
+    // The message has been received from a directly connected client
+    // and this client appears to have no address. The IPv4 address
+    // assigned to the interface on which this message has been received,
+    // will be used to determine the subnet suitable for the client.
     } else {
-
-        // No: Use client's address to select subnet
-        subnet = CfgMgr::instance().getSubnet4(question->getRemoteAddr());
+        subnet = CfgMgr::instance().getSubnet4(question->getIface());
     }
 
-    /// @todo Implement getSubnet4(interface-name)
-
     // Let's execute all callouts registered for subnet4_select
     if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(question);
@@ -1465,7 +1468,93 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
 }
 
 bool
-Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
+Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
+    // Check if the message from directly connected client (if directly
+    // connected) should be dropped or processed.
+    if (!acceptDirectRequest(query)) {
+        LOG_INFO(dhcp4_logger, DHCP4_NO_SUBNET_FOR_DIRECT_CLIENT)
+            .arg(query->getTransid())
+            .arg(query->getIface());
+        return (false);
+    }
+
+    // Check if the DHCPv4 packet has been sent to us or to someone else.
+    // If it hasn't been sent to us, drop it!
+    if (!acceptServerId(query)) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US)
+            .arg(query->getTransid())
+            .arg(query->getIface());
+        return (false);
+    }
+
+    // Check that the message type is accepted by the server. We rely on the
+    // function called to log a message if needed.
+    if (!acceptMessageType(query)) {
+        return (false);
+    }
+
+    return (true);
+}
+
+bool
+Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
+    try {
+        if (pkt->isRelayed()) {
+            return (true);
+        }
+    } catch (const Exception& ex) {
+        return (false);
+    }
+    static const IOAddress bcast("255.255.255.255");
+    return ((pkt->getLocalAddr() != bcast || selectSubnet(pkt)));
+}
+
+bool
+Dhcpv4Srv::acceptMessageType(const Pkt4Ptr& query) const {
+    // When receiving a packet without message type option, getType() will
+    // throw.
+    int type;
+    try {
+        type = query->getType();
+
+    } catch (...) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_DROP_NO_TYPE)
+            .arg(query->getIface());
+        return (false);
+    }
+
+    // If we receive a message with a non-existing type, we are logging it.
+    if (type > DHCPLEASEQUERYDONE) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
+                  DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE)
+            .arg(type)
+            .arg(query->getTransid());
+        return (false);
+    }
+
+    // Once we know that the message type is within a range of defined DHCPv4
+    // messages, we do a detailed check to make sure that the received message
+    // is targeted at server. Note that we could have received some Offer
+    // message broadcasted by the other server to a relay. Even though, the
+    // server would rather unicast its response to a relay, let's be on the
+    // safe side. Also, we want to drop other messages which we don't support.
+    // All these valid messages that we are not going to process are dropped
+    // silently.
+    if ((type != DHCPDISCOVER) && (type != DHCPREQUEST) &&
+        (type != DHCPRELEASE) && (type != DHCPDECLINE) &&
+        (type != DHCPINFORM)) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
+                  DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE)
+            .arg(type)
+            .arg(query->getTransid());
+        return (false);
+    }
+
+    return (true);
+}
+
+bool
+Dhcpv4Srv::acceptServerId(const Pkt4Ptr& query) const {
     // This function is meant to be called internally by the server class, so
     // we rely on the caller to sanity check the pointer and we don't check
     // it here.
@@ -1475,7 +1564,7 @@ Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
     // Note that we don't check cases that server identifier is mandatory
     // but not present. This is meant to be sanity checked in other
     // functions.
-    OptionPtr option = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+    OptionPtr option = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
     if (!option) {
         return (true);
     }
@@ -1603,8 +1692,8 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
 
 size_t
 Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
-                          const std::string& option_space,
-                          isc::dhcp::OptionCollection& options) {
+                         const std::string& option_space,
+                         isc::dhcp::OptionCollection& options) {
     size_t offset = 0;
 
     OptionDefContainer option_defs;

+ 79 - 1
src/bin/dhcp4/dhcp4_srv.h

@@ -167,6 +167,83 @@ public:
 
 protected:
 
+    /// @name Functions filtering and sanity-checking received messages.
+    ///
+    /// @todo These functions are supposed to be moved to a new class which
+    /// will manage different rules for accepting and rejecting messages.
+    /// Perhaps ticket #3116 is a good opportunity to do it.
+    ///
+    //@{
+    /// @brief Checks whether received message should be processed or discarded.
+    ///
+    /// This function checks whether received message should be processed or
+    /// discarded. It should be called on the beginning of message processing
+    /// (just after the message has been decoded). This message calls a number
+    /// of other functions which check whether message should be processed,
+    /// using different criteria.
+    ///
+    /// This function should be extended when new criteria for accepting
+    /// received message have to be implemented. This function is meant to
+    /// aggregate all early filtering checks on the received message. By having
+    /// a single function like this, we are avoiding bloat of the server's main
+    /// loop.
+    ///
+    /// @warning This function should remain exception safe.
+    ///
+    /// @param query Received message.
+    ///
+    /// @return true if the message should be further processed, or false if
+    /// the message should be discarded.
+    bool accept(const Pkt4Ptr& query) const;
+
+    /// @brief Check if a message sent by directly connected client should be
+    /// accepted or discared.
+    ///
+    /// This function checks if the received message is from directly connected
+    /// client. If it is, it checks that it should be processed or discarded.
+    ///
+    /// Note that this function doesn't validate all addresses being carried in
+    /// the message. The primary purpose of this function is to filter out
+    /// direct messages in the local network for which there is no suitable
+    /// subnet configured. For example, this function accepts unicast messages
+    /// because unicasts may be used by clients located in remote networks to
+    /// to renew existing leases. If their notion of address is wrong, the
+    /// server will have to sent a NAK, instead of dropping the message.
+    /// Detailed validation of such messages is performed at later stage of
+    /// processing.
+    ///
+    /// This function accepts the following messages:
+    /// - all valid relayed messages,
+    /// - all unicast messages,
+    /// - all broadcast messages received on the interface for which the
+    /// suitable subnet exists (is configured).
+    ///
+    /// @param query Message sent by a client.
+    ///
+    /// @return true if message is accepted for further processing, false
+    /// otherwise.
+    bool acceptDirectRequest(const Pkt4Ptr& query) const;
+
+    /// @brief Check if received message type is valid for the server to
+    /// process.
+    ///
+    /// This function checks that the received message type belongs to the range
+    /// of types regonized by the server and that the message of this type
+    /// should be processed by the server.
+    ///
+    /// The messages types accepted for processing are:
+    /// - Discover
+    /// - Request
+    /// - Release
+    /// - Decline
+    /// - Inform
+    ///
+    /// @param query Message sent by a client.
+    ///
+    /// @return true if message is accepted for further processing, false
+    /// otherwise.
+    bool acceptMessageType(const Pkt4Ptr& query) const;
+
     /// @brief Verifies if the server id belongs to our server.
     ///
     /// This function checks if the server identifier carried in the specified
@@ -181,6 +258,7 @@ protected:
     /// @return true, if the server identifier is absent or matches one of the
     /// server identifiers that the server is using; false otherwise.
     bool acceptServerId(const Pkt4Ptr& pkt) const;
+    //@}
 
     /// @brief verifies if specified packet meets RFC requirements
     ///
@@ -529,7 +607,7 @@ protected:
     ///
     /// @param question client's message
     /// @return selected subnet (or NULL if no suitable subnet was found)
-    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
+    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question) const;
 
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.

+ 2 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -80,6 +80,7 @@ dhcp4_unittests_SOURCES += dhcp4_test_utils.h
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_test_utils.cc dhcp4_test_utils.h
+dhcp4_unittests_SOURCES += direct_client_unittest.cc
 dhcp4_unittests_SOURCES += wireshark.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_unittest.cc
@@ -95,6 +96,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la

File diff suppressed because it is too large
+ 286 - 84
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc


+ 23 - 62
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-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
@@ -15,12 +15,15 @@
 #include <config.h>
 
 #include <asiolink/io_address.h>
+#include <cc/data.h>
 #include <config/ccsession.h>
+#include <dhcp4/config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -28,17 +31,14 @@
 
 using namespace std;
 using namespace isc::asiolink;
-
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
 namespace test {
 
-/// dummy server-id file location
-static const char* SRVID_FILE = "server-id-test.txt";
-
 Dhcpv4SrvTest::Dhcpv4SrvTest()
-:rcode_(-1) {
+:rcode_(-1), srv_(0) {
     subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
                                      2000, 3000));
     pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
@@ -52,9 +52,6 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
     opt_routers->setAddress(IOAddress("192.0.2.2"));
     subnet_->addOption(opt_routers, false, "dhcp4");
-
-    // it's ok if that fails. There should not be such a file anyway
-    unlink(SRVID_FILE);
 }
 
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
@@ -387,9 +384,6 @@ void Dhcpv4SrvTest::TearDown() {
 
     CfgMgr::instance().deleteSubnets4();
 
-    // Let's clean up if there is such a file.
-    unlink(SRVID_FILE);
-
     // Close all open sockets.
     IfaceMgr::instance().closeSockets();
 
@@ -413,58 +407,11 @@ void Dhcpv4SrvTest::TearDown() {
 
 }
 
-Dhcpv4SrvFakeIfaceTest::Dhcpv4SrvFakeIfaceTest()
-: Dhcpv4SrvTest(), current_pkt_filter_() {
-    // Remove current interface configuration. Instead we want to add
-    // a couple of fake interfaces.
-    IfaceMgr& ifacemgr = IfaceMgr::instance();
-    ifacemgr.closeSockets();
-    ifacemgr.clearIfaces();
-
-    // Add fake interfaces.
-    ifacemgr.addInterface(createIface("lo", 0, "127.0.0.1"));
-    ifacemgr.addInterface(createIface("eth0", 1, "192.0.3.1"));
-    ifacemgr.addInterface(createIface("eth1", 2, "10.0.0.1"));
-
-    // In order to use fake interfaces we have to supply the custom
-    // packet filtering class, which can mimic opening sockets on
-    // fake interafaces.
-    current_pkt_filter_.reset(new PktFilterTest());
-    ifacemgr.setPacketFilter(current_pkt_filter_);
-    ifacemgr.openSockets4();
-}
-
 void
-Dhcpv4SrvFakeIfaceTest::TearDown() {
-    // The base class function restores the original packet filtering class.
-    Dhcpv4SrvTest::TearDown();
-    // The base class however, doesn't re-detect real interfaces.
-    try {
-        IfaceMgr::instance().clearIfaces();
-        IfaceMgr::instance().detectIfaces();
+Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
-    } catch (const Exception& ex) {
-        FAIL() << "Failed to restore interface configuration after using"
-            " fake interfaces";
-    }
-}
-
-Iface
-Dhcpv4SrvFakeIfaceTest::createIface(const std::string& name, const int ifindex,
-                                    const std::string& addr) {
-    Iface iface(name, ifindex);
-    iface.addAddress(IOAddress(addr));
-    if (name == "lo") {
-        iface.flag_loopback_ = true;
-    }
-    iface.flag_up_ = true;
-    iface.flag_running_ = true;
-    iface.inactive4_ = false;
-    return (iface);
-}
-
-void
-Dhcpv4SrvFakeIfaceTest::testDiscoverRequest(const uint8_t msg_type) {
     // Create an instance of the tested class.
     boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
 
@@ -608,6 +555,20 @@ Dhcpv4SrvFakeIfaceTest::testDiscoverRequest(const uint8_t msg_type) {
     EXPECT_TRUE(noBasicOptions(rsp));
 }
 
+void
+Dhcpv4SrvTest::configure(const std::string& config) {
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
+    ASSERT_TRUE(status);
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    ASSERT_EQ(0, rcode);
+}
+
+
 }; // end of isc::dhcp::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 130 - 164
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-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
@@ -87,6 +87,120 @@ public:
 
 typedef boost::shared_ptr<PktFilterTest> PktFilterTestPtr;
 
+/// @brief "Naked" DHCPv4 server, exposes internal fields
+class NakedDhcpv4Srv: public Dhcpv4Srv {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor disables default modes of operation used by the
+    /// Dhcpv4Srv class:
+    /// - Send/receive broadcast messages through sockets on interfaces
+    /// which support broadcast traffic.
+    /// - Direct DHCPv4 traffic - communication with clients which do not
+    /// have IP address assigned yet.
+    ///
+    /// Enabling these modes requires root privilges so they must be
+    /// disabled for unit testing.
+    ///
+    /// Note, that disabling broadcast options on sockets does not impact
+    /// the operation of these tests because they use local loopback
+    /// interface which doesn't have broadcast capability anyway. It rather
+    /// prevents setting broadcast options on other (broadcast capable)
+    /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
+    ///
+    /// The Direct DHCPv4 Traffic capability can be disabled here because
+    /// it is tested with PktFilterLPFTest unittest. The tests which belong
+    /// to PktFilterLPFTest can be enabled on demand when root privileges can
+    /// be guaranteed.
+    ///
+    /// @param port port number to listen on; the default value 0 indicates
+    /// that sockets should not be opened.
+    NakedDhcpv4Srv(uint16_t port = 0)
+        : Dhcpv4Srv(port, "type=memfile", false, false) {
+        // Create fixed server id.
+        server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+                                            asiolink::IOAddress("192.0.3.1")));
+    }
+
+    /// @brief Returns fixed server identifier assigned to the naked server
+    /// instance.
+    OptionPtr getServerID() const {
+        return (server_id_);
+    }
+
+    /// @brief fakes packet reception
+    /// @param timeout ignored
+    ///
+    /// The method receives all packets queued in receive queue, one after
+    /// another. Once the queue is empty, it initiates the shutdown procedure.
+    ///
+    /// See fake_received_ field for description
+    virtual Pkt4Ptr receivePacket(int /*timeout*/) {
+
+        // If there is anything prepared as fake incoming traffic, use it
+        if (!fake_received_.empty()) {
+            Pkt4Ptr pkt = fake_received_.front();
+            fake_received_.pop_front();
+            return (pkt);
+        }
+
+        // If not, just trigger shutdown and return immediately
+        shutdown();
+        return (Pkt4Ptr());
+    }
+
+    /// @brief fake packet sending
+    ///
+    /// Pretend to send a packet, but instead just store it in fake_send_ list
+    /// where test can later inspect server's response.
+    virtual void sendPacket(const Pkt4Ptr& pkt) {
+        fake_sent_.push_back(pkt);
+    }
+
+    /// @brief adds a packet to fake receive queue
+    ///
+    /// See fake_received_ field for description
+    void fakeReceive(const Pkt4Ptr& pkt) {
+        fake_received_.push_back(pkt);
+    }
+
+    virtual ~NakedDhcpv4Srv() {
+    }
+
+    /// @brief Dummy server identifier option used by various tests.
+    OptionPtr server_id_;
+
+    /// @brief packets we pretend to receive
+    ///
+    /// Instead of setting up sockets on interfaces that change between OSes, it
+    /// is much easier to fake packet reception. This is a list of packets that
+    /// we pretend to have received. You can schedule new packets to be received
+    /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
+    std::list<Pkt4Ptr> fake_received_;
+
+    std::list<Pkt4Ptr> fake_sent_;
+
+    using Dhcpv4Srv::adjustIfaceData;
+    using Dhcpv4Srv::appendServerID;
+    using Dhcpv4Srv::processDiscover;
+    using Dhcpv4Srv::processRequest;
+    using Dhcpv4Srv::processRelease;
+    using Dhcpv4Srv::processDecline;
+    using Dhcpv4Srv::processInform;
+    using Dhcpv4Srv::processClientName;
+    using Dhcpv4Srv::computeDhcid;
+    using Dhcpv4Srv::createNameChangeRequests;
+    using Dhcpv4Srv::acceptServerId;
+    using Dhcpv4Srv::sanityCheck;
+    using Dhcpv4Srv::srvidToString;
+    using Dhcpv4Srv::unpackOptions;
+    using Dhcpv4Srv::name_change_reqs_;
+    using Dhcpv4Srv::classifyPacket;
+    using Dhcpv4Srv::accept;
+    using Dhcpv4Srv::acceptMessageType;
+};
+
 class Dhcpv4SrvTest : public ::testing::Test {
 public:
 
@@ -267,61 +381,6 @@ public:
     /// @return created packet
     Pkt4Ptr packetFromCapture(const std::string& hex_string);
 
-    /// @brief This function cleans up after the test.
-    virtual void TearDown();
-
-    /// @brief A subnet used in most tests
-    Subnet4Ptr subnet_;
-
-    /// @brief A pool used in most tests
-    Pool4Ptr pool_;
-
-    /// @brief A client-id used in most tests
-    ClientIdPtr client_id_;
-
-    int rcode_;
-
-    isc::data::ConstElementPtr comment_;
-
-};
-
-/// @brief Test fixture class to be used for tests which require fake
-/// interfaces.
-///
-/// The DHCPv4 server must always append the server identifier to its response.
-/// The server identifier is typically an IP address assigned to the interface
-/// on which the query has been received. The DHCPv4 server uses IfaceMgr to
-/// check this address. In order to test this functionality, a set of interfaces
-/// must be known to the test. This test fixture class creates a set of well
-/// known (fake) interfaces which can be assigned to the test DHCPv4 messages
-/// so as the response (including server identifier) can be validated.
-/// The real interfaces are removed from the IfaceMgr in the constructor and
-/// they are re-assigned in the destructor.
-class Dhcpv4SrvFakeIfaceTest : public Dhcpv4SrvTest {
-public:
-    /// @brief Constructor.
-    ///
-    /// Creates a set of fake interfaces:
-    /// - lo, index: 0, address: 127.0.0.1
-    /// - eth0, index: 1, address: 192.0.3.1
-    /// - eth1, index: 2, address: 10.0.0.1
-    ///
-    /// These interfaces replace the real interfaces detected by the IfaceMgr.
-    Dhcpv4SrvFakeIfaceTest();
-
-    /// @brief Restores the original interface configuration.
-    virtual void TearDown();
-
-    /// @brief Creates an instance of the interface.
-    ///
-    /// @param name Name of the interface.
-    /// @param ifindex Index of the interface.
-    /// @param addr IP address assigned to the interface, represented as string.
-    ///
-    /// @return Iface Instance of the interface.
-    static Iface createIface(const std::string& name, const int ifindex,
-                             const std::string& addr);
-
     /// @brief Tests if Discover or Request message is processed correctly
     ///
     /// This test verifies that the Parameter Request List option is handled
@@ -335,123 +394,30 @@ public:
     /// @param msg_type DHCPDISCOVER or DHCPREQUEST
     void testDiscoverRequest(const uint8_t msg_type);
 
-    /// @brief Holds a pointer to the packet filter object currently used
-    /// by the IfaceMgr.
-    PktFilterTestPtr current_pkt_filter_;
-
-};
-
-/// @brief "Naked" DHCPv4 server, exposes internal fields
-class NakedDhcpv4Srv: public Dhcpv4Srv {
-public:
-
-    /// @brief Constructor.
-    ///
-    /// This constructor disables default modes of operation used by the
-    /// Dhcpv4Srv class:
-    /// - Send/receive broadcast messages through sockets on interfaces
-    /// which support broadcast traffic.
-    /// - Direct DHCPv4 traffic - communication with clients which do not
-    /// have IP address assigned yet.
-    ///
-    /// Enabling these modes requires root privilges so they must be
-    /// disabled for unit testing.
-    ///
-    /// Note, that disabling broadcast options on sockets does not impact
-    /// the operation of these tests because they use local loopback
-    /// interface which doesn't have broadcast capability anyway. It rather
-    /// prevents setting broadcast options on other (broadcast capable)
-    /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
-    ///
-    /// The Direct DHCPv4 Traffic capability can be disabled here because
-    /// it is tested with PktFilterLPFTest unittest. The tests which belong
-    /// to PktFilterLPFTest can be enabled on demand when root privileges can
-    /// be guaranteed.
+    /// @brief Runs DHCPv4 configuration from the JSON string.
     ///
-    /// @param port port number to listen on; the default value 0 indicates
-    /// that sockets should not be opened.
-    NakedDhcpv4Srv(uint16_t port = 0)
-        : Dhcpv4Srv(port, "type=memfile", false, false) {
-        // Create fixed server id.
-        server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
-                                            asiolink::IOAddress("192.0.3.1")));
-    }
-
-    /// @brief Returns fixed server identifier assigned to the naked server
-    /// instance.
-    OptionPtr getServerID() const {
-        return (server_id_);
-    }
+    /// @param config String holding server configuration in JSON format.
+    void configure(const std::string& config);
 
-    /// @brief fakes packet reception
-    /// @param timeout ignored
-    ///
-    /// The method receives all packets queued in receive queue, one after
-    /// another. Once the queue is empty, it initiates the shutdown procedure.
-    ///
-    /// See fake_received_ field for description
-    virtual Pkt4Ptr receivePacket(int /*timeout*/) {
-
-        // If there is anything prepared as fake incoming traffic, use it
-        if (!fake_received_.empty()) {
-            Pkt4Ptr pkt = fake_received_.front();
-            fake_received_.pop_front();
-            return (pkt);
-        }
-
-        // If not, just trigger shutdown and return immediately
-        shutdown();
-        return (Pkt4Ptr());
-    }
+    /// @brief This function cleans up after the test.
+    virtual void TearDown();
 
-    /// @brief fake packet sending
-    ///
-    /// Pretend to send a packet, but instead just store it in fake_send_ list
-    /// where test can later inspect server's response.
-    virtual void sendPacket(const Pkt4Ptr& pkt) {
-        fake_sent_.push_back(pkt);
-    }
+    /// @brief A subnet used in most tests
+    Subnet4Ptr subnet_;
 
-    /// @brief adds a packet to fake receive queue
-    ///
-    /// See fake_received_ field for description
-    void fakeReceive(const Pkt4Ptr& pkt) {
-        pkt->setIface("eth0");
-        fake_received_.push_back(pkt);
-    }
+    /// @brief A pool used in most tests
+    Pool4Ptr pool_;
 
-    virtual ~NakedDhcpv4Srv() {
-    }
+    /// @brief A client-id used in most tests
+    ClientIdPtr client_id_;
 
-    /// @brief Dummy server identifier option used by various tests.
-    OptionPtr server_id_;
+    int rcode_;
 
-    /// @brief packets we pretend to receive
-    ///
-    /// Instead of setting up sockets on interfaces that change between OSes, it
-    /// is much easier to fake packet reception. This is a list of packets that
-    /// we pretend to have received. You can schedule new packets to be received
-    /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
-    std::list<Pkt4Ptr> fake_received_;
+    isc::data::ConstElementPtr comment_;
 
-    std::list<Pkt4Ptr> fake_sent_;
+    /// @brief Server object under test.
+    NakedDhcpv4Srv srv_;
 
-    using Dhcpv4Srv::adjustIfaceData;
-    using Dhcpv4Srv::appendServerID;
-    using Dhcpv4Srv::processDiscover;
-    using Dhcpv4Srv::processRequest;
-    using Dhcpv4Srv::processRelease;
-    using Dhcpv4Srv::processDecline;
-    using Dhcpv4Srv::processInform;
-    using Dhcpv4Srv::processClientName;
-    using Dhcpv4Srv::computeDhcid;
-    using Dhcpv4Srv::createNameChangeRequests;
-    using Dhcpv4Srv::acceptServerId;
-    using Dhcpv4Srv::sanityCheck;
-    using Dhcpv4Srv::srvidToString;
-    using Dhcpv4Srv::unpackOptions;
-    using Dhcpv4Srv::name_change_reqs_;
-    using Dhcpv4Srv::classifyPacket;
 };
 
 }; // end of isc::dhcp::test namespace

+ 403 - 0
src/bin/dhcp4/tests/direct_client_unittest.cc

@@ -0,0 +1,403 @@
+// 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 <dhcp/pkt4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcp4/config_parser.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test fixture class for testing message processing from directly
+/// connected clients.
+///
+/// This class provides mechanisms for testing processing of DHCPv4 messages
+/// from directly connected clients.
+class DirectClientTest : public Dhcpv4SrvTest {
+public:
+    /// @brief Constructor.
+    ///
+    /// Initializes DHCPv4 server object used by various tests.
+    DirectClientTest();
+
+    /// @brief Configures the server with one subnet.
+    ///
+    /// This creates new configuration for the DHCPv4 with one subnet having
+    /// a specified prefix.
+    ///
+    /// The subnet parameters (such as options, timers etc.) are aribitrarily
+    /// selected. The subnet and pool mask is always /24. The real configuration
+    /// would exclude .0 (network address) and .255 (broadcast address), but we
+    /// ignore that fact for the sake of test simplicity.
+    ///
+    /// @param prefix Prefix for a subnet.
+    void configureSubnet(const std::string& prefix);
+
+    /// @brief Configures the server with two subnets.
+    ///
+    /// This function configures DHCPv4 server with two different subnets.
+    /// The subnet parameters (such as options, timers etc.) are aribitrarily
+    /// selected. The subnet and pool mask is /24. The real configuration
+    /// would exclude .0 (network address) and .255 (broadcast address), but we
+    /// ignore that fact for the sake of test simplicity.
+    ///
+    /// @param prefix1 Prefix of the first subnet to be configured.
+    /// @param prefix2 Prefix of the second subnet to be configured.
+    void configureTwoSubnets(const std::string& prefix1,
+                             const std::string& prefix2);
+
+    /// @brief Creates simple message from a client.
+    ///
+    /// This function creates a DHCPv4 message having a specified type
+    /// (e.g. Discover, Request) and sets some properties of this
+    /// message: client identifier, address and interface. The copy of
+    /// this message is then created by parsing wire data of the original
+    /// message. This simulates the case when the message is received and
+    /// parsed by the server.
+    ///
+    /// @param msg_type Type of the message to be created.
+    /// @param iface Name of the interface on which the message has been
+    /// "received" by the server.
+    ///
+    /// @return Generated message.
+    Pkt4Ptr createClientMessage(const uint16_t msg_type,
+                                const std::string& iface);
+
+    /// @brief Creates simple message from a client.
+    ///
+    /// This function configures a client's message by adding client identifier,
+    /// setting interface and addresses. The copy of this message is then
+    /// created by parsing wire data of the original message. This simulates the
+    /// case when the message is received and parsed by the server.
+    ///
+    /// @param msg Caller supplied message to be configured. This object must
+    /// not be NULL.
+    /// @param iface Name of the interface on which the message has been
+    /// "received" by the server.
+    ///
+    /// @return Configured and parsed message.
+    Pkt4Ptr createClientMessage(const Pkt4Ptr &msg, const std::string& iface);
+
+};
+
+DirectClientTest::DirectClientTest() : Dhcpv4SrvTest() {
+}
+
+void
+DirectClientTest::configureSubnet(const std::string& prefix) {
+    std::ostringstream config;
+    config << "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"" << prefix << "/24\" ],"
+        "    \"subnet\": \"" << prefix << "/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000"
+        "} ],"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str());
+
+}
+
+void
+DirectClientTest::configureTwoSubnets(const std::string& prefix1,
+                                      const std::string& prefix2) {
+    std::ostringstream config;
+    config << "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"" << prefix1 << "/24\" ],"
+        "    \"subnet\": \"" << prefix1 << "/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000"
+        " },"
+        "{ "
+        "    \"pool\": [ \"" << prefix2 << "/24\" ],"
+        "    \"subnet\": \"" << prefix2 << "/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000"
+        "} ],"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str());
+}
+
+Pkt4Ptr
+DirectClientTest:: createClientMessage(const uint16_t msg_type,
+                                       const std::string& iface) {
+    // Create a source packet.
+    Pkt4Ptr msg = Pkt4Ptr(new Pkt4(msg_type, 1234));
+    return (createClientMessage(msg, iface));
+
+}
+
+Pkt4Ptr
+DirectClientTest::createClientMessage(const Pkt4Ptr& msg,
+                                      const std::string& iface) {
+    msg->setRemoteAddr(IOAddress("255.255.255.255"));
+    msg->addOption(generateClientId());
+    msg->setIface(iface);
+
+    // Create copy of this packet by parsing its wire data. Make sure that the
+    // local and remote address are set like it was a message sent from the
+    // directly connected client.
+    Pkt4Ptr received;
+    createPacketFromBuffer(msg, received);
+    received->setIface(iface);
+    received->setLocalAddr(IOAddress("255.255.255.255"));
+    received->setRemoteAddr(IOAddress("0.0.0.0"));
+
+    return (received);
+}
+
+// This test checks that the message from directly connected client
+// is processed and that client is offered IPv4 address from the subnet which
+// is suitable for the local interface on which the client's message is
+// received. This test uses two subnets, with two active interfaces which IP
+// addresses belong to these subnets. The address offered to the client
+// which message has been sent over eth0 should belong to a different
+// subnet than the address offered for the client sending its message
+// via eth1.
+TEST_F(DirectClientTest,  twoSubnets) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add two subnets: address on eth0 belongs to the second subnet,
+    // address on eth1 belongs to the first subnet.
+    ASSERT_NO_FATAL_FAILURE(configureTwoSubnets("192.0.2.0", "10.0.0.0"));
+    // Create Discover and simulate reception of this message through eth0.
+    Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0");
+    srv_.fakeReceive(dis);
+    // Create Request and simulate reception of this message through eth1.
+    Pkt4Ptr req = createClientMessage(DHCPREQUEST, "eth1");
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server did send reposonses.
+    ASSERT_EQ(2, srv_.fake_sent_.size());
+
+    // Make sure that we received a response.
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+    srv_.fake_sent_.pop_front();
+
+    // Client should get an Offer (not a NAK).
+    ASSERT_EQ(DHCPOFFER, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+    // A client that sent Request over the other interface should get Ack.
+    response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+
+    // Client should get an Ack (not a NAK).
+    ASSERT_EQ(DHCPACK, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
+
+}
+
+// This test checks that server selects a subnet when receives a message
+// through an interface for which the subnet has been configured. This
+// interface has IPv4 address assigned which belongs to this subnet.
+// This test also verifies that when the message is received through
+// the interface for which there is no suitable subnet, the message
+// is discarded.
+TEST_F(DirectClientTest, oneSubnet) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add a subnet which will be selected when a message from directly
+    // connected client is received through interface eth0.
+    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+    // Create Discover and simulate reception of this message through eth0.
+    Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0");
+    srv_.fakeReceive(dis);
+    // Create Request and simulate reception of this message through eth1.
+    Pkt4Ptr req = createClientMessage(DHCPDISCOVER, "eth1");
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server sent one response for the message received
+    // through eth0. The other client's message should be dicarded.
+    ASSERT_EQ(1, srv_.fake_sent_.size());
+
+    // Check the response. The first Discover was sent via eth0 for which
+    // the subnet has been configured.
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+    srv_.fake_sent_.pop_front();
+
+    // Since Discover has been received through the interface for which
+    // the subnet has been configured, the server should respond with
+    // an Offer message.
+    ASSERT_EQ(DHCPOFFER, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+}
+
+// This test verifies that the server uses ciaddr to select a subnet for a
+// client which renews its lease.
+TEST_F(DirectClientTest, renew) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add a subnet.
+    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+    // Make sure that the subnet has been really added. Also, the subnet
+    // will be needed to create a lease for a client.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"));
+    // Create a lease for a client that we will later renewed. By explicitly
+    // creating a lease we will get to know the lease parameters, such as
+    // leased address etc.
+    const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
+    Lease4Ptr lease(new Lease4(IOAddress("10.0.0.10"), hwaddr, sizeof(hwaddr),
+                               &generateClientId()->getData()[0],
+                               generateClientId()->getData().size(),
+                               100, 50, 75, time(NULL),
+                               subnet->getID()));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    // Create a Request to renew client's lease. The renew request is unicast
+    // through eth1. Note, that in case of renewal the client unicasts its
+    // Request and sets the ciaddr. The server is supposed to use ciaddr to
+    // pick the subnet for the client. In order to make sure that the server
+    // uses ciaddr, we simulate reception of the packet through eth1, for which
+    // there is no subnet for directly connected clients.
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setCiaddr(IOAddress("10.0.0.10"));
+    req = createClientMessage(req, "eth1");
+    req->setLocalAddr(IOAddress("10.0.0.1"));
+    req->setRemoteAddr(req->getCiaddr());
+
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server did send reposonse.
+    ASSERT_EQ(1, srv_.fake_sent_.size());
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+
+    ASSERT_EQ(DHCPACK, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+}
+
+// This test verifies that when a client in the Rebinding state broadcasts
+// a Request message through an interface for which a subnet is configured,
+// the server responds to this Request. It also verifies that when such a
+// Request is sent through the interface for which there is no subnet configured
+// the client's message is discarded.
+TEST_F(DirectClientTest, rebind) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add a subnet.
+    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+    // Make sure that the subnet has been really added. Also, the subnet
+    // will be needed to create a lease for a client.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"));
+    // Create a lease, which will be later renewed. By explicitly creating a
+    // lease we will know the lease parameters, such as leased address etc.
+    const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
+    Lease4Ptr lease(new Lease4(IOAddress("10.0.0.10"), hwaddr, sizeof(hwaddr),
+                               &generateClientId()->getData()[0],
+                               generateClientId()->getData().size(),
+                               100, 50, 75, time(NULL),
+                               subnet->getID()));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    // Broadcast Request through an interface for which there is no subnet
+    // configured. This messag should be discarded by the server.
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setCiaddr(IOAddress("10.0.0.10"));
+    req = createClientMessage(req, "eth1");
+    req->setRemoteAddr(req->getCiaddr());
+
+    srv_.fakeReceive(req);
+
+    // Broadcast another Request through an interface for which there is
+    // a subnet configured. The server should generate a response.
+    req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 5678));
+    req->setCiaddr(IOAddress("10.0.0.10"));
+    req = createClientMessage(req, "eth0");
+    req->setRemoteAddr(req->getCiaddr());
+
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server did send exactly one reposonse.
+    ASSERT_EQ(1, srv_.fake_sent_.size());
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+
+    // Make sure that the server responsed with ACK, not NAK.
+    ASSERT_EQ(DHCPACK, response->getType());
+    // Make sure that the response is generated for the second Request
+    // (transmitted over eth0).
+    EXPECT_EQ(5678, response->getTransid());
+    // Check that the offered address belongs to the suitable subnet.
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr());
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+}
+
+}

+ 38 - 8
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-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
@@ -16,6 +16,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -31,16 +32,15 @@ using namespace isc::dhcp_ddns;
 
 namespace {
 
-class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
+class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
 public:
-
     // Bit Constants for turning on and off DDNS configuration options.
     static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
     static const uint16_t OVERRIDE_NO_UPDATE = 2;
     static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t REPLACE_CLIENT_NAME = 8;
 
-    NameDhcpv4SrvTest() : Dhcpv4SrvFakeIfaceTest() {
+    NameDhcpv4SrvTest() : Dhcpv4SrvTest() {
         srv_ = new NakedDhcpv4Srv(0);
         // Config DDNS to be enabled, all controls off
         enableD2();
@@ -154,7 +154,7 @@ public:
                                 const bool include_clientid = true) {
         Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
         pkt->setRemoteAddr(IOAddress("192.0.2.3"));
-        pkt->setIface("eth0");
+        pkt->setIface("eth1");
         // For DISCOVER we don't include server id, because client broadcasts
         // the message to all servers.
         if (msg_type != DHCPDISCOVER) {
@@ -357,6 +357,10 @@ public:
     /// @param response_flags Mask of expected FQDN flags in the response
     void flagVsConfigScenario(const uint8_t client_flags,
                        const uint8_t response_flags) {
+        // Create fake interfaces and open fake sockets.
+        IfaceMgrTestConfig iface_config(true);
+        IfaceMgr::instance().openSockets4();
+
         Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, client_flags,
                                           "myhost.example.com.",
                                           Option4ClientFqdn::FULL, true);
@@ -726,6 +730,9 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsLeaseMismatch) {
 // Test that the OFFER message generated as a result of the DISCOVER message
 // processing will not result in generation of the NameChangeRequests.
 TEST_F(NameDhcpv4SrvTest, processDiscover) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithFqdn(DHCPDISCOVER, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       "myhost.example.com.",
@@ -741,6 +748,9 @@ TEST_F(NameDhcpv4SrvTest, processDiscover) {
 // Test that server generates client's hostname from the IP address assigned
 // to it when DHCPv4 Client FQDN option specifies an empty domain-name.
 TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       "", Option4ClientFqdn::PARTIAL, true);
@@ -767,6 +777,10 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
 // to it when DHCPv4 Client FQDN option specifies an empty domain-name  AND
 // ddns updates are disabled.
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
+    // Create fake interfaces and open fake sockets.
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     disableD2();
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
@@ -790,10 +804,13 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
 // Test that server generates client's hostname from the IP address assigned
 // to it when Hostname option carries the top level domain-name.
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
-    req->setIface("eth0");
+    req->setIface("eth1");
 
     Pkt4Ptr reply;
     ASSERT_NO_THROW(reply = srv_->processRequest(req));
@@ -817,6 +834,9 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
 // request but modify the DNS entries for the lease according to the contents
 // of the FQDN sent in the second request.
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req1 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                        Option4ClientFqdn::FLAG_E,
                                        "myhost.example.com.",
@@ -869,11 +889,14 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
 // but modify the DNS entries for the lease according to the contents of the
 // Hostname sent in the second request.
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req1 = generatePktWithHostname(DHCPREQUEST, "myhost.example.com.");
 
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
-    req1->setIface("eth0");
+    req1->setIface("eth1");
 
 
     Pkt4Ptr reply;
@@ -896,7 +919,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
 
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
-    req2->setIface("eth0");
+    req2->setIface("eth1");
 
     ASSERT_NO_THROW(reply = srv_->processRequest(req2));
 
@@ -923,6 +946,9 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
 // DDNS updates are enabled that the server genenerates a NameChangeRequest
 // to remove entries corresponding to the released lease.
 TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Verify the updates are enabled.
     ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
 
@@ -965,6 +991,10 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
 // and DDNS updates are disabled that server does NOT generate a
 // NameChangeRequest to remove entries corresponding to the released lease.
 TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
+    // Create fake interfaces and open fake sockets.
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Disable DDNS.
     disableD2();
     ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());

+ 4 - 2
src/lib/dhcp/dhcp4.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004-2011 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 2004-2011, 2014 by Internet Systems Consortium, Inc. ("ISC")
  * Copyright (c) 1995-2003 by Internet Software Consortium
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -154,7 +154,9 @@ enum DHCPMessageType {
     DHCPLEASEQUERY      =  10,
     DHCPLEASEUNASSIGNED =  11,
     DHCPLEASEUNKNOWN    =  12,
-    DHCPLEASEACTIVE     =  13
+    DHCPLEASEACTIVE     =  13,
+    DHCPBULKLEASEQUERY  =  14,
+    DHCPLEASEQUERYDONE  =  15
 };
 
 static const uint16_t DHCP4_CLIENT_PORT = 68;

+ 18 - 0
src/lib/dhcp/iface_mgr.cc

@@ -199,6 +199,24 @@ void Iface::addUnicast(const isc::asiolink::IOAddress& addr) {
     unicasts_.push_back(addr);
 }
 
+bool
+Iface::getAddress4(isc::asiolink::IOAddress& address) const {
+    // Iterate over existing addresses assigned to the interface.
+    // Try to find the one that is IPv4.
+    const AddressCollection& addrs = getAddresses();
+    for (AddressCollection::const_iterator addr = addrs.begin();
+         addr != addrs.end(); ++addr) {
+        // If address is IPv4, we assign it to the function argument
+        // and return true.
+        if (addr->isV4()) {
+            address = *addr;
+            return (true);
+        }
+    }
+    // There is no IPv4 address assigned to this interface.
+    return (false);
+}
+
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {

+ 11 - 0
src/lib/dhcp/iface_mgr.h

@@ -246,6 +246,17 @@ public:
     /// @return collection of addresses
     const AddressCollection& getAddresses() const { return addrs_; }
 
+    /// @brief Returns IPv4 address assigned to the interface.
+    ///
+    /// This function looks for an IPv4 address assigned to the interface
+    /// and returns it through the argument.
+    ///
+    /// @param [out] address IPv4 address assigned to the interface.
+    ///
+    /// @return Boolean value which informs whether IPv4 address has been found
+    /// for the interface (if true), or not (false).
+    bool getAddress4(isc::asiolink::IOAddress& address) const;
+
     /// @brief Adds an address to an interface.
     ///
     /// This only adds an address to collection, it does not physically

+ 2 - 0
src/lib/dhcp/pkt4.cc

@@ -396,6 +396,7 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
     case DHCPRELEASE:
     case DHCPINFORM:
     case DHCPLEASEQUERY:
+    case DHCPBULKLEASEQUERY:
         return (BOOTREQUEST);
 
     case DHCPACK:
@@ -404,6 +405,7 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
     case DHCPLEASEUNASSIGNED:
     case DHCPLEASEUNKNOWN:
     case DHCPLEASEACTIVE:
+    case DHCPLEASEQUERYDONE:
         return (BOOTREPLY);
 
     default:

+ 19 - 0
src/lib/dhcp/tests/Makefile.am

@@ -24,11 +24,29 @@ TESTS_ENVIRONMENT = \
 
 TESTS =
 if HAVE_GTEST
+
+# Creates a library which is shared by various unit tests which require
+# configuration of fake interfaces.
+# The libdhcp++ does not link with this library because this would cause
+# build failures being a result of concurrency between build of this
+# library and the unit tests when make -j option was used, as they
+# are built out of the same makefile. Instead, the libdhcp++ tests link to
+# files belonging to this library, directly.
+noinst_LTLIBRARIES = libdhcptest.la
+
+libdhcptest_la_SOURCES  = iface_mgr_test_config.cc iface_mgr_test_config.h
+libdhcptest_la_SOURCES  += pkt_filter_test_stub.cc pkt_filter_test_stub.h
+libdhcptest_la_CXXFLAGS  = $(AM_CXXFLAGS)
+libdhcptest_la_CPPFLAGS  = $(AM_CPPFLAGS)
+libdhcptest_la_LDFLAGS   = $(AM_LDFLAGS)
+libdhcptest_la_LIBADD    = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+
 TESTS += libdhcp++_unittests
 
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
+libdhcp___unittests_SOURCES += iface_mgr_test_config.cc iface_mgr_test_config.h
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc
@@ -51,6 +69,7 @@ libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet6_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_test_stub.cc pkt_filter_test_stub.h
 libdhcp___unittests_SOURCES += pkt_filter_test_utils.h pkt_filter_test_utils.cc
 libdhcp___unittests_SOURCES += pkt_filter6_test_utils.h pkt_filter6_test_utils.cc
 

+ 137 - 0
src/lib/dhcp/tests/iface_mgr_test_config.cc

@@ -0,0 +1,137 @@
+// 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/pkt_filter.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/tests/pkt_filter_test_stub.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+IfaceMgrTestConfig::IfaceMgrTestConfig(const bool default_config) {
+    IfaceMgr::instance().closeSockets();
+    IfaceMgr::instance().clearIfaces();
+    packet_filter4_ = PktFilterPtr(new PktFilterTestStub());
+    IfaceMgr::instance().setPacketFilter(packet_filter4_);
+
+    // Create default set of fake interfaces: lo, eth0 and eth1.
+    if (default_config) {
+        createIfaces();
+    }
+}
+
+IfaceMgrTestConfig::~IfaceMgrTestConfig() {
+    IfaceMgr::instance().closeSockets();
+    IfaceMgr::instance().clearIfaces();
+    IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+    IfaceMgr::instance().detectIfaces();
+}
+
+void
+IfaceMgrTestConfig::addAddress(const std::string& iface_name,
+                               const IOAddress& address) {
+    Iface* iface = IfaceMgr::instance().getIface(iface_name);
+    if (iface == NULL) {
+        isc_throw(isc::BadValue, "interface '" << iface_name
+                  << "' doesn't exist");
+    }
+    iface->addAddress(address);
+}
+
+void
+IfaceMgrTestConfig::addIface(const Iface& iface) {
+    IfaceMgr::instance().addInterface(iface);
+}
+
+void
+IfaceMgrTestConfig::addIface(const std::string& name, const int ifindex) {
+    IfaceMgr::instance().addInterface(createIface(name, ifindex));
+}
+
+Iface
+IfaceMgrTestConfig::createIface(const std::string &name, const int ifindex) {
+    Iface iface(name, ifindex);
+    if (name == "lo") {
+        iface.flag_loopback_ = true;
+    }
+    iface.flag_multicast_ = true;
+    // On BSD systems, the SO_BINDTODEVICE option is not supported.
+    // Therefore the IfaceMgr will throw an exception on attempt to
+    // open sockets on more than one broadcast-capable interface at
+    // the same time. In order to prevent this error, we mark all
+    // interfaces broadcast-incapable for unit testing.
+    iface.flag_broadcast_ = false;
+    iface.flag_up_ = true;
+    iface.flag_running_ = true;
+    iface.inactive4_ = false;
+    iface.inactive6_ = false;
+    return (iface);
+}
+
+void
+IfaceMgrTestConfig::createIfaces() {
+    // local loopback
+    addIface("lo", 0);
+    addAddress("lo", IOAddress("127.0.0.1"));
+    addAddress("lo", IOAddress("::1"));
+    // eth0
+    addIface("eth0", 1);
+    addAddress("eth0", IOAddress("10.0.0.1"));
+    addAddress("eth0", IOAddress("fe80::3a60:77ff:fed5:cdef"));
+    addAddress("eth0", IOAddress("2001:db8:1::1"));
+    // eth1
+    addIface("eth1", 2);
+    addAddress("eth1", IOAddress("192.0.2.3"));
+    addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:abcd"));
+
+}
+
+void
+IfaceMgrTestConfig::setDirectResponse(const bool direct_resp) {
+    boost::shared_ptr<PktFilterTestStub> stub =
+        boost::dynamic_pointer_cast<PktFilterTestStub>(getPacketFilter4());
+    if (!stub) {
+        isc_throw(isc::Unexpected, "unable to set direct response capability for"
+                  " test packet filter - current packet filter is not"
+                  " of a PktFilterTestStub");
+    }
+    stub->direct_response_supported_ = direct_resp;
+}
+
+void
+IfaceMgrTestConfig::setIfaceFlags(const std::string& name,
+                                  const FlagLoopback& loopback,
+                                  const FlagUp& up,
+                                  const FlagRunning& running,
+                                  const FlagInactive4& inactive4,
+                                  const FlagInactive6& inactive6) {
+    Iface* iface = IfaceMgr::instance().getIface(name);
+    if (iface == NULL) {
+        isc_throw(isc::BadValue, "interface '" << name << "' doesn't exist");
+    }
+    iface->flag_loopback_ = loopback.flag_;
+    iface->flag_up_ = up.flag_;
+    iface->flag_running_ = running.flag_;
+    iface->inactive4_ = inactive4.flag_;
+    iface->inactive6_ = inactive6.flag_;
+}
+
+}
+}
+}

+ 241 - 0
src/lib/dhcp/tests/iface_mgr_test_config.h

@@ -0,0 +1,241 @@
+// 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 IFACE_MGR_TEST_CONFIG_H
+#define IFACE_MGR_TEST_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+///
+/// @name Set of structures describing interface flags.
+///
+/// These flags encapsulate the boolean type to pass the flags values
+/// to @c IfaceMgrTestConfig methods. If the values passed to these methods
+/// were not encapsulated by the types defined here, the API would become
+/// prone to errors like swapping parameters being passed to specific functions.
+/// For example, in the call to @c IfaceMgrTestConfig::setIfaceFlags:
+/// @code
+///     IfaceMgrTestConfig test_config(true);
+///     test_config.setIfaceFlags("eth1", false, false, true, false, false);
+/// @endcode
+///
+/// it is quite likely that the developer by mistake swaps the values and
+/// assigns them to wrong flags. When the flags are encapsulated with dedicated
+/// structs, the compiler will return an error if values are swapped. For
+/// example:
+/// @code
+///     IfaceMgrTestConfig test_config(true);
+///     test_config.setIfaceFlags("eth1", FlagLoopback(false), FlagUp(false),
+///                               FlagRunning(true), FlagInactive4(false),
+///                               FlagInactive6(false));
+/// @endcode
+/// will succeed, but the following code will result in the compilation error
+/// and thus protect a developer from making an error:
+/// @code
+///     IfaceMgrTestConfig test_config(true);
+///     test_config.setIfaceFlags("eth1", FlagLoopback(false),
+///                               FlagRunning(true), FlagUp(false),
+///                               FlagInactive4(false), FlagInactive6(false));
+/// @endcode
+///
+//@{
+/// @brief Structure describing the loopback interface flag.
+struct FlagLoopback {
+    explicit FlagLoopback(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the up interface flag.
+struct FlagUp {
+    explicit FlagUp(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the running interface flag.
+struct FlagRunning {
+    explicit FlagRunning(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the inactive4 interface flag.
+struct FlagInactive4 {
+    explicit FlagInactive4(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the inactive6 interface flag.
+struct FlagInactive6 {
+    explicit FlagInactive6(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+//@}
+
+/// @brief Convenience class for configuring @c IfaceMgr for unit testing.
+///
+/// This class is used by various unit tests which test the code relaying
+/// on IfaceMgr. The use of this class is not limited to libdhcp++ validation.
+/// There are other libraries and applications (e.g. DHCP servers) which
+/// depend on @c IfaceMgr.
+///
+/// During the normal operation, the @c IfaceMgr detects interfaces present
+/// on the machine where it is running. It also provides the means for
+/// applications to open sockets on these interfaces and perform other
+/// IO operations. This however creates dependency of the applications
+/// using @c IfaceMgr on the physical properties of the system and effectively
+/// makes it very hard to unit test the dependent code.
+///
+/// Unit tests usually require that @c IfaceMgr holds a list of well known
+/// interfaces with the well known set of IP addresses and other properties
+/// (a.k.a. interface flags). The solution which works for many test scenarios
+/// is to provide a set of well known fake interfaces, by bypassing the
+/// standard interface detection procedure and manually adding @c Iface objects
+/// which encapsulate the fake interfaces. As a consequence, it becomes
+/// impossible to test IO operations (e.g. sending packets) because real sockets
+/// can't be opened on these interfaces. The @c PktFilterTestStub class
+/// is used by this class to mimic behavior of IO operations on fake sockets.
+///
+/// This class provides a set of convenience functions that should be called
+/// by unit tests to configure the @c IfaceMgr with fake interfaces.
+///
+/// The class allows the caller to create custom fake interfaces (with custom
+/// IPv4 and IPv6 addresses, flags etc.), but it also provides a default
+/// test configuration for interfaces as follows:
+/// - lo
+///   - 127.0.0.1
+///   - ::1
+/// - eth0
+///   - 10.0.0.1
+///   - fe80::3a60:77ff:fed5:cdef
+///   - 2001:db8:1::1
+/// - eth1
+///   - 192.0.2.3
+///   - fe80::3a60:77ff:fed5:abcd
+///
+/// For all interfaces the following flags are set:
+/// - multicast
+/// - up
+/// - running
+class IfaceMgrTestConfig : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// It closes all sockets opened by @c IfaceMgr and removes all interfaces
+    /// being used by @c IfaceMgr.
+    IfaceMgrTestConfig(const bool default_config = false);
+
+    /// @brief Destructor.
+    ///
+    /// Closes all currently opened sockets, removes current interfaces and
+    /// sets the default packet filtering classes. The default packet filtering
+    /// classes are used for IO operations on real sockets/interfaces.
+    ///
+    /// Destructor also re-detects real interfaces.
+    ~IfaceMgrTestConfig();
+
+    /// @brief Adds new IPv4 or IPv6 address to the interface.
+    ///
+    /// @param iface_name Name of the interface on which new address should
+    /// be configured.
+    /// @param address IPv4 or IPv6 address to be configured on the interface.
+    void addAddress(const std::string& iface_name,
+                    const asiolink::IOAddress& address);
+
+    /// @brief Configures new interface for the @c IfaceMgr.
+    ///
+    /// @param iface Object encapsulating interface to be added.
+    void addIface(const Iface& iface);
+
+    /// @brief Configures new interface for the @c IfaceMgr.
+    ///
+    /// @param name Name of the new interface.
+    /// @param ifindex Index for a new interface.
+    void addIface(const std::string& name, const int ifindex);
+
+    /// @brief Create an object representing interface.
+    ///
+    /// Apart from creating an interface, this function also sets the
+    /// interface flags:
+    /// - loopback flag if interface name is "lo"
+    /// - up always true
+    /// - running always true
+    /// - inactive always to false
+    /// - multicast always to true
+    /// - broadcast always to false
+    ///
+    /// If one needs to modify the default flag settings, the setIfaceFlags
+    /// function should be used.
+    ///
+    /// @param name A name of the interface to be created.
+    /// @param ifindex An index of the interface to be created.
+    ///
+    /// @return An object representing interface.
+    static Iface createIface(const std::string& name, const int ifindex);
+
+    /// @brief Creates a default (example) set of fake interfaces.
+    void createIfaces();
+
+    /// @brief Returns currently used packet filter for DHCPv4.
+    PktFilterPtr getPacketFilter4() const {
+        return (packet_filter4_);
+    }
+
+    /// @brief Sets the direct response capability for current packet filter.
+    ///
+    /// The test uses stub implementation of packet filter object. It is
+    /// possible to configure that object to report having a capability
+    /// to directly repond to clients which don't have an address yet.
+    /// This function sets this property for packet filter object.
+    ///
+    /// @param direct_resp Value to be set.
+    ///
+    /// @throw isc::Unexpected if unable to set the property.
+    void setDirectResponse(const bool direct_resp);
+
+    /// @brief Sets various flags on the specified interface.
+    ///
+    /// This function configures interface with new values for flags.
+    ///
+    /// @param name Interface name.
+    /// @param loopback Specifies if interface is a loopback interface.
+    /// @param up Specifies if the interface is up.
+    /// @param running Specifies if the interface is running.
+    /// @param inactive4 Specifies if the interface is inactive for V4
+    /// traffic, i.e. @c IfaceMgr opens V4 sockets on this interface.
+    /// @param inactive6 Specifies if the interface is inactive for V6
+    /// traffic, i.e. @c IfaceMgr opens V6 sockets on this interface.
+    void setIfaceFlags(const std::string& name,
+                       const FlagLoopback& loopback,
+                       const FlagUp& up,
+                       const FlagRunning& running,
+                       const FlagInactive4& inactive4,
+                       const FlagInactive6& inactive6);
+
+private:
+    /// @brief Currently used packet filter for DHCPv4.
+    PktFilterPtr packet_filter4_;
+
+};
+
+};
+};
+};
+
+#endif // IFACE_MGR_TEST_CONFIG_H

+ 39 - 14
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -19,6 +19,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt_filter.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/pkt_filter6_test_utils.h>
 
 #include <boost/bind.hpp>
@@ -529,6 +530,32 @@ TEST_F(IfaceMgrTest, ifaceClass) {
     EXPECT_STREQ("eth5/7", iface.getFullName().c_str());
 }
 
+// Test that the IPv4 address can be retrieved for the interface.
+TEST_F(IfaceMgrTest, ifaceGetAddress) {
+    Iface iface("eth0", 0);
+
+    IOAddress addr("::1");
+    // Initially, the Iface has no addresses assigned.
+    EXPECT_FALSE(iface.getAddress4(addr));
+    // Add some addresses with IPv4 address in the middle.
+    iface.addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+    iface.addAddress(IOAddress("10.1.2.3"));
+    iface.addAddress(IOAddress("2001:db8:1::2"));
+    // The v4 address should be returned.
+    EXPECT_TRUE(iface.getAddress4(addr));
+    EXPECT_EQ("10.1.2.3", addr.toText());
+    // Delete the IPv4 address and leave only two IPv6 addresses.
+    ASSERT_NO_THROW(iface.delAddress(IOAddress("10.1.2.3")));
+    // The IPv4 address should not be returned.
+    EXPECT_FALSE(iface.getAddress4(addr));
+    // Add a different IPv4 address at the end of the list.
+    iface.addAddress(IOAddress("192.0.2.3"));
+    // This new address should now be returned.
+    EXPECT_TRUE(iface.getAddress4(addr));
+    EXPECT_EQ("192.0.2.3", addr.toText());
+
+}
+
 // TODO: Implement getPlainMac() test as soon as interface detection
 // is implemented.
 TEST_F(IfaceMgrTest, getIface) {
@@ -1381,31 +1408,29 @@ TEST_F(IfaceMgrTest, openSockets4) {
 // This test verifies that the socket is not open on the interface which is
 // down, but sockets are open on all other non-loopback interfaces.
 TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
-    NakedIfaceMgr ifacemgr;
-
-    // Remove all real interfaces and create a set of dummy interfaces.
-    ifacemgr.createIfaces();
-
-    boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
-    ASSERT_TRUE(custom_packet_filter);
-    ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+    IfaceMgrTestConfig config(true);
 
     // Boolean parameters specify that eth0 is:
     // - not a loopback
     // - is "down" (not up)
     // - is not running
     // - is active (is not inactive)
-    ifacemgr.setIfaceFlags("eth0", false, false, true, false, false);
-    ASSERT_FALSE(ifacemgr.getIface("eth0")->flag_up_);
-    ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
+    config.setIfaceFlags("eth0", FlagLoopback(false), FlagUp(false),
+                         FlagRunning(false), FlagInactive4(false),
+                         FlagInactive6(false));
+    ASSERT_FALSE(IfaceMgr::instance().getIface("eth0")->flag_up_);
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4(DHCP4_SERVER_PORT, true,
+                                                      NULL));
 
     // There should be no socket on eth0 open, because interface was down.
-    EXPECT_TRUE(ifacemgr.getIface("eth0")->getSockets().empty());
+    EXPECT_TRUE(IfaceMgr::instance().getIface("eth0")->getSockets().empty());
+
     // Expecting that the socket is open on eth1 because it was up, running
     // and active.
-    EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
+    EXPECT_EQ(1, IfaceMgr::instance().getIface("eth1")->getSockets().size());
     // Never open socket on loopback interface.
-    EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
+    EXPECT_TRUE(IfaceMgr::instance().getIface("lo")->getSockets().empty());
+
 }
 
 // This test verifies that the socket is not open on the interface which is

+ 49 - 0
src/lib/dhcp/tests/pkt_filter_test_stub.cc

@@ -0,0 +1,49 @@
+// 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/tests/pkt_filter_test_stub.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PktFilterTestStub::PktFilterTestStub()
+    : direct_response_supported_(true) {
+}
+
+bool
+PktFilterTestStub::isDirectResponseSupported() const {
+    return (direct_response_supported_);
+}
+
+SocketInfo
+PktFilterTestStub::openSocket(const Iface&,
+           const isc::asiolink::IOAddress& addr,
+           const uint16_t port, const bool, const bool) {
+    return (SocketInfo(addr, port, 0));
+}
+
+Pkt4Ptr
+PktFilterTestStub::receive(const Iface&, const SocketInfo&) {
+    return Pkt4Ptr();
+}
+
+int
+PktFilterTestStub::send(const Iface&, uint16_t, const Pkt4Ptr&) {
+    return (0);
+}
+
+}
+}
+}

+ 105 - 0
src/lib/dhcp/tests/pkt_filter_test_stub.h

@@ -0,0 +1,105 @@
+// 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 PKT_FILTER_TEST_STUB_H
+#define PKT_FILTER_TEST_STUB_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt4.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief A stub implementation of the PktFilter class.
+///
+/// This class implements abstract methods of the @c isc::dhcp::PktFilter
+/// class. It is used by unit tests, which test protected methods of the
+/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are
+/// no-op.
+class PktFilterTestStub : public PktFilter {
+public:
+
+    /// @brief Constructor.
+    PktFilterTestStub();
+
+    /// @brief Checks if the direct DHCPv4 response is supported.
+    ///
+    /// This function checks if the direct response capability is supported,
+    /// i.e. if the server can respond to the client which doesn't have an
+    /// address yet. For this dummy class, the true is alaways returned.
+    ///
+    /// @return always true.
+    virtual bool isDirectResponseSupported() const;
+
+    /// @brief Simulate opening of the socket.
+    ///
+    /// This function simulates opening a primary socket. In reality, it doesn't
+    /// open a socket but the socket descriptor returned in the SocketInfo
+    /// structure is always set to 0.
+    ///
+    /// @param iface An interface descriptor.
+    /// @param addr Address on the interface to be used to send packets.
+    /// @param port Port number to bind socket to.
+    /// @param receive_bcast A flag which indicates if socket should be
+    /// configured to receive broadcast packets (if true).
+    /// @param send_bcast A flag which indicates if the socket should be
+    /// configured to send broadcast packets (if true).
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @return A SocketInfo structure with the socket descriptor set to 0. The
+    /// fallback socket descriptor is set to a negative value.
+    virtual SocketInfo openSocket(const Iface& iface,
+                                  const isc::asiolink::IOAddress& addr,
+                                  const uint16_t port,
+                                  const bool receive_bcast,
+                                  const bool send_bcast);
+
+    /// @brief Simulate reception of the DHCPv4 message.
+    ///
+    /// @param iface An interface to be used to receive DHCPv4 message.
+    /// @param sock_info A descriptor of the primary and fallback sockets.
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @return always a NULL object.
+    virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& sock_info);
+
+    /// @brief Simulates sending a DHCPv4 message.
+    ///
+    /// This function does nothing.
+    ///
+    /// @param iface An interface to be used to send DHCPv4 message.
+    /// @param port A port used to send a message.
+    /// @param pkt A DHCPv4 to be sent.
+    ///
+    /// @note All parameters are ignored.
+    ///
+    /// @return 0.
+    virtual int send(const Iface& iface, uint16_t port, const Pkt4Ptr& pkt);
+
+    // Change the scope of the protected function so as they can be unit tested.
+    using PktFilter::openFallbackSocket;
+
+    bool direct_response_supported_;
+};
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_TEST_STUB_H

+ 4 - 2
src/lib/dhcp/tests/pkt_filter_test_utils.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-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
@@ -129,7 +129,9 @@ public:
     /// fallback socket descriptor is set to a negative value.
     virtual SocketInfo openSocket(const Iface& iface,
                                   const isc::asiolink::IOAddress& addr,
-                                  const uint16_t port, const bool, const bool);
+                                  const uint16_t port,
+                                  const bool receive_bcast,
+                                  const bool send_bcast);
 
     /// @brief Simulate reception of the DHCPv4 message.
     ///

+ 21 - 19
src/lib/dhcpsrv/cfgmgr.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
@@ -210,25 +211,10 @@ void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
 }
 
 Subnet4Ptr
-CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
-
-    // If there's only one subnet configured, let's just use it
-    // The idea is to keep small deployments easy. In a small network - one
-    // router that also runs DHCPv6 server. Users specifies a single pool and
-    // expects it to just work. Without this, the server would complain that it
-    // doesn't have IP address on its interfaces that matches that
-    // configuration. Such requirement makes sense in IPv4, but not in IPv6.
-    // The server does not need to have a global address (using just link-local
-    // is ok for DHCPv6 server) from the pool it serves.
-    if (subnets4_.size() == 1) {
-        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-                  DHCPSRV_CFGMGR_ONLY_SUBNET4)
-                  .arg(subnets4_[0]->toText()).arg(hint.toText());
-        return (subnets4_[0]);
-    }
-
-    // If there is more than one, we need to choose the proper one
-    for (Subnet4Collection::iterator subnet = subnets4_.begin();
+CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) const {
+    // Iterate over existing subnets to find a suitable one for the
+    // given address.
+    for (Subnet4Collection::const_iterator subnet = subnets4_.begin();
          subnet != subnets4_.end(); ++subnet) {
         if ((*subnet)->inRange(hint)) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
@@ -244,6 +230,22 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
     return (Subnet4Ptr());
 }
 
+Subnet4Ptr
+CfgMgr::getSubnet4(const std::string& iface_name) const {
+    Iface* iface = IfaceMgr::instance().getIface(iface_name);
+    // This should never happen in the real life. Hence we throw an exception.
+    if (iface == NULL) {
+        isc_throw(isc::BadValue, "interface " << iface_name <<
+                  " doesn't exist and therefore it is impossible"
+                  " to find a suitable subnet for its IPv4 address");
+    }
+    IOAddress addr("0.0.0.0");
+    // If IPv4 address assigned to the interface exists, find a suitable
+    // subnet for it, else return NULL pointer to indicate that no subnet
+    // could be found.
+    return (iface->getAddress4(addr) ? getSubnet4(addr) : Subnet4Ptr());
+}
+
 void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
     /// @todo: Check that this new subnet does not cross boundaries of any
     /// other already defined subnet.

+ 14 - 2
src/lib/dhcpsrv/cfgmgr.h

@@ -219,7 +219,7 @@ public:
     /// 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 Subnet4Collection* getSubnets4() {
+    const Subnet4Collection* getSubnets4() const {
         return (&subnets4_);
     }
 
@@ -244,7 +244,19 @@ public:
     /// @param hint an address that belongs to a searched subnet
     ///
     /// @return a subnet object
-    Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
+    Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint) const;
+
+    /// @brief Returns a subnet for the specified local interface.
+    ///
+    /// This function checks that the IP address assigned to the specified
+    /// interface belongs to any IPv4 subnet configured, and returns this
+    /// subnet.
+    ///
+    /// @param iface Short name of the interface which is being checked.
+    ///
+    /// @return Pointer to the subnet matching interface specified or NULL
+    /// pointer if IPv4 address on the interface doesn't match any subnet.
+    Subnet4Ptr getSubnet4(const std::string& iface) const;
 
     /// @brief adds a subnet4
     void addSubnet4(const Subnet4Ptr& subnet);

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -89,6 +89,7 @@ libdhcpsrv_unittests_CXXFLAGS += -Wno-unused-variable -Wno-unused-parameter
 endif
 
 libdhcpsrv_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la

+ 43 - 0
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -18,6 +18,7 @@
 #include <dhcpsrv/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 
 #include <gtest/gtest.h>
 
@@ -29,6 +30,7 @@
 using namespace std;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::dhcp::test;
 using namespace isc::util;
 using namespace isc;
 
@@ -739,6 +741,47 @@ TEST_F(CfgMgrTest, d2ClientConfig) {
     EXPECT_NE(*original_config, *updated_config);
 }
 
+// This test verfies that CfgMgr correctly determines that the address of the
+// interface belongs to existing IPv4 subnet.
+TEST_F(CfgMgrTest, getSubnet4ForInterface) {
+    IfaceMgrTestConfig config(true);
+
+    // Initially, there are no subnets configured, so none of the IPv4
+    // addresses assigned to eth0 and eth1 can match with any subnet.
+    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth0"));
+    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth1"));
+
+    // Configure first subnet which address on eth0 corresponds to.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.1"), 24, 1, 2, 3));
+    CfgMgr::instance().addSubnet4(subnet1);
+
+    // The address on eth0 should match the existing subnet.
+    Subnet4Ptr subnet1_ret;
+    subnet1_ret = CfgMgr::instance().getSubnet4("eth0");
+    ASSERT_TRUE(subnet1_ret);
+    EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
+    // There should still be no match for eth1.
+    EXPECT_FALSE(CfgMgr::instance().getSubnet4("eth1"));
+
+    // Configure a second subnet.
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.1"), 24, 1, 2, 3));
+    CfgMgr::instance().addSubnet4(subnet2);
+
+    // There should be match between eth0 and subnet1 and between eth1 and
+    // subnet 2.
+    subnet1_ret = CfgMgr::instance().getSubnet4("eth0");
+    ASSERT_TRUE(subnet1_ret);
+    EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
+    Subnet4Ptr subnet2_ret = CfgMgr::instance().getSubnet4("eth1");
+    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.
+    EXPECT_THROW(CfgMgr::instance().getSubnet4("bogus-interface"),
+                 isc::BadValue);
+
+}
+
 
 /// @todo Add unit-tests for testing:
 /// - addActiveIface() with invalid interface name