Browse Source

[3512] Implemented support for unicast sockets selection in IfaceCfg class.

Marcin Siodelski 10 years ago
parent
commit
230e6813fc

+ 20 - 0
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) {

+ 9 - 0
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

+ 13 - 0
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) {

+ 123 - 19
src/lib/dhcpsrv/iface_cfg.cc

@@ -18,6 +18,8 @@
 #include <util/strutil.h>
 #include <boost/bind.hpp>
 
+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<IfaceSet::iterator, bool> 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<IfaceSet::iterator, bool> 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<UnicastMap::iterator, bool> res =
+            unicast_map_.insert(std::pair<std::string, IOAddress>(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

+ 61 - 10
src/lib/dhcpsrv/iface_cfg.h

@@ -15,6 +15,8 @@
 #ifndef IFACE_CFG_H
 #define IFACE_CFG_H
 
+#include <asiolink/io_address.h>
+#include <map>
 #include <set>
 
 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<std::string> 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<std::string, asiolink::IOAddress> 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_;

+ 58 - 5
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