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
 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
 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
 the access string.  The access string (less any passwords) is included
 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
 A debug message issued when the server is about to add an IPv4 lease
 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.
 
 #include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/iface_cfg.h>
 #include <util/strutil.h>
+#include <boost/bind.hpp>
 
 namespace isc {
 namespace dhcp {
@@ -32,12 +34,22 @@ IfaceCfg::closeSockets() {
 }
 
 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_) {
-        setState(true);
         for (IfaceSet::const_iterator iface_name = iface_set_.begin();
              iface_name != iface_set_.end(); ++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) {
                 isc_throw(Unexpected,
                           "fail to open socket on interface '"
@@ -51,29 +63,55 @@ IfaceCfg::openSockets(const uint16_t /* port */) {
                 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 {
-        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
 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* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
         if (getFamily() == V4) {
-            (*iface)->inactive4_ = inactive;
+            iface_ptr->inactive4_ = inactive;
         } else {
-            (*iface)->inactive6_ = inactive;
+            iface_ptr->inactive6_ = inactive;
         }
     }
 }
 
 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) {
     // 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
@@ -82,9 +120,8 @@ IfaceCfg::use(const std::string& iface_name) {
     if (name.empty()) {
         isc_throw(InvalidIfaceName,
                   "empty interface name used in configuration");
-    }
 
-    if (name != ALL_IFACES_KEYWORD) {
+    } else if (name != ALL_IFACES_KEYWORD) {
         if (IfaceMgr::instance().getIface(name) == NULL) {
             isc_throw(NoSuchIface, "interface '" << name
                       << "' doesn't exist in the system");

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

@@ -41,33 +41,100 @@ public:
         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 {
 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;
 
+    /// @brief Protocol family: IPv4 or IPv6.
+    ///
+    /// Depending on the family specified, the IPv4 or IPv6 sockets are
+    /// opened.
     enum Family {
         V4, V6
     };
 
+    /// @brief Constructor.
+    ///
+    /// @param family Protocol family.
     IfaceCfg(Family family);
 
+    /// @brief Convenience function which closes all open sockets.
     void closeSockets();
 
+    /// @brief Returns protocol family used by the @c IfaceCfg.
     Family getFamily() const {
         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);
 
 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);
 
+    /// @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_;
+    /// @brief Represents a set of interface names.
     typedef std::set<std::string> IfaceSet;
+    /// @brief A set of interface names specified by the user.
     IfaceSet iface_set_;
+    /// @brief A booolean value which indicates that the wildcard interface name
+    /// has been specified (*).
     bool wildcard_used_;
 };
 

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

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
+#include <dhcp/dhcp4.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/iface_cfg.h>
 #include <gtest/gtest.h>
@@ -35,15 +36,114 @@ public:
         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.
     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("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