Browse Source

[3512] Implemented IfaceCfg::openSockets function.

Also, added better documentation for the IfaceCfg class.
Marcin Siodelski 10 years ago
parent
commit
bd13ad7b6c

+ 9 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -393,12 +393,21 @@ lease from the MySQL database for the specified address.
 A debug message issued when the server is attempting to update IPv6
 A debug message issued when the server is attempting to update IPv6
 lease from the MySQL database for the specified address.
 lease from the MySQL database for the specified address.
 
 
+% DHCPSRV_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
+This warning message is issued when current server configuration specifies
+no interfaces that server should listen on, or specified interfaces are not
+configured to receive the traffic.
+
 % DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
 % DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
 This is an error message, logged when an attempt has been made to access
 This is an error message, logged when an attempt has been made to access
 a database backend, but where no 'type' keyword has been included in
 a database backend, but where no 'type' keyword has been included in
 the access string.  The access string (less any passwords) is included
 the access string.  The access string (less any passwords) is included
 in the message.
 in the message.
 
 
+% DHCPSRV_OPEN_SOCKET_FAIL failed to open 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.
+
 % DHCPSRV_PGSQL_ADD_ADDR4 adding IPv4 lease with address %1
 % DHCPSRV_PGSQL_ADD_ADDR4 adding IPv4 lease with address %1
 A debug message issued when the server is about to add an IPv4 lease
 A debug message issued when the server is about to add an IPv4 lease
 with the specified address to the PostgreSQL backend database.
 with the specified address to the PostgreSQL backend database.

+ 47 - 10
src/lib/dhcpsrv/iface_cfg.cc

@@ -13,8 +13,10 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/iface_cfg.h>
 #include <dhcpsrv/iface_cfg.h>
 #include <util/strutil.h>
 #include <util/strutil.h>
+#include <boost/bind.hpp>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -32,12 +34,22 @@ IfaceCfg::closeSockets() {
 }
 }
 
 
 void
 void
-IfaceCfg::openSockets(const uint16_t /* port */) {
+IfaceCfg::openSockets(const uint16_t port, const bool use_bcast) {
+    // If wildcard interface '*' was not specified, set all interfaces to
+    // inactive state. We will later enable them selectively using the
+    // interface names specified by the user. If wildcard interface was
+    // specified, mark all interfaces active.
+    setState(!wildcard_used_);
+    // If there is no wildcard interface specified, we will have to iterate
+    // over the names specified by the caller and enable them.
     if (!wildcard_used_) {
     if (!wildcard_used_) {
-        setState(true);
         for (IfaceSet::const_iterator iface_name = iface_set_.begin();
         for (IfaceSet::const_iterator iface_name = iface_set_.begin();
              iface_name != iface_set_.end(); ++iface_name) {
              iface_name != iface_set_.end(); ++iface_name) {
             Iface* iface = IfaceMgr::instance().getIface(*iface_name);
             Iface* iface = IfaceMgr::instance().getIface(*iface_name);
+            // This shouldn't really happen because we are checking the
+            // names of interfaces when they are being added (use()
+            // function). But, if someone has triggered detection of
+            // interfaces since then, some interfaces may have disappeared.
             if (iface == NULL) {
             if (iface == NULL) {
                 isc_throw(Unexpected,
                 isc_throw(Unexpected,
                           "fail to open socket on interface '"
                           "fail to open socket on interface '"
@@ -51,29 +63,55 @@ IfaceCfg::openSockets(const uint16_t /* port */) {
                 iface->inactive6_ = false;
                 iface->inactive6_ = false;
             }
             }
         }
         }
+    }
 
 
+    // Set the callback which is called when the socket fails to open
+    // for some specific interface. This callback will simply log a
+    // warning message.
+    IfaceMgrErrorMsgCallback error_callback =
+        boost::bind(&IfaceCfg::socketOpenErrorHandler, this, _1);
+    bool sopen;
+    if (getFamily() == V4) {
+        sopen = IfaceMgr::instance().openSockets4(port, use_bcast,
+                                                  error_callback);
     } else {
     } else {
-        setState(false);
+        // use_bcast is ignored for V6.
+        sopen = IfaceMgr::instance().openSockets6(port, error_callback);
     }
     }
 
 
-    // @todo open sockets here.
+    // If no socket were opened, log a warning because the server will
+    // not respond to any queries.
+    if (!sopen) {
+        LOG_WARN(dhcpsrv_logger, DHCPSRV_NO_SOCKETS_OPEN);
+    }
+}
 
 
+void
+IfaceCfg::reset() {
+    wildcard_used_ = false;
+    iface_set_.clear();
 }
 }
 
 
 void
 void
 IfaceCfg::setState(const bool inactive) {
 IfaceCfg::setState(const bool inactive) {
-    const IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
-    for (IfaceCollection::iterator iface = ifaces.begin();
+    IfaceMgr::IfaceCollection ifaces = IfaceMgr::instance().getIfaces();
+    for (IfaceMgr::IfaceCollection::iterator iface = ifaces.begin();
          iface != ifaces.end(); ++iface) {
          iface != ifaces.end(); ++iface) {
+        Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
         if (getFamily() == V4) {
         if (getFamily() == V4) {
-            (*iface)->inactive4_ = inactive;
+            iface_ptr->inactive4_ = inactive;
         } else {
         } else {
-            (*iface)->inactive6_ = inactive;
+            iface_ptr->inactive6_ = inactive;
         }
         }
     }
     }
 }
 }
 
 
 void
 void
+IfaceCfg::socketOpenErrorHandler(const std::string& errmsg) {
+    LOG_WARN(dhcpsrv_logger, DHCPSRV_OPEN_SOCKET_FAIL).arg(errmsg);
+}
+
+void
 IfaceCfg::use(const std::string& iface_name) {
 IfaceCfg::use(const std::string& iface_name) {
     // In theory the configuration parser should strip extraneous spaces but
     // In theory the configuration parser should strip extraneous spaces but
     // since this is a common library it may be better to make sure that it
     // since this is a common library it may be better to make sure that it
@@ -82,9 +120,8 @@ IfaceCfg::use(const std::string& iface_name) {
     if (name.empty()) {
     if (name.empty()) {
         isc_throw(InvalidIfaceName,
         isc_throw(InvalidIfaceName,
                   "empty interface name used in configuration");
                   "empty interface name used in configuration");
-    }
 
 
-    if (name != ALL_IFACES_KEYWORD) {
+    } else if (name != ALL_IFACES_KEYWORD) {
         if (IfaceMgr::instance().getIface(name) == NULL) {
         if (IfaceMgr::instance().getIface(name) == NULL) {
             isc_throw(NoSuchIface, "interface '" << name
             isc_throw(NoSuchIface, "interface '" << name
                       << "' doesn't exist in the system");
                       << "' doesn't exist in the system");

+ 69 - 2
src/lib/dhcpsrv/iface_cfg.h

@@ -41,33 +41,100 @@ public:
         isc::Exception(file, line, what) { };
         isc::Exception(file, line, what) { };
 };
 };
 
 
+
+/// @brief Represents selection of interfaces for DHCP server.
+///
+/// This class manages selection of interfaces on which the DHCP server is
+/// listening to queries. The interfaces are selected in the server
+/// configuration by their names. This class performs sanity checks on the
+/// interface names specified in the configuration and reports errors in the
+/// following conditions:
+/// - user specifies the same interface more than once,
+/// - user specifies the interface which doesn't exist,
+/// - user specifies an empty interface.
+///
+/// This class also accepts "wildcard" interface name which, if specified,
+/// instructs the server to listen on all available interfaces.
+///
+/// Once interfaces have been specified the sockets (either IPv4 or IPv6)
+/// can be opened by calling @c IfaceCfg::openSockets function.
 class IfaceCfg {
 class IfaceCfg {
 public:
 public:
+    /// @brief Keyword used to enable all interfaces.
+    ///
+    /// This keyword can be used instead of the interface name to specify
+    /// that DHCP server should listen on all interfaces.
     static const char* ALL_IFACES_KEYWORD;
     static const char* ALL_IFACES_KEYWORD;
 
 
+    /// @brief Protocol family: IPv4 or IPv6.
+    ///
+    /// Depending on the family specified, the IPv4 or IPv6 sockets are
+    /// opened.
     enum Family {
     enum Family {
         V4, V6
         V4, V6
     };
     };
 
 
+    /// @brief Constructor.
+    ///
+    /// @param family Protocol family.
     IfaceCfg(Family family);
     IfaceCfg(Family family);
 
 
+    /// @brief Convenience function which closes all open sockets.
     void closeSockets();
     void closeSockets();
 
 
+    /// @brief Returns protocol family used by the @c IfaceCfg.
     Family getFamily() const {
     Family getFamily() const {
         return (family_);
         return (family_);
     }
     }
 
 
-    void openSockets(const uint16_t port);
-
+    /// @brief Tries to open sockets on selected interfaces.
+    void openSockets(const uint16_t port, const bool use_bcast = true);
+
+    /// @brief Puts the interface configuration into default state.
+    ///
+    /// This function removes interface names from the set.
+    void reset();
+
+    /// @brief Select interface to be used to receive DHCP traffic.
+    ///
+    /// @param iface_name Explicit interface name or a wildcard name (*) of
+    /// the interface(s) to be used to receive DHCP traffic.
+    ///
+    /// @throw InvalidIfaceName If the interface name is incorrect, e.g. empty.
+    /// @throw NoSuchIface If the specified interface is not present.
+    /// @throw DuplicateIfaceName If the interface is already selected, i.e.
+    /// @c IfaceCfg::use has been already called for this interface.
     void use(const std::string& iface_name);
     void use(const std::string& iface_name);
 
 
 private:
 private:
 
 
+    /// @brief Selects or deselects all interfaces.
+    ///
+    /// This function selects all interfaces to receive DHCP traffic or
+    /// deselects all interfaces so as none of them receives a DHCP traffic.
+    ///
+    /// @param inactive A boolean value which indicates if all interfaces
+    /// should be deselected/inactive (true) or selected/active (false).
     void setState(const bool inactive);
     void setState(const bool inactive);
 
 
+    /// @brief Error handler for executed when opening a socket fail.
+    ///
+    /// A pointer to this function is passed to the @c IfaceMgr::openSockets4
+    /// or @c IfaceMgr::openSockets6. These functions call this handler when
+    /// they fail to open a socket. The handler logs an error passed in the
+    /// parameter.
+    ///
+    /// @param errmsg Error message being logged by the function.
+    void socketOpenErrorHandler(const std::string& errmsg);
+
+    /// @brief Protocol family.
     Family family_;
     Family family_;
+    /// @brief Represents a set of interface names.
     typedef std::set<std::string> IfaceSet;
     typedef std::set<std::string> IfaceSet;
+    /// @brief A set of interface names specified by the user.
     IfaceSet iface_set_;
     IfaceSet iface_set_;
+    /// @brief A booolean value which indicates that the wildcard interface name
+    /// has been specified (*).
     bool wildcard_used_;
     bool wildcard_used_;
 };
 };
 
 

+ 102 - 2
src/lib/dhcpsrv/tests/iface_cfg_unittest.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <config.h>
 #include <config.h>
+#include <dhcp/dhcp4.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/iface_cfg.h>
 #include <dhcpsrv/iface_cfg.h>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
@@ -35,15 +36,114 @@ public:
         iface_mgr_test_config_(true) {
         iface_mgr_test_config_(true) {
     }
     }
 
 
+    /// @brief Checks if socket of the specified family is opened on interface.
+    ///
+    /// @param iface_name Interface name.
+    /// @param family One of: AF_INET or AF_INET6
+    bool socketOpen(const std::string& iface_name, const int family) const;
+
     /// @brief Holds a fake configuration of the interfaces.
     /// @brief Holds a fake configuration of the interfaces.
     IfaceMgrTestConfig iface_mgr_test_config_;
     IfaceMgrTestConfig iface_mgr_test_config_;
 
 
 };
 };
 
 
-TEST_F(IfaceCfgTest, explicitNames) {
-    IfaceCfg cfg;
+bool
+IfaceCfgTest::socketOpen(const std::string& iface_name,
+                         const int family) const {
+    Iface* iface = IfaceMgr::instance().getIface(iface_name);
+    if (iface == NULL) {
+        ADD_FAILURE() << "No such interface '" << iface_name << "'";
+        return (false);
+    }
+
+    const Iface::SocketCollection& sockets = iface->getSockets();
+    for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+         sock != sockets.end(); ++sock) {
+        if (sock->family_ == family) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+// This test checks that the interface names can be explicitly selected
+// by their names and IPv4 sockets are opened on these interfaces.
+TEST_F(IfaceCfgTest, explicitNamesV4) {
+    IfaceCfg cfg(IfaceCfg::V4);
+    // Specify valid interface names. There should be no error.
     ASSERT_NO_THROW(cfg.use("eth0"));
     ASSERT_NO_THROW(cfg.use("eth0"));
     ASSERT_NO_THROW(cfg.use("eth1"));
     ASSERT_NO_THROW(cfg.use("eth1"));
+
+    // Open sockets on specified interfaces.
+    cfg.openSockets(DHCP4_SERVER_PORT);
+
+    // Sockets should be now open on eth0 and eth1, but not on loopback.
+    EXPECT_TRUE(socketOpen("eth0", AF_INET));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET));
+    EXPECT_FALSE(socketOpen("lo", AF_INET));
+
+    // No IPv6 sockets should be present because we wanted IPv4 sockets.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET6));
+    EXPECT_FALSE(socketOpen("eth1", AF_INET6));
+    EXPECT_FALSE(socketOpen("lo", AF_INET6));
+
+    // Close all sockets and make sure they are really closed.
+    cfg.closeSockets();
+    ASSERT_FALSE(socketOpen("eth0", AF_INET));
+    ASSERT_FALSE(socketOpen("eth1", AF_INET));
+    ASSERT_FALSE(socketOpen("lo", AF_INET));
+
+    // Reset configuration and select only one interface this time.
+    cfg.reset();
+    ASSERT_NO_THROW(cfg.use("eth1"));
+    
+    cfg.openSockets(DHCP4_SERVER_PORT);
+
+    // Socket should be open on eth1 only.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET));
+    EXPECT_FALSE(socketOpen("lo", AF_INET));
+    
+}
+
+// This test checks that the interface names can be explicitly selected
+// by their names and IPv6 sockets are opened on these interfaces.
+TEST_F(IfaceCfgTest, explicitNamesV6) {
+    IfaceCfg cfg(IfaceCfg::V6);
+    // Specify valid interface names. There should be no error.
+    ASSERT_NO_THROW(cfg.use("eth0"));
+    ASSERT_NO_THROW(cfg.use("eth1"));
+
+    // Open sockets on specified interfaces.
+    cfg.openSockets(DHCP6_SERVER_PORT);
+
+    // Sockets should be now open on eth0 and eth1, but not on loopback.
+    EXPECT_TRUE(socketOpen("eth0", AF_INET6));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET6));
+    EXPECT_FALSE(socketOpen("lo", AF_INET6));
+
+    // No IPv4 sockets should be present because we wanted IPv4 sockets.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET));
+    EXPECT_FALSE(socketOpen("eth1", AF_INET));
+    EXPECT_FALSE(socketOpen("lo", AF_INET));
+
+    // Close all sockets and make sure they are really closed.
+    cfg.closeSockets();
+    ASSERT_FALSE(socketOpen("eth0", AF_INET6));
+    ASSERT_FALSE(socketOpen("eth1", AF_INET6));
+    ASSERT_FALSE(socketOpen("lo", AF_INET6));
+
+    // Reset configuration and select only one interface this time.
+    cfg.reset();
+    ASSERT_NO_THROW(cfg.use("eth1"));
+    
+    cfg.openSockets(DHCP6_SERVER_PORT);
+
+    // Socket should be open on eth1 only.
+    EXPECT_FALSE(socketOpen("eth0", AF_INET6));
+    EXPECT_TRUE(socketOpen("eth1", AF_INET6));
+    EXPECT_FALSE(socketOpen("lo", AF_INET6));
+    
 }
 }
 
 
 } // end of anonymous namespace
 } // end of anonymous namespace