From: Marcin Siodelski Date: Wed, 20 Aug 2014 10:13:22 +0000 (+0200) Subject: [3512] Implemented support for unicast sockets selection in IfaceCfg class. X-Git-Tag: trac3482_base~11^2~2^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=230e6813fc588f293936588731cff3de718cc998;p=thirdparty%2Fkea.git [3512] Implemented support for unicast sockets selection in IfaceCfg class. --- diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc index 3486201050..381b0f3546 100644 --- a/src/lib/dhcp/iface_mgr.cc +++ b/src/lib/dhcp/iface_mgr.cc @@ -242,6 +242,18 @@ Iface::getAddress4(isc::asiolink::IOAddress& address) const { return (false); } +bool +Iface::hasAddress(const isc::asiolink::IOAddress& address) const { + const AddressCollection& addrs = getAddresses(); + for (AddressCollection::const_iterator addr = addrs.begin(); + addr != addrs.end(); ++addr) { + if (address == *addr) { + return (true); + } + } + return (false); +} + void IfaceMgr::closeSockets() { for (IfaceCollection::iterator iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) { @@ -682,6 +694,14 @@ IfaceMgr::clearIfaces() { ifaces_.clear(); } +void +IfaceMgr::clearUnicasts() { + for (IfaceCollection::iterator iface=ifaces_.begin(); + iface!=ifaces_.end(); ++iface) { + iface->clearUnicasts(); + } +} + int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr, const uint16_t port, const bool receive_bcast, const bool send_bcast) { diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h index 22a6771d6d..8917920e54 100644 --- a/src/lib/dhcp/iface_mgr.h +++ b/src/lib/dhcp/iface_mgr.h @@ -279,6 +279,12 @@ public: /// for the interface (if true), or not (false). bool getAddress4(isc::asiolink::IOAddress& address) const; + /// @brief Check if the interface has the specified address assigned. + /// + /// @param address Address to be checked. + /// @return true if address is assigned to the intefrace, false otherwise. + bool hasAddress(const isc::asiolink::IOAddress& address) const; + /// @brief Adds an address to an interface. /// /// This only adds an address to collection, it does not physically @@ -549,6 +555,9 @@ public: /// IPv6 address is read from interfaces.txt file. void detectIfaces(); + /// @brief Clears unicast addresses on all interfaces. + void clearUnicasts(); + /// @brief Return most suitable socket for transmitting specified IPv6 packet. /// /// This method takes Pkt6 (see overloaded implementation that takes diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc index 45148b5802..06c74c6835 100644 --- a/src/lib/dhcp/tests/iface_mgr_unittest.cc +++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc @@ -606,6 +606,19 @@ TEST_F(IfaceMgrTest, ifaceGetAddress) { } +// This test checks if it is possible to check that the specific address is +// assigned to the interface. +TEST_F(IfaceMgrTest, ifaceHasAddress) { + IfaceMgrTestConfig config(true); + + Iface* iface = IfaceMgr::instance().getIface("eth0"); + ASSERT_FALSE(iface == NULL); + EXPECT_TRUE(iface->hasAddress(IOAddress("10.0.0.1"))); + EXPECT_TRUE(iface->hasAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"))); + EXPECT_TRUE(iface->hasAddress(IOAddress("2001:db8:1::1"))); + EXPECT_FALSE(iface->hasAddress(IOAddress("2001:db8:1::2"))); +} + // TODO: Implement getPlainMac() test as soon as interface detection // is implemented. TEST_F(IfaceMgrTest, getIface) { diff --git a/src/lib/dhcpsrv/iface_cfg.cc b/src/lib/dhcpsrv/iface_cfg.cc index 6c03071d19..f9c7d1729e 100644 --- a/src/lib/dhcpsrv/iface_cfg.cc +++ b/src/lib/dhcpsrv/iface_cfg.cc @@ -18,6 +18,8 @@ #include #include +using namespace isc::asiolink; + namespace isc { namespace dhcp { @@ -40,6 +42,8 @@ IfaceCfg::openSockets(const uint16_t port, const bool use_bcast) { // interface names specified by the user. If wildcard interface was // specified, mark all interfaces active. setState(!wildcard_used_); + // Remove selection of unicast addresses from all interfaces. + IfaceMgr::instance().clearUnicasts(); // 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_) { @@ -65,6 +69,21 @@ IfaceCfg::openSockets(const uint16_t port, const bool use_bcast) { } } + // Select unicast sockets. It works only for V6. Ignore for V4. + if (getFamily() == V6) { + for (UnicastMap::const_iterator unicast = unicast_map_.begin(); + unicast != unicast_map_.end(); ++unicast) { + Iface* iface = IfaceMgr::instance().getIface(unicast->first); + if (iface == NULL) { + isc_throw(Unexpected, + "fail to open unicast socket on interface '" + << unicast->first << "' as this interface doesn't" + " exist"); + } + iface->addUnicast(unicast->second); + } + } + // Set the callback which is called when the socket fails to open // for some specific interface. This callback will simply log a // warning message. @@ -113,34 +132,119 @@ IfaceCfg::socketOpenErrorHandler(const std::string& 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 - // is really the case. - std::string name = util::str::trim(iface_name); - if (name.empty()) { - isc_throw(InvalidIfaceName, - "empty interface name used in configuration"); - - } else if (name != ALL_IFACES_KEYWORD) { - if (IfaceMgr::instance().getIface(name) == NULL) { + // The interface name specified may have two formats, e.g.: + // - eth0 + // - eth0/2001:db8:1::1. + // The latter format is used to open unicast socket on the specified + // interface. Here we are detecting which format was used and we strip + // all extraneous spaces. + size_t pos = iface_name.find("/"); + std::string name; + std::string addr_str; + // There is no unicast address so the whole string is an interface name. + if (pos == std::string::npos) { + name = util::str::trim(iface_name); + if (name.empty()) { + isc_throw(InvalidIfaceName, + "empty interface name used in configuration"); + + } if (name != ALL_IFACES_KEYWORD) { + if (IfaceMgr::instance().getIface(name) == NULL) { + isc_throw(NoSuchIface, "interface '" << name + << "' doesn't exist in the system"); + } + + std::pair res = iface_set_.insert(name); + if (!res.second) { + isc_throw(DuplicateIfaceName, "interface '" << name + << "' has already been specified"); + } + + } else if (wildcard_used_) { + isc_throw(DuplicateIfaceName, "the wildcard interface '" + << ALL_IFACES_KEYWORD << "' can only be specified once"); + + } else { + wildcard_used_ = true; + + } + + } else if (getFamily() == V4) { + isc_throw(InvalidIfaceName, "unicast addresses in the format of: " + "iface-name/unicast-addr_stress can only be specified for" + " IPv6 addr_stress family"); + + } else { + // The interface name includes the unicast addr_stress, so we split + // interface name and the unicast addr_stress to two variables. + name = util::str::trim(iface_name.substr(0, pos)); + addr_str = util::str::trim(iface_name.substr(pos + 1)); + + // Interface name must not be empty. + if (name.empty()) { + isc_throw(InvalidIfaceName, + "empty interface name specified in the" + " interface configuration"); + + } + // Unicast addr_stress following the interface name must not be empty. + if (addr_str.empty()) { + isc_throw(InvalidIfaceName, + "empty unicast addr_stress specified in the interface" + << " configuration"); + + } + + // Interface name must not be the wildcard name. + if (name == ALL_IFACES_KEYWORD) { + isc_throw(InvalidIfaceName, + "wildcard interface name '" << ALL_IFACES_KEYWORD + << "' must not be used in conjunction with a" + " unicast addr_stress"); + + } + + // Interface must exist. + Iface* iface = IfaceMgr::instance().getIface(name); + if (iface == NULL) { isc_throw(NoSuchIface, "interface '" << name << "' doesn't exist in the system"); + } - std::pair res = iface_set_.insert(name); - if (!res.second) { - isc_throw(DuplicateIfaceName, "interface '" << name - << "' has already been specified"); + // Convert address string. This may throw an exception if the address + // is invalid. + IOAddress addr(addr_str); + + // Check that the address is a valid unicast address. + if (!addr.isV6() || addr.isV6LinkLocal() || addr.isV6Multicast()) { + isc_throw(InvalidIfaceName, "address '" << addr << "' is not" + " a valid IPv6 unicast address"); } - } else if (wildcard_used_) { - isc_throw(DuplicateIfaceName, "the wildcard interface '" - << ALL_IFACES_KEYWORD << "' can only be specified once"); + // Interface must have this address assigned. + if (!iface->hasAddress(addr)) { + isc_throw(NoSuchAddress, + "interface '" << name << "' doesn't have address '" + << addr << "' assigned"); + } - } else { - wildcard_used_ = true; + // Insert address and the interface to the collection of unicast + // addresses. + std::pair res = + unicast_map_.insert(std::pair(name, addr)); + + // If some other unicast address has been added for the interface + // return an error. The new address didn't override the existing one. + if (!res.second) { + isc_throw(DuplicateIfaceName, "must not specify unicast address '" + << addr << "' for interface '" << name << "' " + "because other unicast address has already been" + " specified for this interface"); + } } + } } // end of isc::dhcp namespace diff --git a/src/lib/dhcpsrv/iface_cfg.h b/src/lib/dhcpsrv/iface_cfg.h index 82944a19a9..e715450b80 100644 --- a/src/lib/dhcpsrv/iface_cfg.h +++ b/src/lib/dhcpsrv/iface_cfg.h @@ -15,6 +15,8 @@ #ifndef IFACE_CFG_H #define IFACE_CFG_H +#include +#include #include namespace isc { @@ -41,17 +43,21 @@ public: isc::Exception(file, line, what) { }; }; +/// @brief Exception thrown when specified unicast address is not assigned +/// to the interface specified. +class NoSuchAddress : public Exception { +public: + NoSuchAddress(const char* file, size_t line, const char* 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. +/// configuration by their names or by the pairs of interface names and unicast +/// addresses (e.g. eth0/2001:db8:1::1). The latter format is only accepted when +/// IPv6 configuration is in use. /// /// This class also accepts "wildcard" interface name which, if specified, /// instructs the server to listen on all available interfaces. @@ -76,8 +82,8 @@ public: /// @brief Constructor. /// - /// @param family Protocol family. - IfaceCfg(Family family); + /// @param family Protocol family (default is V4). + IfaceCfg(Family family = V4); /// @brief Convenience function which closes all open sockets. void closeSockets(); @@ -88,6 +94,16 @@ public: } /// @brief Tries to open sockets on selected interfaces. + /// + /// This function opens sockets bound to link-local address as well as + /// sockets bound to unicast address. See @c IfaceCfg::use function + /// documentation for details how to specify interfaces and unicast + /// addresses to bind the sockets to. + /// + /// @param port Port number to be used to bind sockets to. + /// @param use_bcast A boolean flag which indicates if the broadcast + /// traffic should be received through the socket. This parameter is + /// ignored for IPv6. void openSockets(const uint16_t port, const bool use_bcast = true); /// @brief Puts the interface configuration into default state. @@ -95,14 +111,42 @@ public: /// This function removes interface names from the set. void reset(); + /// @brief Sets protocol family. + /// + /// @param family New family value (V4 or V6). + void setFamily(Family family) { + family_ = family; + } + /// @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. + /// This function controls the selection of the interface on which the + /// DHCP queries should be received by the server. The interface name + /// passed as the argument of this function may appear in one of the following + /// formats: + /// - interface-name, e.g. eth0 + /// - interface-name/unicast-address, e.g. eth0/2001:db8:1::1 (V6 only) + /// + /// Extraneous spaces surrounding the interface name and/or unicast address + /// are accepted. For example: eth0 / 2001:db8:1::1 will be accepted. + /// + /// When only interface name is specified (without an address) it is allowed + /// to use the "wildcard" interface name (*) which indicates that the server + /// should open sockets on all interfaces. When IPv6 is in use, the sockets + /// will be bound to the link local addresses. Wildcard interface names are + /// not allowed when specifying a unicast address. For example: + /// */2001:db8:1::1 is not allowed. + /// + /// @param iface_name Explicit interface name, a wildcard name (*) of + /// the interface(s) or the pair of iterface/unicast-address 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 NoSuchAddress If the specified unicast address is not assigned + /// to the interface. /// @throw DuplicateIfaceName If the interface is already selected, i.e. + /// @throw IOError when specified unicast address is invalid. /// @c IfaceCfg::use has been already called for this interface. void use(const std::string& iface_name); @@ -133,6 +177,13 @@ private: typedef std::set IfaceSet; /// @brief A set of interface names specified by the user. IfaceSet iface_set_; + /// @brief A map of interfaces and unicast addresses. + typedef std::map UnicastMap; + /// @brief A map which holds the pairs of interface names and unicast + /// addresses for which the unicast sockets should be opened. + /// + /// This is only used for V6 family. + UnicastMap unicast_map_; /// @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 cfa5058c2c..a19f49447b 100644 --- a/src/lib/dhcpsrv/tests/iface_cfg_unittest.cc +++ b/src/lib/dhcpsrv/tests/iface_cfg_unittest.cc @@ -42,6 +42,11 @@ public: /// @param family One of: AF_INET or AF_INET6 bool socketOpen(const std::string& iface_name, const int family) const; + /// @brief Checks if unicast socket is opened on interface. + /// + /// @param iface_name Interface name. + bool unicastOpen(const std::string& iface_name) const; + /// @brief Holds a fake configuration of the interfaces. IfaceMgrTestConfig iface_mgr_test_config_; @@ -66,6 +71,25 @@ IfaceCfgTest::socketOpen(const std::string& iface_name, return (false); } +bool +IfaceCfgTest::unicastOpen(const std::string& iface_name) 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->addr_.isV6LinkLocal()) && + (!sock->addr_.isV6Multicast())) { + 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) { @@ -184,18 +208,47 @@ TEST_F(IfaceCfgTest, wildcardV6) { EXPECT_FALSE(socketOpen("lo", AF_INET)); } +// Test that unicast address can be specified for the socket to be opened on +// the interface on which the socket bound to link local address is also +// opened. +TEST_F(IfaceCfgTest, validUnicast) { + IfaceCfg cfg(IfaceCfg::V6); + + // One socket will be opened on link-local address, one on unicast but + // on the same interface. + ASSERT_NO_THROW(cfg.use("eth0")); + ASSERT_NO_THROW(cfg.use("eth0/2001:db8:1::1")); + + cfg.openSockets(DHCP6_SERVER_PORT); + + EXPECT_TRUE(socketOpen("eth0", AF_INET6)); + EXPECT_TRUE(unicastOpen("eth0")); +} + // Test that when invalid interface names are specified an exception is thrown. TEST_F(IfaceCfgTest, invalidValues) { IfaceCfg cfg(IfaceCfg::V4); - EXPECT_THROW(cfg.use(""), InvalidIfaceName); - EXPECT_THROW(cfg.use(" "), InvalidIfaceName); - EXPECT_THROW(cfg.use("bogus"), NoSuchIface); + ASSERT_THROW(cfg.use(""), InvalidIfaceName); + ASSERT_THROW(cfg.use(" "), InvalidIfaceName); + ASSERT_THROW(cfg.use("bogus"), NoSuchIface); ASSERT_NO_THROW(cfg.use("eth0")); - EXPECT_THROW(cfg.use("eth0"), DuplicateIfaceName); + ASSERT_THROW(cfg.use("eth0"), DuplicateIfaceName); + + ASSERT_THROW(cfg.use("eth0/2001:db8:1::1"), InvalidIfaceName); + + cfg.setFamily(IfaceCfg::V6); + + ASSERT_THROW(cfg.use("eth0/"), InvalidIfaceName); + ASSERT_THROW(cfg.use("/2001:db8:1::1"), InvalidIfaceName); + ASSERT_THROW(cfg.use("*/2001:db8:1::1"), InvalidIfaceName); + ASSERT_THROW(cfg.use("bogus/2001:db8:1::1"), NoSuchIface); + ASSERT_THROW(cfg.use("eth0/fe80::3a60:77ff:fed5:cdef"), InvalidIfaceName); + ASSERT_THROW(cfg.use("eth0/fe80::3a60:77ff:fed5:cdef"), InvalidIfaceName); + ASSERT_THROW(cfg.use("eth0/2001:db8:1::2"), NoSuchAddress); ASSERT_NO_THROW(cfg.use(IfaceCfg::ALL_IFACES_KEYWORD)); - EXPECT_THROW(cfg.use(IfaceCfg::ALL_IFACES_KEYWORD), DuplicateIfaceName); + ASSERT_THROW(cfg.use(IfaceCfg::ALL_IFACES_KEYWORD), DuplicateIfaceName); } } // end of anonymous namespace