Browse Source

Merge branch 'trac1237'

Conflicts:
	ChangeLog
	src/bin/dhcp4/dhcp4_srv.cc
	src/bin/dhcp6/dhcp6_srv.cc
	src/lib/dhcp/iface_mgr.cc
	src/lib/dhcp/iface_mgr.h
	src/lib/dhcp/tests/iface_mgr_unittest.cc
Tomek Mrugalski 13 years ago
parent
commit
d82df10e19

+ 6 - 0
ChangeLog

@@ -1,3 +1,9 @@
+362.	[func]		tomek
+	libdhcp++: Interface detection in Linux implemented. libdhcp++
+	if now able to detect available network interfaces, its link-layer
+	addresses, flags and configured IPv4 and IPv6 addresses.
+	(Trac #1237, git 8a040737426aece7cc92a795f2b712d7c3407513)
+
 361.	[func]		tomek
 	libdhcp++: Transmission and reception of DHCPv4 packets is now
 	implemented. Low-level hacks are not implemented for transmission

+ 31 - 0
configure.ac

@@ -369,6 +369,36 @@ AC_HEADER_STDBOOL
 AC_TYPE_SIZE_T
 
 
+# Detect OS type (it may be used to do OS-specific things, e.g.
+# interface detection in DHCP)
+AC_MSG_CHECKING(OS family)
+system=`uname -s`
+case $system in
+    Linux)
+      OS_TYPE="Linux"
+      CPPFLAGS="$CPPFLAGS -DOS_LINUX"
+      ;;
+    Darwin | FreeBSD | NetBSD | OpenBSD)
+      OS_TYPE="BSD"
+      CPPFLAGS="$CPPFLAGS -DOS_BSD"
+      ;;
+    Solaris)
+      OS_TYPE="Solaris"
+      CPPFLAGS="$CPPFLAGS -DOS_SOLARIS"
+      ;;
+    *)
+      OS_TYPE="Unknown"
+      AC_MSG_WARN("Unsupported OS: uname returned $system")
+      ;;
+esac
+AC_MSG_RESULT($OS_TYPE)
+
+AM_CONDITIONAL(OS_LINUX, test $OS_TYPE = Linux)
+AM_COND_IF([OS_LINUX], [AC_DEFINE([OS_LINUX], [1], [Running on Linux?])])
+AM_CONDITIONAL(OS_BSD, test $OS_TYPE = BSD)
+AM_COND_IF([OS_BSD], [AC_DEFINE([OS_BSD], [1], [Running on BSD?])])
+AM_CONDITIONAL(OS_SOLARIS, test $OS_TYPE = Solaris)
+AM_COND_IF([OS_SOLARIS], [AC_DEFINE([OS_SOLARIS], [1], [Running on Solaris?])])
 
 AC_MSG_CHECKING(for sa_len in struct sockaddr)
 AC_TRY_COMPILE([
@@ -1144,6 +1174,7 @@ Flags:
   CXXFLAGS:      $CXXFLAGS
   LDFLAGS:       $LDFLAGS
   B10_CXXFLAGS:  $B10_CXXFLAGS
+  OS Family:     $OS_TYPE
 dnl includes too
   Python:        ${PYTHON_INCLUDES}
                  ${PYTHON_CXXFLAGS}

+ 2 - 2
src/bin/dhcp6/Makefile.am

@@ -34,10 +34,10 @@ pkglibexec_PROGRAMS = b10-dhcp6
 
 b10_dhcp6_SOURCES = main.cc dhcp6_srv.cc dhcp6_srv.h
 
-b10_dhcp6_LDADD = $(top_builddir)/src/lib/dhcp/libdhcp++.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/liblog.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
 
 # TODO: config.h.in is wrong because doesn't honor pkgdatadir
 # and can't use @datadir@ because doesn't expand default ${prefix}

+ 13 - 5
src/bin/dhcp6/dhcp6_srv.cc

@@ -27,13 +27,21 @@ using namespace isc::dhcp;
 using namespace isc::asiolink;
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
-
-//void Dhcpv6Srv::Dhcpv6Srv_impl(uint16_t port) {
     cout << "Initialization" << endl;
 
-    // First call to instance() will create IfaceMgr (it's a singleton).
-    // It may throw something if things go wrong.
-    IfaceMgr::instance();
+    // first call to instance() will create IfaceMgr (it's a singleton)
+    // it may throw something if things go wrong
+    try {
+	IfaceMgr::instance();
+    } catch (const std::exception &e) {
+	cout << "Failed to instantiate InterfaceManager:" << e.what() << ". Aborting." << endl;
+	shutdown = true;
+    }
+
+    if (IfaceMgr::instance().countIfaces() == 0) {
+	cout << "Failed to detect any network interfaces. Aborting." << endl;
+	shutdown = true;
+    }
 
     // Now try to open IPv6 sockets on detected interfaces.
     IfaceMgr::instance().openSockets6(port);

+ 2 - 0
src/lib/dhcp/Makefile.am

@@ -11,6 +11,8 @@ lib_LTLIBRARIES = libdhcp++.la
 libdhcp___la_SOURCES  =
 libdhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
 libdhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
+libdhcp___la_SOURCES += iface_mgr_linux.cc
+libdhcp___la_SOURCES += iface_mgr_bsd.cc
 libdhcp___la_SOURCES += option.cc option.h
 libdhcp___la_SOURCES += option6_ia.cc option6_ia.h
 libdhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h

+ 36 - 17
src/lib/dhcp/iface_mgr.cc

@@ -12,6 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <config.h>
 #include <sstream>
 #include <fstream>
 #include <string.h>
@@ -53,7 +54,9 @@ IfaceMgr::instance() {
 }
 
 IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
-    :name_(name), ifindex_(ifindex), mac_len_(0)
+    :name_(name), ifindex_(ifindex), mac_len_(0), flag_loopback_(false),
+     flag_up_(false), flag_running_(false), flag_multicast_(false),
+     flag_broadcast_(false), flags_(0), hardware_type_(0)
 {
     memset(mac_, 0, sizeof(mac_));
 }
@@ -72,7 +75,7 @@ IfaceMgr::Iface::getPlainMac() const {
     tmp << hex;
     for (int i = 0; i < mac_len_; i++) {
         tmp.width(2);
-        tmp <<  int(mac_[i]);
+        tmp <<  static_cast<int>(mac_[i]);
         if (i < mac_len_-1) {
             tmp << ":";
         }
@@ -153,7 +156,7 @@ IfaceMgr::~IfaceMgr() {
 }
 
 void
-IfaceMgr::detectIfaces() {
+IfaceMgr::stubDetectIfaces() {
     string ifaceName, linkLocal;
 
     // TODO do the actual detection. Currently interface detection is faked
@@ -167,8 +170,8 @@ IfaceMgr::detectIfaces() {
         ifstream interfaces("interfaces.txt");
 
         if (!interfaces.good()) {
-            cout << "Failed to read interfaces.txt file." << endl;
-            isc_throw(Unexpected, "Failed to read interfaces.txt");
+            cout << "interfaces.txt file is not available. Stub interface detection skipped." << endl;
+            return;
         }
         interfaces >> ifaceName;
         interfaces >> linkLocal;
@@ -192,6 +195,12 @@ IfaceMgr::detectIfaces() {
     }
 }
 
+#if !defined(OS_LINUX) && !defined(OS_BSD)
+void IfaceMgr::detectIfaces() {
+    stubDetectIfaces();
+}
+#endif
+
 bool IfaceMgr::openSockets4(uint16_t port) {
     int sock;
     int count = 0;
@@ -259,8 +268,8 @@ bool IfaceMgr::openSockets6(uint16_t port) {
                 return (false);
             }
 
-            if ( !joinMcast(sock, iface->getName(),
-                             string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
+            if ( !joinMulticast(sock, iface->getName(),
+                                string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
                 close(sock);
                 isc_throw(Unexpected, "Failed to join " << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
                           << " multicast group.");
@@ -289,15 +298,25 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
          iface!=ifaces_.end();
          ++iface) {
 
+        const AddressCollection& addrs = iface->getAddresses();
+
         out << "Detected interface " << iface->getFullName()
-             << ", mac=" << iface->getPlainMac() << endl;
-        out << "flags=" <<  endl;
-        out << "  " << iface->getAddresses().size() << " addr(s):" << endl;
-        for (AddressCollection::const_iterator addr=iface->getAddresses().begin();
-             addr != iface->getAddresses().end();
-             ++addr) {
-            out << "  " << addr->toText() << endl;
+             << ", hwtype=" << iface->hardware_type_ << ", maclen=" << iface->mac_len_
+             << ", mac=" << iface->getPlainMac();
+        out << ", flags=" << hex << iface->flags_ << dec << "("
+            << (iface->flag_loopback_?"LOOPBACK ":"")
+            << (iface->flag_up_?"UP ":"")
+            << (iface->flag_running_?"RUNNING ":"")
+            << (iface->flag_multicast_?"MULTICAST ":"")
+            << (iface->flag_broadcast_?"BROADCAST ":"")
+            << ")" << endl;
+        out << "  " << addrs.size() << " addr(s):";
+
+        for (AddressCollection::const_iterator addr = addrs.begin();
+             addr != addrs.end(); ++addr) {
+            out << "  " << addr->toText();
         }
+        out << endl;
     }
 }
 
@@ -403,7 +422,7 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, int port) {
            addr.getAddress().to_v6().to_bytes().data(),
            sizeof(addr6.sin6_addr));
 #ifdef HAVE_SA_LEN
-    addr6->sin6_len = sizeof(addr6);
+    addr6.sin6_len = sizeof(addr6);
 #endif
 
     // TODO: use sockcreator once it becomes available
@@ -450,7 +469,7 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, int port) {
         // are link and site-scoped, so there is no sense to join those groups
         // with global addresses.
 
-        if ( !joinMcast( sock, iface.getName(),
+        if ( !joinMulticast( sock, iface.getName(),
                          string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
             close(sock);
             isc_throw(Unexpected, "Failed to join " << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
@@ -468,7 +487,7 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, int port) {
 }
 
 bool
-IfaceMgr::joinMcast(int sock, const std::string& ifname,
+IfaceMgr::joinMulticast(int sock, const std::string& ifname,
 const std::string & mcast) {
 
     struct ipv6_mreq mreq;

+ 48 - 2
src/lib/dhcp/iface_mgr.h

@@ -87,6 +87,14 @@ public:
         /// @return MAC address as a plain text (string)
         std::string getPlainMac() const;
 
+        /// @brief Sets flag_*_ fields based on bitmask value returned by OS
+        ///
+        /// Note: Implementation of this method is OS-dependent as bits have
+        /// different meaning on each OS.
+        ///
+        /// @param flags bitmask value returned by OS in interface detection
+        void setFlags(uint32_t flags);
+
         /// @brief Returns interface index.
         ///
         /// @return interface index
@@ -159,11 +167,35 @@ public:
         /// list of assigned addresses
         AddressCollection addrs_;
 
+    public:
         /// link-layer address
         uint8_t mac_[MAX_MAC_LEN];
 
         /// length of link-layer address (usually 6)
         int mac_len_;
+
+        /// specifies if selected interface is loopback
+        bool flag_loopback_;
+
+        /// specifies if selected interface is up
+        bool flag_up_;
+
+        /// flag specifies if selected interface is running
+        /// (e.g. cable plugged in, wifi associated)
+        bool flag_running_;
+
+        /// flag specifies if selected interface is multicast capable
+        bool flag_multicast_;
+
+        /// flag specifies if selected interface is broadcast capable
+        bool flag_broadcast_;
+
+        /// interface flags (this value is as is returned by OS,
+        /// it may mean different things on different OSes)
+        uint32_t flags_;
+
+        /// hardware type
+        uint16_t hardware_type_;
     };
 
     // TODO performance improvement: we may change this into
@@ -316,6 +348,11 @@ public:
     /// @return true if any sockets were open
     bool openSockets4(uint16_t port = DHCP4_SERVER_PORT);
 
+    /// @brief returns number of detected interfaces
+    ///
+    /// @return number of detected interfaces
+    uint16_t countIfaces() { return ifaces_.size(); }
+
     // don't use private, we need derived classes in tests
 protected:
 
@@ -368,6 +405,15 @@ protected:
     void
     detectIfaces();
 
+    /// @brief Stub implementation of network interface detection.
+    ///
+    /// This implementations reads a single line from interfaces.txt file
+    /// and pretends to detect such interface. First interface name and
+    /// link-local IPv6 address or IPv4 address is read from the
+    /// intefaces.txt file.
+    void
+    stubDetectIfaces();
+
     // TODO: having 2 maps (ifindex->iface and ifname->iface would)
     //      probably be better for performance reasons
 
@@ -413,8 +459,8 @@ private:
     /// @return true if multicast join was successful
     ///
     bool
-    joinMcast(int sock, const std::string& ifname,
-              const std::string& mcast);
+    joinMulticast(int sock, const std::string& ifname,
+                  const std::string& mcast);
 
 };
 

+ 38 - 0
src/lib/dhcp/iface_mgr_bsd.cc

@@ -0,0 +1,38 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#if defined(OS_BSD)
+
+#include <dhcp/iface_mgr.h>
+#include <exceptions/exceptions.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace isc {
+
+void
+IfaceMgr::detectIfaces() {
+    // TODO do the actual detection on BSDs. Currently just calling
+    // stub implementation.
+    stubDetectIfaces();
+}
+
+}
+
+#endif

+ 381 - 0
src/lib/dhcp/iface_mgr_linux.cc

@@ -0,0 +1,381 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#include <config.h>
+
+#if defined(OS_LINUX)
+
+#include <dhcp/iface_mgr.h>
+#include <exceptions/exceptions.h>
+
+#include <net/if.h>
+#include <linux/if_link.h>
+#include <linux/rtnetlink.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+/// This is a structure that defines context for netlink connection.
+struct rtnl_handle
+{
+    int                fd;
+    struct sockaddr_nl local;
+    struct sockaddr_nl peer;
+    __u32              seq;
+    __u32              dump;
+};
+
+struct nlmsg_list
+{
+    struct nlmsg_list *next;
+    struct nlmsghdr h;
+};
+
+const int sndbuf = 32768;
+const int rcvbuf = 32768;
+
+namespace isc {
+
+
+/// @brief Opens netlink socket and initializes handle structure.
+///
+/// @exception Unexpected Thrown if socket configuration fails.
+///
+/// @param handle Context will be stored in this structure.
+void rtnl_open_socket(struct rtnl_handle& handle) {
+    // equivalent of rtnl_open
+    handle.fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+    if (handle.fd < 0) {
+        isc_throw(Unexpected, "Failed to create NETLINK socket.");
+    }
+
+    if (setsockopt(handle.fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) {
+        isc_throw(Unexpected, "Failed to set send buffer in NETLINK socket.");
+    }
+
+    if (setsockopt(handle.fd, SOL_SOCKET, SO_RCVBUF, &sndbuf, sizeof(rcvbuf)) < 0) {
+        isc_throw(Unexpected, "Failed to set receive buffer in NETLINK socket.");
+    }
+
+    memset(&handle.local, 0, sizeof(handle.local));
+    handle.local.nl_family = AF_NETLINK;
+    handle.local.nl_groups = 0;
+
+    if (bind(handle.fd, (struct sockaddr*)&handle.local, sizeof(handle.local)) < 0) {
+        isc_throw(Unexpected, "Failed to bind netlink socket.");
+    }
+
+    socklen_t addr_len = sizeof(handle.local);
+    if (getsockname(handle.fd, (struct sockaddr*)&handle.local, &addr_len) < 0) {
+        isc_throw(Unexpected, "Getsockname for netlink socket failed.");
+    }
+
+    // just 2 sanity checks and we are done
+    if ( (addr_len != sizeof(handle.local)) ||
+         (handle.local.nl_family != AF_NETLINK) ) {
+        isc_throw(Unexpected, "getsockname() returned unexpected data for netlink socket.");
+    }
+}
+
+/// @brief Sends request over NETLINK socket.
+///
+/// @param handle context structure
+/// @param family requested information family
+/// @param type request type (RTM_GETLINK or RTM_GETADDR)
+void rtnl_send_request(struct rtnl_handle& handle, int family, int type) {
+    struct {
+        struct nlmsghdr nlh;
+        struct rtgenmsg g;
+    } req;
+    struct sockaddr_nl nladdr;
+
+    memset(&nladdr, 0, sizeof(nladdr));
+    nladdr.nl_family = AF_NETLINK;
+
+    memset(&req, 0, sizeof(req));
+    req.nlh.nlmsg_len = sizeof(req);
+    req.nlh.nlmsg_type = type;
+    req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
+    req.nlh.nlmsg_pid = 0;
+    req.nlh.nlmsg_seq = handle.dump = ++handle.seq;
+    req.g.rtgen_family = family;
+
+    int status =  sendto(handle.fd, (void*)&req, sizeof(req), 0,
+                         (struct sockaddr*)&nladdr, sizeof(nladdr));
+
+    if (status<0) {
+        isc_throw(Unexpected, "Failed to send " << sizeof(nladdr) << " bytes over netlink socket.");
+    }
+}
+
+/// @brief Appends nlmsg to a list
+///
+/// @param n a message to be added
+/// @param link_info a list
+void rtnl_store_reply(struct nlmsghdr *n, struct nlmsg_list** link_info)
+{
+    struct nlmsg_list *h;
+    struct nlmsg_list **lp;
+
+    h = (nlmsg_list*)malloc(n->nlmsg_len+sizeof(void*));
+    if (h == NULL) {
+        isc_throw(Unexpected, "Failed to allocate " << n->nlmsg_len+sizeof(void*)
+                  << " bytes.");
+    }
+
+    memcpy(&h->h, n, n->nlmsg_len);
+    h->next = NULL;
+
+    for (lp = link_info; *lp; lp = &(*lp)->next) /* NOTHING */;
+    *lp = h;
+}
+
+void parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
+{
+    memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
+    while (RTA_OK(rta, len)) {
+        if (rta->rta_type <= max)
+            tb[rta->rta_type] = rta;
+        rta = RTA_NEXT(rta,len);
+    }
+    if (len) {
+        isc_throw(Unexpected, "Failed to parse RTATTR in netlink message.");
+    }
+}
+
+void ipaddrs_get(IfaceMgr::Iface& iface, struct nlmsg_list *addr_info) {
+    uint8_t addr[16];
+    struct rtattr * rta_tb[IFA_MAX+1];
+
+    for ( ;addr_info ;  addr_info = addr_info->next) {
+        struct nlmsghdr *n = &addr_info->h;
+        struct ifaddrmsg *ifa = (ifaddrmsg*)NLMSG_DATA(n);
+
+        // these are not the addresses you are looking for
+        if ( ifa->ifa_index != iface.getIndex()) {
+            continue;
+        }
+
+        if ( ifa->ifa_family == AF_INET6 ) {
+            memset(rta_tb, 0, sizeof(rta_tb));
+            parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
+            if (!rta_tb[IFA_LOCAL])
+                rta_tb[IFA_LOCAL]   = rta_tb[IFA_ADDRESS];
+            if (!rta_tb[IFA_ADDRESS])
+                rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
+
+            memcpy(addr,(char*)RTA_DATA(rta_tb[IFLA_ADDRESS]),16);
+            IOAddress a = IOAddress::from_bytes(AF_INET6, addr);
+            iface.addAddress(a);
+
+            /// TODO: Read lifetimes of configured addresses
+        }
+
+        if ( ifa->ifa_family == AF_INET ) {
+            memset(rta_tb, 0, sizeof(rta_tb));
+            parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
+            if (!rta_tb[IFA_LOCAL])
+                rta_tb[IFA_LOCAL]   = rta_tb[IFA_ADDRESS];
+            if (!rta_tb[IFA_ADDRESS])
+                rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
+
+            memcpy(addr,(char*)RTA_DATA(rta_tb[IFLA_ADDRESS]),4);
+            IOAddress a = IOAddress::from_bytes(AF_INET, addr);
+            iface.addAddress(a);
+        }
+    }
+}
+
+
+void rtnl_process_reply(struct rtnl_handle &rth, struct nlmsg_list *&info) {
+
+    struct sockaddr_nl nladdr;
+    struct iovec iov;
+    struct msghdr msg;
+    memset(&msg, 0, sizeof(struct msghdr));
+    msg.msg_name = &nladdr;
+    msg.msg_namelen = sizeof(nladdr);
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+
+    char buf[rcvbuf];
+
+    iov.iov_base = buf;
+    while (1) {
+        int status;
+        struct nlmsghdr *h;
+
+        iov.iov_len = sizeof(buf);
+        status = recvmsg(rth.fd, &msg, 0);
+
+        if (status < 0) {
+            if (errno == EINTR)
+                continue;
+            isc_throw(Unexpected, "Overrun while processing reply from netlink socket.");
+        }
+
+        if (status == 0) {
+            isc_throw(Unexpected, "EOF while reading netlink socket.");
+        }
+
+        h = (struct nlmsghdr*)buf;
+        while (NLMSG_OK(h, status)) {
+
+            // why we received this anyway?
+            if (nladdr.nl_pid != 0 ||
+                h->nlmsg_pid != rth.local.nl_pid ||
+                h->nlmsg_seq != rth.dump) {
+                h = NLMSG_NEXT(h, status);
+                continue;
+            }
+
+            if (h->nlmsg_type == NLMSG_DONE) {
+                // end of message
+                return;
+            }
+
+            if (h->nlmsg_type == NLMSG_ERROR) {
+                struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
+                if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
+                    // we are really out of luck here. We can't even say what is
+                    // wrong as error message is truncated. D'oh.
+                    isc_throw(Unexpected, "Netlink reply read failed.");
+                } else {
+                    isc_throw(Unexpected, "Netlink reply read error " << -err->error);
+                }
+                // never happens we throw before we reach here
+                return;
+            }
+
+            // store the data
+            rtnl_store_reply(h, &info);
+
+            h = NLMSG_NEXT(h, status);
+        }
+        if (msg.msg_flags & MSG_TRUNC) {
+            isc_throw(Unexpected, "Message received over netlink truncated.");
+        }
+        if (status) {
+            isc_throw(Unexpected, "Trailing garbage of " << status << " bytes received over netlink.");
+        }
+    }
+}
+
+/// @brief releases nlmsg list
+///
+/// @param head first element of the list to be released
+void release_list(struct nlmsg_list *head) {
+    struct nlmsg_list *tmp;
+    while (head) {
+        tmp = head->next;
+        free(head);
+        head = tmp;
+    }
+}
+
+void IfaceMgr::detectIfaces() {
+    cout << "Linux: detecting interfaces." << endl;
+
+    struct nlmsg_list* link_info = NULL; // link info list
+    struct nlmsg_list* addr_info = NULL; // address info list
+    struct nlmsg_list* l = NULL;
+    struct rtnl_handle rth;
+
+    // required to display information about interface
+    struct ifinfomsg* ifi = NULL;
+    struct rtattr* tb[IFLA_MAX+1];
+    int len = 0;
+    memset(tb, 0, sizeof(tb));
+    memset(&rth,0, sizeof(rth));
+
+    // open socket
+    rtnl_open_socket(rth);
+
+    // now we have open functional socket, let's use it!
+    // ask for list of interface...
+    rtnl_send_request(rth, AF_PACKET, RTM_GETLINK);
+
+    // Get reply and store it in link_info list.
+    rtnl_process_reply(rth, link_info);
+
+    // Now ask for list of addresses (AF_UNSPEC = of any family)
+    rtnl_send_request(rth, AF_UNSPEC, RTM_GETADDR);
+
+    // Get reply and store it in addr_info list.
+    rtnl_process_reply(rth, addr_info);
+
+    // Now build list with interface names
+    for (l=link_info; l; l = l->next) {
+        ifi = (ifinfomsg*)NLMSG_DATA(&l->h);
+        len = (&l->h)->nlmsg_len;
+        len -= NLMSG_LENGTH(sizeof(*ifi));
+        parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+
+        Iface* iface = new Iface(string( (char*)RTA_DATA(tb[IFLA_IFNAME])), ifi->ifi_index);
+
+        iface->hardware_type_ = ifi->ifi_type;
+        iface->setFlags(ifi->ifi_flags);
+
+        iface->mac_len_ = 0;
+        memset(iface->mac_, 0, IfaceMgr::MAX_MAC_LEN);
+        // Does inetface has LL_ADDR?
+        if (tb[IFLA_ADDRESS]) {
+            iface->mac_len_ = RTA_PAYLOAD(tb[IFLA_ADDRESS]);
+            if (iface->mac_len_ > IfaceMgr::MAX_MAC_LEN) {
+                iface->mac_len_ = 0;
+                isc_throw(Unexpected, "Interface " << iface->getFullName()
+                          << " was detected to have link address of length " << RTA_PAYLOAD(tb[IFLA_ADDRESS])
+                          << ", but maximum supported length is " << IfaceMgr::MAX_MAC_LEN);
+            }
+            memcpy(iface->mac_, RTA_DATA(tb[IFLA_ADDRESS]), iface->mac_len_);
+        }
+        else {
+            // Tunnels can have no LL_ADDR. RTA_PAYLOAD doesn't check it and try to
+            // dereference it in this manner
+            // #define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0))
+            iface->mac_len_ = 0;
+        }
+
+        ipaddrs_get(*iface, addr_info);
+        ifaces_.push_back(*iface);
+    }
+
+    release_list(link_info);
+    release_list(addr_info);
+
+    printIfaces();
+}
+
+/// @brief sets flag_*_ fields.
+///
+/// This implementation is OS-specific as bits have different meaning
+/// on different OSes.
+///
+/// @param flags flags bitfield read from OS
+void IfaceMgr::Iface::setFlags(uint32_t flags) {
+    flags_ = flags;
+
+    flag_loopback_ = flags & IFF_LOOPBACK;
+    flag_up_ = flags & IFF_UP;
+    flag_running_ = flags & IFF_RUNNING;
+    flag_multicast_ = flags & IFF_MULTICAST;
+    flag_broadcast_ = flags & IFF_BROADCAST;
+}
+
+}
+
+#endif // if defined(LINUX)

+ 3 - 0
src/lib/dhcp/tests/Makefile.am

@@ -21,6 +21,8 @@ libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES += ../libdhcp++.h ../libdhcp++.cc
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += ../iface_mgr.cc ../iface_mgr.h iface_mgr_unittest.cc
+libdhcp___unittests_SOURCES += ../iface_mgr_linux.cc
+libdhcp___unittests_SOURCES += ../iface_mgr_bsd.cc
 libdhcp___unittests_SOURCES += ../option6_iaaddr.h ../option6_iaaddr.cc option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += ../option6_ia.h ../option6_ia.cc option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += ../option6_addrlst.h ../option6_addrlst.cc option6_addrlst_unittest.cc
@@ -33,6 +35,7 @@ libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCL
 libdhcp___unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
 
 libdhcp___unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
 if USE_CLANGPP
 # This is to workaround unused variables tcout and tcerr in
 # log4cplus's streams.h.

+ 264 - 7
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -58,7 +58,7 @@ public:
     }
 
     ~IfaceMgrTest() {
-            unlink(INTERFACE_FILE);
+        unlink(INTERFACE_FILE);
     }
 };
 
@@ -151,6 +151,7 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
 
 TEST_F(IfaceMgrTest, basic) {
     // checks that IfaceManager can be instantiated
+    createLoInterfacesTxt();
 
     createLoInterfacesTxt();
 
@@ -220,7 +221,8 @@ TEST_F(IfaceMgrTest, getIface) {
 
 }
 
-TEST_F(IfaceMgrTest, detectIfaces) {
+#if !defined(OS_LINUX)
+TEST_F(IfaceMgrTest, detectIfaces_stub) {
 
     // test detects that interfaces can be detected
     // there is no code for that now, but interfaces are
@@ -248,8 +250,8 @@ TEST_F(IfaceMgrTest, detectIfaces) {
     EXPECT_STREQ( "fe80::1234", addr.toText().c_str() );
 
     delete ifacemgr;
-    unlink(INTERFACE_FILE);
 }
+#endif
 
 TEST_F(IfaceMgrTest, sockets6) {
     // testing socket operation in a portable way is tricky
@@ -281,7 +283,6 @@ TEST_F(IfaceMgrTest, sockets6) {
     close(socket2);
 
     delete ifacemgr;
-    unlink(INTERFACE_FILE);
 }
 
 // TODO: disabled due to other naming on various systems
@@ -368,7 +369,6 @@ TEST_F(IfaceMgrTest, sendReceive6) {
     EXPECT_TRUE( (rcvPkt->remote_port_ == 10546) || (rcvPkt->remote_port_ == 10547) );
 
     delete ifacemgr;
-    unlink(INTERFACE_FILE);
 }
 
 TEST_F(IfaceMgrTest, sendReceive4) {
@@ -480,7 +480,6 @@ TEST_F(IfaceMgrTest, socket4) {
     close(socket1);
 
     delete ifacemgr;
-    unlink(INTERFACE_FILE);
 }
 
 // Test the Iface structure itself
@@ -610,7 +609,265 @@ TEST_F(IfaceMgrTest, socketInfo) {
     );
 
     delete ifacemgr;
-    unlink(INTERFACE_FILE);
 }
 
+/// @brief parses text representation of MAC address
+///
+/// This function parses text representation of a MAC address and stores
+/// it in binary format. Text format is expecte to be separate with
+/// semicolons, e.g. f4:6d:04:96:58:f2
+///
+/// TODO: IfaceMgr::Iface::mac_ uses uint8_t* type, should be vector<uint8_t>
+///
+/// @param textMac string with MAC address to parse
+/// @param mac pointer to output buffer
+/// @param macLen length of output buffer
+///
+/// @return number of bytes filled in output buffer
+size_t parse_mac(const std::string& textMac, uint8_t* mac, size_t macLen) {
+    stringstream tmp(textMac);
+    tmp.flags(ios::hex);
+    int i = 0;
+    uint8_t octet = 0; // output octet
+    uint8_t byte;  // parsed charater from text representation
+    while (!tmp.eof()) {
+
+        tmp >> byte; // hex value
+        if (byte == ':') {
+            mac[i++] = octet;
+
+            if (i == macLen) {
+                // parsing aborted. We hit output buffer size
+                return(i);
+            }
+            octet = 0;
+            continue;
+        }
+        if (isalpha(byte)) {
+            byte = toupper(byte) - 'A' + 10;
+        } else if (isdigit(byte)) {
+            byte -= '0';
+        } else {
+            // parse error. Let's return what we were able to parse so far
+            break;
+        }
+        octet <<= 4;
+        octet += byte;
+    }
+    mac[i++] = octet;
+
+    return (i);
+}
+
+#if defined(OS_LINUX)
+
+/// @brief Parses 'ifconfig -a' output and creates list of interfaces
+///
+/// This method tries to parse ifconfig output. Note that there are some
+/// oddities in recent versions of ifconfig, like putting extra spaces
+/// after MAC address, inconsistent naming and spacing between inet and inet6.
+/// This is an attempt to find a balance between tight parsing of every piece
+/// of text that ifconfig prints and robustness to handle slight differences
+/// in ifconfig output.
+///
+/// @param textFile name of a text file that holds output of ifconfig -a
+/// @param ifaces empty list of interfaces to be filled
+void parse_ifconfig(const std::string textFile, IfaceMgr::IfaceCollection& ifaces) {
+    fstream f(textFile.c_str());
+
+    bool first_line = true;
+    IfaceMgr::IfaceCollection::iterator iface;
+    while (!f.eof()) {
+        string line;
+        getline(f, line);
+
+        // interfaces are separated by empty line
+        if (line.length() == 0) {
+            first_line = true;
+            continue;
+        }
+
+        // uncomment this for ifconfig output debug
+        // cout << "line[" << line << "]" << endl;
+
+        // this is first line of a new interface
+        if (first_line) {
+            first_line = false;
+
+            size_t offset;
+            offset = line.find_first_of(" ");
+            if (offset == string::npos) {
+                isc_throw(BadValue, "Malformed output of ifconfig");
+            }
+            string name = line.substr(0, offset);
+
+            // sadly, ifconfig does not return ifindex
+            ifaces.push_back(IfaceMgr::Iface(name, 0));
+            iface = ifaces.end();
+            --iface; // points to the last element
+
+            offset = line.find(string("HWaddr"));
+
+            string mac = "";
+            if (offset != string::npos) { // some interfaces don't have MAC (e.g. lo)
+                offset += 7;
+                mac = line.substr(offset, string::npos);
+                mac = mac.substr(0, mac.find_first_of(" "));
+
+                iface->mac_len_ = parse_mac(mac, iface->mac_, IfaceMgr::MAX_MAC_LEN);
+            }
+        }
+
+        if (line.find("inet6") != string::npos) {
+            // IPv6 address
+            string addr = line.substr(line.find("inet6")+12, string::npos);
+            addr = addr.substr(0, addr.find("/"));
+            IOAddress a(addr);
+            iface->addAddress(a);
+        } else if(line.find("inet") != string::npos) {
+            // IPv4 address
+            string addr = line.substr(line.find("inet")+10, string::npos);
+            addr = addr.substr(0, addr.find_first_of(" "));
+            IOAddress a(addr);
+            iface->addAddress(a);
+        } else if(line.find("Metric")) {
+            // flags
+            if (line.find("UP") != string::npos) {
+                iface->flag_up_ = true;
+            }
+            if (line.find("LOOPBACK") != string::npos) {
+                iface->flag_loopback_ = true;
+            }
+            if (line.find("RUNNING") != string::npos) {
+                iface->flag_running_ = true;
+            }
+            if (line.find("BROADCAST") != string::npos) {
+                iface->flag_broadcast_ = true;
+            }
+            if (line.find("MULTICAST") != string::npos) {
+                iface->flag_multicast_ = true;
+            }
+        }
+    }
+}
+
+
+// This test compares implemented detection routines to output of "ifconfig -a" command.
+// It is far from perfect, but it is able to verify that interface names, flags,
+// MAC address, IPv4 and IPv6 addresses are detected properly. Interface list completeness
+// (check that each interface is reported, i.e. no missing or extra interfaces) and
+// address completeness is verified.
+//
+// Things that are not tested: 
+// - ifindex (ifconfig does not print it out)
+// - address scopes and lifetimes (we don't need it, so it is not implemented in IfaceMgr)
+TEST_F(IfaceMgrTest, detectIfaces_linux) {
+
+    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    IfaceMgr::IfaceCollection& detectedIfaces = ifacemgr->getIfacesLst();
+
+    const std::string textFile = "ifconfig.txt";
+
+    unlink(textFile.c_str());
+    int result = system( ("/sbin/ifconfig -a > " + textFile).c_str());
+
+    ASSERT_EQ(0, result);
+
+    // list of interfaces parsed from ifconfig
+    IfaceMgr::IfaceCollection parsedIfaces;
+
+    EXPECT_NO_THROW(
+        parse_ifconfig(textFile, parsedIfaces);
+    );
+    unlink(textFile.c_str());
+
+    cout << "------Parsed interfaces---" << endl;
+    for (IfaceMgr::IfaceCollection::iterator i = parsedIfaces.begin();
+         i != parsedIfaces.end(); ++i) {
+        cout << i->getName() << ": ifindex=" << i->getIndex() << ", mac=" << i->getPlainMac();
+        cout << ", flags:";
+        if (i->flag_up_) {
+            cout << " UP";
+        }
+        if (i->flag_running_) {
+            cout << " RUNNING";
+        }
+        if (i->flag_multicast_) {
+            cout << " MULTICAST";
+        }
+        if (i->flag_broadcast_) {
+            cout << " BROADCAST";
+        }
+        cout << ", addrs:";
+        const IfaceMgr::AddressCollection& addrs = i->getAddresses();
+        for (IfaceMgr::AddressCollection::const_iterator a= addrs.begin();
+             a != addrs.end(); ++a) {
+            cout << a->toText() << " ";
+        }
+        cout << endl;
+    }
+
+    // Ok, now we have 2 lists of interfaces. Need to compare them
+    ASSERT_EQ(detectedIfaces.size(), parsedIfaces.size());
+
+    // TODO: This could could probably be written simple with find()
+    for (IfaceMgr::IfaceCollection::iterator detected = detectedIfaces.begin();
+         detected != detectedIfaces.end(); ++detected) {
+        // let's find out if this interface is
+
+        bool found = false;
+        for (IfaceMgr::IfaceCollection::iterator i = parsedIfaces.begin();
+             i != parsedIfaces.end(); ++i) {
+            if (detected->getName() != i->getName()) {
+                continue;
+            }
+            found = true;
+
+            cout << "Checking interface " << detected->getName() << endl;
+
+            // start with checking flags
+            EXPECT_EQ(detected->flag_loopback_, i->flag_loopback_);
+            EXPECT_EQ(detected->flag_up_, i->flag_up_);
+            EXPECT_EQ(detected->flag_running_, i->flag_running_);
+            EXPECT_EQ(detected->flag_multicast_, i->flag_multicast_);
+            EXPECT_EQ(detected->flag_broadcast_, i->flag_broadcast_);
+
+            // skip MAC comparison for loopback as netlink returns MAC
+            // 00:00:00:00:00:00 for lo
+            if (!detected->flag_loopback_) {
+                ASSERT_EQ(detected->mac_len_, i->mac_len_);
+                EXPECT_EQ(0, memcmp(detected->mac_, i->mac_, i->mac_len_));
+            }
+
+            EXPECT_EQ(detected->getAddresses().size(), i->getAddresses().size());
+
+            // now compare addresses
+            const IfaceMgr::AddressCollection& addrs = detected->getAddresses();
+            for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+                 addr != addrs.end(); ++addr) {
+                bool addr_found = false;
+
+                const IfaceMgr::AddressCollection& addrs2 = detected->getAddresses();
+                for (IfaceMgr::AddressCollection::const_iterator a = addrs2.begin();
+                     a != addrs2.end(); ++a) {
+                    if (*addr != *a) {
+                        continue;
+                    }
+                    addr_found = true;
+                }
+                if (!addr_found) {
+                    cout << "ifconfig does not seem to report " << addr->toText()
+                         << " address on " << detected->getFullName() << " interface." << endl;
+                    FAIL();
+                }
+                cout << "Address " << addr->toText() << " on iterface " << detected->getFullName()
+                     << " matched with 'ifconfig -a' output." << endl;
+            }
+        }
+    }
+
+    delete ifacemgr;
+}
+#endif
+
 }