BIND 10 master, updated. ea193899c17695b2a626cd5fe733506518eedcc0 [master] Merge branch 'trac3195' (DHCPv6 unicast sockets)

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Oct 23 10:00:52 UTC 2013


The branch, master has been updated
       via  ea193899c17695b2a626cd5fe733506518eedcc0 (commit)
       via  72e601f2a57ab70b25d50877c8e49242739d1c9f (commit)
       via  358735b6bc9fe4caf2b12466e54a90eb69a4e673 (commit)
       via  07a72f62c2e93585de92d032b5af56c9a2ca562b (commit)
       via  ebeb291e7a086a17a6fcddb1abcab583dc59b46c (commit)
       via  e3683e45ff2288d6b1307a0c9d15271df9d0c722 (commit)
       via  4d71b071d1feafe88cc3756fd4e678301e5f0b70 (commit)
       via  770f4dfb5ac6abd620a01215ed85c1719b946f11 (commit)
       via  426203928242be40458c4e2ea861257e32424b14 (commit)
       via  f8487b5689486f3563f6b7546ecd867f473d74b1 (commit)
      from  3d320e51a5518a6bb3578ff8f804ac1479aee9b6 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ea193899c17695b2a626cd5fe733506518eedcc0
Merge: 3d320e5 72e601f
Author: Tomek Mrugalski <tomasz at isc.org>
Date:   Wed Oct 23 12:00:40 2013 +0200

    [master] Merge branch 'trac3195' (DHCPv6 unicast sockets)
    
    Conflicts:
    	ChangeLog

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                |   14 ++++
 doc/guide/bind10-guide.xml               |   37 +++++++++
 src/bin/dhcp6/ctrl_dhcp6_srv.cc          |    2 +-
 src/bin/dhcp6/dhcp6_messages.mes         |    3 +
 src/bin/dhcp6/dhcp6_srv.cc               |   11 ++-
 src/lib/dhcp/iface_mgr.cc                |   90 ++++++++++++++++++--
 src/lib/dhcp/iface_mgr.h                 |   24 ++++++
 src/lib/dhcp/tests/iface_mgr_unittest.cc |  134 ++++++++++++++++++++++++++++++
 src/lib/dhcpsrv/cfgmgr.cc                |   37 ++++++++-
 src/lib/dhcpsrv/cfgmgr.h                 |   17 ++++
 src/lib/dhcpsrv/dhcp_parsers.cc          |    1 +
 src/lib/dhcpsrv/tests/cfgmgr_unittest.cc |   62 +++++++++++++-
 12 files changed, 414 insertions(+), 18 deletions(-)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 8abd1db..e734b12 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+695.	[func]		tomek
+	b10-dhcp6 is now able to listen on global IPv6 unicast addresses.
+	(Trac #3195, git 72e601f2a57ab70b25d50877c8e49242739d1c9f)
+
+694.	[bug]		tomek
+	b10-dhcp6 now handles exceptions better when processing initial
+	configuration. In particular, errors with socket binding do not
+	prevent b10-dhcp6 from establishing configuration session anymore.
+	(Trac #3195, git 72e601f2a57ab70b25d50877c8e49242739d1c9f)
+
+693.	[bug]		tomek
+	b10-dhcp6 now handles IPv6 interface enabling correctly.
+	(Trac #3195, git 72e601f2a57ab70b25d50877c8e49242739d1c9f)
+
 692.	[bug]		marcin
 	b10-dhcp4: Fix a bug whereby the Parameter Request List was not parsed
 	by the server and requested DHCPv4 options were not returned to the
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index b2ee52d..9ab1a9a 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -4669,6 +4669,43 @@ Dhcp6/subnet6/	list
       </para>
     </section>
 
+    <section id="dhcp6-unicast">
+      <title>Unicast traffic support</title>
+      <para>
+        When DHCPv6 server starts up, by default it listens to the DHCP traffic
+        sent to multicast address ff02::1:2 on each interface that it is
+        configured to listen on (see <xref linkend="dhcp6-interface-selection"/>).
+        In some cases it is useful to configure a server to handle incoming
+        traffic sent to the global unicast addresses as well. The most common
+        reason for that is to have relays send their traffic to the server
+        directly. To configure server to listen on specific unicast address, a
+        notation to specify interfaces has been extended. Interface name can be
+        optionally followed by a slash, followed by global unicast address that
+        server should listen on. That will be done in addition to normal
+        link-local binding + listening on ff02::1:2 address. The sample commands
+        listed below show how to listen on 2001:db8::1 (a global address)
+        configured on the eth1 interface.
+      </para>
+      <para>
+        <screen>
+> <userinput>config set Dhcp6/interfaces[0] eth1/2001:db8::1</userinput>
+> <userinput>config commit</userinput></screen>
+        When configuration gets committed, the server will start to listen on
+        eth1 on link-local address, mutlicast group (ff02::1:2) and 2001:db8::1.
+      </para>
+      <para>
+        It is possible to mix interface names, wildcards and interface name/addresses
+        on the Dhcp6/interface list. It is not possible to specify more than one
+        unicast address on a given interface.
+      </para>
+      <para>
+        Care should be taken to specify proper unicast addresses. The server will
+        attempt to bind to those addresses specified, without any additional checks.
+        That approach is selected on purpose, so in the software can be used to
+        communicate over uncommon addresses if the administrator desires so.
+      </para>
+    </section>
+
     <section>
       <title>Subnet and Address Pool</title>
       <para>
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 7168b91..e42c83b 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -216,7 +216,7 @@ void ControlledDhcpv6Srv::establishSession() {
         // reopen sockets according to new configuration.
         openActiveSockets(getPort());
 
-    } catch (const DhcpConfigError& ex) {
+    } catch (const std::exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
 
     }
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index fb50e77..9678b4f 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -450,6 +450,9 @@ This debug message indicates that a shutdown of the IPv6 server has
 been requested via a call to the 'shutdown' method of the core Dhcpv6Srv
 object.
 
+% DHCP6_SOCKET_UNICAST server is about to open socket on address %1 on interface %2
+This is a debug message that inform that a unicast socket will be opened.
+
 % DHCP6_SRV_CONSTRUCT_ERROR error creating Dhcpv6Srv object, reason: %1
 This error message indicates that during startup, the construction of a
 core component within the IPv6 DHCP server (the Dhcpv6 server object)
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 2b26c3b..381d7c0 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -2229,7 +2229,7 @@ Dhcpv6Srv::openActiveSockets(const uint16_t port) {
                       << " trying to reopen sockets after reconfiguration");
         }
         if (CfgMgr::instance().isActiveIface(iface->getName())) {
-            iface_ptr->inactive4_ = false;
+            iface_ptr->inactive6_ = false;
             LOG_INFO(dhcp6_logger, DHCP6_ACTIVATE_INTERFACE)
                 .arg(iface->getFullName());
 
@@ -2242,6 +2242,15 @@ Dhcpv6Srv::openActiveSockets(const uint16_t port) {
             iface_ptr->inactive6_ = true;
 
         }
+
+        iface_ptr->clearUnicasts();
+
+        const IOAddress* unicast = CfgMgr::instance().getUnicast(iface->getName());
+        if (unicast) {
+            LOG_INFO(dhcp6_logger, DHCP6_SOCKET_UNICAST).arg(unicast->toText())
+                .arg(iface->getName());
+            iface_ptr->addUnicast(*unicast);
+        }
     }
     // Let's reopen active sockets. openSockets6 will check internally whether
     // sockets are marked active or inactive.
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index 128aafe..0ad35a5 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -177,6 +177,17 @@ 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) {
+            isc_throw(BadValue, "Address " << addr.toText()
+                      << " already defined on the " << name_ << " interface.");
+        }
+    }
+    unicasts_.push_back(addr);
+}
+
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {
@@ -343,8 +354,10 @@ bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
 
             }
             if (sock < 0) {
+                const char* errstr = strerror(errno);
                 isc_throw(SocketConfigError, "failed to open IPv4 socket"
-                          << " supporting broadcast traffic");
+                          << " supporting broadcast traffic, reason:"
+                          << errstr);
             }
 
             count++;
@@ -368,6 +381,23 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
             continue;
         }
 
+        // Open unicast sockets if there are any unicast addresses defined
+        Iface::AddressCollection unicasts = iface->getUnicasts();
+        for (Iface::AddressCollection::iterator addr = unicasts.begin();
+             addr != unicasts.end(); ++addr) {
+
+            sock = openSocket(iface->getName(), *addr, port);
+            if (sock < 0) {
+                const char* errstr = strerror(errno);
+                isc_throw(SocketConfigError, "failed to open unicast socket on "
+                          << addr->toText() << " on interface " << iface->getName()
+                          << ", reason: " << errstr);
+            }
+
+            count++;
+
+        }
+
         Iface::AddressCollection addrs = iface->getAddresses();
         for (Iface::AddressCollection::iterator addr = addrs.begin();
              addr != addrs.end();
@@ -389,7 +419,10 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
 
             sock = openSocket(iface->getName(), *addr, port);
             if (sock < 0) {
-                isc_throw(SocketConfigError, "failed to open unicast socket");
+                const char* errstr = strerror(errno);
+                isc_throw(SocketConfigError, "failed to open link-local socket on "
+                          << addr->toText() << " on interface "
+                          << iface->getName() << ", reason: " << errstr);
             }
 
             // Binding socket to unicast address and then joining multicast group
@@ -414,8 +447,10 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
                                    IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
                                    port);
             if (sock2 < 0) {
+                const char* errstr = strerror(errno);
                 isc_throw(SocketConfigError, "Failed to open multicast socket on "
-                          << " interface " << iface->getFullName());
+                          << " interface " << iface->getFullName() << ", reason:"
+                          << errstr);
                 iface->delSocket(sock); // delete previously opened socket
             }
 #endif
@@ -608,7 +643,9 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
         // interface.
         sock.open(asio::ip::udp::v4(), err_code);
         if (err_code) {
-            isc_throw(Unexpected, "failed to open UDPv4 socket");
+            const char* errstr = strerror(errno);
+            isc_throw(Unexpected, "failed to open UDPv4 socket, reason:"
+                      << errstr);
         }
         sock.set_option(asio::socket_base::broadcast(true), err_code);
         if (err_code) {
@@ -1137,16 +1174,50 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
                   << pkt.getIface());
     }
 
+
     const Iface::SocketCollection& socket_collection = iface->getSockets();
+
+    Iface::SocketCollection::const_iterator candidate = socket_collection.end();
+
     Iface::SocketCollection::const_iterator s;
     for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
-        if ((s->family_ == AF_INET6) &&
-            (!s->addr_.getAddress().to_v6().is_multicast())) {
+
+        // We should not merge those conditions for debugging reasons.
+
+        // V4 sockets are useless for sending v6 packets.
+        if (s->family_ != AF_INET6) {
+            continue;
+        }
+
+        // Sockets bound to multicast address are useless for sending anything.
+        if (s->addr_.getAddress().to_v6().is_multicast()) {
+            continue;
+        }
+
+        if (s->addr_ == pkt.getLocalAddr()) {
+            // This socket is bound to the source address. This is perfect
+            // match, no need to look any further.
             return (s->sockfd_);
         }
-        /// @todo: Add more checks here later. If remote address is
-        /// not link-local, we can't use link local bound socket
-        /// to send data.
+
+        // If we don't have any other candidate, this one will do
+        if (candidate == socket_collection.end()) {
+            candidate = s;
+        } else {
+            // If we want to send something to link-local and the socket is
+            // bound to link-local or we want to send to global and the socket
+            // is bound to global, then use it as candidate
+            if ( (pkt.getRemoteAddr().getAddress().to_v6().is_link_local() &&
+                s->addr_.getAddress().to_v6().is_link_local()) ||
+                 (!pkt.getRemoteAddr().getAddress().to_v6().is_link_local() &&
+                  !s->addr_.getAddress().to_v6().is_link_local()) ) {
+                candidate = s;
+            }
+        }
+    }
+
+    if (candidate != socket_collection.end()) {
+        return (candidate->sockfd_);
     }
 
     isc_throw(Unexpected, "Interface " << iface->getFullName()
@@ -1175,5 +1246,6 @@ uint16_t IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
               << " does not have any suitable IPv4 sockets open.");
 }
 
+
 } // end of namespace isc::dhcp
 } // end of namespace isc
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index 217524d..2f48813 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -264,6 +264,27 @@ public:
     /// @return collection of sockets added to interface
     const SocketCollection& getSockets() const { return sockets_; }
 
+    /// @brief Removes any unicast addresses
+    ///
+    /// Removes any unicast addresses that the server was configured to
+    /// listen on
+    void clearUnicasts() {
+        unicasts_.clear();
+    }
+
+    /// @brief Adds unicast the server should listen on
+    ///
+    /// @throw BadValue if specified address is already defined on interface
+    /// @param addr unicast address to listen on
+    void addUnicast(const isc::asiolink::IOAddress& addr);
+
+    /// @brief Returns a container of addresses the server should listen on
+    ///
+    /// @return address collection (may be empty)
+    const AddressCollection& getUnicasts() const {
+        return unicasts_;
+    }
+
 protected:
     /// Socket used to send data.
     SocketCollection sockets_;
@@ -277,6 +298,9 @@ protected:
     /// List of assigned addresses.
     AddressCollection addrs_;
 
+    /// List of unicast addresses the server should listen on
+    AddressCollection unicasts_;
+
     /// Link-layer address.
     uint8_t mac_[MAX_MAC_LEN];
 
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 488ecb3..b936b5c 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -145,6 +145,27 @@ public:
         return (sockets_count);
     }
 
+    /// @brief returns socket bound to a specific address (or NULL)
+    ///
+    /// A helper function, used to pick a socketinfo that is bound to a given
+    /// address.
+    ///
+    /// @param sockets sockets collection
+    /// @param addr address the socket is bound to
+    ///
+    /// @return socket info structure (or NULL)
+    const isc::dhcp::SocketInfo*
+    getSocketByAddr(const isc::dhcp::Iface::SocketCollection& sockets,
+                    const IOAddress& addr) {
+        for (isc::dhcp::Iface::SocketCollection::const_iterator s =
+                 sockets.begin(); s != sockets.end(); ++s) {
+            if (s->addr_ == addr) {
+                return (&(*s));
+            }
+        }
+        return (NULL);
+    }
+
 };
 
 // We need some known interface to work reliably. Loopback interface is named
@@ -781,6 +802,7 @@ TEST_F(IfaceMgrTest, sendReceive6) {
     // try to send/receive data over the closed socket. Closed socket's descriptor is
     // still being hold by IfaceMgr which will try to use it to receive data.
     close(socket1);
+    close(socket2);
     EXPECT_THROW(ifacemgr->receive6(10), SocketReadError);
     EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
 }
@@ -1520,4 +1542,116 @@ TEST_F(IfaceMgrTest, controlSession) {
     close(pipefd[0]);
 }
 
+// Test checks if the unicast sockets can be opened.
+// This test is now disabled, because there is no reliable way to test it. We
+// can't even use loopback, beacuse openSockets() skips loopback interface
+// (as it should be, because DHCP server is not supposed to listen on loopback).
+TEST_F(IfaceMgrTest, DISABLED_openUnicastSockets) {
+    /// @todo Need to implement a test that is able to check whether we can open
+    /// unicast sockets. There are 2 problems with it:
+    /// 1. We need to have a non-link-local address on an interface that is
+    ///    up, running, IPv6 and multicast capable
+    /// 2. We need that information on every OS that we run tests on. So far
+    ///    we are only supporting interface detection in Linux.
+    ///
+    /// To achieve this, we will probably need a pre-test setup, similar to what
+    /// BIND9 is doing (i.e. configuring well known addresses on loopback).
+
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+    // Get the interface (todo: which interface)
+    Iface* iface = ifacemgr->getIface("eth0");
+    ASSERT_TRUE(iface);
+    iface->inactive6_ = false;
+
+    // Tell the interface that it should bind to this global interface
+    EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1")));
+
+    // Tell IfaceMgr to open sockets. This should trigger at least 2 sockets
+    // to open on eth0: link-local and global. On some systems (Linux), an
+    // additional socket for multicast may be opened.
+    EXPECT_TRUE(ifacemgr->openSockets6(PORT1));
+
+    const Iface::SocketCollection& sockets = iface->getSockets();
+    ASSERT_GE(2, sockets.size());
+
+    // Global unicast should be first
+    EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("2001:db8::1")));
+    EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("figure-out-link-local-addr")));
+}
+
+// Checks if there is a protection against unicast duplicates.
+TEST_F(IfaceMgrTest, unicastDuplicates) {
+    NakedIfaceMgr ifacemgr;
+
+    Iface* iface = ifacemgr.getIface(LOOPBACK);
+    if (iface == NULL) {
+        cout << "Local loopback interface not found. Skipping test. " << endl;
+        return;
+    }
+
+    // Tell the interface that it should bind to this global interface
+    EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1")));
+
+    // Tell the interface that it should bind to this global interface
+    EXPECT_THROW(iface->addUnicast(IOAddress("2001:db8::1")), BadValue);
+}
+
+// This test requires addresses 2001:db8:15c::1/128 and fe80::1/64 to be
+// configured on loopback interface
+//
+// Useful commands:
+// ip a a 2001:db8:15c::1/128 dev lo
+// ip a a fe80::1/64 dev lo
+//
+// If you do not issue those commands before running this test, it will fail.
+TEST_F(IfaceMgrTest, DISABLED_getSocket) {
+    // Testing socket operation in a portable way is tricky
+    // without interface detection implemented.
+
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+    IOAddress lo_addr("::1");
+    IOAddress link_local("fe80::1");
+    IOAddress global("2001:db8:15c::1");
+
+    IOAddress dst_link_local("fe80::dead:beef");
+    IOAddress dst_global("2001:db8:15c::dead:beef");
+
+    // Bind loopback address
+    int socket1 = ifacemgr->openSocket(LOOPBACK, lo_addr, 10547);
+    EXPECT_GE(socket1, 0); // socket >= 0
+
+    // Bind link-local address
+    int socket2 = ifacemgr->openSocket(LOOPBACK, link_local, 10547);
+    EXPECT_GE(socket2, 0);
+
+    int socket3 = ifacemgr->openSocket(LOOPBACK, global, 10547);
+    EXPECT_GE(socket3, 0);
+
+    // Let's make sure those sockets are unique
+    EXPECT_NE(socket1, socket2);
+    EXPECT_NE(socket2, socket3);
+    EXPECT_NE(socket3, socket1);
+
+    // Create a packet
+    Pkt6 pkt6(DHCPV6_SOLICIT, 123);
+    pkt6.setIface(LOOPBACK);
+
+    // Check that packets sent to link-local will get socket bound to link local
+    pkt6.setLocalAddr(global);
+    pkt6.setRemoteAddr(dst_global);
+    EXPECT_EQ(socket3, ifacemgr->getSocket(pkt6));
+
+    // Check that packets sent to link-local will get socket bound to link local
+    pkt6.setLocalAddr(link_local);
+    pkt6.setRemoteAddr(dst_link_local);
+    EXPECT_EQ(socket2, ifacemgr->getSocket(pkt6));
+
+    // Close sockets here because the following tests will want to
+    // open sockets on the same ports.
+    ifacemgr->closeSockets();
+}
+
+
 }
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 56e4c8e..b4fb11d 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -16,6 +16,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
+#include <string>
 
 using namespace isc::asiolink;
 using namespace isc::util;
@@ -269,14 +270,31 @@ std::string CfgMgr::getDataDir() {
 
 void
 CfgMgr::addActiveIface(const std::string& iface) {
-    if (isIfaceListedActive(iface)) {
+
+    size_t pos = iface.find("/");
+    std::string iface_copy = iface;
+
+    if (pos != std::string::npos) {
+        std::string addr_string = iface.substr(pos + 1);
+        try {
+            IOAddress addr(addr_string);
+            iface_copy = iface.substr(0,pos);
+            unicast_addrs_.insert(make_pair(iface_copy, addr));
+        } catch (...) {
+            isc_throw(BadValue, "Can't convert '" << addr_string
+                      << "' into address in interface defition ('"
+                      << iface << "')");
+        }
+    }
+
+    if (isIfaceListedActive(iface_copy)) {
         isc_throw(DuplicateListeningIface,
-                  "attempt to add duplicate interface '" << iface << "'"
+                  "attempt to add duplicate interface '" << iface_copy << "'"
                   " to the set of interfaces on which server listens");
     }
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_IFACE)
-        .arg(iface);
-    active_ifaces_.push_back(iface);
+        .arg(iface_copy);
+    active_ifaces_.push_back(iface_copy);
 }
 
 void
@@ -292,6 +310,8 @@ CfgMgr::deleteActiveIfaces() {
               DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES);
     active_ifaces_.clear();
     all_ifaces_active_ = false;
+
+    unicast_addrs_.clear();
 }
 
 bool
@@ -319,6 +339,15 @@ CfgMgr::isIfaceListedActive(const std::string& iface) const {
     return (false);
 }
 
+const isc::asiolink::IOAddress*
+CfgMgr::getUnicast(const std::string& iface) const {
+    UnicastIfacesCollection::const_iterator addr = unicast_addrs_.find(iface);
+    if (addr == unicast_addrs_.end()) {
+        return (NULL);
+    }
+    return (&(*addr).second);
+}
+
 CfgMgr::CfgMgr()
     : datadir_(DHCP_DATA_DIR),
       all_ifaces_active_(false) {
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 0ec51d0..b063f92 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -305,6 +305,17 @@ public:
     /// interfaces on which server is configured to listen.
     bool isActiveIface(const std::string& iface) const;
 
+    /// @brief returns unicast a given interface should listen on (or NULL)
+    ///
+    /// This method will return an address for a specified interface, if the
+    /// server is supposed to listen on unicast address. This address is
+    /// intended to be used immediately. This pointer is valid only until
+    /// the next configuration change.
+    ///
+    /// @return IOAddress pointer (or NULL if none)
+    const isc::asiolink::IOAddress*
+    getUnicast(const std::string& iface) const;
+
 protected:
 
     /// @brief Protected constructor.
@@ -372,6 +383,12 @@ private:
     std::list<std::string> active_ifaces_;
     //@}
 
+    /// @name a collection of unicast addresses and the interfaces names the
+    //        server is supposed to listen on
+    //@{
+    typedef std::map<std::string, isc::asiolink::IOAddress> UnicastIfacesCollection;
+    UnicastIfacesCollection unicast_addrs_;
+
     /// A flag which indicates that server should listen on all available
     /// interfaces.
     bool all_ifaces_active_;
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index e03d13f..2684e2f 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -231,6 +231,7 @@ InterfaceListConfigParser::commit() {
 
 bool
 InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
+
     for (IfaceListStorage::const_iterator it = interfaces_.begin();
          it != interfaces_.end(); ++it) {
         if (iface == *it) {
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 38d2f0a..94f78c3 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -579,20 +579,55 @@ TEST_F(CfgMgrTest, optionSpace6) {
 TEST_F(CfgMgrTest, addActiveIface) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
 
-    cfg_mgr.addActiveIface("eth0");
-    cfg_mgr.addActiveIface("eth1");
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth0"));
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth1"));
 
     EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
     EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
 
-    cfg_mgr.deleteActiveIfaces();
+    EXPECT_NO_THROW(cfg_mgr.deleteActiveIfaces());
 
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
 }
 
+
+// This test verifies that it is possible to specify interfaces that server
+// should listen on.
+TEST_F(CfgMgrTest, addUnicastAddresses) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth1/2001:db8::1"));
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth2/2001:db8::2"));
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth3"));
+
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth3"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth4"));
+
+    ASSERT_TRUE(cfg_mgr.getUnicast("eth1"));
+    EXPECT_EQ("2001:db8::1", cfg_mgr.getUnicast("eth1")->toText());
+    EXPECT_EQ("2001:db8::2", cfg_mgr.getUnicast("eth2")->toText());
+    EXPECT_FALSE(cfg_mgr.getUnicast("eth3"));
+    EXPECT_FALSE(cfg_mgr.getUnicast("eth4"));
+
+    EXPECT_NO_THROW(cfg_mgr.deleteActiveIfaces());
+
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth3"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth4"));
+
+    ASSERT_FALSE(cfg_mgr.getUnicast("eth1"));
+    ASSERT_FALSE(cfg_mgr.getUnicast("eth2"));
+    EXPECT_FALSE(cfg_mgr.getUnicast("eth3"));
+    EXPECT_FALSE(cfg_mgr.getUnicast("eth4"));
+}
+
+
 // This test verifies that it is possible to set the flag which configures the
 // server to listen on all interfaces.
 TEST_F(CfgMgrTest, activateAllIfaces) {
@@ -618,6 +653,27 @@ TEST_F(CfgMgrTest, activateAllIfaces) {
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
 }
 
+/// @todo Add unit-tests for testing:
+/// - addActiveIface() with invalid interface name
+/// - addActiveIface() with the same interface twice
+/// - addActiveIface() with a bogus address
+///
+/// This is somewhat tricky. Care should be taken here, because it is rather
+/// difficult to decide if interface name is valid or not. Some servers, e.g.
+/// dibbler, allow to specify interface names that are not currently present in
+/// the system. The server accepts them, but upon discovering that they are
+/// yet available (for different definitions of not being available), adds
+/// the to to-be-activated list.
+///
+/// Cases covered by dibbler are:
+/// - missing interface (e.g. PPP connection that is not established yet)
+/// - downed interface (no link local address, no way to open sockets)
+/// - up, but not running interface (wifi up, but not associated)
+/// - tentative addresses (interface up and running, but DAD procedure is
+///   still in progress)
+/// - weird interfaces without link-local addresses (don't ask, 6rd tunnels
+///   look weird to me as well)
+
 // No specific tests for getSubnet6. That method (2 overloaded versions) is tested
 // in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
 // (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)



More information about the bind10-changes mailing list