Browse Source

[1237] Test for interface detection on Linux implemented.

Tomek Mrugalski 13 years ago
parent
commit
697a02295a
2 changed files with 270 additions and 8 deletions
  1. 5 7
      src/lib/dhcp/iface_mgr.cc
  2. 265 1
      src/lib/dhcp/tests/iface_mgr_unittest.cc

+ 5 - 7
src/lib/dhcp/iface_mgr.cc

@@ -97,9 +97,6 @@ IfaceMgr::IfaceMgr()
 
         detectIfaces();
 
-        if (!openSockets6()) {
-            isc_throw(Unexpected, "Failed to open/bind sockets.");
-        }
     } catch (const std::exception& ex) {
         cout << "IfaceMgr creation failed:" << ex.what() << endl;
 
@@ -209,20 +206,21 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
 
         out << "Detected interface " << iface->getFullName()
              << ", hwtype=" << iface->hardware_type_ << ", maclen=" << iface->mac_len_
-             << ", mac=" << iface->getPlainMac() << endl;
-        out << "flags=" << hex << iface->flags_ << dec << "("
+             << ", 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 << "  " << iface->addrs_.size() << " addr(s):" << endl;
+        out << "  " << iface->addrs_.size() << " addr(s):";
         for (Addr6Lst::const_iterator addr=iface->addrs_.begin();
              addr != iface->addrs_.end();
              ++addr) {
-            out << "  " << addr->toText() << endl;
+            out << "  " << addr->toText();
         }
+        out << endl;
     }
 }
 

+ 265 - 1
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -211,7 +211,8 @@ TEST_F(IfaceMgrTest, getIface) {
     delete ifacemgr;
 }
 
-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
@@ -240,6 +241,7 @@ TEST_F(IfaceMgrTest, detectIfaces) {
 
     delete ifacemgr;
 }
+#endif
 
 // TODO: disabled due to other naming on various systems
 // (lo in Linux, lo0 in BSD systems)
@@ -364,4 +366,266 @@ TEST_F(IfaceMgrTest, DISABLED_sendReceive) {
     delete ifacemgr;
 }
 
+/// @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::IfaceLst& ifaces) {
+    fstream f(textFile.c_str());
+
+    bool first_line = true;
+    IfaceMgr::IfaceLst::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->addrs_.push_back(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->addrs_.push_back(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::IfaceLst& 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::IfaceLst parsedIfaces;
+
+    EXPECT_NO_THROW(
+        parse_ifconfig(textFile, parsedIfaces);
+    );
+    unlink(textFile.c_str());
+
+    cout << "------Parsed interfaces---" << endl;
+    for (IfaceMgr::IfaceLst::iterator i = parsedIfaces.begin();
+         i != parsedIfaces.end(); ++i) {
+        cout << i->name_ << ": ifindex=" << i->ifindex_ << ", 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:";
+        for (IfaceMgr::Addr6Lst::iterator a= i->addrs_.begin();
+             a != i->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::IfaceLst::iterator detected = detectedIfaces.begin();
+         detected != detectedIfaces.end(); ++detected) {
+        // let's find out if this interface is
+
+        bool found = false;
+        for (IfaceMgr::IfaceLst::iterator i = parsedIfaces.begin();
+             i != parsedIfaces.end(); ++i) {
+            if (detected->name_ != i->name_) {
+                continue;
+            }
+            found = true;
+
+            cout << "Checking interface " << detected->name_ << 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->addrs_.size(), i->addrs_.size());
+
+            // now compare addresses
+            for (IfaceMgr::Addr6Lst::iterator addr = detected->addrs_.begin();
+                 addr != detected->addrs_.end(); ++addr) {
+                bool addr_found = false;
+
+                for (IfaceMgr::Addr6Lst::iterator a = i->addrs_.begin();
+                     a != i->addrs_.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
+
 }