Also, added better documentation for the IfaceCfg class.
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.
// 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 {
}
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 '"
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
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");
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_;
};
// 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>
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