From: Marcin Siodelski Date: Tue, 19 Aug 2014 18:14:32 +0000 (+0200) Subject: [3512] Implemented IfaceCfg::openSockets function. X-Git-Tag: trac3482_base~11^2~2^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bd13ad7b6c0ab1a98afdd84178cf8ee0181e6c97;p=thirdparty%2Fkea.git [3512] Implemented IfaceCfg::openSockets function. Also, added better documentation for the IfaceCfg class. --- diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes index b3847dbd96..2d72a3c92c 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.mes +++ b/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. diff --git a/src/lib/dhcpsrv/iface_cfg.cc b/src/lib/dhcpsrv/iface_cfg.cc index 42273d0537..6c03071d19 100644 --- a/src/lib/dhcpsrv/iface_cfg.cc +++ b/src/lib/dhcpsrv/iface_cfg.cc @@ -13,8 +13,10 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include #include #include +#include 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,28 +63,54 @@ 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 @@ -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"); diff --git a/src/lib/dhcpsrv/iface_cfg.h b/src/lib/dhcpsrv/iface_cfg.h index 006d4ac4da..82944a19a9 100644 --- a/src/lib/dhcpsrv/iface_cfg.h +++ b/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 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_; }; diff --git a/src/lib/dhcpsrv/tests/iface_cfg_unittest.cc b/src/lib/dhcpsrv/tests/iface_cfg_unittest.cc index db7c004051..f007e6cae2 100644 --- a/src/lib/dhcpsrv/tests/iface_cfg_unittest.cc +++ b/src/lib/dhcpsrv/tests/iface_cfg_unittest.cc @@ -13,6 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include #include #include #include @@ -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