From: Tomek Mrugalski Date: Tue, 13 Dec 2011 13:32:21 +0000 (+0100) Subject: [1237] Test for interface detection on Linux implemented. X-Git-Tag: trac2351_base~299^2~6^2~11 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=697a02295a8d2a7579aaa7dd6b599dcdab14bd2c;p=thirdparty%2Fkea.git [1237] Test for interface detection on Linux implemented. --- diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc index 69d7f85e59..18d2cd68bd 100644 --- a/src/lib/dhcp/iface_mgr.cc +++ b/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; } } diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc index f713ac528f..3f73f51afe 100644 --- a/src/lib/dhcp/tests/iface_mgr_unittest.cc +++ b/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 +/// +/// @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 + }