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 std;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
+using namespace isc::util;
 using namespace isc::util::io::internal;
 using namespace isc::util::io::internal;
 
 
 namespace isc {
 namespace isc {
@@ -149,7 +150,7 @@ void Iface::setMac(const uint8_t* mac, size_t len) {
 bool Iface::delAddress(const isc::asiolink::IOAddress& addr) {
 bool Iface::delAddress(const isc::asiolink::IOAddress& addr) {
     for (AddressCollection::iterator a = addrs_.begin();
     for (AddressCollection::iterator a = addrs_.begin();
          a!=addrs_.end(); ++a) {
          a!=addrs_.end(); ++a) {
-        if (*a==addr) {
+        if (a->get() == addr) {
             addrs_.erase(a);
             addrs_.erase(a);
             return (true);
             return (true);
         }
         }
@@ -216,12 +217,12 @@ IfaceMgr::IfaceMgr()
 void Iface::addUnicast(const isc::asiolink::IOAddress& addr) {
 void Iface::addUnicast(const isc::asiolink::IOAddress& addr) {
     for (Iface::AddressCollection::const_iterator i = unicasts_.begin();
     for (Iface::AddressCollection::const_iterator i = unicasts_.begin();
          i != unicasts_.end(); ++i) {
          i != unicasts_.end(); ++i) {
-        if (*i == addr) {
+        if (i->get() == addr) {
             isc_throw(BadValue, "Address " << addr
             isc_throw(BadValue, "Address " << addr
                       << " already defined on the " << name_ << " interface.");
                       << " already defined on the " << name_ << " interface.");
         }
         }
     }
     }
-    unicasts_.push_back(addr);
+    unicasts_.push_back(OptionalValue<IOAddress>(addr, true));
 }
 }
 
 
 bool
 bool
@@ -233,8 +234,8 @@ Iface::getAddress4(isc::asiolink::IOAddress& address) const {
          addr != addrs.end(); ++addr) {
          addr != addrs.end(); ++addr) {
         // If address is IPv4, we assign it to the function argument
         // If address is IPv4, we assign it to the function argument
         // and return true.
         // and return true.
-        if (addr->isV4()) {
-            address = *addr;
+        if (addr->get().isV4()) {
+            address = addr->get();
             return (true);
             return (true);
         }
         }
     }
     }
@@ -247,13 +248,39 @@ Iface::hasAddress(const isc::asiolink::IOAddress& address) const {
     const AddressCollection& addrs = getAddresses();
     const AddressCollection& addrs = getAddresses();
     for (AddressCollection::const_iterator addr = addrs.begin();
     for (AddressCollection::const_iterator addr = addrs.begin();
          addr != addrs.end(); ++addr) {
          addr != addrs.end(); ++addr) {
-        if (address == *addr) {
+        if (address == addr->get()) {
             return (true);
             return (true);
         }
         }
     }
     }
     return (false);
     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() {
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {
          iface != ifaces_.end(); ++iface) {
@@ -393,7 +420,7 @@ IfaceMgr::hasOpenSocket(const IOAddress& addr) const {
                          iface->getAddresses().begin();
                          iface->getAddresses().begin();
                      addr_it != iface->getAddresses().end();
                      addr_it != iface->getAddresses().end();
                      ++addr_it) {
                      ++addr_it) {
-                    if (addr == *addr_it) {
+                    if (addr == addr_it->get()) {
                         return (true);
                         return (true);
                     }
                     }
                 }
                 }
@@ -488,7 +515,7 @@ IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
              ++addr) {
              ++addr) {
 
 
             // Skip all but V4 addresses.
             // Skip all but V4 addresses.
-            if (!addr->isV4()) {
+            if (!addr->get().isV4()) {
                 continue;
                 continue;
             }
             }
 
 
@@ -616,7 +643,7 @@ IfaceMgr::openSockets6(const uint16_t port,
              ++addr) {
              ++addr) {
 
 
             // Skip all but V6 addresses.
             // Skip all but V6 addresses.
-            if (!addr->isV6()) {
+            if (!addr->get().isV6()) {
                 continue;
                 continue;
             }
             }
 
 
@@ -625,7 +652,7 @@ IfaceMgr::openSockets6(const uint16_t port,
             // with interface with 2 global addresses, we would bind 3 sockets
             // with interface with 2 global addresses, we would bind 3 sockets
             // (one for link-local and two for global). That would result in
             // (one for link-local and two for global). That would result in
             // getting each message 3 times.
             // getting each message 3 times.
-            if (!addr->isV6LinkLocal()){
+            if (!addr->get().isV6LinkLocal()){
                 continue;
                 continue;
             }
             }
 
 
@@ -663,7 +690,7 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
 
 
         for (Iface::AddressCollection::const_iterator addr = addrs.begin();
         for (Iface::AddressCollection::const_iterator addr = addrs.begin();
              addr != addrs.end(); ++addr) {
              addr != addrs.end(); ++addr) {
-            out << "  " << addr->toText();
+            out << "  " << addr->get().toText();
         }
         }
         out << endl;
         out << endl;
     }
     }
@@ -743,7 +770,7 @@ int IfaceMgr::openSocketFromIface(const std::string& ifname,
         Iface::AddressCollection addrs = iface->getAddresses();
         Iface::AddressCollection addrs = iface->getAddresses();
         Iface::AddressCollection::iterator addr_it = addrs.begin();
         Iface::AddressCollection::iterator addr_it = addrs.begin();
         while (addr_it != addrs.end()) {
         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.
                 // We have interface and address so let's open socket.
                 // This may cause isc::Unexpected exception.
                 // This may cause isc::Unexpected exception.
                 return (openSocket(iface->getName(), *addr_it, port, false));
                 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
             // on detected interfaces. If it does, we have
             // address and interface detected so we can open
             // address and interface detected so we can open
             // socket.
             // socket.
-            if (*addr_it == addr) {
+            if (addr_it->get() == addr) {
                 // Open socket using local interface, address and port.
                 // Open socket using local interface, address and port.
                 // This may cause isc::Unexpected exception.
                 // This may cause isc::Unexpected exception.
                 return (openSocket(iface->getName(), *addr_it, port, false));
                 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/pkt6.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter6.h>
 #include <dhcp/pkt_filter6.h>
+#include <util/optional_value.h>
 
 
 #include <boost/function.hpp>
 #include <boost/function.hpp>
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
@@ -150,7 +151,8 @@ public:
     static const unsigned int MAX_MAC_LEN = 20;
     static const unsigned int MAX_MAC_LEN = 20;
 
 
     /// Type that defines list of addresses
     /// 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.
     /// @brief Type that holds a list of socket information.
     ///
     ///
@@ -291,9 +293,30 @@ public:
     /// configure address on actual network interface.
     /// configure address on actual network interface.
     ///
     ///
     /// @param addr address to be added
     /// @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.
     /// @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
     // eth1
     addIface("eth1", 2);
     addIface("eth1", 2);
     addAddress("eth1", IOAddress("192.0.2.3"));
     addAddress("eth1", IOAddress("192.0.2.3"));
+    addAddress("eth1", IOAddress("192.0.2.5"));
     addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:abcd"));
     addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:abcd"));
 
 
 }
 }
@@ -143,7 +144,7 @@ IfaceMgrTestConfig::setIfaceFlags(const std::string& name,
 
 
 bool
 bool
 IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
 IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
-                         const int family) const {
+                               const int family) const {
     Iface* iface = IfaceMgr::instance().getIface(iface_name);
     Iface* iface = IfaceMgr::instance().getIface(iface_name);
     if (iface == NULL) {
     if (iface == NULL) {
         isc_throw(Unexpected, "No such interface '" << iface_name << "'");
         isc_throw(Unexpected, "No such interface '" << iface_name << "'");
@@ -160,6 +161,25 @@ IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
 }
 }
 
 
 bool
 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 {
 IfaceMgrTestConfig::unicastOpen(const std::string& iface_name) const {
     Iface* iface = IfaceMgr::instance().getIface(iface_name);
     Iface* iface = IfaceMgr::instance().getIface(iface_name);
     if (iface == NULL) {
     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
     /// @param family One of: AF_INET or AF_INET6
     bool socketOpen(const std::string& iface_name, const int family) const;
     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.
     /// @brief Checks if unicast socket is opened on interface.
     ///
     ///
     /// @param iface_name Interface name.
     /// @param iface_name Interface name.

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

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

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

@@ -37,7 +37,7 @@ CfgIface::closeSockets() const {
 bool
 bool
 CfgIface::equals(const CfgIface& other) const {
 CfgIface::equals(const CfgIface& other) const {
     return (iface_set_ == other.iface_set_ &&
     return (iface_set_ == other.iface_set_ &&
-            unicast_map_ == other.unicast_map_ &&
+            address_map_ == other.address_map_ &&
             wildcard_used_ == other.wildcard_used_);
             wildcard_used_ == other.wildcard_used_);
 }
 }
 
 
@@ -70,6 +70,10 @@ CfgIface::openSockets(const uint16_t family, const uint16_t port,
 
 
             } else if (family == AF_INET) {
             } else if (family == AF_INET) {
                 iface->inactive4_ = false;
                 iface->inactive4_ = false;
+                ExplicitAddressMap::const_iterator addr = address_map_.find(iface->getName());
+                if (addr != address_map_.end()) {
+                    iface->setActive(addr->second, true);
+                }
 
 
             } else {
             } else {
                 iface->inactive6_ = false;
                 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.
     // Select unicast sockets. It works only for V6. Ignore for V4.
     if (family == AF_INET6) {
     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);
             Iface* iface = IfaceMgr::instance().getIface(unicast->first);
             if (iface == NULL) {
             if (iface == NULL) {
                 isc_throw(Unexpected,
                 isc_throw(Unexpected,
@@ -130,13 +134,23 @@ CfgIface::setState(const uint16_t family, const bool inactive,
     for (IfaceMgr::IfaceCollection::iterator iface = ifaces.begin();
     for (IfaceMgr::IfaceCollection::iterator iface = ifaces.begin();
          iface != ifaces.end(); ++iface) {
          iface != ifaces.end(); ++iface) {
         Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
         Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
+        bool iface_inactive = iface_ptr->flag_loopback_ ?
+            loopback_inactive : inactive;
         if (family == AF_INET) {
         if (family == AF_INET) {
-            iface_ptr->inactive4_ = iface_ptr->flag_loopback_ ?
-                loopback_inactive : inactive;
+            iface_ptr->inactive4_ = iface_inactive;
         } else {
         } 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");
                           << "' 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_) {
         } else if (wildcard_used_) {
             isc_throw(DuplicateIfaceName, "the wildcard interface '"
             isc_throw(DuplicateIfaceName, "the wildcard interface '"
                       << ALL_IFACES_KEYWORD << "' can only be specified once");
                       << 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 {
     } 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));
         name = util::str::trim(iface_name.substr(0, pos));
         addr_str = util::str::trim(iface_name.substr(pos + 1));
         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");
                       " 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()) {
         if (addr_str.empty()) {
             isc_throw(InvalidIfaceName,
             isc_throw(InvalidIfaceName,
-                      "empty unicast addr_stress specified in the interface"
+                      "empty address specified in the interface"
                       << " configuration");
                       << " configuration");
 
 
         }
         }
@@ -222,8 +220,8 @@ CfgIface::use(const uint16_t family, const std::string& iface_name) {
         if (name == ALL_IFACES_KEYWORD) {
         if (name == ALL_IFACES_KEYWORD) {
             isc_throw(InvalidIfaceName,
             isc_throw(InvalidIfaceName,
                       "wildcard interface name '" << ALL_IFACES_KEYWORD
                       "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.
         // is invalid.
         IOAddress addr(addr_str);
         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.
         // 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
         // Insert address and the interface to the collection of unicast
         // addresses.
         // 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 '"
             isc_throw(DuplicateIfaceName, "must not specify unicast address '"
                       << addr << "' for interface '" << name << "' "
                       << addr << "' for interface '" << name << "' "
                       "because other unicast address has already been"
                       "because other unicast address has already been"
                       " specified for this interface");
                       " 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
 } // 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.
     /// @brief A set of interface names specified by the user.
     IfaceSet iface_set_;
     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
     /// @brief A booolean value which indicates that the wildcard interface name
     /// has been specified (*).
     /// 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
 receive unicast traffic. The warning message is issued because it is an
 uncommon use.
 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
 % DHCPSRV_CLOSE_DB closing currently open %1 database
 This is a debug message, issued when the DHCP server closes the currently
 This is a debug message, issued when the DHCP server closes the currently
 open lease database.  It is issued at program shutdown and whenever
 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
     /// @param family One of: AF_INET or AF_INET6
     bool socketOpen(const std::string& iface_name, const int family) const;
     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.
     /// @brief Checks if unicast socket is opened on interface.
     ///
     ///
     /// @param iface_name Interface name.
     /// @param iface_name Interface name.
@@ -57,6 +65,15 @@ CfgIfaceTest::socketOpen(const std::string& iface_name,
                          const int family) const {
                          const int family) const {
     return (iface_mgr_test_config_.socketOpen(iface_name, family));
     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
 bool
 CfgIfaceTest::unicastOpen(const std::string& iface_name) const {
 CfgIfaceTest::unicastOpen(const std::string& iface_name) const {
     return (iface_mgr_test_config_.unicastOpen(iface_name));
     return (iface_mgr_test_config_.unicastOpen(iface_name));
@@ -99,7 +116,38 @@ TEST_F(CfgIfaceTest, explicitNamesV4) {
     EXPECT_FALSE(socketOpen("eth0", AF_INET));
     EXPECT_FALSE(socketOpen("eth0", AF_INET));
     EXPECT_TRUE(socketOpen("eth1", AF_INET));
     EXPECT_TRUE(socketOpen("eth1", AF_INET));
     EXPECT_FALSE(socketOpen("lo", 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
 // 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_TRUE(socketOpen("eth1", AF_INET6));
     EXPECT_FALSE(socketOpen("lo", 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("eth0", AF_INET));
     EXPECT_FALSE(socketOpen("eth1", AF_INET));
     EXPECT_FALSE(socketOpen("eth1", AF_INET));
     EXPECT_FALSE(socketOpen("lo", AF_INET));
     EXPECT_FALSE(socketOpen("lo", AF_INET));