Parcourir la source

[master] Merge branch 'trac3242'

Conflicts:
	src/bin/dhcp4/tests/fqdn_unittest.cc
Marcin Siodelski il y a 11 ans
Parent
commit
9e571cc217

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

@@ -4475,6 +4475,40 @@ Dhcp4/subnet4	[]	list	(default)
 
 
     </section>
     </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">
     <section id="dhcp4-std">
       <title>Supported Standards</title>
       <title>Supported Standards</title>
       <para>The following standards and draft standards are currently
       <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
 some reason the reload failed.  Other error messages issued from the
 hooks framework will indicate the nature of the problem.
 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)
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 This debug message indicates that the server successfully advertised
 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
 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
 no interfaces that server should listen on, or specified interfaces are not
 configured to receive the traffic.
 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
 % DHCP4_NOT_RUNNING IPv4 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
 A warning message is issued when an attempt is made to shut down the
 IPv4 DHCP server but it is not running.
 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;
             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)
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_RECEIVED)
             .arg(serverReceivedPacketName(type))
             .arg(serverReceivedPacketName(type))
             .arg(type)
             .arg(type)
@@ -1413,24 +1402,38 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
 }
 }
 
 
 Subnet4Ptr
 Subnet4Ptr
-Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
 
 
     Subnet4Ptr subnet;
     Subnet4Ptr subnet;
-    // Is this relayed message?
-    IOAddress relay = question->getGiaddr();
     static const IOAddress notset("0.0.0.0");
     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 {
     } 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
     // Let's execute all callouts registered for subnet4_select
     if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
     if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(question);
         CalloutHandlePtr callout_handle = getCalloutHandle(question);
@@ -1465,7 +1468,93 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
 }
 }
 
 
 bool
 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
     // 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
     // we rely on the caller to sanity check the pointer and we don't check
     // it here.
     // it here.
@@ -1475,7 +1564,7 @@ Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
     // Note that we don't check cases that server identifier is mandatory
     // Note that we don't check cases that server identifier is mandatory
     // but not present. This is meant to be sanity checked in other
     // but not present. This is meant to be sanity checked in other
     // functions.
     // functions.
-    OptionPtr option = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+    OptionPtr option = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
     if (!option) {
     if (!option) {
         return (true);
         return (true);
     }
     }
@@ -1603,8 +1692,8 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
 
 
 size_t
 size_t
 Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
 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;
     size_t offset = 0;
 
 
     OptionDefContainer option_defs;
     OptionDefContainer option_defs;

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

@@ -167,6 +167,83 @@ public:
 
 
 protected:
 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.
     /// @brief Verifies if the server id belongs to our server.
     ///
     ///
     /// This function checks if the server identifier carried in the specified
     /// 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
     /// @return true, if the server identifier is absent or matches one of the
     /// server identifiers that the server is using; false otherwise.
     /// server identifiers that the server is using; false otherwise.
     bool acceptServerId(const Pkt4Ptr& pkt) const;
     bool acceptServerId(const Pkt4Ptr& pkt) const;
+    //@}
 
 
     /// @brief verifies if specified packet meets RFC requirements
     /// @brief verifies if specified packet meets RFC requirements
     ///
     ///
@@ -529,7 +607,7 @@ protected:
     ///
     ///
     /// @param question client's message
     /// @param question client's message
     /// @return selected subnet (or NULL if no suitable subnet was found)
     /// @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
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
     /// 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_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_test_utils.cc dhcp4_test_utils.h
 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 += wireshark.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_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/cc/libb10-cc.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.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/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/dhcp_ddns/libb10-dhcp_ddns.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la

Fichier diff supprimé car celui-ci est trop grand
+ 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -15,12 +15,15 @@
 #include <config.h>
 #include <config.h>
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
+#include <cc/data.h>
 #include <config/ccsession.h>
 #include <config/ccsession.h>
+#include <dhcp4/config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -28,17 +31,14 @@
 
 
 using namespace std;
 using namespace std;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
-
+using namespace isc::data;
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 namespace test {
 namespace test {
 
 
-/// dummy server-id file location
-static const char* SRVID_FILE = "server-id-test.txt";
-
 Dhcpv4SrvTest::Dhcpv4SrvTest()
 Dhcpv4SrvTest::Dhcpv4SrvTest()
-:rcode_(-1) {
+:rcode_(-1), srv_(0) {
     subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
     subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
                                      2000, 3000));
                                      2000, 3000));
     pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
     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));
     Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
     opt_routers->setAddress(IOAddress("192.0.2.2"));
     opt_routers->setAddress(IOAddress("192.0.2.2"));
     subnet_->addOption(opt_routers, false, "dhcp4");
     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() {
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
@@ -387,9 +384,6 @@ void Dhcpv4SrvTest::TearDown() {
 
 
     CfgMgr::instance().deleteSubnets4();
     CfgMgr::instance().deleteSubnets4();
 
 
-    // Let's clean up if there is such a file.
-    unlink(SRVID_FILE);
-
     // Close all open sockets.
     // Close all open sockets.
     IfaceMgr::instance().closeSockets();
     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
 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.
     // Create an instance of the tested class.
     boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
     boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
 
 
@@ -608,6 +555,20 @@ Dhcpv4SrvFakeIfaceTest::testDiscoverRequest(const uint8_t msg_type) {
     EXPECT_TRUE(noBasicOptions(rsp));
     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::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc::dhcp namespace
 }; // end of isc 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -87,6 +87,120 @@ public:
 
 
 typedef boost::shared_ptr<PktFilterTest> PktFilterTestPtr;
 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 {
 class Dhcpv4SrvTest : public ::testing::Test {
 public:
 public:
 
 
@@ -267,61 +381,6 @@ public:
     /// @return created packet
     /// @return created packet
     Pkt4Ptr packetFromCapture(const std::string& hex_string);
     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
     /// @brief Tests if Discover or Request message is processed correctly
     ///
     ///
     /// This test verifies that the Parameter Request List option is handled
     /// This test verifies that the Parameter Request List option is handled
@@ -335,123 +394,30 @@ public:
     /// @param msg_type DHCPDISCOVER or DHCPREQUEST
     /// @param msg_type DHCPDISCOVER or DHCPREQUEST
     void testDiscoverRequest(const uint8_t msg_type);
     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
 }; // 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -16,6 +16,7 @@
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -31,16 +32,15 @@ using namespace isc::dhcp_ddns;
 
 
 namespace {
 namespace {
 
 
-class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
+class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
 public:
 public:
-
     // Bit Constants for turning on and off DDNS configuration options.
     // Bit Constants for turning on and off DDNS configuration options.
     static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
     static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
     static const uint16_t OVERRIDE_NO_UPDATE = 2;
     static const uint16_t OVERRIDE_NO_UPDATE = 2;
     static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
     static const uint16_t REPLACE_CLIENT_NAME = 8;
     static const uint16_t REPLACE_CLIENT_NAME = 8;
 
 
-    NameDhcpv4SrvTest() : Dhcpv4SrvFakeIfaceTest() {
+    NameDhcpv4SrvTest() : Dhcpv4SrvTest() {
         srv_ = new NakedDhcpv4Srv(0);
         srv_ = new NakedDhcpv4Srv(0);
         // Config DDNS to be enabled, all controls off
         // Config DDNS to be enabled, all controls off
         enableD2();
         enableD2();
@@ -154,7 +154,7 @@ public:
                                 const bool include_clientid = true) {
                                 const bool include_clientid = true) {
         Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
         Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
         pkt->setRemoteAddr(IOAddress("192.0.2.3"));
         pkt->setRemoteAddr(IOAddress("192.0.2.3"));
-        pkt->setIface("eth0");
+        pkt->setIface("eth1");
         // For DISCOVER we don't include server id, because client broadcasts
         // For DISCOVER we don't include server id, because client broadcasts
         // the message to all servers.
         // the message to all servers.
         if (msg_type != DHCPDISCOVER) {
         if (msg_type != DHCPDISCOVER) {
@@ -357,6 +357,10 @@ public:
     /// @param response_flags Mask of expected FQDN flags in the response
     /// @param response_flags Mask of expected FQDN flags in the response
     void flagVsConfigScenario(const uint8_t client_flags,
     void flagVsConfigScenario(const uint8_t client_flags,
                        const uint8_t response_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,
         Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, client_flags,
                                           "myhost.example.com.",
                                           "myhost.example.com.",
                                           Option4ClientFqdn::FULL, true);
                                           Option4ClientFqdn::FULL, true);
@@ -726,6 +730,9 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsLeaseMismatch) {
 // Test that the OFFER message generated as a result of the DISCOVER message
 // Test that the OFFER message generated as a result of the DISCOVER message
 // processing will not result in generation of the NameChangeRequests.
 // processing will not result in generation of the NameChangeRequests.
 TEST_F(NameDhcpv4SrvTest, processDiscover) {
 TEST_F(NameDhcpv4SrvTest, processDiscover) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithFqdn(DHCPDISCOVER, Option4ClientFqdn::FLAG_S |
     Pkt4Ptr req = generatePktWithFqdn(DHCPDISCOVER, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       Option4ClientFqdn::FLAG_E,
                                       "myhost.example.com.",
                                       "myhost.example.com.",
@@ -741,6 +748,9 @@ TEST_F(NameDhcpv4SrvTest, processDiscover) {
 // Test that server generates client's hostname from the IP address assigned
 // Test that server generates client's hostname from the IP address assigned
 // to it when DHCPv4 Client FQDN option specifies an empty domain-name.
 // to it when DHCPv4 Client FQDN option specifies an empty domain-name.
 TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
 TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       Option4ClientFqdn::FLAG_E,
                                       "", Option4ClientFqdn::PARTIAL, true);
                                       "", Option4ClientFqdn::PARTIAL, true);
@@ -767,6 +777,10 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
 // to it when DHCPv4 Client FQDN option specifies an empty domain-name  AND
 // to it when DHCPv4 Client FQDN option specifies an empty domain-name  AND
 // ddns updates are disabled.
 // ddns updates are disabled.
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
+    // Create fake interfaces and open fake sockets.
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     disableD2();
     disableD2();
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       Option4ClientFqdn::FLAG_E,
@@ -790,10 +804,13 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
 // Test that server generates client's hostname from the IP address assigned
 // Test that server generates client's hostname from the IP address assigned
 // to it when Hostname option carries the top level domain-name.
 // to it when Hostname option carries the top level domain-name.
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
     Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
     // Set interface for the incoming packet. The server requires it to
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
     // generate client id.
-    req->setIface("eth0");
+    req->setIface("eth1");
 
 
     Pkt4Ptr reply;
     Pkt4Ptr reply;
     ASSERT_NO_THROW(reply = srv_->processRequest(req));
     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
 // request but modify the DNS entries for the lease according to the contents
 // of the FQDN sent in the second request.
 // of the FQDN sent in the second request.
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req1 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
     Pkt4Ptr req1 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                        Option4ClientFqdn::FLAG_E,
                                        Option4ClientFqdn::FLAG_E,
                                        "myhost.example.com.",
                                        "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
 // but modify the DNS entries for the lease according to the contents of the
 // Hostname sent in the second request.
 // Hostname sent in the second request.
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req1 = generatePktWithHostname(DHCPREQUEST, "myhost.example.com.");
     Pkt4Ptr req1 = generatePktWithHostname(DHCPREQUEST, "myhost.example.com.");
 
 
     // Set interface for the incoming packet. The server requires it to
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
     // generate client id.
-    req1->setIface("eth0");
+    req1->setIface("eth1");
 
 
 
 
     Pkt4Ptr reply;
     Pkt4Ptr reply;
@@ -896,7 +919,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
 
 
     // Set interface for the incoming packet. The server requires it to
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
     // generate client id.
-    req2->setIface("eth0");
+    req2->setIface("eth1");
 
 
     ASSERT_NO_THROW(reply = srv_->processRequest(req2));
     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
 // DDNS updates are enabled that the server genenerates a NameChangeRequest
 // to remove entries corresponding to the released lease.
 // to remove entries corresponding to the released lease.
 TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
 TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Verify the updates are enabled.
     // Verify the updates are enabled.
     ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
     ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
 
 
@@ -965,6 +991,10 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
 // and DDNS updates are disabled that server does NOT generate a
 // and DDNS updates are disabled that server does NOT generate a
 // NameChangeRequest to remove entries corresponding to the released lease.
 // NameChangeRequest to remove entries corresponding to the released lease.
 TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
 TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
+    // Create fake interfaces and open fake sockets.
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     // Disable DDNS.
     // Disable DDNS.
     disableD2();
     disableD2();
     ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
     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
  * Copyright (c) 1995-2003 by Internet Software Consortium
  *
  *
  * Permission to use, copy, modify, and distribute this software for any
  * Permission to use, copy, modify, and distribute this software for any
@@ -154,7 +154,9 @@ enum DHCPMessageType {
     DHCPLEASEQUERY      =  10,
     DHCPLEASEQUERY      =  10,
     DHCPLEASEUNASSIGNED =  11,
     DHCPLEASEUNASSIGNED =  11,
     DHCPLEASEUNKNOWN    =  12,
     DHCPLEASEUNKNOWN    =  12,
-    DHCPLEASEACTIVE     =  13
+    DHCPLEASEACTIVE     =  13,
+    DHCPBULKLEASEQUERY  =  14,
+    DHCPLEASEQUERYDONE  =  15
 };
 };
 
 
 static const uint16_t DHCP4_CLIENT_PORT = 68;
 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);
     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() {
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {
          iface != ifaces_.end(); ++iface) {

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

@@ -246,6 +246,17 @@ public:
     /// @return collection of addresses
     /// @return collection of addresses
     const AddressCollection& getAddresses() const { return addrs_; }
     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.
     /// @brief Adds an address to an interface.
     ///
     ///
     /// This only adds an address to collection, it does not physically
     /// 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 DHCPRELEASE:
     case DHCPINFORM:
     case DHCPINFORM:
     case DHCPLEASEQUERY:
     case DHCPLEASEQUERY:
+    case DHCPBULKLEASEQUERY:
         return (BOOTREQUEST);
         return (BOOTREQUEST);
 
 
     case DHCPACK:
     case DHCPACK:
@@ -404,6 +405,7 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
     case DHCPLEASEUNASSIGNED:
     case DHCPLEASEUNASSIGNED:
     case DHCPLEASEUNKNOWN:
     case DHCPLEASEUNKNOWN:
     case DHCPLEASEACTIVE:
     case DHCPLEASEACTIVE:
+    case DHCPLEASEQUERYDONE:
         return (BOOTREPLY);
         return (BOOTREPLY);
 
 
     default:
     default:

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

@@ -24,11 +24,29 @@ TESTS_ENVIRONMENT = \
 
 
 TESTS =
 TESTS =
 if HAVE_GTEST
 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
 TESTS += libdhcp++_unittests
 
 
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_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 += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option4_client_fqdn_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_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet6_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_filter_test_utils.h pkt_filter_test_utils.cc
 libdhcp___unittests_SOURCES += pkt_filter6_test_utils.h pkt_filter6_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/iface_mgr.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/pkt_filter6_test_utils.h>
 #include <dhcp/tests/pkt_filter6_test_utils.h>
 
 
 #include <boost/bind.hpp>
 #include <boost/bind.hpp>
@@ -529,6 +530,32 @@ TEST_F(IfaceMgrTest, ifaceClass) {
     EXPECT_STREQ("eth5/7", iface.getFullName().c_str());
     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
 // TODO: Implement getPlainMac() test as soon as interface detection
 // is implemented.
 // is implemented.
 TEST_F(IfaceMgrTest, getIface) {
 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
 // 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.
 // down, but sockets are open on all other non-loopback interfaces.
 TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
 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:
     // Boolean parameters specify that eth0 is:
     // - not a loopback
     // - not a loopback
     // - is "down" (not up)
     // - is "down" (not up)
     // - is not running
     // - is not running
     // - is active (is not inactive)
     // - 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.
     // 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
     // Expecting that the socket is open on eth1 because it was up, running
     // and active.
     // 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.
     // 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
 // 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // 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.
     /// fallback socket descriptor is set to a negative value.
     virtual SocketInfo openSocket(const Iface& iface,
     virtual SocketInfo openSocket(const Iface& iface,
                                   const isc::asiolink::IOAddress& addr,
                                   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.
     /// @brief Simulate reception of the DHCPv4 message.
     ///
     ///

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

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/dhcpsrv_log.h>
@@ -210,25 +211,10 @@ void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
 }
 }
 
 
 Subnet4Ptr
 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) {
          subnet != subnets4_.end(); ++subnet) {
         if ((*subnet)->inRange(hint)) {
         if ((*subnet)->inRange(hint)) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
@@ -244,6 +230,22 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
     return (Subnet4Ptr());
     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) {
 void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
     /// @todo: Check that this new subnet does not cross boundaries of any
     /// @todo: Check that this new subnet does not cross boundaries of any
     /// other already defined subnet.
     /// 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
     /// to choose a different subnet. Server code has to offer a list
     /// of possible choices (i.e. all subnets).
     /// of possible choices (i.e. all subnets).
     /// @return a pointer to const Subnet6 collection
     /// @return a pointer to const Subnet6 collection
-    const Subnet4Collection* getSubnets4() {
+    const Subnet4Collection* getSubnets4() const {
         return (&subnets4_);
         return (&subnets4_);
     }
     }
 
 
@@ -244,7 +244,19 @@ public:
     /// @param hint an address that belongs to a searched subnet
     /// @param hint an address that belongs to a searched subnet
     ///
     ///
     /// @return a subnet object
     /// @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
     /// @brief adds a subnet4
     void addSubnet4(const Subnet4Ptr& subnet);
     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
 endif
 
 
 libdhcpsrv_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 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/libb10-dhcp++.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.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
 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 <dhcpsrv/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
@@ -29,6 +30,7 @@
 using namespace std;
 using namespace std;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::dhcp::test;
 using namespace isc::util;
 using namespace isc::util;
 using namespace isc;
 using namespace isc;
 
 
@@ -739,6 +741,47 @@ TEST_F(CfgMgrTest, d2ClientConfig) {
     EXPECT_NE(*original_config, *updated_config);
     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:
 /// @todo Add unit-tests for testing:
 /// - addActiveIface() with invalid interface name
 /// - addActiveIface() with invalid interface name