Commit 697a0229 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[1237] Test for interface detection on Linux implemented.

parent d1a22e48
......@@ -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;
}
}
......
......@@ -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
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment