Browse Source

[master] Merge branch 'trac3539'

Marcin Siodelski 10 years ago
parent
commit
ff71887c60

+ 11 - 1
doc/guide/dhcp4-srv.xml

@@ -348,9 +348,19 @@ url="http://jsonviewer.stack.hu/"/>.
   with explicit interface names:
   <screen>
 "Dhcp4": { <userinput>"interfaces": [ "eth1", "eth3", "*" ]</userinput>, ... }</screen>
-It is anticipated that this will form of usage only be used where it is desired to
+It is anticipated that this form of usage will only be used when it is desired to
 temporarily override a list of interface names and listen on all interfaces.
   </para>
+  <para>Some deployments of the DHCP servers require that the servers listen
+  on the interfaces with multiple IPv4 addresses configured. In some cases,
+  multiple instances of the DHCP servers are running concurrently and each
+  instance should be bound to a different address on the particular interface.
+  In these situations, the address to use can be selected by
+  appending an IPv4 address to the interface name in the following manner:
+<screen>
+"Dhcp4": { <userinput>"interfaces": [ "eth1/10.0.0.1", "eth3/192.0.2.3" ]</userinput>, ... }</screen>
+  Note that only one address can be specified on each interface.
+  </para>
 </section>
 
 <section id="ipv4-subnet-id">

+ 39 - 0
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -3002,6 +3002,45 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
     ASSERT_TRUE(test_config.socketOpen("eth1", AF_INET));
 }
 
+// This test verifies that it is possible to select subset of interfaces
+// and addresses.
+TEST_F(Dhcp4ParserTest, selectedInterfacesAndAddresses) {
+    IfaceMgrTestConfig test_config(true);
+
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"eth0/10.0.0.1\", \"eth1/192.0.2.3\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    ConstElementPtr status;
+
+    // Make sure the config manager is clean and there is no hanging
+    // interface configuration.
+    ASSERT_FALSE(test_config.socketOpen("eth0", "10.0.0.1"));
+    ASSERT_FALSE(test_config.socketOpen("eth1", "192.0.2.3"));
+    ASSERT_FALSE(test_config.socketOpen("eth1", "192.0.2.5"));
+
+    // Apply configuration.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    CfgMgr::instance().getStagingCfg()->
+        getCfgIface().openSockets(AF_INET, 10000);
+
+    // An address on eth0 was selected
+    EXPECT_TRUE(test_config.socketOpen("eth0", "10.0.0.1"));
+    // The 192.0.2.3 address on eth1 was selected.
+    EXPECT_TRUE(test_config.socketOpen("eth1", "192.0.2.3"));
+    // The 192.0.2.5 was not selected, thus the socket should not
+    // be bound to this address.
+    EXPECT_FALSE(test_config.socketOpen("eth1", "192.0.2.5"));
+}
+
+
 // This test checks the ability of the server to parse a configuration
 // containing a full, valid dhcp-ddns (D2ClientConfig) entry.
 TEST_F(Dhcp4ParserTest, d2ClientConfig) {

+ 41 - 14
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(true)));
+}
+
+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);
                     }
                 }
@@ -487,8 +514,8 @@ IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
              addr != addrs.end();
              ++addr) {
 
-            // Skip all but V4 addresses.
-            if (!addr->isV4()) {
+            // Skip non-IPv4 addresses and those that weren't selected..
+            if (!addr->get().isV4() || !addr->isSpecified()) {
                 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));

+ 36 - 5
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.
     ///
@@ -256,7 +258,15 @@ public:
     /// @return hardware type
     uint16_t getHWType() const { return hardware_type_; }
 
-    /// @brief Returns all interfaces available on an interface.
+    /// @brief Returns all addresses available on an interface.
+    ///
+    /// The returned addresses are encapsulated in the @c util::OptionalValue
+    /// class to be able to selectively flag some of the addresses as active
+    /// (when optional value is specified) or inactive (when optional value
+    /// is specified). If the address is marked as active, the
+    /// @c IfaceMgr::openSockets4 method will open socket and bind to this
+    /// address. Otherwise, it will not bind any socket to this address.
+    /// This is useful when an interface has multiple IPv4 addresses assigned.
     ///
     /// Care should be taken to not use this collection after Iface object
     /// ceases to exist. That is easy in most cases as Iface objects are
@@ -291,9 +301,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);
                 }
             }

+ 88 - 46
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,20 @@ 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 user has specified an address to listen on, let's activate
+                // only this address.
+                if (addr != address_map_.end()) {
+                    iface->setActive(addr->second, true);
+
+                // Otherwise, activate first one.
+                } else {
+                    IOAddress address(0);
+                    if (iface->getAddress4(address)) {
+                        iface->setActive(address, true);
+                    }
+                }
 
             } else {
                 iface->inactive6_ = false;
@@ -79,8 +93,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,
@@ -121,6 +135,7 @@ void
 CfgIface::reset() {
     wildcard_used_ = false;
     iface_set_.clear();
+    address_map_.clear();
 }
 
 void
@@ -130,13 +145,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 +194,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 +205,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 +219,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 +231,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 +248,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.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 +279,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()) {
-            isc_throw(DuplicateIfaceName, "must not specify unicast address '"
+        if (address_map_.find(name) != address_map_.end()) {
+            isc_throw(DuplicateIfaceName, "must not select address '"
                       << addr << "' for interface '" << name << "' "
-                      "because other unicast address has already been"
+                      "because another 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_USE_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

+ 9 - 10
src/lib/dhcpsrv/cfg_iface.h

@@ -12,8 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#ifndef IFACE_CFG_H
-#define IFACE_CFG_H
+#ifndef CFG_IFACE_H
+#define CFG_IFACE_H
 
 #include <asiolink/io_address.h>
 #include <map>
@@ -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 (*).
@@ -210,4 +209,4 @@ private:
 }
 }
 
-#endif // IFACE_CFG_H
+#endif // CFG_IFACE_H

+ 17 - 13
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -20,7 +20,7 @@ reason for the failure being contained in the message.  The server will
 return a message to the client refusing a lease.
 
 % DHCPSRV_ADDRESS4_ALLOC_FAIL failed to allocate an IPv4 address after %1 attempt(s)
-THE DHCP allocation engine gave up trying to allocate an IPv4 address
+The DHCP allocation engine gave up trying to allocate an IPv4 address
 after the specified number of attempts.  This probably means that the
 address pool from which the allocation is being attempted is either
 empty, or very nearly empty.  As a result, the client will have been
@@ -55,12 +55,8 @@ to clients that are no longer active on the network will become available
 available sooner.
 
 % DHCPSRV_CFGMGR_ADD_IFACE listening on interface %1
-An info message issued when new interface is being added to the collection of
-interfaces on which server listens to DHCP messages.
-
-% DHCPSRV_CFGMGR_ADD_UNICAST listening on unicast address %1, on interface %2
-A debug message issued when new configuring DHCP server to listen on unicast
-address on the specific interface.
+An info message issued when a new interface is being added to the collection of
+interfaces on which the server listens to DHCP messages.
 
 % DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
 A debug message reported when the DHCP configuration manager is adding the
@@ -71,7 +67,7 @@ A debug message reported when the DHCP configuration manager is adding the
 specified IPv6 subnet to its database.
 
 % DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE enabling listening on all interfaces
-A debug message issued when server is being configured to listen on all
+A debug message issued when the server is being configured to listen on all
 interfaces.
 
 % DHCPSRV_CFGMGR_CFG_DHCP_DDNS Setting DHCP-DDNS configuration to: %1
@@ -129,7 +125,7 @@ was specified as being directly reachable over given interface. (see
 This is a debug message reporting that the DHCP configuration manager
 has returned the specified IPv6 subnet for a received packet. This particular
 subnet was selected, because value of interface-id option matched what was
-configured in server's interface-id option for that selected subnet6.
+configured in the server's interface-id option for that selected subnet6.
 (see 'interface-id' parameter in the subnet6 definition).
 
 % DHCPSRV_CFGMGR_SUBNET6_RELAY selected subnet %1, because of matching relay addr %2
@@ -142,6 +138,14 @@ 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 the server is configured to listen on the explicitly specified
+IP address on the given interface.
+
+% DHCPSRV_CFGMGR_USE_UNICAST listening on unicast address %1, on interface %2
+An info message issued when configuring the DHCP server to listen on the unicast
+address on the specific 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
@@ -281,12 +285,12 @@ A debug message issued when the server is about to obtain schema version
 information from the memory file database.
 
 % DHCPSRV_MEMFILE_LEASES_RELOAD4 reloading leases from %1
-An info message issued when server is about to start reading DHCPv4 leases
+An info message issued when the server is about to start reading DHCPv4 leases
 from the lease file. All leases currently held in the memory will be
 replaced by those read from the file.
 
 % DHCPSRV_MEMFILE_LEASES_RELOAD6 reloading leases from %1
-An info message issued when server is about to start reading DHCPv6 leases
+An info message issued when the server is about to start reading DHCPv6 leases
 from the lease file. All leases currently held in the memory will be
 replaced by those read from the file.
 
@@ -401,8 +405,8 @@ 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
+This warning message is issued when the current server configuration specifies
+no interfaces that the server should listen on, or when the specified interfaces are not
 configured to receive the traffic.
 
 % DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1

+ 71 - 5
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,60 @@ 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"));
+
+    // Reset configuration.
+    cfg.reset();
 
+    // 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);
+
+    // Open sockets according to the new configuration.
+    cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+
+    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 invalid interface name and/or IPv4 address
+// results in error.
+TEST_F(CfgIfaceTest, explicitNamesAndAddressesInvalidV4) {
+    CfgIface cfg;
+    // An address not assigned to the interface.
+    EXPECT_THROW(cfg.use(AF_INET, "eth0/10.0.0.2"), NoSuchAddress);
+    // IPv6 address.
+    EXPECT_THROW(cfg.use(AF_INET, "eth0/2001:db8:1::1"), InvalidIfaceName);
+    // Wildcard interface name with an address.
+    EXPECT_THROW(cfg.use(AF_INET, "*/10.0.0.1"), InvalidIfaceName);
+
+    // Duplicated interface.
+    ASSERT_NO_THROW(cfg.use(AF_INET, "eth1"));
+    EXPECT_THROW(cfg.use(AF_INET, "eth1/192.0.2.3"), DuplicateIfaceName);
 }
 
 // This test checks that the interface names can be explicitly selected
@@ -118,7 +188,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));
@@ -213,10 +283,6 @@ TEST_F(CfgIfaceTest, invalidValues) {
     ASSERT_THROW(cfg.use(AF_INET6, "/2001:db8:1::1"), InvalidIfaceName);
     ASSERT_THROW(cfg.use(AF_INET6, "*/2001:db8:1::1"), InvalidIfaceName);
     ASSERT_THROW(cfg.use(AF_INET6, "bogus/2001:db8:1::1"), NoSuchIface);
-    ASSERT_THROW(cfg.use(AF_INET6, "eth0/fe80::3a60:77ff:fed5:cdef"),
-                 InvalidIfaceName);
-    ASSERT_THROW(cfg.use(AF_INET6, "eth0/fe80::3a60:77ff:fed5:cdef"),
-                 InvalidIfaceName);
     ASSERT_THROW(cfg.use(AF_INET6, "eth0/2001:db8:1::2"), NoSuchAddress);
     ASSERT_NO_THROW(cfg.use(AF_INET6, "*"));
     ASSERT_THROW(cfg.use(AF_INET6, "*"), DuplicateIfaceName);