Browse Source

[2246] Interface detection code for BSD and Solaris

 - Contributed by David Carlier
 - Clean-up by tomek
Tomek Mrugalski 11 years ago
parent
commit
92740b11ed

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

@@ -176,9 +176,10 @@ public:
     ///
     /// @note Implementation of this method is OS-dependent as bits have
     /// different meaning on each OS.
+    /// We need to make it 64 bits, because Solaris uses 64, not 32 bits.
     ///
     /// @param flags bitmask value returned by OS in interface detection
-    void setFlags(uint32_t flags);
+    void setFlags(uint64_t flags);
 
     /// @brief Returns interface index.
     ///
@@ -308,7 +309,9 @@ public:
 
     /// Interface flags (this value is as is returned by OS,
     /// it may mean different things on different OSes).
-    uint32_t flags_;
+    /// Solaris based os have unsigned long flags field (64 bits).
+    /// It is usually 32 bits, though.
+    uint64_t flags_;
 
     /// Indicates that IPv4 sockets should (true) or should not (false)
     /// be opened on this interface.

+ 97 - 3
src/lib/dhcp/iface_mgr_bsd.cc

@@ -20,6 +20,12 @@
 #include <dhcp/pkt_filter_inet.h>
 #include <exceptions/exceptions.h>
 
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if_dl.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
 using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
@@ -28,11 +34,99 @@ using namespace isc::dhcp;
 namespace isc {
 namespace dhcp {
 
+/// This is a BSD specific interface detection method.
 void
 IfaceMgr::detectIfaces() {
-    /// @todo do the actual detection on BSDs. Currently just calling
-    /// stub implementation.
-    stubDetectIfaces();
+    struct ifaddrs* iflist = 0;// The whole interface list
+    struct ifaddrs* ifptr = 0; // The interface we're processing now
+
+    // Gets list of ifaddrs struct
+    if(getifaddrs(&iflist) != 0) {
+        isc_throw(Unexpected, "Network interfaces detection failed.");
+    }
+
+    typedef map<string, Iface> ifaceLst;
+    ifaceLst::iterator itf;
+    ifaceLst ifaces;
+
+    // First lookup for getting interfaces ...
+    for(ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+        const char * ifname = ifptr->ifa_name;
+        uint ifindex = 0;
+
+        if(!(ifindex = if_nametoindex(ifname))) {
+            // Interface name does not have corresponding index ...
+            freeifaddrs(iflist);
+            isc_throw(Unexpected, "Interface " << ifname << " has no index");
+        }
+
+        if((itf = ifaces.find(ifname)) != ifaces.end()) {
+            continue;
+        }
+
+        Iface iface(ifname, ifindex);
+        iface.setFlags(ifptr->ifa_flags);
+        ifaces.insert(pair<string, Iface>(ifname, iface));
+    }
+
+    // Second lookup to get MAC and IP addresses
+    for(ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+        if((itf = ifaces.find(ifptr->ifa_name)) == ifaces.end()) {
+            continue;
+        }
+        // Common byte pointer for following data
+        const uint8_t * ptr = 0;
+        if(ifptr->ifa_addr->sa_family == AF_LINK) {
+            // HWAddr
+            struct sockaddr_dl * ldata =
+                reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(LLADDR(ldata));
+
+            itf->second.setHWType(ldata->sdl_type);
+            itf->second.setMac(ptr, ldata->sdl_alen);
+        } else if(ifptr->ifa_addr->sa_family == AF_INET6) {
+            // IPv6 Addr
+            struct sockaddr_in6 * adata =
+                reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(&adata->sin6_addr);
+
+            IOAddress a = IOAddress::fromBytes(AF_INET6, ptr);
+            itf->second.addAddress(a);
+        } else {
+            // IPv4 Addr
+            struct sockaddr_in * adata =
+                reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(&adata->sin_addr);
+
+            IOAddress a = IOAddress::fromBytes(AF_INET, ptr);
+            itf->second.addAddress(a);
+        }
+    }
+
+    freeifaddrs(iflist);
+
+    // Registers interfaces with at least an IP addresses
+    for(ifaceLst::const_iterator itf = ifaces.begin();
+        itf != ifaces.end(); ++ itf) {
+        if(itf->second.getAddresses().size() > 0) {
+            ifaces_.push_back(itf->second);
+        }
+    }
+}
+
+/// @brief sets flag_*_ fields
+///
+/// Like Linux version, os specific flags
+///
+/// @params flags
+void 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;
 }
 
 void IfaceMgr::os_send4(struct msghdr& /*m*/,

+ 1 - 1
src/lib/dhcp/iface_mgr_linux.cc

@@ -502,7 +502,7 @@ void IfaceMgr::detectIfaces() {
 /// on different OSes.
 ///
 /// @param flags flags bitfield read from OS
-void Iface::setFlags(uint32_t flags) {
+void Iface::setFlags(uint64_t flags) {
     flags_ = flags;
 
     flag_loopback_ = flags & IFF_LOOPBACK;

+ 99 - 4
src/lib/dhcp/iface_mgr_sun.cc

@@ -20,6 +20,12 @@
 #include <dhcp/pkt_filter_inet.h>
 #include <exceptions/exceptions.h>
 
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if_dl.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
 using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
@@ -28,11 +34,99 @@ using namespace isc::dhcp;
 namespace isc {
 namespace dhcp {
 
+/// This is a Solaris specific interface detection code. It works on Solaris 11
+/// only, as earlier versions did not support getifaddrs() API.
 void
 IfaceMgr::detectIfaces() {
-    /// @todo do the actual detection on Solaris. Currently just calling
-    /// stub implementation.
-    stubDetectIfaces();
+    struct ifaddrs * iflist = 0, * ifptr = 0;
+
+    // Gets list of ifaddrs struct
+    if(getifaddrs(& iflist) != 0) {
+        isc_throw(Unexpected, "Network interfaces detection failed.");
+    }
+
+    typedef std::map<string, Iface> ifaceLst;
+    ifaceLst::iterator itf;
+    ifaceLst ifaces;
+
+    // First lookup for getting interfaces ...
+    for(ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+        const char * ifname = ifptr->ifa_name;
+        uint ifindex = 0;
+
+        if(!(ifindex = if_nametoindex(ifname))) {
+            // Interface name does not have corresponding index ...
+            freeifaddrs(iflist);
+            isc_throw(Unexpected, "Interface " << ifname << " has no index");
+        }
+
+        if((itf = ifaces.find(ifname)) != iface.end()) {
+            continue;
+        }
+
+        Iface iface(ifname, ifindex);
+        iface.setFlags(ifptr->ifa_flags);
+        ifaces.insert(pair<string, Iface>(ifname, iface));
+    }
+
+    // Second lookup to get MAC and IP addresses
+    for(ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+        if((itf = ifaces.find(ifptr->ifa_name)) == ifaces.end()) {
+            continue;
+        }
+        // Common byte pointer for following data
+        const uint8_t * ptr = 0;
+        if(ifptr->ifa_addr->sa_family == AF_LINK) {
+            // HWAddr
+            struct sockaddr_dl * ldata =
+                reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(LLADDR(ldata));
+
+            itf->second.setHWType(ldata->sdl_type);
+            itf->second.setMac(ptr, ldata->sdl_alen);
+        } else if(ifptr->ifa_addr->sa_family == AF_INET6) {
+            // IPv6 Addr
+            struct sockaddr_in6 * adata =
+                reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(& adata->sin6_addr);
+
+            IOAddress a = IOAddress::fromBytes(AF_INET6, ptr);
+            itf->second.addAddress(a);
+        } else {
+            // IPv4 Addr
+            struct sockaddr_in * adata =
+                reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(& adata->sin_addr);
+
+            IOAddress a = IOAddress::fromBytes(AF_INET, ptr);
+            itf->second.addAddress(a);
+        }
+    }
+
+    freeifaddrs(iflist);
+
+    // Registers interfaces with at least an IP addresses
+    for(ifaceLst::const_iterator itf = ifaces.begin();
+        itf != ifaces.end(); ++ itf) {
+        if(itf->second.getAddresses().size() > 0) {
+            ifaces_.push_back(itf->second);
+        }
+    }
+}
+
+/// @brief sets flag_*_ fields
+///
+/// Like Linux version, os specific flags
+///
+/// @params flags
+void Iface::setFlags(uint64_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;
 }
 
 void IfaceMgr::os_send4(struct msghdr& /*m*/,
@@ -40,7 +134,8 @@ void IfaceMgr::os_send4(struct msghdr& /*m*/,
                         size_t /*control_buf_len*/,
                         const Pkt4Ptr& /*pkt*/) {
   // @todo: Are there any specific actions required before sending IPv4 packet
-  // on BSDs? See iface_mgr_linux.cc for working Linux implementation.
+  // on Solaris based systems? See iface_mgr_linux.cc
+  // for working Linux implementation.
 }
 
 bool IfaceMgr::os_receive4(struct msghdr& /*m*/, Pkt4Ptr& /*pkt*/) {

+ 193 - 0
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -1474,6 +1474,199 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
 }
 #endif
 
+#if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SUN)
+
+#if defined(OS_BSD)
+#include <net/if_dl.h>
+#endif
+
+#include <sys/socket.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
+// Checks the index of this interface
+bool
+checkIfIndex(const Iface & iface,
+             struct ifaddrs *& ifptr) {
+    cout << "Checks index of " << iface.getName() << endl;
+    return (iface.getIndex() == if_nametoindex(ifptr->ifa_name));
+}
+
+// Checks if the interface has proper flags set
+bool
+checkIfFlags(const Iface & iface,
+             struct ifaddrs *& ifptr) {
+    cout << "Checks flags of " << iface.getName() << endl;
+        bool flag_loopback_ = ifptr->ifa_flags & IFF_LOOPBACK;
+        bool flag_up_ = ifptr->ifa_flags & IFF_UP;
+        bool flag_running_ = ifptr->ifa_flags & IFF_RUNNING;
+        bool flag_multicast_ = ifptr->ifa_flags & IFF_MULTICAST;
+
+    return ((iface.flag_loopback_ == flag_loopback_) &&
+                        (iface.flag_up_ == flag_up_) &&
+                        (iface.flag_running_ == flag_running_) &&
+                        (iface.flag_multicast_ == flag_multicast_));
+}
+
+/// @brief Checks if MAC Address/IP Addresses are properly well formed
+/// @param iface Kea interface structure to be checked
+/// @param ifptr original structure returned by getifaddrs
+bool
+checkIfAddrs(const Iface & iface, struct ifaddrs *& ifptr) {
+    const unsigned char * p = 0;
+#if defined(OS_LINUX)
+    // Workaround for Linux ...
+    if(ifptr->ifa_data != 0) {
+        // We avoid localhost as it has no MAC Address
+        if(!strncmp(iface.getName().c_str(), "lo", 2)) {
+            return (true);
+        }
+
+        // Typically unit-tests try to be silent. But interface detection is
+        // tricky enough to warrant additional prints.
+        cout << "Checks MAC Addr of " << iface.getName() << endl;
+        struct ifreq ifr;
+        memset(& ifr.ifr_name, 0, sizeof ifr.ifr_name);
+        strncpy(ifr.ifr_name, iface.getName().c_str(), sizeof ifr.ifr_name);
+
+        int s = -1; // Socket descriptor
+
+        if((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+            isc_throw(Unexpected, "Cannot create AF_INET socket");
+        }
+
+        if(ioctl(s, SIOCGIFHWADDR, & ifr) < 0) {
+            close(s);
+            isc_throw(Unexpected, "Cannot set SIOCGIFHWADDR flag");
+        }
+
+        const uint8_t * p =
+            reinterpret_cast<uint8_t *>(ifr.ifr_ifru.ifru_hwaddr.sa_data);
+
+        close(s);
+
+        /// @todo: Check MAC address length. For some interfaces it is
+        /// different than 6. Some have 0, while some exotic ones (like
+        /// infiniband) have 20.
+        int i = 0;
+        while(i < 6) {
+            if(* (p + i) != * (iface.getMac() + i)) {
+                return (false);
+            }
+            ++ i;
+        }
+
+        return (true);
+    }
+#endif
+
+    if(!ifptr->ifa_addr) {
+        return (false);
+    }
+
+    switch(ifptr->ifa_addr->sa_family) {
+#if defined(OS_BSD)
+        case AF_LINK: {
+            // We avoid localhost as it has no MAC Address
+            if(!strncmp(iface.getName().c_str(), "lo", 2)) {
+                return (true);
+            }
+
+            cout << "Checks MAC Addr of " << iface.getName() << endl;
+            struct sockaddr_dl * hwdata =
+                   reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
+            p = reinterpret_cast<uint8_t *>(LLADDR(hwdata));
+
+            // Extract MAC address length
+            if(hwdata->sdl_alen != iface.getMacLen()) {
+                return (false);
+            }
+
+            int i = 0;
+            while(i < hwdata->sdl_alen) {
+                if(* (p + i) != * (iface.getMac() + i)) {
+                    return (false);
+                }
+                ++ i;
+            }
+
+            return (true);
+        }
+#endif
+        case AF_INET: {
+            cout << "Checks IPv4 address of " << iface.getName() << endl;
+            struct sockaddr_in * v4data =
+                   reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
+            p = reinterpret_cast<uint8_t *>(& v4data->sin_addr);
+
+            IOAddress addrv4 = IOAddress::fromBytes(AF_INET, p);
+
+            for(Iface::AddressCollection::const_iterator a = iface.getAddresses().begin();
+                a != iface.getAddresses().end(); ++ a) {
+                if((* a).isV4() && (* a) == addrv4) {
+                    return (true);
+                }
+            }
+
+            return (false);
+        }
+        case AF_INET6: {
+            cout << "Checks IPv6 address of " << iface.getName() << endl;
+            struct sockaddr_in6 * v6data =
+                   reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
+            p = reinterpret_cast<uint8_t *>(& v6data->sin6_addr);
+
+            IOAddress addrv6 = IOAddress::fromBytes(AF_INET6, p);
+
+            for(Iface::AddressCollection::const_iterator a = iface.getAddresses().begin();
+                a != iface.getAddresses().end(); ++ a) {
+                if((* a).isV6() && (* a) == addrv6) {
+                    return (true);
+                }
+            }
+
+            return (false);
+        }
+        default:
+            return (true);
+    }
+}
+
+TEST_F(IfaceMgrTest, detectIfaces) {
+
+    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    IfaceMgr::IfaceCollection& detectedIfaces = ifacemgr->getIfacesLst();
+
+    // We are using struct ifaddrs as it is the only good portable one
+    // ifreq and ioctls are far from portabe. For BSD ifreq::ifa_flags field
+    // is only a short which, nowadays, can be negative
+    struct ifaddrs * iflist = 0, * ifptr = 0;
+
+    if(getifaddrs(& iflist) != 0) {
+        isc_throw(Unexpected, "Cannot detect interfaces");
+    }
+
+    for(ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+        for(IfaceMgr::IfaceCollection::const_iterator i = detectedIfaces.begin();
+            i != detectedIfaces.end(); ++ i) {
+            if(!strncmp(ifptr->ifa_name, (*i).getName().c_str(),
+                        (*i).getName().size())) {
+
+                checkIfIndex(*i, ifptr);
+                checkIfFlags(*i, ifptr);
+                checkIfAddrs(*i, ifptr);
+
+            }
+        }
+    }
+
+    freeifaddrs(iflist);
+    iflist = 0;
+
+    delete ifacemgr;
+}
+#endif
+
 volatile bool callback_ok;
 
 void my_callback(void) {