Browse Source

[3539] Iface configuration allows for specifying IPv4 address to listen on.

Marcin Siodelski 10 years ago
parent
commit
3fb6d907f6

+ 40 - 13
src/lib/dhcp/iface_mgr.cc

@@ -40,6 +40,7 @@
 
 using namespace std;
 using namespace isc::asiolink;
+using namespace isc::util;
 using namespace isc::util::io::internal;
 
 namespace isc {
@@ -149,7 +150,7 @@ void Iface::setMac(const uint8_t* mac, size_t len) {
 bool Iface::delAddress(const isc::asiolink::IOAddress& addr) {
     for (AddressCollection::iterator a = addrs_.begin();
          a!=addrs_.end(); ++a) {
-        if (*a==addr) {
+        if (a->get() == addr) {
             addrs_.erase(a);
             return (true);
         }
@@ -216,12 +217,12 @@ IfaceMgr::IfaceMgr()
 void Iface::addUnicast(const isc::asiolink::IOAddress& addr) {
     for (Iface::AddressCollection::const_iterator i = unicasts_.begin();
          i != unicasts_.end(); ++i) {
-        if (*i == addr) {
+        if (i->get() == addr) {
             isc_throw(BadValue, "Address " << addr
                       << " already defined on the " << name_ << " interface.");
         }
     }
-    unicasts_.push_back(addr);
+    unicasts_.push_back(OptionalValue<IOAddress>(addr, true));
 }
 
 bool
@@ -233,8 +234,8 @@ Iface::getAddress4(isc::asiolink::IOAddress& address) const {
          addr != addrs.end(); ++addr) {
         // If address is IPv4, we assign it to the function argument
         // and return true.
-        if (addr->isV4()) {
-            address = *addr;
+        if (addr->get().isV4()) {
+            address = addr->get();
             return (true);
         }
     }
@@ -247,13 +248,39 @@ 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) {
+        if (address == addr->get()) {
             return (true);
         }
     }
     return (false);
 }
 
+void
+Iface::addAddress(const isc::asiolink::IOAddress& addr) {
+    addrs_.push_back(OptionalValue<IOAddress>(addr, OptionalValueState(false)));
+}
+
+void
+Iface::setActive(const IOAddress& address, const bool active) {
+    for (AddressCollection::iterator addr_it = addrs_.begin();
+         addr_it != addrs_.end(); ++addr_it) {
+        if (address == addr_it->get()) {
+            addr_it->specify(active);
+            return;
+        }
+    }
+    isc_throw(BadValue, "specified address " << address << " was not"
+              " found on the interface " << getName());
+}
+
+void
+Iface::setActive(const bool active) {
+    for (AddressCollection::iterator addr_it = addrs_.begin();
+         addr_it != addrs_.end(); ++addr_it) {
+        addr_it->specify(active);
+    }
+}
+
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {
@@ -393,7 +420,7 @@ IfaceMgr::hasOpenSocket(const IOAddress& addr) const {
                          iface->getAddresses().begin();
                      addr_it != iface->getAddresses().end();
                      ++addr_it) {
-                    if (addr == *addr_it) {
+                    if (addr == addr_it->get()) {
                         return (true);
                     }
                 }
@@ -488,7 +515,7 @@ IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
              ++addr) {
 
             // Skip all but V4 addresses.
-            if (!addr->isV4()) {
+            if (!addr->get().isV4()) {
                 continue;
             }
 
@@ -616,7 +643,7 @@ IfaceMgr::openSockets6(const uint16_t port,
              ++addr) {
 
             // Skip all but V6 addresses.
-            if (!addr->isV6()) {
+            if (!addr->get().isV6()) {
                 continue;
             }
 
@@ -625,7 +652,7 @@ IfaceMgr::openSockets6(const uint16_t port,
             // with interface with 2 global addresses, we would bind 3 sockets
             // (one for link-local and two for global). That would result in
             // getting each message 3 times.
-            if (!addr->isV6LinkLocal()){
+            if (!addr->get().isV6LinkLocal()){
                 continue;
             }
 
@@ -663,7 +690,7 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
 
         for (Iface::AddressCollection::const_iterator addr = addrs.begin();
              addr != addrs.end(); ++addr) {
-            out << "  " << addr->toText();
+            out << "  " << addr->get().toText();
         }
         out << endl;
     }
@@ -743,7 +770,7 @@ int IfaceMgr::openSocketFromIface(const std::string& ifname,
         Iface::AddressCollection addrs = iface->getAddresses();
         Iface::AddressCollection::iterator addr_it = addrs.begin();
         while (addr_it != addrs.end()) {
-            if (addr_it->getFamily() == family) {
+            if (addr_it->get().getFamily() == family) {
                 // We have interface and address so let's open socket.
                 // This may cause isc::Unexpected exception.
                 return (openSocket(iface->getName(), *addr_it, port, false));
@@ -787,7 +814,7 @@ int IfaceMgr::openSocketFromAddress(const IOAddress& addr,
             // on detected interfaces. If it does, we have
             // address and interface detected so we can open
             // socket.
-            if (*addr_it == addr) {
+            if (addr_it->get() == addr) {
                 // Open socket using local interface, address and port.
                 // This may cause isc::Unexpected exception.
                 return (openSocket(iface->getName(), *addr_it, port, false));

+ 27 - 4
src/lib/dhcp/iface_mgr.h

@@ -22,6 +22,7 @@
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter6.h>
+#include <util/optional_value.h>
 
 #include <boost/function.hpp>
 #include <boost/noncopyable.hpp>
@@ -150,7 +151,8 @@ public:
     static const unsigned int MAX_MAC_LEN = 20;
 
     /// Type that defines list of addresses
-    typedef std::vector<isc::asiolink::IOAddress> AddressCollection;
+    typedef
+    std::list<util::OptionalValue<asiolink::IOAddress> > AddressCollection;
 
     /// @brief Type that holds a list of socket information.
     ///
@@ -291,9 +293,30 @@ public:
     /// configure address on actual network interface.
     ///
     /// @param addr address to be added
-    void addAddress(const isc::asiolink::IOAddress& addr) {
-        addrs_.push_back(addr);
-    }
+    void addAddress(const isc::asiolink::IOAddress& addr);
+
+    /// @brief Activates or deactivates address for the interface.
+    ///
+    /// This method marks a specified address on the interface active or
+    /// inactive. If the address is marked inactive, the
+    /// @c IfaceMgr::openSockets4 method will NOT open socket for this address.
+    ///
+    /// @param address An address which should be activated, deactivated.
+    /// @param active A boolean flag which indicates that the specified address
+    /// should be active (if true) or inactive (if false).
+    ///
+    /// @throw BadValue if specified address doesn't exist for the interface.
+    void setActive(const isc::asiolink::IOAddress& address, const bool active);
+
+    /// @brief Activates or deactivates all addresses for the interface.
+    ///
+    /// This method marks all addresses on the interface active or inactive.
+    /// If the address is marked inactive, the @c IfaceMgr::openSockets4
+    /// method will NOT open socket for this address.
+    ///
+    /// @param active A boolean flag which indicates that the addresses
+    /// should be active (if true) or inactive (if false).
+    void setActive(const bool active);
 
     /// @brief Deletes an address from an interface.
     ///

+ 21 - 1
src/lib/dhcp/tests/iface_mgr_test_config.cc

@@ -107,6 +107,7 @@ IfaceMgrTestConfig::createIfaces() {
     // eth1
     addIface("eth1", 2);
     addAddress("eth1", IOAddress("192.0.2.3"));
+    addAddress("eth1", IOAddress("192.0.2.5"));
     addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:abcd"));
 
 }
@@ -143,7 +144,7 @@ IfaceMgrTestConfig::setIfaceFlags(const std::string& name,
 
 bool
 IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
-                         const int family) const {
+                               const int family) const {
     Iface* iface = IfaceMgr::instance().getIface(iface_name);
     if (iface == NULL) {
         isc_throw(Unexpected, "No such interface '" << iface_name << "'");
@@ -160,6 +161,25 @@ IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
 }
 
 bool
+IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
+                               const std::string& address) const {
+    Iface* iface = IfaceMgr::instance().getIface(iface_name);
+    if (iface == NULL) {
+        isc_throw(Unexpected, "No such interface '" << iface_name << "'");
+    }
+
+    const Iface::SocketCollection& sockets = iface->getSockets();
+    for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+         sock != sockets.end(); ++sock) {
+        if ((sock->family_ == AF_INET) &&
+            (sock->addr_ == IOAddress(address))) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+bool
 IfaceMgrTestConfig::unicastOpen(const std::string& iface_name) const {
     Iface* iface = IfaceMgr::instance().getIface(iface_name);
     if (iface == NULL) {

+ 8 - 0
src/lib/dhcp/tests/iface_mgr_test_config.h

@@ -235,6 +235,14 @@ public:
     /// @param family One of: AF_INET or AF_INET6
     bool socketOpen(const std::string& iface_name, const int family) const;
 
+    /// @brief Checks is socket is opened on the interface and bound to a
+    /// specified address.
+    ///
+    /// @param iface_name Interface name.
+    /// @param address Address to which the socket is bound.
+    bool socketOpen(const std::string& iface_name,
+                    const std::string & address) const;
+
     /// @brief Checks if unicast socket is opened on interface.
     ///
     /// @param iface_name Interface name.

+ 5 - 5
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -289,7 +289,7 @@ public:
                          iface->getAddresses().begin();
                      addr_it != iface->getAddresses().end();
                      ++addr_it) {
-                    if (*addr_it == IOAddress(addr)) {
+                    if (addr_it->get() == IOAddress(addr)) {
                         return (true);
                     }
                 }
@@ -1501,7 +1501,7 @@ TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
 
     // Expecting that the socket is open on eth1 because it was up, running
     // and active.
-    EXPECT_EQ(1, IfaceMgr::instance().getIface("eth1")->getSockets().size());
+    EXPECT_EQ(2, IfaceMgr::instance().getIface("eth1")->getSockets().size());
     // Never open socket on loopback interface.
     EXPECT_TRUE(IfaceMgr::instance().getIface("lo")->getSockets().empty());
 
@@ -2072,7 +2072,7 @@ TEST_F(IfaceMgrTest, iface) {
 
     addrs = iface->getAddresses();
     ASSERT_EQ(1, addrs.size());
-    EXPECT_EQ("192.0.2.6", addrs.at(0).toText());
+    EXPECT_EQ("192.0.2.6", addrs.begin()->get().toText());
 
     // No such address, should return false.
     EXPECT_FALSE(iface->delAddress(IOAddress("192.0.8.9")));
@@ -2332,7 +2332,7 @@ checkIfAddrs(const Iface & iface, struct ifaddrs *& ifptr) {
             for (Iface::AddressCollection::const_iterator a =
                      iface.getAddresses().begin();
                  a != iface.getAddresses().end(); ++ a) {
-                if(a->isV4() && (*a) == addrv4) {
+                if(a->get().isV4() && (a->get()) == addrv4) {
                     return (true);
                 }
             }
@@ -2349,7 +2349,7 @@ checkIfAddrs(const Iface & iface, struct ifaddrs *& ifptr) {
             for(Iface::AddressCollection::const_iterator a =
                     iface.getAddresses().begin();
                 a != iface.getAddresses().end(); ++ a) {
-                if(a->isV6() && (*a) == addrv6) {
+                if (a->get().isV6() && (a->get() == addrv6)) {
                     return (true);
                 }
             }

+ 75 - 44
src/lib/dhcpsrv/cfg_iface.cc

@@ -37,7 +37,7 @@ CfgIface::closeSockets() const {
 bool
 CfgIface::equals(const CfgIface& other) const {
     return (iface_set_ == other.iface_set_ &&
-            unicast_map_ == other.unicast_map_ &&
+            address_map_ == other.address_map_ &&
             wildcard_used_ == other.wildcard_used_);
 }
 
@@ -70,6 +70,10 @@ CfgIface::openSockets(const uint16_t family, const uint16_t port,
 
             } else if (family == AF_INET) {
                 iface->inactive4_ = false;
+                ExplicitAddressMap::const_iterator addr = address_map_.find(iface->getName());
+                if (addr != address_map_.end()) {
+                    iface->setActive(addr->second, true);
+                }
 
             } else {
                 iface->inactive6_ = false;
@@ -79,8 +83,8 @@ CfgIface::openSockets(const uint16_t family, const uint16_t port,
 
     // Select unicast sockets. It works only for V6. Ignore for V4.
     if (family == AF_INET6) {
-        for (UnicastMap::const_iterator unicast = unicast_map_.begin();
-             unicast != unicast_map_.end(); ++unicast) {
+        for (ExplicitAddressMap::const_iterator unicast = address_map_.begin();
+             unicast != address_map_.end(); ++unicast) {
             Iface* iface = IfaceMgr::instance().getIface(unicast->first);
             if (iface == NULL) {
                 isc_throw(Unexpected,
@@ -130,13 +134,23 @@ CfgIface::setState(const uint16_t family, const bool inactive,
     for (IfaceMgr::IfaceCollection::iterator iface = ifaces.begin();
          iface != ifaces.end(); ++iface) {
         Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
+        bool iface_inactive = iface_ptr->flag_loopback_ ?
+            loopback_inactive : inactive;
         if (family == AF_INET) {
-            iface_ptr->inactive4_ = iface_ptr->flag_loopback_ ?
-                loopback_inactive : inactive;
+            iface_ptr->inactive4_ = iface_inactive;
         } else {
-            iface_ptr->inactive6_ = iface_ptr->flag_loopback_ ?
-                loopback_inactive : inactive;
+            iface_ptr->inactive6_ = iface_inactive;
         }
+
+        // Activate/deactivate all addresses.
+        const Iface::AddressCollection addresses = iface_ptr->getAddresses();
+        for (Iface::AddressCollection::const_iterator addr_it =
+                 addresses.begin(); addr_it != addresses.end(); ++addr_it) {
+            if (addr_it->get().getFamily() == family) {
+                iface_ptr->setActive(addr_it->get(), !iface_inactive);
+            }
+        }
+
     }
 }
 
@@ -169,18 +183,6 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
                           << "' doesn't exist in the system");
             }
 
-            // If interface has already been specified.
-            if (iface_set_.find(name) != iface_set_.end()) {
-                isc_throw(DuplicateIfaceName, "interface '" << name
-                          << "' has already been specified");
-
-            }
-
-            // All ok, add interface.
-            LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_IFACE)
-                .arg(name);
-            iface_set_.insert(name);
-
         } else if (wildcard_used_) {
             isc_throw(DuplicateIfaceName, "the wildcard interface '"
                       << ALL_IFACES_KEYWORD << "' can only be specified once");
@@ -192,14 +194,10 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
 
         }
 
-    } else if (family == AF_INET) {
-        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.
+        // The interface name includes the address on which the socket should
+        // be opened, we we need to split interface name and the address to
+        // two variables.
         name = util::str::trim(iface_name.substr(0, pos));
         addr_str = util::str::trim(iface_name.substr(pos + 1));
 
@@ -210,10 +208,10 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
                       " interface configuration");
 
         }
-        // Unicast addr_stress following the interface name must not be empty.
+        // An address following the interface name must not be empty.
         if (addr_str.empty()) {
             isc_throw(InvalidIfaceName,
-                      "empty unicast addr_stress specified in the interface"
+                      "empty address specified in the interface"
                       << " configuration");
 
         }
@@ -222,8 +220,8 @@ CfgIface::use(const uint16_t family, const std::string& iface_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");
+                      << "' must not be used in conjunction with an"
+                      " address");
 
         }
 
@@ -239,17 +237,26 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
         // 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");
-        }
+        // Validate V6 address.
+        if (family == AF_INET6) {
+            // 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");
+            }
 
-        // There are valid cases where link local address can be specified to
-        // receive unicast traffic, e.g. sent by relay agent.
-        if (addr.isV6LinkLocal()) {
-            LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL)
-                .arg(addr.toText()).arg(name);
+            // There are valid cases where link local address can be specified to
+            // receive unicast traffic, e.g. sent by relay agent.
+            if (addr.isV6LinkLocal()) {
+                LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL)
+                    .arg(addr.toText()).arg(name);
+            }
+
+        } else {
+            if (!addr.isV4()) {
+                isc_throw(InvalidIfaceName, "address '" << addr << "' is not"
+                          " a valid IPv4 address");
+            }
         }
 
         // Interface must have this address assigned.
@@ -261,17 +268,41 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
 
         // Insert address and the interface to the collection of unicast
         // addresses.
-        if (unicast_map_.find(name) != unicast_map_.end()) {
+        if (address_map_.find(name) != address_map_.end()) {
             isc_throw(DuplicateIfaceName, "must not specify unicast address '"
                       << addr << "' for interface '" << name << "' "
                       "because other unicast address has already been"
                       " specified for this interface");
         }
-        LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_UNICAST)
-            .arg(addr.toText()).arg(name);
-        unicast_map_.insert(std::pair<std::string, IOAddress>(name, addr));
+
+        if (family == AF_INET6) {
+            LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_UNICAST)
+                .arg(addr.toText()).arg(name);
+
+        } else {
+            LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_ADDRESS)
+                .arg(addr.toText()).arg(name);
+        }
+        address_map_.insert(std::pair<std::string, IOAddress>(name, addr));
     }
 
+    // If interface name was explicitly specified and we're not parsing
+    // a unicast IPv6 address, add the interface to the interface set.
+    if ((name != ALL_IFACES_KEYWORD) &&
+        ((family == AF_INET) || ((family == AF_INET6) && addr_str.empty()))) {
+        // If interface has already been specified.
+        if (iface_set_.find(name) != iface_set_.end()) {
+            isc_throw(DuplicateIfaceName, "interface '" << name
+                      << "' has already been specified");
+        }
+
+        // Log that we're listening on the specific interface and that the
+        // address is not explicitly specified.
+        if (addr_str.empty()) {
+            LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_IFACE).arg(name);
+        }
+        iface_set_.insert(name);
+    }
 }
 
 } // end of isc::dhcp namespace

+ 6 - 7
src/lib/dhcpsrv/cfg_iface.h

@@ -193,14 +193,13 @@ private:
     /// @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 of interfaces and addresses to which the server
+    /// should bind sockets.
+    typedef std::map<std::string, asiolink::IOAddress> ExplicitAddressMap;
 
-    /// @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 map which holds the pairs of interface names and addresses
+    /// for which the sockets should be opened.
+    ExplicitAddressMap address_map_;
 
     /// @brief A booolean value which indicates that the wildcard interface name
     /// has been specified (*).

+ 4 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -142,6 +142,10 @@ This warning message is logged when user specified a link-local address to
 receive unicast traffic. The warning message is issued because it is an
 uncommon use.
 
+% DHCPSRV_CFGMGR_USE_ADDRESS listening on address %1, on interface %2
+A message issued when server is configured to listen on the explicitly specified
+IP address on the given interface.
+
 % DHCPSRV_CLOSE_DB closing currently open %1 database
 This is a debug message, issued when the DHCP server closes the currently
 open lease database.  It is issued at program shutdown and whenever

+ 49 - 1
src/lib/dhcpsrv/tests/cfg_iface_unittest.cc

@@ -42,6 +42,14 @@ public:
     /// @param family One of: AF_INET or AF_INET6
     bool socketOpen(const std::string& iface_name, const int family) const;
 
+    /// @brief Checks if socket is opened on the specified interface and bound
+    /// to a specific IPv4 address.
+    ///
+    /// @param iface_name Interface name.
+    /// @param address Address that the socket should be bound to.
+    bool socketOpen(const std::string& iface_name,
+                    const std::string& address) const;
+
     /// @brief Checks if unicast socket is opened on interface.
     ///
     /// @param iface_name Interface name.
@@ -57,6 +65,15 @@ CfgIfaceTest::socketOpen(const std::string& iface_name,
                          const int family) const {
     return (iface_mgr_test_config_.socketOpen(iface_name, family));
 }
+
+bool
+CfgIfaceTest::socketOpen(const std::string& iface_name,
+                         const std::string& address) const {
+    return (iface_mgr_test_config_.socketOpen(iface_name, address));
+}
+
+
+
 bool
 CfgIfaceTest::unicastOpen(const std::string& iface_name) const {
     return (iface_mgr_test_config_.unicastOpen(iface_name));
@@ -99,7 +116,38 @@ TEST_F(CfgIfaceTest, explicitNamesV4) {
     EXPECT_FALSE(socketOpen("eth0", AF_INET));
     EXPECT_TRUE(socketOpen("eth1", AF_INET));
     EXPECT_FALSE(socketOpen("lo", AF_INET));
+}
 
+// This test checks that it is possible to specify an interface and address
+// on this interface to which the socket should be bound. The sockets should
+// not be opened on other addresses on this interface.
+TEST_F(CfgIfaceTest, explicitNamesAndAddressesV4) {
+    CfgIface cfg;
+    ASSERT_NO_THROW(cfg.use(AF_INET, "eth0/10.0.0.1"));
+    ASSERT_NO_THROW(cfg.use(AF_INET, "eth1/192.0.2.3"));
+    ASSERT_THROW(cfg.use(AF_INET, "eth1/192.0.2.5"), DuplicateIfaceName);
+
+    // Open sockets on specified interfaces and addresses.
+    cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+
+    EXPECT_TRUE(socketOpen("eth0", "10.0.0.1"));
+    EXPECT_TRUE(socketOpen("eth1", "192.0.2.3"));
+    EXPECT_FALSE(socketOpen("eth1", "192.0.2.5"));
+
+    // Close all sockets and make sure they are really closed.
+    cfg.closeSockets();
+    ASSERT_FALSE(socketOpen("eth0", "10.0.0.1"));
+    ASSERT_FALSE(socketOpen("eth1", "192.0.2.3"));
+    ASSERT_FALSE(socketOpen("eth1", "192.0.2.5"));
+
+    // Now check that the socket can be bound to a different address on
+    // eth1.
+    ASSERT_NO_THROW(cfg.use(AF_INET, "eth1/192.0.2.5"));
+    ASSERT_THROW(cfg.use(AF_INET, "eth1/192.0.2.3"), DuplicateIfaceName);
+
+    EXPECT_FALSE(socketOpen("eth0", "10.0.0.1"));
+    EXPECT_FALSE(socketOpen("eth1", "192.0.2.3"));
+    EXPECT_TRUE(socketOpen("eth1", "192.0.2.5"));
 }
 
 // This test checks that the interface names can be explicitly selected
@@ -118,7 +166,7 @@ TEST_F(CfgIfaceTest, explicitNamesV6) {
     EXPECT_TRUE(socketOpen("eth1", AF_INET6));
     EXPECT_FALSE(socketOpen("lo", AF_INET6));
 
-    // No IPv4 sockets should be present because we wanted IPv4 sockets.
+    // No IPv4 sockets should be present because we wanted IPv6 sockets.
     EXPECT_FALSE(socketOpen("eth0", AF_INET));
     EXPECT_FALSE(socketOpen("eth1", AF_INET));
     EXPECT_FALSE(socketOpen("lo", AF_INET));