Browse Source

[master] Merge branch 'trac2765'

Marcin Siodelski 11 years ago
parent
commit
f49c4b8942

+ 1 - 0
doc/devel/mainpage.dox

@@ -67,6 +67,7 @@
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpRelay
  *   - @subpage libdhcpRelay
  *   - @subpage libdhcpIfaceMgr
  *   - @subpage libdhcpIfaceMgr
+ *   - @subpage libdhcpPktFilter
  * - @subpage libdhcpsrv
  * - @subpage libdhcpsrv
  *   - @subpage leasemgr
  *   - @subpage leasemgr
  *   - @subpage cfgmgr
  *   - @subpage cfgmgr

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

@@ -175,6 +175,10 @@ IPv4 DHCP server but it is not running.
 A debug message issued during startup, this indicates that the IPv4 DHCP
 A debug message issued during startup, this indicates that the IPv4 DHCP
 server is about to open sockets on the specified port.
 server is about to open sockets on the specified port.
 
 
+% DHCP4_OPEN_SOCKET_FAIL failed to create socket: %1
+A warning message issued when IfaceMgr fails to open and bind a socket. The reason
+for the failure is appended as an argument of the log message.
+
 % DHCP4_PACKET_PARSE_FAIL failed to parse incoming packet: %1
 % DHCP4_PACKET_PARSE_FAIL failed to parse incoming packet: %1
 The IPv4 DHCP server has received a packet that it is unable to
 The IPv4 DHCP server has received a packet that it is unable to
 interpret. The reason why the packet is invalid is included in the message.
 interpret. The reason why the packet is invalid is included in the message.

+ 16 - 4
src/bin/dhcp4/dhcp4_srv.cc

@@ -140,10 +140,15 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
         // will be able to respond directly.
         // will be able to respond directly.
         IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
         IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
 
 
+        // Open sockets only if port is non-zero. Port 0 is used
+        // for non-socket related testing.
         if (port) {
         if (port) {
-            // open sockets only if port is non-zero. Port 0 is used
+            // Create error handler. This handler will be called every time
-            // for non-socket related testing.
+            // the socket opening operation fails. We use this handler to
-            IfaceMgr::instance().openSockets4(port_, use_bcast_);
+            // log a warning.
+            isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
+                boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
+            IfaceMgr::instance().openSockets4(port_, use_bcast_, error_handler);
         }
         }
 
 
         string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
         string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
@@ -1674,7 +1679,9 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
     // sockets are marked active or inactive.
     // sockets are marked active or inactive.
     // @todo Optimization: we should not reopen all sockets but rather select
     // @todo Optimization: we should not reopen all sockets but rather select
     // those that have been affected by the new configuration.
     // those that have been affected by the new configuration.
-    if (!IfaceMgr::instance().openSockets4(port, use_bcast)) {
+    isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
+        boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
+    if (!IfaceMgr::instance().openSockets4(port, use_bcast, error_handler)) {
         LOG_WARN(dhcp4_logger, DHCP4_NO_SOCKETS_OPEN);
         LOG_WARN(dhcp4_logger, DHCP4_NO_SOCKETS_OPEN);
     }
     }
 }
 }
@@ -1768,6 +1775,11 @@ Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
     return (offset);
     return (offset);
 }
 }
 
 
+void
+Dhcpv4Srv::ifaceMgrSocket4ErrorHandler(const std::string& errmsg) {
+    // Log the reason for socket opening failure and return.
+    LOG_WARN(dhcp4_logger, DHCP4_OPEN_SOCKET_FAIL).arg(errmsg);
+}
 
 
 }   // namespace dhcp
 }   // namespace dhcp
 }   // namespace isc
 }   // namespace isc

+ 9 - 0
src/bin/dhcp4/dhcp4_srv.h

@@ -525,6 +525,15 @@ private:
     /// @return Option that contains netmask information
     /// @return Option that contains netmask information
     static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
     static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
 
 
+    /// @brief Implements the error handler for socket open failure.
+    ///
+    /// This callback function is installed on the @c isc::dhcp::IfaceMgr
+    /// when IPv4 sockets are being open. When socket fails to open for
+    /// any reason, this function is called. It simply logs the error message.
+    ///
+    /// @param errmsg An error message containing a cause of the failure.
+    static void ifaceMgrSocket4ErrorHandler(const std::string& errmsg);
+
     /// @brief Allocation Engine.
     /// @brief Allocation Engine.
     /// Pointer to the allocation engine that we are currently using
     /// Pointer to the allocation engine that we are currently using
     /// It must be a pointer, because we will support changing engines
     /// It must be a pointer, because we will support changing engines

+ 20 - 0
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -89,6 +89,26 @@ TEST_F(Dhcpv4SrvTest, basic) {
     EXPECT_TRUE(naked_srv->getServerID());
     EXPECT_TRUE(naked_srv->getServerID());
 }
 }
 
 
+// This test verifies that exception is not thrown when an error occurs during
+// opening sockets. This test forces an error by adding a fictious interface
+// to the IfaceMgr. An attempt to open socket on this interface must always
+// fail. The DHCPv4 installs the error handler function to prevent exceptions
+// being thrown from the openSockets4 function.
+// @todo The server tests for socket should be extended but currently the
+// ability to unit test the sockets code is somewhat limited.
+TEST_F(Dhcpv4SrvTest, openActiveSockets) {
+    ASSERT_NO_THROW(CfgMgr::instance().activateAllIfaces());
+
+    Iface iface("bogusiface", 255);
+    iface.flag_loopback_ = false;
+    iface.flag_up_ = true;
+    iface.flag_running_ = true;
+    iface.inactive4_ = false;
+    iface.addAddress(IOAddress("192.0.0.0"));
+    IfaceMgr::instance().addInterface(iface);
+    ASSERT_NO_THROW(Dhcpv4Srv::openActiveSockets(DHCP4_SERVER_PORT, false));
+}
+
 // This test verifies that the destination address of the response
 // This test verifies that the destination address of the response
 // message is set to giaddr, when giaddr is set to non-zero address
 // message is set to giaddr, when giaddr is set to non-zero address
 // in the received message.
 // in the received message.

+ 1 - 0
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -44,6 +44,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     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")));
     subnet_->addPool(pool_);
     subnet_->addPool(pool_);
 
 
+    CfgMgr::instance().deleteActiveIfaces();
     CfgMgr::instance().deleteSubnets4();
     CfgMgr::instance().deleteSubnets4();
     CfgMgr::instance().addSubnet4(subnet_);
     CfgMgr::instance().addSubnet4(subnet_);
 
 

+ 5 - 3
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -20,6 +20,7 @@
 #define DHCP4_TEST_UTILS_H
 #define DHCP4_TEST_UTILS_H
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter_inet.h>
 #include <dhcp/pkt_filter_inet.h>
@@ -52,9 +53,10 @@ public:
     }
     }
 
 
     /// Does nothing.
     /// Does nothing.
-    virtual int openSocket(const Iface&, const isc::asiolink::IOAddress&,
+    virtual SocketInfo openSocket(const Iface&,
-                           const uint16_t, const bool, const bool) {
+                                  const isc::asiolink::IOAddress& addr,
-        return (0);
+                                  const uint16_t port, const bool, const bool) {
+        return (SocketInfo(addr, port, 0));
     }
     }
 
 
     /// Does nothing.
     /// Does nothing.

+ 1 - 1
src/lib/dhcp/Makefile.am

@@ -41,7 +41,7 @@ libb10_dhcp___la_SOURCES += option_string.cc option_string.h
 libb10_dhcp___la_SOURCES += protocol_util.cc protocol_util.h
 libb10_dhcp___la_SOURCES += protocol_util.cc protocol_util.h
 libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
-libb10_dhcp___la_SOURCES += pkt_filter.h
+libb10_dhcp___la_SOURCES += pkt_filter.h pkt_filter.cc
 libb10_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
 libb10_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
 
 
 if OS_LINUX
 if OS_LINUX

+ 64 - 24
src/lib/dhcp/iface_mgr.cc

@@ -88,6 +88,10 @@ Iface::closeSockets(const uint16_t family) {
             // Close and delete the socket and move to the
             // Close and delete the socket and move to the
             // next one.
             // next one.
             close(sock->sockfd_);
             close(sock->sockfd_);
+            // Close fallback socket if open.
+            if (sock->fallbackfd_ >= 0) {
+                close(sock->fallbackfd_);
+            }
             sockets_.erase(sock++);
             sockets_.erase(sock++);
 
 
         } else {
         } else {
@@ -148,6 +152,10 @@ bool Iface::delSocket(uint16_t sockfd) {
     while (sock!=sockets_.end()) {
     while (sock!=sockets_.end()) {
         if (sock->sockfd_ == sockfd) {
         if (sock->sockfd_ == sockfd) {
             close(sockfd);
             close(sockfd);
+            // Close fallback socket if open.
+            if (sock->fallbackfd_ >= 0) {
+                close(sock->fallbackfd_);
+            }
             sockets_.erase(sock);
             sockets_.erase(sock);
             return (true); //socket found
             return (true); //socket found
         }
         }
@@ -284,8 +292,9 @@ void IfaceMgr::stubDetectIfaces() {
     addInterface(iface);
     addInterface(iface);
 }
 }
 
 
-bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
+bool
-    int sock;
+IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
+                       IfaceMgrErrorMsgCallback error_handler) {
     int count = 0;
     int count = 0;
 
 
 // This option is used to bind sockets to particular interfaces.
 // This option is used to bind sockets to particular interfaces.
@@ -322,6 +331,7 @@ bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
                 continue;
                 continue;
             }
             }
 
 
+            int sock = -1;
             // If selected interface is broadcast capable set appropriate
             // If selected interface is broadcast capable set appropriate
             // options on the socket so as it can receive and send broadcast
             // options on the socket so as it can receive and send broadcast
             // messages.
             // messages.
@@ -331,36 +341,53 @@ bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
                 // bind to INADDR_ANY address but we can do it only once. Thus,
                 // bind to INADDR_ANY address but we can do it only once. Thus,
                 // if one socket has been bound we can't do it any further.
                 // if one socket has been bound we can't do it any further.
                 if (!bind_to_device && bcast_num > 0) {
                 if (!bind_to_device && bcast_num > 0) {
-                    isc_throw(SocketConfigError, "SO_BINDTODEVICE socket option is"
+                    handleSocketConfigError("SO_BINDTODEVICE socket option is"
-                              << " not supported on this OS; therefore, DHCP"
+                                            " not supported on this OS;"
-                              << " server can only listen broadcast traffic on"
+                                            " therefore, DHCP server can only"
-                              << " a single interface");
+                                            " listen broadcast traffic on a"
+                                            " single interface",
+                                            error_handler);
+                    continue;
 
 
                 } else {
                 } else {
-                    // We haven't open any broadcast sockets yet, so we can
+                    try {
-                    // open at least one more.
+                        // We haven't open any broadcast sockets yet, so we can
-                    sock = openSocket(iface->getName(), *addr, port, true, true);
+                        // open at least one more.
-                    // Binding socket to an interface is not supported so we can't
+                        sock = openSocket(iface->getName(), *addr, port,
-                    // open any more broadcast sockets. Increase the number of
+                                          true, true);
-                    // opened broadcast sockets.
+                    } catch (const Exception& ex) {
+                        handleSocketConfigError(ex.what(), error_handler);
+                        continue;
+
+                    }
+                    // Binding socket to an interface is not supported so we
+                    // can't open any more broadcast sockets. Increase the
+                    // number of open broadcast sockets.
                     if (!bind_to_device) {
                     if (!bind_to_device) {
                         ++bcast_num;
                         ++bcast_num;
                     }
                     }
                 }
                 }
 
 
             } else {
             } else {
-                // Not broadcast capable, do not set broadcast flags.
+                try {
-                sock = openSocket(iface->getName(), *addr, port, false, false);
+                    // Not broadcast capable, do not set broadcast flags.
+                    sock = openSocket(iface->getName(), *addr, port,
+                                      false, false);
+                } catch (const Exception& ex) {
+                    handleSocketConfigError(ex.what(), error_handler);
+                    continue;
+                }
 
 
             }
             }
             if (sock < 0) {
             if (sock < 0) {
                 const char* errstr = strerror(errno);
                 const char* errstr = strerror(errno);
-                isc_throw(SocketConfigError, "failed to open IPv4 socket"
+                handleSocketConfigError(std::string("failed to open IPv4 socket,"
-                          << " supporting broadcast traffic, reason:"
+                                                    " reason:") + errstr,
-                          << errstr);
+                                        error_handler);
+            } else {
+                ++count;
             }
             }
 
 
-            count++;
         }
         }
     }
     }
     return (count > 0);
     return (count > 0);
@@ -460,6 +487,21 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
 }
 }
 
 
 void
 void
+IfaceMgr::handleSocketConfigError(const std::string& errmsg,
+                                  IfaceMgrErrorMsgCallback handler) {
+    // If error handler is installed, we don't want to throw an exception, but
+    // rather call this handler.
+    if (handler != NULL) {
+        handler(errmsg);
+
+    } else {
+        isc_throw(SocketConfigError, errmsg);
+
+    }
+}
+
+
+void
 IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
 IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
     for (IfaceCollection::const_iterator iface=ifaces_.begin();
     for (IfaceCollection::const_iterator iface=ifaces_.begin();
          iface!=ifaces_.end();
          iface!=ifaces_.end();
@@ -741,7 +783,7 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
         }
         }
     }
     }
 
 
-    SocketInfo info(sock, addr, port);
+    SocketInfo info(addr, port, sock);
     iface.addSocket(info);
     iface.addSocket(info);
 
 
     return (sock);
     return (sock);
@@ -754,13 +796,11 @@ int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr,
     // Skip checking if the packet_filter_ is non-NULL because this check
     // Skip checking if the packet_filter_ is non-NULL because this check
     // has been already done when packet filter object was set.
     // has been already done when packet filter object was set.
 
 
-    int sock = packet_filter_->openSocket(iface, addr, port,
+    SocketInfo info = packet_filter_->openSocket(iface, addr, port,
-                                          receive_bcast, send_bcast);
+                                                 receive_bcast, send_bcast);
-
-    SocketInfo info(sock, addr, port);
     iface.addSocket(info);
     iface.addSocket(info);
 
 
-    return (sock);
+    return (info.sockfd_);
 }
 }
 
 
 bool
 bool

+ 144 - 21
src/lib/dhcp/iface_mgr.h

@@ -22,6 +22,7 @@
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter.h>
 
 
+#include <boost/function.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/scoped_array.hpp>
 #include <boost/scoped_array.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -72,19 +73,49 @@ public:
 
 
 /// Holds information about socket.
 /// Holds information about socket.
 struct SocketInfo {
 struct SocketInfo {
-    uint16_t sockfd_; /// socket descriptor
+
     isc::asiolink::IOAddress addr_; /// bound address
     isc::asiolink::IOAddress addr_; /// bound address
     uint16_t port_;   /// socket port
     uint16_t port_;   /// socket port
     uint16_t family_; /// IPv4 or IPv6
     uint16_t family_; /// IPv4 or IPv6
 
 
+    /// @brief Socket descriptor (a.k.a. primary socket).
+    int sockfd_;
+
+    /// @brief Fallback socket descriptor.
+    ///
+    /// This socket descriptor holds the handle to the fallback socket.
+    /// The fallback socket is created when there is a need for the regular
+    /// datagram socket to be bound to an IP address and port, besides
+    /// primary socket (sockfd_) which is actually used to receive and process
+    /// the DHCP messages. The fallback socket (if exists) is always associated
+    /// with the primary socket. In particular, the need for the fallback socket
+    /// arises when raw socket is a primary one. When primary socket is open,
+    /// it is bound to an interface not the address and port. The implications
+    /// include the possibility that the other process (e.g. the other instance
+    /// of DHCP server) will bind to the same address and port through which the
+    /// raw socket receives the DHCP messages.Another implication is that the
+    /// kernel, being unaware of the DHCP server operating through the raw
+    /// socket, will respond with the ICMP "Destination port unreachable"
+    /// messages when DHCP messages are only received through the raw socket.
+    /// In order to workaround the issues mentioned here, the fallback socket
+    /// should be opened so as/ the kernel is aware that the certain address
+    /// and port is in use.
+    ///
+    /// The fallback description is supposed to be set to a negative value if
+    /// the fallback socket is closed (not open).
+    int fallbackfd_;
+
     /// @brief SocketInfo constructor.
     /// @brief SocketInfo constructor.
     ///
     ///
-    /// @param sockfd socket descriptor
+    /// @param addr An address the socket is bound to.
-    /// @param addr an address the socket is bound to
+    /// @param port A port the socket is bound to.
-    /// @param port a port the socket is bound to
+    /// @param sockfd Socket descriptor.
-    SocketInfo(uint16_t sockfd, const isc::asiolink::IOAddress& addr,
+    /// @param fallbackfd A descriptor of the fallback socket.
-               uint16_t port)
+    SocketInfo(const isc::asiolink::IOAddress& addr, const uint16_t port,
-        :sockfd_(sockfd), addr_(addr), port_(port), family_(addr.getFamily()) { }
+               const int sockfd, const int fallbackfd = -1)
+        : addr_(addr), port_(port), family_(addr.getFamily()),
+          sockfd_(sockfd), fallbackfd_(fallbackfd) { }
+
 };
 };
 
 
 
 
@@ -343,6 +374,13 @@ public:
     bool inactive6_;
     bool inactive6_;
 };
 };
 
 
+/// @brief This type describes the callback function invoked when error occurs
+/// in the IfaceMgr.
+///
+/// @param errmsg An error message.
+typedef
+boost::function<void(const std::string& errmsg)> IfaceMgrErrorMsgCallback;
+
 /// @brief Handles network interfaces, transmission and reception.
 /// @brief Handles network interfaces, transmission and reception.
 ///
 ///
 /// IfaceMgr is an interface manager class that detects available network
 /// IfaceMgr is an interface manager class that detects available network
@@ -386,7 +424,7 @@ public:
     /// @return true if direct response is supported.
     /// @return true if direct response is supported.
     bool isDirectResponseSupported() const;
     bool isDirectResponseSupported() const;
 
 
-    /// @brief Returns interface with specified interface index
+    /// @brief Returns interfac specified interface index
     ///
     ///
     /// @param ifindex index of searched interface
     /// @param ifindex index of searched interface
     ///
     ///
@@ -579,9 +617,20 @@ public:
                                     const uint16_t port);
                                     const uint16_t port);
 
 
 
 
-    /// Opens IPv6 sockets on detected interfaces.
+    /// @brief Opens IPv6 sockets on detected interfaces.
     ///
     ///
-    /// Will throw exception if socket creation fails.
+    /// @todo This function will throw an exception immediately when a socket
+    /// fails to open. This is undersired behavior because it will preclude
+    /// other sockets from opening. We should strive to provide similar mechanism
+    /// that has been introduced for V4 sockets. If socket creation fails the
+    /// appropriate error handler is called and once the handler returns the
+    /// function contnues to open other sockets. The change in the IfaceMgr
+    /// is quite straight forward and it is proven to work for V4. However,
+    /// unit testing it is a bit involved, because for unit testing we need
+    /// a replacement of the openSocket6 function which will mimic the
+    /// behavior of the real socket opening. For the V4 we have the means to
+    /// to achieve that with the replaceable PktFilter class. For V6, the
+    /// implementation is hardcoded in the openSocket6.
     ///
     ///
     /// @param port specifies port number (usually DHCP6_SERVER_PORT)
     /// @param port specifies port number (usually DHCP6_SERVER_PORT)
     ///
     ///
@@ -589,16 +638,71 @@ public:
     /// @return true if any sockets were open
     /// @return true if any sockets were open
     bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT);
     bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT);
 
 
-    /// Opens IPv4 sockets on detected interfaces.
+    /// @brief Opens IPv4 sockets on detected interfaces.
-    /// Will throw exception if socket creation fails.
+    ///
+    /// This function attempts to open sockets on all interfaces which have been
+    /// detected by @c IfaceMgr and meet the following conditions:
+    /// - interface is not a local loopback,
+    /// - interface is running (connected),
+    /// - interface is up,
+    /// - interface is active, e.g. selected from the configuration to be used
+    /// to listen DHCPv4 messages,
+    /// - interface has an IPv4 address assigned.
+    ///
+    /// The type of the socket being open depends on the selected Packet Filter
+    /// represented by a class derived from @c isc::dhcp::PktFilter abstract
+    /// class.
+    ///
+    /// It is possible to specify whether sockets should be broadcast capable.
+    /// In most of the cases, the sockets should support broadcast traffic, e.g.
+    /// DHCPv4 server and relay need to listen to broadcast messages sent by
+    /// clients. If the socket has to be open on the particular interface, this
+    /// interface must have broadcast flag set. If this condition is not met,
+    /// the socket will be created in the unicast-only mode. If there are
+    /// multiple broadcast-capable interfaces present, they may be all open
+    /// in a broadcast mode only if the OS supports SO_BINDTODEVICE (bind socket
+    /// to a device) socket option. If this option is not supported, only the
+    /// first broadcast-capable socket will be opened in the broadcast mode.
+    /// The error will be reported for sockets being opened on other interfaces.
+    /// If the socket is bound to a device (interface), the broadcast traffic
+    /// sent to this interface will be received on this interface only.
+    /// This allows the DHCPv4 server or relay to detect the interface on which
+    /// the broadcast message has been received. This interface is later used
+    /// to send a response.
+    ///
+    /// On the systems with multiple interfaces, it is often desired that the
+    /// failure to open a socket on a particular interface doesn't cause a
+    /// fatal error and sockets should be opened on remaining interfaces.
+    /// However, the warning about the failure for the particular socket should
+    /// be communicated to the caller. The libdhcp++ is a common library with
+    /// no logger associated with it. Most of the functions in this library
+    /// communicate errors via exceptions. In case of openSockets4 function
+    /// exception must not be thrown if the function is supposed to continue
+    /// opening sockets, despite an error. Therefore, if such a behavior is
+    /// desired, the error handler function can be passed as a parameter.
+    /// This error handler is called (if present) with an error string.
+    /// Typically, error handler will simply log an error using an application
+    /// logger, but it can do more sophisticated error handling too.
+    ///
+    /// @todo It is possible that additional parameters will have to be added
+    /// to the error handler, e.g. Iface if it was really supposed to do
+    /// some more sophisticated error handling.
+    ///
+    /// If the error handler is not installed (is NULL), the exception is thrown
+    /// for each failure (default behavior).
     ///
     ///
     /// @param port specifies port number (usually DHCP4_SERVER_PORT)
     /// @param port specifies port number (usually DHCP4_SERVER_PORT)
     /// @param use_bcast configure sockets to support broadcast messages.
     /// @param use_bcast configure sockets to support broadcast messages.
+    /// @param error_handler A pointer to an error handler function which is
+    /// called by the openSockets4 when it fails to open a socket. This
+    /// parameter can be NULL to indicate that the callback should not be used.
     ///
     ///
-    /// @throw SocketOpenFailure if tried and failed to open socket.
+    /// @throw SocketOpenFailure if tried and failed to open socket and callback
+    /// function hasn't been specified.
     /// @return true if any sockets were open
     /// @return true if any sockets were open
     bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT,
     bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT,
-                      const bool use_bcast = true);
+                      const bool use_bcast = true,
+                      IfaceMgrErrorMsgCallback error_handler = NULL);
 
 
     /// @brief Closes all open sockets.
     /// @brief Closes all open sockets.
     /// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
     /// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
@@ -676,6 +780,15 @@ public:
     /// not having address assigned.
     /// not having address assigned.
     void setMatchingPacketFilter(const bool direct_response_desired = false);
     void setMatchingPacketFilter(const bool direct_response_desired = false);
 
 
+    /// @brief Adds an interface to list of known interfaces.
+    ///
+    /// @param iface reference to Iface object.
+    /// @note This function must be public because it has to be callable
+    /// from unit tests.
+    void addInterface(const Iface& iface) {
+        ifaces_.push_back(iface);
+    }
+
     /// A value of socket descriptor representing "not specified" state.
     /// A value of socket descriptor representing "not specified" state.
     static const int INVALID_SOCKET = -1;
     static const int INVALID_SOCKET = -1;
 
 
@@ -720,13 +833,6 @@ protected:
     /// @return socket descriptor
     /// @return socket descriptor
     int openSocket6(Iface& iface, const isc::asiolink::IOAddress& addr, uint16_t port);
     int openSocket6(Iface& iface, const isc::asiolink::IOAddress& addr, uint16_t port);
 
 
-    /// @brief Adds an interface to list of known interfaces.
-    ///
-    /// @param iface reference to Iface object.
-    void addInterface(const Iface& iface) {
-        ifaces_.push_back(iface);
-    }
-
     /// @brief Detects network interfaces.
     /// @brief Detects network interfaces.
     ///
     ///
     /// This method will eventually detect available interfaces. For now
     /// This method will eventually detect available interfaces. For now
@@ -825,6 +931,23 @@ private:
     getLocalAddress(const isc::asiolink::IOAddress& remote_addr,
     getLocalAddress(const isc::asiolink::IOAddress& remote_addr,
                     const uint16_t port);
                     const uint16_t port);
 
 
+    /// @brief Handles an error which occurs during configuration of a socket.
+    ///
+    /// If the handler callback is specified (non-NULL), this handler is
+    /// called and the specified error message is passed to it. If the
+    /// handler is not specified, the @c isc::dhcpSocketConfigError exception
+    /// is thrown with the specified message.
+    ///
+    /// This function should be called to handle errors which occur during
+    /// socket opening, binding or configuration (e.g. setting socket options
+    /// etc).
+    ///
+    /// @param errmsg An error message to be passed to a handlder function or
+    /// to the @c isc::dhcp::SocketConfigError exception.
+    /// @param handler An error handler function or NULL.
+    void handleSocketConfigError(const std::string& errmsg,
+                                 IfaceMgrErrorMsgCallback handler);
+
     /// Holds instance of a class derived from PktFilter, used by the
     /// Holds instance of a class derived from PktFilter, used by the
     /// IfaceMgr to open sockets and send/receive packets through these
     /// IfaceMgr to open sockets and send/receive packets through these
     /// sockets. It is possible to supply custom object using
     /// sockets. It is possible to supply custom object using

+ 49 - 1
src/lib/dhcp/libdhcp++.dox

@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013  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
@@ -137,4 +137,52 @@ Another useful methods are dedicated to transmission
 Note that receive4() and receive6() methods may return NULL, e.g.
 Note that receive4() and receive6() methods may return NULL, e.g.
 when timeout is reached or if dhcp daemon receives a signal.
 when timeout is reached or if dhcp daemon receives a signal.
 
 
+@section libdhcpPktFilter Switchable Packet Filter objects used by Interface Manager
+
+The well known problem of DHCPv4 implementation is that it must be able to
+provision devices which don't have an IPv4 address yet (the IPv4 address is
+one of the configuration parameters provided by DHCP server to a client).
+One way to communicate with such a device is to send server's response to
+a broadcast address. An obvious drawback of this approach is that the server's
+response will be received and processed by all clients in the particular
+network. Therefore, the preferred approach is that the server unicasts its
+response to a new address being assigned for the client. This client will
+identify itself as a target of this message by checking chaddr and/or
+Client Identifier value. At the same time, the other clients in the network
+will not receive the unicast message. The major problem that arises with this
+approach is that the client without an IP address doesn't respond to ARP
+messages. As a result, server's response will not be sent over IP/UDP
+socket because the system kernel will fail to resolve client's link-layer
+address.
+
+Kea supports the use of raw sockets to create a complete Data-link/IP/UDP/DHCPv4
+stack. By creating each layer of the outgoing packet, the Kea logic has full
+control over the frame contents and it may bypass the use of ARP to inject the
+link layer address into the frame. The raw socket is bound to a specific interface,
+not to the IP address/UDP port. Therefore, the system kernel doesn't have
+means to verify that Kea is listening to the DHCP traffic on the specific address
+and port. This has two major implications:
+- It is possible to run another DHCPv4 sever instance which will bind socket to the
+same address and port.
+- An attempt to send a unicast message to the DHCPv4 server will result in ICMP
+"Port Unreachable" message being sent by the kernel (which is unaware that the
+DHCPv4 service is actually running).
+In order to overcome these issues, the isc::dhcp::PktFilterLPF opens a
+regular IP/UDP socket which coexists with the raw socket. The socket is referred
+to as "fallback socket" in the Kea code. All packets received through this socket
+are discarded.
+
+In general, the use of datagram sockets is preferred over raw sockets.
+For convenience, the switchable Packet Filter objects are used to manage
+sockets for different purposes. These objects implement the socket opening
+operation and sending/receiving messages over this socket. For example:
+the isc::dhcp::PktFilterLPF object opens a raw socket.
+The isc::dhcp::PktFilterLPF::send and isc::dhcp::PktFilterLPF::receive
+methods encode/decode full data-link/IP/UDP/DHCPv4 stack. The
+isc::dhcp::PktFilterInet supports sending and receiving messages over
+the regular IP/UDP socket. The isc::dhcp::PktFilterInet should be used in all
+cases when an application using the libdhcp++ doesn't require sending
+DHCP messages to a device which doesn't have an address yet.
+
+
 */
 */

+ 66 - 0
src/lib/dhcp/pkt_filter.cc

@@ -0,0 +1,66 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+
+#include <sys/fcntl.h>
+#include <sys/socket.h>
+
+namespace isc {
+namespace dhcp {
+
+int
+PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr,
+                              const uint16_t port) {
+    // Create socket.
+    int sock = socket(AF_INET, SOCK_DGRAM, 0);
+    if (sock < 0) {
+        isc_throw(SocketConfigError, "failed to create fallback socket for"
+                  " address " << addr.toText() << ", port " << port
+                  << ", reason: " << strerror(errno));
+    }
+    // Bind the socket to a specified address and port.
+    struct sockaddr_in addr4;
+    memset(&addr4, 0, sizeof(addr4));
+    addr4.sin_family = AF_INET;
+    addr4.sin_addr.s_addr = htonl(addr);
+    addr4.sin_port = htons(port);
+
+    if (bind(sock, reinterpret_cast<struct sockaddr*>(&addr4),
+             sizeof(addr4)) < 0) {
+        // Remember to close the socket if we failed to bind it.
+        close(sock);
+        isc_throw(SocketConfigError, "failed to bind fallback socket to"
+                  " address " << addr.toText() << ", port " << port
+                  << ", reason: " << strerror(errno)
+                  << " - is another DHCP server running?");
+    }
+
+    // Set socket to non-blocking mode. This is to prevent the read from the
+    // fallback socket to block message processing on the primary socket.
+    if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
+        close(sock);
+        isc_throw(SocketConfigError, "failed to set SO_NONBLOCK option on the"
+                  " fallback socket, bound to " << addr.toText() << ", port "
+                  << port << ", reason: " << strerror(errno));
+    }
+    // Successfully created and bound a fallback socket. Return a descriptor.
+    return (sock);
+}
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 47 - 11
src/lib/dhcp/pkt_filter.h

@@ -67,20 +67,30 @@ public:
     /// @return true of the direct response is supported.
     /// @return true of the direct response is supported.
     virtual bool isDirectResponseSupported() const = 0;
     virtual bool isDirectResponseSupported() const = 0;
 
 
-    /// @brief Open socket.
+    /// @brief Open primary and fallback socket.
     ///
     ///
-    /// @param iface interface descriptor
+    /// A method implementation in the derived class may open one or two
-    /// @param addr address on the interface to be used to send packets.
+    /// sockets:
-    /// @param port port number.
+    /// - a primary socket - used for communication with clients. DHCP messages
-    /// @param receive_bcast configure socket to receive broadcast messages
+    /// received using this socket are processed and the same socket is used
+    /// to send a response to the client.
+    /// - a fallback socket which is optionally opened if there is a need for
+    /// the presence of the socket which can be bound to a specific IP address
+    /// and UDP port (e.g. raw primary socket can't be). For the details, see
+    /// the documentation of @c isc::dhcp::SocketInfo.
+    ///
+    /// @param iface Interface descriptor.
+    /// @param addr Address on the interface to be used to send packets.
+    /// @param port Port number.
+    /// @param receive_bcast Configure socket to receive broadcast messages
     /// @param send_bcast configure socket to send broadcast messages.
     /// @param send_bcast configure socket to send broadcast messages.
     ///
     ///
-    /// @return created socket's descriptor
+    /// @return A structure describing a primary and fallback socket.
-    virtual int 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 uint16_t port,
-                           const bool receive_bcast,
+                                  const bool receive_bcast,
-                           const bool send_bcast) = 0;
+                                  const bool send_bcast) = 0;
 
 
     /// @brief Receive packet over specified socket.
     /// @brief Receive packet over specified socket.
     ///
     ///
@@ -100,6 +110,32 @@ public:
     /// @return result of sending the packet. It is 0 if successful.
     /// @return result of sending the packet. It is 0 if successful.
     virtual int send(const Iface& iface, uint16_t sockfd,
     virtual int send(const Iface& iface, uint16_t sockfd,
                      const Pkt4Ptr& pkt) = 0;
                      const Pkt4Ptr& pkt) = 0;
+
+protected:
+
+    /// @brief Default implementation to open a fallback socket.
+    ///
+    /// This method provides a means to open a fallback socket and bind it
+    /// to a given IPv4 address and UDP port. This function may be used by the
+    /// derived classes to create a fallback socket. It can be overriden
+    /// in the derived classes if it happens to be insufficient on some
+    /// environments.
+    ///
+    /// The fallback socket is meant to be opened together with the other socket
+    /// (a.k.a. primary socket) used to receive and handle DHCPv4 traffic. The
+    /// traffic received through the fallback should be dropped. The reasoning
+    /// behind opening the fallback socket is explained in the documentation of
+    /// @c isc::dhcp::SocketInfo structure.
+    ///
+    /// @param addr An IPv4 address to bind the socket to.
+    /// @param port A port number to bind socket to.
+    ///
+    /// @return A fallback socket descriptor. This descriptor should be assigned
+    /// to the @c fallbackfd_ field of the @c isc::dhcp::SocketInfo structure.
+    /// @throw isc::dhcp::SocketConfigError if socket opening, binding or
+    /// configuration fails.
+    virtual int openFallbackSocket(const isc::asiolink::IOAddress& addr,
+                                   const uint16_t port);
 };
 };
 
 
 /// Pointer to a PktFilter object.
 /// Pointer to a PktFilter object.

+ 8 - 6
src/lib/dhcp/pkt_filter_inet.cc

@@ -28,11 +28,12 @@ PktFilterInet::PktFilterInet()
 {
 {
 }
 }
 
 
-int PktFilterInet::openSocket(const Iface& iface,
+SocketInfo
-                              const isc::asiolink::IOAddress& addr,
+PktFilterInet::openSocket(const Iface& iface,
-                              const uint16_t port,
+                          const isc::asiolink::IOAddress& addr,
-                              const bool receive_bcast,
+                          const uint16_t port,
-                              const bool send_bcast) {
+                          const bool receive_bcast,
+                          const bool send_bcast) {
 
 
     struct sockaddr_in addr4;
     struct sockaddr_in addr4;
     memset(&addr4, 0, sizeof(sockaddr));
     memset(&addr4, 0, sizeof(sockaddr));
@@ -90,7 +91,8 @@ int PktFilterInet::openSocket(const Iface& iface,
     }
     }
 #endif
 #endif
 
 
-    return (sock);
+    SocketInfo sock_desc(addr, port, sock);
+    return (sock_desc);
 
 
 }
 }
 
 

+ 20 - 12
src/lib/dhcp/pkt_filter_inet.h

@@ -44,20 +44,22 @@ public:
         return (false);
         return (false);
     }
     }
 
 
-    /// @brief Open socket.
+    /// @brief Open primary and fallback socket.
     ///
     ///
-    /// @param iface interface descriptor
+    /// @param iface Interface descriptor.
-    /// @param addr address on the interface to be used to send packets.
+    /// @param addr Address on the interface to be used to send packets.
-    /// @param port port number.
+    /// @param port Port number.
-    /// @param receive_bcast configure socket to receive broadcast messages
+    /// @param receive_bcast Configure socket to receive broadcast messages
-    /// @param send_bcast configure socket to send broadcast messages.
+    /// @param send_bcast Configure socket to send broadcast messages.
     ///
     ///
-    /// @return created socket's descriptor
+    /// @return A structure describing a primary and fallback socket.
-    virtual int openSocket(const Iface& iface,
+    /// @throw isc::dhcp::SocketConfigError if error occurs when opening,
-                           const isc::asiolink::IOAddress& addr,
+    /// binding or configuring the socket.
-                           const uint16_t port,
+    virtual SocketInfo openSocket(const Iface& iface,
-                           const bool receive_bcast,
+                                  const isc::asiolink::IOAddress& addr,
-                           const bool send_bcast);
+                                  const uint16_t port,
+                                  const bool receive_bcast,
+                                  const bool send_bcast);
 
 
     /// @brief Receive packet over specified socket.
     /// @brief Receive packet over specified socket.
     ///
     ///
@@ -65,6 +67,10 @@ public:
     /// @param socket_info structure holding socket information
     /// @param socket_info structure holding socket information
     ///
     ///
     /// @return Received packet
     /// @return Received packet
+    /// @throw isc::dhcp::SocketReadError if an error occurs during reception
+    /// of the packet.
+    /// @throw An execption thrown by the isc::dhcp::Pkt4 object if DHCPv4
+    /// message parsing fails.
     virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& socket_info);
     virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& socket_info);
 
 
     /// @brief Send packet over specified socket.
     /// @brief Send packet over specified socket.
@@ -74,6 +80,8 @@ public:
     /// @param pkt packet to be sent
     /// @param pkt packet to be sent
     ///
     ///
     /// @return result of sending a packet. It is 0 if successful.
     /// @return result of sending a packet. It is 0 if successful.
+    /// @throw isc::dhcp::SocketWriteError if an error occures during sending
+    /// a DHCP message through the socket.
     virtual int send(const Iface& iface, uint16_t sockfd,
     virtual int send(const Iface& iface, uint16_t sockfd,
                      const Pkt4Ptr& pkt);
                      const Pkt4Ptr& pkt);
 
 

+ 45 - 6
src/lib/dhcp/pkt_filter_lpf.cc

@@ -102,13 +102,22 @@ using namespace isc::util;
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
-int
+SocketInfo
-PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
+PktFilterLPF::openSocket(const Iface& iface,
+                         const isc::asiolink::IOAddress& addr,
                          const uint16_t port, const bool,
                          const uint16_t port, const bool,
                          const bool) {
                          const bool) {
 
 
+    // Open fallback socket first. If it fails, it will give us an indication
+    // that there is another service (perhaps DHCP server) running.
+    // The function will throw an exception and effectivelly cease opening
+    // raw socket below.
+    int fallback = openFallbackSocket(addr, port);
+
+    // The fallback is open, so we are good to open primary socket.
     int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
     int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
     if (sock < 0) {
     if (sock < 0) {
+        close(fallback);
         isc_throw(SocketConfigError, "Failed to create raw LPF socket");
         isc_throw(SocketConfigError, "Failed to create raw LPF socket");
     }
     }
 
 
@@ -126,6 +135,7 @@ PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
     if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
     if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
                    sizeof(filter_program)) < 0) {
                    sizeof(filter_program)) < 0) {
         close(sock);
         close(sock);
+        close(fallback);
         isc_throw(SocketConfigError, "Failed to install packet filtering program"
         isc_throw(SocketConfigError, "Failed to install packet filtering program"
                   << " on the socket " << sock);
                   << " on the socket " << sock);
     }
     }
@@ -142,17 +152,39 @@ PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
     if (bind(sock, reinterpret_cast<const struct sockaddr*>(&sa),
     if (bind(sock, reinterpret_cast<const struct sockaddr*>(&sa),
              sizeof(sa)) < 0) {
              sizeof(sa)) < 0) {
         close(sock);
         close(sock);
+        close(fallback);
         isc_throw(SocketConfigError, "Failed to bind LPF socket '" << sock
         isc_throw(SocketConfigError, "Failed to bind LPF socket '" << sock
                   << "' to interface '" << iface.getName() << "'");
                   << "' to interface '" << iface.getName() << "'");
     }
     }
 
 
-    return (sock);
+    return (SocketInfo(addr, port, sock, fallback));
 
 
 }
 }
 
 
 Pkt4Ptr
 Pkt4Ptr
 PktFilterLPF::receive(const Iface& iface, const SocketInfo& socket_info) {
 PktFilterLPF::receive(const Iface& iface, const SocketInfo& socket_info) {
     uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
     uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
+    // First let's get some data from the fallback socket. The data will be
+    // discarded but we don't want the socket buffer to bloat. We get the
+    // packets from the socket in loop but most of the time the loop will
+    // end after receiving one packet. The call to recv returns immediately
+    // when there is no data left on the socket because the socket is
+    // non-blocking.
+    // @todo In the normal conditions, both the primary socket and the fallback
+    // socket are in sync as they are set to receive packets on the same
+    // address and port. The reception of packets on the fallback socket
+    // shouldn't cause significant lags in packet reception. If we find in the
+    // future that it does, the sort of threshold could be set for the maximum
+    // bytes received on the fallback socket in a single round. Further
+    // optimizations would include an asynchronous read from the fallback socket
+    // when the DHCP server is idle.
+    int datalen;
+    do {
+        datalen = recv(socket_info.fallbackfd_, raw_buf, sizeof(raw_buf), 0);
+    } while (datalen > 0);
+
+    // Now that we finished getting data from the fallback socket, we
+    // have to get the data from the raw socket too.
     int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf));
     int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf));
     // If negative value is returned by read(), it indicates that an
     // If negative value is returned by read(), it indicates that an
     // error occured. If returned value is 0, no data was read from the
     // error occured. If returned value is 0, no data was read from the
@@ -209,9 +241,16 @@ PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
 
 
     OutputBuffer buf(14);
     OutputBuffer buf(14);
 
 
-    HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
+    // Some interfaces may have no HW address - e.g. loopback interface.
-                                iface.getHWType()));
+    // For these interfaces the HW address length is 0. If this is the case,
-    pkt->setLocalHWAddr(hwaddr);
+    // then we will rely on the functions which construct the IP/UDP headers
+    // to provide a default HW addres. Otherwise, create the HW address
+    // object using the HW address of the interface.
+    if (iface.getMacLen() > 0) {
+        HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
+                                    iface.getHWType()));
+        pkt->setLocalHWAddr(hwaddr);
+    }
 
 
 
 
     // Ethernet frame header.
     // Ethernet frame header.

+ 12 - 13
src/lib/dhcp/pkt_filter_lpf.h

@@ -41,21 +41,20 @@ public:
         return (true);
         return (true);
     }
     }
 
 
-    /// @brief Open socket.
+    /// @brief Open primary and fallback socket.
     ///
     ///
-    /// @param iface interface descriptor
+    /// @param iface Interface descriptor.
-    /// @param addr address on the interface to be used to send packets.
+    /// @param addr Address on the interface to be used to send packets.
-    /// @param port port number.
+    /// @param port Port number.
-    /// @param receive_bcast configure socket to receive broadcast messages
+    /// @param receive_bcast Configure socket to receive broadcast messages
-    /// @param send_bcast configure socket to send broadcast messages.
+    /// @param send_bcast Configure socket to send broadcast messages.
     ///
     ///
-    /// @throw isc::NotImplemented always
+    /// @return A structure describing a primary and fallback socket.
-    /// @return created socket's descriptor
+    virtual SocketInfo openSocket(const Iface& iface,
-    virtual int openSocket(const Iface& iface,
+                                  const isc::asiolink::IOAddress& addr,
-                           const isc::asiolink::IOAddress& addr,
+                                  const uint16_t port,
-                           const uint16_t port,
+                                  const bool receive_bcast,
-                           const bool receive_bcast,
+                                  const bool send_bcast);
-                           const bool send_bcast);
 
 
     /// @brief Receive packet over specified socket.
     /// @brief Receive packet over specified socket.
     ///
     ///

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

@@ -48,7 +48,9 @@ libdhcp___unittests_SOURCES += option_string_unittest.cc
 libdhcp___unittests_SOURCES += option_vendor_unittest.cc
 libdhcp___unittests_SOURCES += option_vendor_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_test_utils.h pkt_filter_test_utils.cc
 
 
 if OS_LINUX
 if OS_LINUX
 libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc

+ 352 - 22
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -20,6 +20,7 @@
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter.h>
 
 
+#include <boost/bind.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
@@ -78,19 +79,36 @@ public:
         return (false);
         return (false);
     }
     }
 
 
-    /// Pretends to open socket. Only records a call to this function.
+    /// @brief Pretend to open a socket.
-    /// This function returns fake socket descriptor (always the same).
+    ///
-    /// Note that the returned value has been selected to be unique
+    /// This function doesn't open a real socket. It always returns the
-    /// (because real values are rather less than 255). Values greater
+    /// same fake socket descriptor. It also records the fact that it has
-    /// than 255 are not recommended because they cause warnings to be
+    /// been called in the public open_socket_called_ member.
-    /// reported by Valgrind when invoking close() on them.
+    /// As in the case of opening a real socket, this function will check
-    virtual int openSocket(const Iface&,
+    /// if there is another fake socket "bound" to the same address and port.
-                           const isc::asiolink::IOAddress&,
+    /// If there is, it will throw an exception. This allows to simulate the
-                           const uint16_t,
+    /// conditions when one of the sockets can't be open because there is
-                           const bool,
+    /// a socket already open and test how IfaceMgr will handle it.
-                           const bool) {
+    ///
+    /// @param iface An interface on which the socket is to be opened.
+    /// @param addr An address to which the socket is to be bound.
+    /// @param port A port to which the socket is to be bound.
+    virtual SocketInfo openSocket(const Iface& iface,
+                                  const isc::asiolink::IOAddress& addr,
+                                  const uint16_t port,
+                                  const bool,
+                                  const bool) {
+        // Check if there is any other socket bound to the specified address
+        // and port on this interface.
+        const Iface::SocketCollection& sockets = iface.getSockets();
+        for (Iface::SocketCollection::const_iterator socket = sockets.begin();
+             socket != sockets.end(); ++socket) {
+            if ((socket->addr_ == addr) && (socket->port_ == port)) {
+                isc_throw(SocketConfigError, "test socket bind error");
+            }
+        }
         open_socket_called_ = true;
         open_socket_called_ = true;
-        return (255);
+        return (SocketInfo(addr, port, 255));
     }
     }
 
 
     /// Does nothing
     /// Does nothing
@@ -112,16 +130,97 @@ public:
 class NakedIfaceMgr: public IfaceMgr {
 class NakedIfaceMgr: public IfaceMgr {
     // "Naked" Interface Manager, exposes internal fields
     // "Naked" Interface Manager, exposes internal fields
 public:
 public:
+
+    /// @brief Constructor.
     NakedIfaceMgr() {
     NakedIfaceMgr() {
     }
     }
-    IfaceCollection & getIfacesLst() { return ifaces_; }
+
+    /// @brief Returns the collection of existing interfaces.
+    IfaceCollection& getIfacesLst() { return (ifaces_); }
+
+    /// @brief This function creates fictious interfaces with fictious
+    /// addresses.
+    ///
+    /// These interfaces can be used in tests that don't actually try
+    /// to open the sockets on these interfaces. Some tests use mock
+    /// objects to mimic sockets being open. These interfaces are
+    /// suitable for such tests.
+    void createIfaces() {
+
+        ifaces_.clear();
+
+        // local loopback
+        ifaces_.push_back(createIface("lo", 0, "127.0.0.1"));
+        // eth0
+        ifaces_.push_back(createIface("eth0", 1, "10.0.0.1"));
+        // eth1
+        ifaces_.push_back(createIface("eth1", 2, "192.0.2.3"));
+    }
+
+    /// @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
+    ///
+    /// 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.
+    /// @param addr An IP address to be assigned to the interface.
+    ///
+    /// @return An object representing interface.
+    static Iface 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);
+    }
+
+    /// @brief Modified flags on the interface.
+    ///
+    /// @param name A name of the interface.
+    /// @param loopback A new value of the loopback flag.
+    /// @param up A new value of the up flag.
+    /// @param running A new value of the running flag.
+    /// @param inactive A new value of the inactive flag.
+    void setIfaceFlags(const std::string& name, const bool loopback,
+                       const bool up, const bool running,
+                       const bool inactive) {
+        for (IfaceMgr::IfaceCollection::iterator iface = ifaces_.begin();
+             iface != ifaces_.end(); ++iface) {
+            if (iface->getName() == name) {
+                iface->flag_loopback_ = loopback;
+                iface->flag_up_ = up;
+                iface->flag_running_ = running;
+                iface->inactive4_ = inactive;
+            }
+        }
+    }
 };
 };
 
 
-// Dummy class for now, but this will be expanded when needed
+/// @brief A test fixture class for IfaceMgr.
+///
+/// @todo Sockets being opened by IfaceMgr tests should be managed by
+/// the test fixture. In particular, the class should close sockets after
+/// each test. Current approach where test cases are responsible for
+/// closing sockets is resource leak prone, especially in case of the
+/// test failure path.
 class IfaceMgrTest : public ::testing::Test {
 class IfaceMgrTest : public ::testing::Test {
 public:
 public:
-    // These are empty for now, but let's keep them around
+    /// @brief Constructor.
-    IfaceMgrTest() {
+    IfaceMgrTest()
+        : errors_count_(0) {
     }
     }
 
 
     ~IfaceMgrTest() {
     ~IfaceMgrTest() {
@@ -166,6 +265,26 @@ public:
         return (NULL);
         return (NULL);
     }
     }
 
 
+    /// @brief Implements an IfaceMgr error handler.
+    ///
+    /// This function can be installed as an error handler for the
+    /// IfaceMgr::openSockets4 function. The error handler is invoked
+    /// when an attempt to open a particular socket fails for any reason.
+    /// Typically, the error handler will log a warning. When the error
+    /// handler returns, the openSockets4 function should continue opening
+    /// sockets on other interfaces.
+    ///
+    /// @param errmsg An error string indicating the reason for failure.
+    void ifaceMgrErrorHandler(const std::string&) {
+        // Increase the counter of invocations to this function. By checking
+        // this number, a test amy check if the expected number of errors
+        // has occurred.
+        ++errors_count_;
+    }
+
+    /// Holds the invocation counter for ifaceMgrErrorHandler.
+    int errors_count_;
+
 };
 };
 
 
 // We need some known interface to work reliably. Loopback interface is named
 // We need some known interface to work reliably. Loopback interface is named
@@ -986,6 +1105,54 @@ TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
     EXPECT_TRUE(iface_mgr->isDirectResponseSupported());
     EXPECT_TRUE(iface_mgr->isDirectResponseSupported());
 }
 }
 
 
+// This test checks that it is not possible to open two sockets: IP/UDP
+// and raw (LPF) socket and bind to the same address and port. The
+// raw socket should be opened together with the fallback IP/UDP socket.
+// The fallback socket should fail to open when there is another IP/UDP
+// socket bound to the same address and port. Failing to open the fallback
+// socket should preclude the raw socket from being open.
+TEST_F(IfaceMgrTest, checkPacketFilterLPFSocket) {
+    IOAddress loAddr("127.0.0.1");
+    int socket1 = -1, socket2 = -1;
+    // Create two instances of IfaceMgr.
+    boost::scoped_ptr<NakedIfaceMgr> iface_mgr1(new NakedIfaceMgr());
+    ASSERT_TRUE(iface_mgr1);
+    boost::scoped_ptr<NakedIfaceMgr> iface_mgr2(new NakedIfaceMgr());
+    ASSERT_TRUE(iface_mgr2);
+
+    // Let IfaceMgr figure out which Packet Filter to use when
+    // direct response capability is not desired. It should pick
+    // PktFilterInet.
+    EXPECT_NO_THROW(iface_mgr1->setMatchingPacketFilter(false));
+    // Let's open a loopback socket with handy unpriviliged port number
+    socket1 = iface_mgr1->openSocket(LOOPBACK, loAddr,
+                                     DHCP4_SERVER_PORT + 10000);
+
+    EXPECT_GE(socket1, 0);
+
+    // Then the second use PkFilterLPF mode
+    EXPECT_NO_THROW(iface_mgr2->setMatchingPacketFilter(true));
+
+    // The socket is open and bound. Another attempt to open socket and
+    // bind to the same address and port should result in an exception.
+    EXPECT_THROW(
+        socket2 = iface_mgr2->openSocket(LOOPBACK, loAddr,
+                                         DHCP4_SERVER_PORT + 10000),
+        isc::dhcp::SocketConfigError
+    );
+    // Surprisingly we managed to open another socket. We have to close it
+    // to prevent resource leak.
+    if (socket2 >= 0) {
+        close(socket2);
+        ADD_FAILURE() << "Two sockets opened and bound to the same IP"
+            " address and UDP port";
+    }
+
+    if (socket1 >= 0) {
+        close(socket1);
+    }
+}
+
 #else
 #else
 
 
 // This non-Linux specific test checks whether it is possible to use
 // This non-Linux specific test checks whether it is possible to use
@@ -1045,6 +1212,157 @@ TEST_F(IfaceMgrTest, socket4) {
     close(socket1);
     close(socket1);
 }
 }
 
 
+// This test verifies that IPv4 sockets are open on all interfaces (except
+// loopback), when interfaces are up, running and active (not disabled from
+// the DHCP configuration).
+TEST_F(IfaceMgrTest, openSockets4) {
+    NakedIfaceMgr ifacemgr;
+
+    // Remove all real interfaces and create a set of dummy interfaces.
+    ifacemgr.createIfaces();
+
+    // Use the custom packet filter object. This object mimics the socket
+    // opening operation - the real socket is not open.
+    boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+    ASSERT_TRUE(custom_packet_filter);
+    ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+    // Simulate opening sockets using the dummy packet filter.
+    ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
+
+    // Expect that the sockets are open on both eth0 and eth1.
+    EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
+    EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
+    // Socket shouldn't have been opened on loopback.
+    EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
+}
+
+// This test verifies that the socket is not open on the interface which is
+// down, but sockets are open on all other non-loopback interfaces.
+TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
+    NakedIfaceMgr ifacemgr;
+
+    // Remove all real interfaces and create a set of dummy interfaces.
+    ifacemgr.createIfaces();
+
+    boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+    ASSERT_TRUE(custom_packet_filter);
+    ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+    // Boolean parameters specify that eth0 is:
+    // - not a loopback
+    // - is "down" (not up)
+    // - is not running
+    // - is active (is not inactive)
+    ifacemgr.setIfaceFlags("eth0", false, false, true, false);
+    ASSERT_FALSE(ifacemgr.getIface("eth0")->flag_up_);
+    ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
+
+    // There should be no socket on eth0 open, because interface was down.
+    EXPECT_TRUE(ifacemgr.getIface("eth0")->getSockets().empty());
+    // Expecting that the socket is open on eth1 because it was up, running
+    // and active.
+    EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
+    // Never open socket on loopback interface.
+    EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
+}
+
+// This test verifies that the socket is not open on the interface which is
+// disabled from the DHCP configuration, but sockets are open on all other
+// non-loopback interfaces.
+TEST_F(IfaceMgrTest, openSockets4IfaceInactive) {
+    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));
+
+    // Boolean parameters specify that eth1 is:
+    // - not a loopback
+    // - is up
+    // - is running
+    // - is inactive
+    ifacemgr.setIfaceFlags("eth1", false, true, true, true);
+    ASSERT_TRUE(ifacemgr.getIface("eth1")->inactive4_);
+    ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
+
+    // The socket on eth0 should be open because interface is up, running and
+    // active (not disabled through DHCP configuration, for example).
+    EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
+    // There should be no socket open on eth1 because it was marked inactive.
+    EXPECT_TRUE(ifacemgr.getIface("eth1")->getSockets().empty());
+    // Sockets are not open on loopback interfaces too.
+    EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
+}
+
+// Test that exception is thrown when trying to bind a new socket to the port
+// and address which is already in use by another socket.
+TEST_F(IfaceMgrTest, openSockets4NoErrorHandler) {
+    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));
+
+    // Open socket on eth1. The openSockets4 should detect that this
+    // socket has been already open and an attempt to open another socket
+    // and bind to this address and port should fail.
+    ASSERT_NO_THROW(ifacemgr.openSocket("eth1", IOAddress("192.0.2.3"),
+                                        DHCP4_SERVER_PORT));
+
+    // The function throws an exception when it tries to open a socket
+    // and bind it to the address in use.
+    EXPECT_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL),
+                 isc::dhcp::SocketConfigError);
+
+}
+
+// Test that the external error handler is called when trying to bind a new
+// socket to the address and port being in use. The sockets on the other
+// interfaces should open just fine..
+TEST_F(IfaceMgrTest, openSocket4ErrorHandler) {
+    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));
+
+    // Open socket on eth0. The openSockets4 should detect that this
+    // socket has been already open and an attempt to open another socket
+    // and bind to this address and port should fail.
+    ASSERT_NO_THROW(ifacemgr.openSocket("eth0", IOAddress("10.0.0.1"),
+                                        DHCP4_SERVER_PORT));
+
+    // Install an error handler before trying to open sockets. This handler
+    // should be called when the IfaceMgr fails to open socket on eth0.
+    isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
+        boost::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, _1);
+    ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler));
+    // We expect that an error occured when we tried to open a socket on
+    // eth0, but the socket on eth1 should open just fine.
+    EXPECT_EQ(1, errors_count_);
+
+    // Reset errors count.
+    errors_count_ = 0;
+
+    // Now that we have two sockets open, we can try this again but this time
+    // we should get two errors: one when opening a socket on eth0, another one
+    // when opening a socket on eth1.
+    ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler));
+    EXPECT_EQ(2, errors_count_);
+
+}
+
+
 // Test the Iface structure itself
 // Test the Iface structure itself
 TEST_F(IfaceMgrTest, iface) {
 TEST_F(IfaceMgrTest, iface) {
     boost::scoped_ptr<Iface> iface;
     boost::scoped_ptr<Iface> iface;
@@ -1118,18 +1436,28 @@ TEST_F(IfaceMgrTest, iface_methods) {
 TEST_F(IfaceMgrTest, socketInfo) {
 TEST_F(IfaceMgrTest, socketInfo) {
 
 
     // Check that socketinfo for IPv4 socket is functional
     // Check that socketinfo for IPv4 socket is functional
-    SocketInfo sock1(7, IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7);
+    SocketInfo sock1(IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7, 7);
     EXPECT_EQ(7, sock1.sockfd_);
     EXPECT_EQ(7, sock1.sockfd_);
+    EXPECT_EQ(-1, sock1.fallbackfd_);
     EXPECT_EQ("192.0.2.56", sock1.addr_.toText());
     EXPECT_EQ("192.0.2.56", sock1.addr_.toText());
     EXPECT_EQ(AF_INET, sock1.family_);
     EXPECT_EQ(AF_INET, sock1.family_);
     EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_);
     EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_);
 
 
+    // Check that non-default value of the fallback socket descriptor is set
+    SocketInfo sock2(IOAddress("192.0.2.53"), DHCP4_SERVER_PORT + 8, 8, 10);
+    EXPECT_EQ(8, sock2.sockfd_);
+    EXPECT_EQ(10, sock2.fallbackfd_);
+    EXPECT_EQ("192.0.2.53", sock2.addr_.toText());
+    EXPECT_EQ(AF_INET, sock2.family_);
+    EXPECT_EQ(DHCP4_SERVER_PORT + 8, sock2.port_);
+
     // Check that socketinfo for IPv6 socket is functional
     // Check that socketinfo for IPv6 socket is functional
-    SocketInfo sock2(9, IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9);
+    SocketInfo sock3(IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9, 9);
-    EXPECT_EQ(9, sock2.sockfd_);
+    EXPECT_EQ(9, sock3.sockfd_);
-    EXPECT_EQ("2001:db8:1::56", sock2.addr_.toText());
+    EXPECT_EQ(-1, sock3.fallbackfd_);
-    EXPECT_EQ(AF_INET6, sock2.family_);
+    EXPECT_EQ("2001:db8:1::56", sock3.addr_.toText());
-    EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock2.port_);
+    EXPECT_EQ(AF_INET6, sock3.family_);
+    EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock3.port_);
 
 
     // Now let's test if IfaceMgr handles socket info properly
     // Now let's test if IfaceMgr handles socket info properly
     scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
     scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
@@ -1137,6 +1465,7 @@ TEST_F(IfaceMgrTest, socketInfo) {
     ASSERT_TRUE(loopback);
     ASSERT_TRUE(loopback);
     loopback->addSocket(sock1);
     loopback->addSocket(sock1);
     loopback->addSocket(sock2);
     loopback->addSocket(sock2);
+    loopback->addSocket(sock3);
 
 
     Pkt6 pkt6(DHCPV6_REPLY, 123456);
     Pkt6 pkt6(DHCPV6_REPLY, 123456);
 
 
@@ -1191,6 +1520,7 @@ TEST_F(IfaceMgrTest, socketInfo) {
 
 
     EXPECT_NO_THROW(
     EXPECT_NO_THROW(
         ifacemgr->getIface(LOOPBACK)->delSocket(7);
         ifacemgr->getIface(LOOPBACK)->delSocket(7);
+        ifacemgr->getIface(LOOPBACK)->delSocket(8);
     );
     );
 
 
     // It should throw again, there's no usable socket anymore.
     // It should throw again, there's no usable socket anymore.

+ 31 - 161
src/lib/dhcp/tests/pkt_filter_inet_unittest.cc

@@ -17,6 +17,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt_filter_inet.h>
 #include <dhcp/pkt_filter_inet.h>
+#include <dhcp/tests/pkt_filter_test_utils.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
@@ -32,63 +33,12 @@ const uint16_t PORT = 10067;
 /// Size of the buffer holding received packets.
 /// Size of the buffer holding received packets.
 const size_t RECV_BUF_SIZE = 2048;
 const size_t RECV_BUF_SIZE = 2048;
 
 
-/// This class handles has simple algorithm checking
+// Test fixture class inherits from the class common for all packet
-/// presence of loopback interface and initializing
+// filter tests.
-/// its index.
+class PktFilterInetTest : public isc::dhcp::test::PktFilterTest {
-class PktFilterInetTest : public ::testing::Test {
 public:
 public:
-
+    PktFilterInetTest() : PktFilterTest(PORT) {
-    /// @brief Constructor
-    ///
-    /// This constructor initializes socket_ member to a negative value.
-    /// Explcit initialization is performed here because some of the
-    /// tests do not initialize this value. In such cases, destructor
-    /// could invoke close() on uninitialized socket descriptor which
-    /// would result in errors being reported by Valgrind.
-    PktFilterInetTest()
-        : socket_(-1) {
-        // Initialize ifname_ and ifindex_.
-        loInit();
-    }
-
-    /// @brief Destructor
-    ///
-    /// Closes open socket (if any).
-    ~PktFilterInetTest() {
-        // Cleanup after each test. This guarantees
-        // that the socket does not hang after a test.
-        if (socket_ >= 0) {
-            close(socket_);
-        }
     }
     }
-
-    /// @brief Detect loopback interface.
-    ///
-    /// @todo this function will be removed once cross-OS interface
-    /// detection is implemented
-    void loInit() {
-        if (if_nametoindex("lo") > 0) {
-            ifname_ = "lo";
-            ifindex_ = if_nametoindex("lo");
-
-        } else if (if_nametoindex("lo0") > 0) {
-            ifname_ = "lo0";
-            ifindex_ = if_nametoindex("lo0");
-
-        } else {
-            std::cout << "Failed to detect loopback interface. Neither "
-                      << "lo nor lo0 worked. Giving up." << std::endl;
-            FAIL();
-
-
-
-        }
-    }
-
-    std::string ifname_; ///< Loopback interface name
-    uint16_t ifindex_;   ///< Loopback interface index.
-    int socket_;         ///< Socket descriptor.
-
 };
 };
 
 
 // This test verifies that the PktFilterInet class reports its lack
 // This test verifies that the PktFilterInet class reports its lack
@@ -112,55 +62,19 @@ TEST_F(PktFilterInetTest, openSocket) {
 
 
     // Try to open socket.
     // Try to open socket.
     PktFilterInet pkt_filter;
     PktFilterInet pkt_filter;
-    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT,
-    // Check that socket has been opened.
+                                       false, false);
-    ASSERT_GE(socket_, 0);
+    // For the packet filter in use, the fallback socket shouldn't be opened.
-
+    // Fallback is typically opened for raw sockets.
-    // Verify that the socket belongs to AF_INET family.
+    EXPECT_LT(sock_info_.fallbackfd_, 0);
-    sockaddr_in sock_address;
+
-    socklen_t sock_address_len = sizeof(sock_address);
+    // Test the primary socket.
-    ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
+    testDgramSocket(sock_info_.sockfd_);
-                             &sock_address_len));
-    EXPECT_EQ(AF_INET, sock_address.sin_family);
-
-    // Verify that the socket is bound the appropriate address.
-    const std::string bind_addr(inet_ntoa(sock_address.sin_addr));
-    EXPECT_EQ("127.0.0.1", bind_addr);
-
-    // Verify that the socket is bound to appropriate port.
-    EXPECT_EQ(PORT, ntohs(sock_address.sin_port));
-
-    // Verify that the socket has SOCK_DGRAM type.
-    int sock_type;
-    socklen_t sock_type_len = sizeof(sock_type);
-    ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
-    EXPECT_EQ(SOCK_DGRAM, sock_type);
 }
 }
 
 
 // This test verifies that the packet is correctly sent over the INET
 // This test verifies that the packet is correctly sent over the INET
 // datagram socket.
 // datagram socket.
 TEST_F(PktFilterInetTest, send) {
 TEST_F(PktFilterInetTest, send) {
-    // Let's create a DHCPv4 packet.
-    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
-    ASSERT_TRUE(pkt);
-
-    // Set required fields.
-    pkt->setLocalAddr(IOAddress("127.0.0.1"));
-    pkt->setRemoteAddr(IOAddress("127.0.0.1"));
-    pkt->setRemotePort(PORT);
-    pkt->setLocalPort(PORT + 1);
-    pkt->setIndex(ifindex_);
-    pkt->setIface(ifname_);
-    pkt->setHops(6);
-    pkt->setSecs(42);
-    pkt->setCiaddr(IOAddress("192.0.2.1"));
-    pkt->setSiaddr(IOAddress("192.0.2.2"));
-    pkt->setYiaddr(IOAddress("192.0.2.3"));
-    pkt->setGiaddr(IOAddress("192.0.2.4"));
-
-    // Create the on-wire data.
-    ASSERT_NO_THROW(pkt->pack());
-
     // Packet will be sent over loopback interface.
     // Packet will be sent over loopback interface.
     Iface iface(ifname_, ifindex_);
     Iface iface(ifname_, ifindex_);
     IOAddress addr("127.0.0.1");
     IOAddress addr("127.0.0.1");
@@ -170,27 +84,27 @@ TEST_F(PktFilterInetTest, send) {
     // Open socket. We don't check that the socket has appropriate
     // Open socket. We don't check that the socket has appropriate
     // options and family set because we have checked that in the
     // options and family set because we have checked that in the
     // openSocket test already.
     // openSocket test already.
-    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
-    ASSERT_GE(socket_, 0);
+    ASSERT_GE(sock_info_.sockfd_, 0);
 
 
     // Send the packet over the socket.
     // Send the packet over the socket.
-    ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+    ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
 
 
     // Read the data from socket.
     // Read the data from socket.
     fd_set readfds;
     fd_set readfds;
     FD_ZERO(&readfds);
     FD_ZERO(&readfds);
-    FD_SET(socket_, &readfds);
+    FD_SET(sock_info_.sockfd_, &readfds);
-    
+
     struct timeval timeout;
     struct timeval timeout;
     timeout.tv_sec = 5;
     timeout.tv_sec = 5;
     timeout.tv_usec = 0;
     timeout.tv_usec = 0;
-    int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
+    int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
     // We should receive some data from loopback interface.
     // We should receive some data from loopback interface.
     ASSERT_GT(result, 0);
     ASSERT_GT(result, 0);
 
 
     // Get the actual data.
     // Get the actual data.
     uint8_t rcv_buf[RECV_BUF_SIZE];
     uint8_t rcv_buf[RECV_BUF_SIZE];
-    result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
+    result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0);
     ASSERT_GT(result, 0);
     ASSERT_GT(result, 0);
 
 
     // Create the DHCPv4 packet from the received data.
     // Create the DHCPv4 packet from the received data.
@@ -200,47 +114,16 @@ TEST_F(PktFilterInetTest, send) {
     // Parse the packet.
     // Parse the packet.
     ASSERT_NO_THROW(rcvd_pkt->unpack());
     ASSERT_NO_THROW(rcvd_pkt->unpack());
 
 
-    // Verify that the received packet matches sent packet.
+    // Check if the received message is correct.
-    EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+    testRcvdMessage(rcvd_pkt);
-    EXPECT_EQ(pkt->getOp(),   rcvd_pkt->getOp());
+
-    EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
-    EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
-    EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
-    EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
-    EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
-    EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
-    EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
-    EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
-    EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
-    EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
-    EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
 }
 }
 
 
 // This test verifies that the DHCPv4 packet is correctly received via
 // This test verifies that the DHCPv4 packet is correctly received via
 // INET datagram socket and that it matches sent packet.
 // INET datagram socket and that it matches sent packet.
 TEST_F(PktFilterInetTest, receive) {
 TEST_F(PktFilterInetTest, receive) {
-    // Let's create a DHCPv4 packet.
-    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
-    ASSERT_TRUE(pkt);
-
-    // Set required fields.
-    pkt->setLocalAddr(IOAddress("127.0.0.1"));
-    pkt->setRemoteAddr(IOAddress("127.0.0.1"));
-    pkt->setRemotePort(PORT);
-    pkt->setLocalPort(PORT + 1);
-    pkt->setIndex(ifindex_);
-    pkt->setIface(ifname_);
-    pkt->setHops(6);
-    pkt->setSecs(42);
-    pkt->setCiaddr(IOAddress("192.0.2.1"));
-    pkt->setSiaddr(IOAddress("192.0.2.2"));
-    pkt->setYiaddr(IOAddress("192.0.2.3"));
-    pkt->setGiaddr(IOAddress("192.0.2.4"));
 
 
-    // Create the on-wire data.
+    // Packet will be received over loopback interface.
-    ASSERT_NO_THROW(pkt->pack());
-
-    // Packet will be sent over loopback interface.
     Iface iface(ifname_, ifindex_);
     Iface iface(ifname_, ifindex_);
     IOAddress addr("127.0.0.1");
     IOAddress addr("127.0.0.1");
 
 
@@ -249,35 +132,22 @@ TEST_F(PktFilterInetTest, receive) {
     // Open socket. We don't check that the socket has appropriate
     // Open socket. We don't check that the socket has appropriate
     // options and family set because we have checked that in the
     // options and family set because we have checked that in the
     // openSocket test already.
     // openSocket test already.
-    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
-    ASSERT_GE(socket_, 0);
+    ASSERT_GE(sock_info_.sockfd_, 0);
 
 
-    // Send the packet over the socket.
+    // Send a DHCPv4 message to the local loopback address and server's port.
-    ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+    sendMessage();
 
 
     // Receive the packet.
     // Receive the packet.
-    SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
+    Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_);
-    Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
     // Check that the packet has been correctly received.
     // Check that the packet has been correctly received.
     ASSERT_TRUE(rcvd_pkt);
     ASSERT_TRUE(rcvd_pkt);
 
 
     // Parse the packet.
     // Parse the packet.
     ASSERT_NO_THROW(rcvd_pkt->unpack());
     ASSERT_NO_THROW(rcvd_pkt->unpack());
 
 
-    // Verify that the received packet matches sent packet.
+    // Check if the received message is correct.
-    EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+    testRcvdMessage(rcvd_pkt);
-    EXPECT_EQ(pkt->getOp(),   rcvd_pkt->getOp());
-    EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
-    EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
-    EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
-    EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
-    EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
-    EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
-    EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
-    EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
-    EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
-    EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
-    EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
 }
 }
 
 
 } // anonymous namespace
 } // anonymous namespace

+ 35 - 150
src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc

@@ -18,6 +18,7 @@
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt_filter_lpf.h>
 #include <dhcp/pkt_filter_lpf.h>
 #include <dhcp/protocol_util.h>
 #include <dhcp/protocol_util.h>
+#include <dhcp/tests/pkt_filter_test_utils.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
@@ -36,63 +37,12 @@ const uint16_t PORT = 10067;
 /// Size of the buffer holding received packets.
 /// Size of the buffer holding received packets.
 const size_t RECV_BUF_SIZE = 2048;
 const size_t RECV_BUF_SIZE = 2048;
 
 
-/// This class handles has simple algorithm checking
+// Test fixture class inherits from the class common for all packet
-/// presence of loopback interface and initializing
+// filter tests.
-/// its index.
+class PktFilterLPFTest : public isc::dhcp::test::PktFilterTest {
-class PktFilterLPFTest : public ::testing::Test {
 public:
 public:
-
+    PktFilterLPFTest() : PktFilterTest(PORT) {
-    /// @brief Constructor
-    ///
-    /// This constructor initializes socket_ member to a negative value.
-    /// Explcit initialization is performed here because some of the
-    /// tests do not initialize this value. In such cases, destructor
-    /// could invoke close() on uninitialized socket descriptor which
-    /// would result in errors being reported by Valgrind.
-    PktFilterLPFTest()
-        : socket_(-1) {
-        // Initialize ifname_ and ifindex_.
-        loInit();
-    }
-
-    /// @brief Destructor
-    ///
-    /// Closes open socket (if any).
-    ~PktFilterLPFTest() {
-        // Cleanup after each test. This guarantees
-        // that the socket does not hang after a test.
-        if (socket_ >= 0) {
-            close(socket_);
-        }
-    }
-
-    /// @brief Detect loopback interface.
-    ///
-    /// @todo this function will be removed once cross-OS interface
-    /// detection is implemented
-    void loInit() {
-        if (if_nametoindex("lo") > 0) {
-            ifname_ = "lo";
-            ifindex_ = if_nametoindex("lo");
-
-        } else if (if_nametoindex("lo0") > 0) {
-            ifname_ = "lo0";
-            ifindex_ = if_nametoindex("lo0");
-
-        } else {
-            std::cout << "Failed to detect loopback interface. Neither "
-                      << "lo nor lo0 worked. Giving up." << std::endl;
-            FAIL();
-
-
-
-        }
     }
     }
-
-    std::string ifname_; ///< Loopback interface name
-    uint16_t ifindex_;   ///< Loopback interface index.
-    int socket_;         ///< Socket descriptor.
-
 };
 };
 
 
 // This test verifies that the PktFilterLPF class reports its capability
 // This test verifies that the PktFilterLPF class reports its capability
@@ -124,14 +74,18 @@ TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
 
 
     // Try to open socket.
     // Try to open socket.
     PktFilterLPF pkt_filter;
     PktFilterLPF pkt_filter;
-    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
-    // Check that socket has been opened.
+
-    ASSERT_GE(socket_, 0);
+    // Check that the primary socket has been opened.
+    ASSERT_GE(sock_info_.sockfd_, 0);
+    // Check that the fallback socket has been opened too.
+    ASSERT_GE(sock_info_.fallbackfd_, 0);
 
 
     // Verify that the socket belongs to AF_PACKET family.
     // Verify that the socket belongs to AF_PACKET family.
     sockaddr_ll sock_address;
     sockaddr_ll sock_address;
     socklen_t sock_address_len = sizeof(sock_address);
     socklen_t sock_address_len = sizeof(sock_address);
-    ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
+    ASSERT_EQ(0, getsockname(sock_info_.sockfd_,
+                             reinterpret_cast<sockaddr*>(&sock_address),
                              &sock_address_len));
                              &sock_address_len));
     EXPECT_EQ(AF_PACKET, sock_address.sll_family);
     EXPECT_EQ(AF_PACKET, sock_address.sll_family);
 
 
@@ -141,39 +95,14 @@ TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
     // Verify that the socket has SOCK_RAW type.
     // Verify that the socket has SOCK_RAW type.
     int sock_type;
     int sock_type;
     socklen_t sock_type_len = sizeof(sock_type);
     socklen_t sock_type_len = sizeof(sock_type);
-    ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
+    ASSERT_EQ(0, getsockopt(sock_info_.sockfd_, SOL_SOCKET, SO_TYPE,
+                            &sock_type, &sock_type_len));
     EXPECT_EQ(SOCK_RAW, sock_type);
     EXPECT_EQ(SOCK_RAW, sock_type);
 }
 }
 
 
 // This test verifies correctness of sending DHCP packet through the raw
 // This test verifies correctness of sending DHCP packet through the raw
 // socket, whereby all IP stack headers are hand-crafted.
 // socket, whereby all IP stack headers are hand-crafted.
 TEST_F(PktFilterLPFTest, DISABLED_send) {
 TEST_F(PktFilterLPFTest, DISABLED_send) {
-        // Let's create a DHCPv4 packet.
-    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
-    ASSERT_TRUE(pkt);
-
-    // Set required fields.
-    // By setting the local address to broadcast we simulate the
-    // typical scenario when client's request was send to broadcast
-    // address and server by default used it as a source address
-    // in its response. The send() function should be able to detect
-    // it and correct the source address.
-    pkt->setLocalAddr(IOAddress("255.255.255.255"));
-    pkt->setRemoteAddr(IOAddress("127.0.0.1"));
-    pkt->setRemotePort(PORT);
-    pkt->setLocalPort(PORT + 1);
-    pkt->setIndex(ifindex_);
-    pkt->setIface(ifname_);
-    pkt->setHops(6);
-    pkt->setSecs(42);
-    pkt->setCiaddr(IOAddress("192.0.2.1"));
-    pkt->setSiaddr(IOAddress("192.0.2.2"));
-    pkt->setYiaddr(IOAddress("192.0.2.3"));
-    pkt->setGiaddr(IOAddress("192.0.2.4"));
-
-    // Create the on-wire data.
-    ASSERT_NO_THROW(pkt->pack());
-
     // Packet will be sent over loopback interface.
     // Packet will be sent over loopback interface.
     Iface iface(ifname_, ifindex_);
     Iface iface(ifname_, ifindex_);
     IOAddress addr("127.0.0.1");
     IOAddress addr("127.0.0.1");
@@ -183,27 +112,29 @@ TEST_F(PktFilterLPFTest, DISABLED_send) {
     // Open socket. We don't check that the socket has appropriate
     // Open socket. We don't check that the socket has appropriate
     // options and family set because we have checked that in the
     // options and family set because we have checked that in the
     // openSocket test already.
     // openSocket test already.
-    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+
-    ASSERT_GE(socket_, 0);
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+
+    ASSERT_GE(sock_info_.sockfd_, 0);
 
 
     // Send the packet over the socket.
     // Send the packet over the socket.
-    ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+    ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
 
 
     // Read the data from socket.
     // Read the data from socket.
     fd_set readfds;
     fd_set readfds;
     FD_ZERO(&readfds);
     FD_ZERO(&readfds);
-    FD_SET(socket_, &readfds);
+    FD_SET(sock_info_.sockfd_, &readfds);
-    
+
     struct timeval timeout;
     struct timeval timeout;
     timeout.tv_sec = 5;
     timeout.tv_sec = 5;
     timeout.tv_usec = 0;
     timeout.tv_usec = 0;
-    int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
+    int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
     // We should receive some data from loopback interface.
     // We should receive some data from loopback interface.
     ASSERT_GT(result, 0);
     ASSERT_GT(result, 0);
 
 
     // Get the actual data.
     // Get the actual data.
     uint8_t rcv_buf[RECV_BUF_SIZE];
     uint8_t rcv_buf[RECV_BUF_SIZE];
-    result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
+    result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0);
     ASSERT_GT(result, 0);
     ASSERT_GT(result, 0);
 
 
     Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
     Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
@@ -223,48 +154,15 @@ TEST_F(PktFilterLPFTest, DISABLED_send) {
     // Parse the packet.
     // Parse the packet.
     ASSERT_NO_THROW(rcvd_pkt->unpack());
     ASSERT_NO_THROW(rcvd_pkt->unpack());
 
 
-    // Verify that the received packet matches sent packet.
+    // Check if the received message is correct.
-    EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+    testRcvdMessage(rcvd_pkt);
-    EXPECT_EQ(pkt->getOp(),   rcvd_pkt->getOp());
-    EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
-    EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
-    EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
-    EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
-    EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
-    EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
-    EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
-    EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
-    EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
-    EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
-    EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
 }
 }
 
 
 // This test verifies correctness of reception of the DHCP packet over
 // This test verifies correctness of reception of the DHCP packet over
 // raw socket, whereby all IP stack headers are hand-crafted.
 // raw socket, whereby all IP stack headers are hand-crafted.
 TEST_F(PktFilterLPFTest, DISABLED_receive) {
 TEST_F(PktFilterLPFTest, DISABLED_receive) {
 
 
-    // Let's create a DHCPv4 packet.
+    // Packet will be received over loopback interface.
-    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
-    ASSERT_TRUE(pkt);
-
-    // Set required fields.
-    pkt->setLocalAddr(IOAddress("127.0.0.1"));
-    pkt->setRemoteAddr(IOAddress("127.0.0.1"));
-    pkt->setRemotePort(PORT);
-    pkt->setLocalPort(PORT + 1);
-    pkt->setIndex(ifindex_);
-    pkt->setIface(ifname_);
-    pkt->setHops(6);
-    pkt->setSecs(42);
-    pkt->setCiaddr(IOAddress("192.0.2.1"));
-    pkt->setSiaddr(IOAddress("192.0.2.2"));
-    pkt->setYiaddr(IOAddress("192.0.2.3"));
-    pkt->setGiaddr(IOAddress("192.0.2.4"));
-
-    // Create the on-wire data.
-    ASSERT_NO_THROW(pkt->pack());
-
-    // Packet will be sent over loopback interface.
     Iface iface(ifname_, ifindex_);
     Iface iface(ifname_, ifindex_);
     IOAddress addr("127.0.0.1");
     IOAddress addr("127.0.0.1");
 
 
@@ -273,35 +171,22 @@ TEST_F(PktFilterLPFTest, DISABLED_receive) {
     // Open socket. We don't check that the socket has appropriate
     // Open socket. We don't check that the socket has appropriate
     // options and family set because we have checked that in the
     // options and family set because we have checked that in the
     // openSocket test already.
     // openSocket test already.
-    socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
-    ASSERT_GE(socket_, 0);
+    ASSERT_GE(sock_info_.sockfd_, 0);
 
 
-    // Send the packet over the socket.
+    // Send DHCPv4 message to the local loopback address and server's port.
-    ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+    sendMessage();
 
 
-    // Receive the packet.
+    // Receive the packet using LPF packet filter.
-    SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
+    Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_);
-    Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
     // Check that the packet has been correctly received.
     // Check that the packet has been correctly received.
     ASSERT_TRUE(rcvd_pkt);
     ASSERT_TRUE(rcvd_pkt);
 
 
     // Parse the packet.
     // Parse the packet.
     ASSERT_NO_THROW(rcvd_pkt->unpack());
     ASSERT_NO_THROW(rcvd_pkt->unpack());
 
 
-    // Verify that the received packet matches sent packet.
+    // Check if the received message is correct.
-    EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+    testRcvdMessage(rcvd_pkt);
-    EXPECT_EQ(pkt->getOp(),   rcvd_pkt->getOp());
-    EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
-    EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
-    EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
-    EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
-    EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
-    EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
-    EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
-    EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
-    EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
-    EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
-    EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
 }
 }
 
 
 } // anonymous namespace
 } // anonymous namespace

+ 194 - 0
src/lib/dhcp/tests/pkt_filter_test_utils.cc

@@ -0,0 +1,194 @@
+// Copyright (C) 2013 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 <asiolink/io_address.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/tests/pkt_filter_test_utils.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PktFilterTest::PktFilterTest(const uint16_t port)
+    : port_(port),
+      sock_info_(isc::asiolink::IOAddress("127.0.0.1"), port, -1, -1),
+      send_msg_sock_(-1) {
+    // Initialize ifname_ and ifindex_.
+    loInit();
+    // Initialize test_message_.
+    initTestMessage();
+}
+
+PktFilterTest::~PktFilterTest() {
+    // Cleanup after each test. This guarantees
+    // that the sockets do not hang after a test.
+    if (sock_info_.sockfd_ >= 0) {
+        close(sock_info_.sockfd_);
+    }
+    if (sock_info_.fallbackfd_ >=0) {
+        close(sock_info_.fallbackfd_);
+    }
+    if (send_msg_sock_ >= 0) {
+        close(send_msg_sock_);
+    }
+}
+
+void
+PktFilterTest::initTestMessage() {
+    // Let's create a DHCPv4 message instance.
+    test_message_.reset(new Pkt4(DHCPOFFER, 0));
+
+    // Set required fields.
+    test_message_->setLocalAddr(IOAddress("127.0.0.1"));
+    test_message_->setRemoteAddr(IOAddress("127.0.0.1"));
+    test_message_->setRemotePort(port_);
+    test_message_->setLocalPort(port_ + 1);
+    test_message_->setIndex(ifindex_);
+    test_message_->setIface(ifname_);
+    test_message_->setHops(6);
+    test_message_->setSecs(42);
+    test_message_->setCiaddr(IOAddress("192.0.2.1"));
+    test_message_->setSiaddr(IOAddress("192.0.2.2"));
+    test_message_->setYiaddr(IOAddress("192.0.2.3"));
+    test_message_->setGiaddr(IOAddress("192.0.2.4"));
+
+    try {
+        test_message_->pack();
+    } catch (const isc::Exception& ex) {
+        ADD_FAILURE() << "failed to create test message for PktFilterTest";
+    }
+}
+
+void
+PktFilterTest::loInit() {
+    if (if_nametoindex("lo") > 0) {
+        ifname_ = "lo";
+        ifindex_ = if_nametoindex("lo");
+
+    } else if (if_nametoindex("lo0") > 0) {
+        ifname_ = "lo0";
+        ifindex_ = if_nametoindex("lo0");
+
+    } else {
+        std::cout << "Failed to detect loopback interface. Neither "
+                  << "lo nor lo0 worked. Giving up." << std::endl;
+        FAIL();
+
+    }
+}
+
+void
+PktFilterTest::sendMessage() {
+
+    // Packet will be sent over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    IOAddress addr("127.0.0.1");
+
+    struct sockaddr_in addr4;
+    memset(&addr4, 0, sizeof(sockaddr));
+    addr4.sin_family = AF_INET;
+    addr4.sin_port = htons(port_ + 1);
+
+    send_msg_sock_ = socket(AF_INET, SOCK_DGRAM, 0);
+    ASSERT_GE(send_msg_sock_, 0);
+
+    ASSERT_GE(bind(send_msg_sock_, (struct sockaddr *)&addr4,
+                   sizeof(addr4)), 0);
+
+    struct sockaddr_in dest_addr4;
+    memset(&dest_addr4, 0, sizeof(sockaddr));
+    dest_addr4.sin_family = AF_INET;
+    dest_addr4.sin_port = htons(port_);
+    ASSERT_EQ(sendto(send_msg_sock_, test_message_->getBuffer().getData(),
+                     test_message_->getBuffer().getLength(), 0,
+                     reinterpret_cast<struct sockaddr*>(&dest_addr4),
+                     sizeof(sockaddr)), test_message_->getBuffer().getLength());
+    close(send_msg_sock_);
+    send_msg_sock_ = -1;
+
+}
+
+void
+PktFilterTest::testDgramSocket(const int sock) const {
+    // Check that socket has been opened.
+    ASSERT_GE(sock, 0);
+
+    // Verify that the socket belongs to AF_INET family.
+    sockaddr_in sock_address;
+    socklen_t sock_address_len = sizeof(sock_address);
+    ASSERT_EQ(0, getsockname(sock,
+                             reinterpret_cast<sockaddr*>(&sock_address),
+                             &sock_address_len));
+    EXPECT_EQ(AF_INET, sock_address.sin_family);
+
+    // Verify that the socket is bound the appropriate address.
+    const std::string bind_addr(inet_ntoa(sock_address.sin_addr));
+    EXPECT_EQ("127.0.0.1", bind_addr);
+
+    // Verify that the socket is bound to appropriate port.
+    EXPECT_EQ(port_, ntohs(sock_address.sin_port));
+
+    // Verify that the socket has SOCK_DGRAM type.
+    int sock_type;
+    socklen_t sock_type_len = sizeof(sock_type);
+    ASSERT_EQ(0, getsockopt(sock, SOL_SOCKET, SO_TYPE,
+                            &sock_type, &sock_type_len));
+    EXPECT_EQ(SOCK_DGRAM, sock_type);
+}
+
+void
+PktFilterTest::testRcvdMessage(const Pkt4Ptr& rcvd_msg) const {
+    EXPECT_EQ(test_message_->getHops(), rcvd_msg->getHops());
+    EXPECT_EQ(test_message_->getOp(),   rcvd_msg->getOp());
+    EXPECT_EQ(test_message_->getSecs(), rcvd_msg->getSecs());
+    EXPECT_EQ(test_message_->getFlags(), rcvd_msg->getFlags());
+    EXPECT_EQ(test_message_->getCiaddr(), rcvd_msg->getCiaddr());
+    EXPECT_EQ(test_message_->getSiaddr(), rcvd_msg->getSiaddr());
+    EXPECT_EQ(test_message_->getYiaddr(), rcvd_msg->getYiaddr());
+    EXPECT_EQ(test_message_->getGiaddr(), rcvd_msg->getGiaddr());
+    EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid());
+    EXPECT_TRUE(test_message_->getSname() == rcvd_msg->getSname());
+    EXPECT_TRUE(test_message_->getFile() == rcvd_msg->getFile());
+    EXPECT_EQ(test_message_->getHtype(), rcvd_msg->getHtype());
+    EXPECT_EQ(test_message_->getHlen(), rcvd_msg->getHlen());
+}
+
+bool
+PktFilterStub::isDirectResponseSupported() const {
+    return (true);
+}
+
+SocketInfo
+PktFilterStub::openSocket(const Iface&,
+           const isc::asiolink::IOAddress& addr,
+           const uint16_t port, const bool, const bool) {
+    return (SocketInfo(addr, port, 0));
+}
+
+Pkt4Ptr
+PktFilterStub::receive(const Iface&, const SocketInfo&) {
+    return Pkt4Ptr();
+}
+
+int
+PktFilterStub::send(const Iface&, uint16_t, const Pkt4Ptr&) {
+    return (0);
+}
+
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 167 - 0
src/lib/dhcp/tests/pkt_filter_test_utils.h

@@ -0,0 +1,167 @@
+// Copyright (C) 2013  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_UTILS_H
+#define PKT_FILTER_TEST_UTILS_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test fixture class for testing classes derived from PktFilter class.
+///
+/// This class implements a simple algorithm checking presence of the loopback
+/// interface and initializing its index. It assumes that the loopback interface
+/// name is one of 'lo' or 'lo0'. If none of those interfaces is found, the
+/// constructor will report a failure.
+///
+/// @todo The interface detection algorithm should be more generic. This will
+/// be possible once the cross-OS interface detection is implemented.
+class PktFilterTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor
+    ///
+    /// This constructor initializes sock_info_ structure to a default value.
+    /// The socket descriptors should be set to a negative value to indicate
+    /// that no socket has been opened. Specific tests will reinitialize this
+    /// structure with the values of the open sockets. For non-negative socket
+    /// descriptors, the class destructor will close associated sockets.
+    PktFilterTest(const uint16_t port);
+
+    /// @brief Destructor
+    ///
+    /// Closes open sockets (if any).
+    virtual ~PktFilterTest();
+
+    /// @brief Initializes DHCPv4 message used by tests.
+    void initTestMessage();
+
+    /// @brief Detect loopback interface.
+    ///
+    /// @todo this function will be removed once cross-OS interface
+    /// detection is implemented
+    void loInit();
+
+    /// @brief Sends a single DHCPv4 message to the loopback address.
+    ///
+    /// This function opens a datagram socket and binds it to the local loopback
+    /// address and client port. The client's port is assumed to be port_ + 1.
+    /// The send_msg_sock_ member holds the socket descriptor so as the socket
+    /// is closed automatically in the destructor. If the function succeeds to
+    /// send a DHCPv4 message, the socket is closed so as the function can be
+    /// called again within the same test.
+    void sendMessage();
+
+    /// @brief Test that the datagram socket is opened correctly.
+    ///
+    /// This function is used by multiple tests.
+    ///
+    /// @param sock A descriptor of the open socket.
+    void testDgramSocket(const int sock) const;
+
+    /// @brief Checks if the received message matches the test_message_.
+    ///
+    /// @param rcvd_msg An instance of the message to be tested.
+    void testRcvdMessage(const Pkt4Ptr& rcvd_msg) const;
+
+    std::string ifname_;   ///< Loopback interface name
+    uint16_t ifindex_;     ///< Loopback interface index.
+    uint16_t port_;        ///< A port number used for the test.
+    isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info.
+    int send_msg_sock_;    ///< Holds a descriptor of the socket used by
+                           ///< sendMessage function.
+    Pkt4Ptr test_message_; ///< A DHCPv4 message used by tests.
+
+};
+
+/// @brief A stub implementation of the PktFilter class.
+///
+/// This class implements abstract methods of the @c isc::dhcp::test::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 PktFilterStub : public PktFilter {
+public:
+
+    /// @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, const bool);
+
+    /// @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;
+
+};
+
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // PKT_FILTER_TEST_UTILS_H

+ 74 - 0
src/lib/dhcp/tests/pkt_filter_unittest.cc

@@ -0,0 +1,74 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/tests/pkt_filter_test_utils.h>
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+
+class PktFilterBaseClassTest : public isc::dhcp::test::PktFilterTest {
+public:
+    /// @brief Constructor
+    ///
+    /// Does nothing but setting up the UDP port for the test.
+    PktFilterBaseClassTest() : PktFilterTest(PORT) {
+    }
+};
+
+// This test verifies that the fallback socket is successfuly opened and
+// bound using the protected function of the PktFilter class.
+TEST_F(PktFilterBaseClassTest, openFallbackSocket) {
+    // Open socket using the function under test. Note that, we don't have to
+    // close the socket on our own because the test fixture constructor
+    // will handle it.
+    PktFilterStub pkt_filter;
+    ASSERT_NO_THROW(sock_info_.fallbackfd_ =
+                    pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT))
+        << "Failed to open fallback socket.";
+
+    // Test that the socket has been successfully created.
+    testDgramSocket(sock_info_.fallbackfd_);
+
+    // In addition, we should check that the fallback socket is non-blocking.
+    int sock_flags = fcntl(sock_info_.fallbackfd_, F_GETFL);
+    EXPECT_EQ(O_NONBLOCK, sock_flags & O_NONBLOCK)
+        << "Fallback socket is blocking, it should be non-blocking - check"
+        " fallback socket flags (fcntl).";
+
+    // Now that we have the socket open, let's try to open another one. This
+    // should cause a binding error.
+    int another_sock = -1;
+    EXPECT_THROW(another_sock =
+                 pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT),
+                 isc::dhcp::SocketConfigError)
+        << "it should be not allowed to open and bind two fallback sockets"
+        " to the same address and port. Surprisingly, the socket bound.";
+    // Hard to believe we managed to open another socket. But if so, we have
+    // to close it to prevent a resource leak.
+    if (another_sock >= 0) {
+        close(another_sock);
+    }
+}
+
+} // anonymous namespace

+ 1 - 1
tests/tools/perfdhcp/test_control.cc

@@ -47,7 +47,7 @@ namespace perfdhcp {
 bool TestControl::interrupted_ = false;
 bool TestControl::interrupted_ = false;
 
 
 TestControl::TestControlSocket::TestControlSocket(const int socket) :
 TestControl::TestControlSocket::TestControlSocket(const int socket) :
-    SocketInfo(socket, asiolink::IOAddress("127.0.0.1"), 0),
+    SocketInfo(asiolink::IOAddress("127.0.0.1"), 0, socket),
     ifindex_(0), valid_(true) {
     ifindex_(0), valid_(true) {
     try {
     try {
         initSocketData();
         initSocketData();