2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-08 18:05:18 +00:00
Files
kea/src/lib/dhcp/tests/iface_mgr_unittest.cc

605 lines
18 KiB
C++
Raw Normal View History

// 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>
#include <iostream>
#include <fstream>
#include <sstream>
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <asiolink/io_address.h>
#include <dhcp/pkt6.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/dhcp4.h>
using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
// name of loopback interface detection
char LOOPBACK[32] = "lo";
namespace {
const char* const INTERFACE_FILE = TEST_DATA_BUILDDIR "/interfaces.txt";
class NakedIfaceMgr: public IfaceMgr {
// "naked" Interface Manager, exposes internal fields
public:
NakedIfaceMgr() { }
IfaceCollection & getIfacesLst() { return ifaces_; }
};
// dummy class for now, but this will be expanded when needed
class IfaceMgrTest : public ::testing::Test {
public:
IfaceMgrTest() {
}
void createLoInterfacesTxt() {
unlink(INTERFACE_FILE);
fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
fakeifaces << LOOPBACK << " ::1";
fakeifaces.close();
}
};
// We need some known interface to work reliably. Loopback interface
// is named lo on Linux and lo0 on BSD boxes. We need to find out
// which is available. This is not a real test, but rather a workaround
// that will go away when interface detection is implemented.
// NOTE: At this stage of development, write access to current directory
// during running tests is required.
TEST_F(IfaceMgrTest, loDetect) {
// poor man's interface detection
// it will go away as soon as proper interface detection
// is implemented
if (if_nametoindex("lo")>0) {
cout << "This is Linux, using lo as loopback." << endl;
sprintf(LOOPBACK, "lo");
} else if (if_nametoindex("lo0")>0) {
cout << "This is BSD, using lo0 as loopback." << endl;
sprintf(LOOPBACK, "lo0");
} else {
cout << "Failed to detect loopback interface. Neither "
<< "lo or lo0 worked. I give up." << endl;
ASSERT_TRUE(false);
}
}
// uncomment this test to create packet writer. It will
// write incoming DHCPv6 packets as C arrays. That is useful
// for generating test sequences based on actual traffic
//
// TODO: this potentially should be moved to a separate tool
//
#if 0
TEST_F(IfaceMgrTest, dhcp6Sniffer) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
unlink("interfaces.txt");
ofstream interfaces("interfaces.txt", ios::ate);
interfaces << "eth0 fe80::21e:8cff:fe9b:7349";
interfaces.close();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
Pkt6* pkt = NULL;
int cnt = 0;
cout << "---8X-----------------------------------------" << endl;
while (true) {
pkt = ifacemgr->receive();
cout << "// this code is autogenerated. Do NOT edit." << endl;
cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl;
cout << "Pkt6 *capture" << cnt++ << "() {" << endl;
cout << " Pkt6* pkt;" << endl;
cout << " pkt = new Pkt6(" << pkt->data_len_ << ");" << endl;
cout << " pkt->remote_port_ = " << pkt-> remote_port_ << ";" << endl;
cout << " pkt->remote_addr_ = IOAddress(\""
<< pkt->remote_addr_.toText() << "\");" << endl;
cout << " pkt->local_port_ = " << pkt-> local_port_ << ";" << endl;
cout << " pkt->local_addr_ = IOAddress(\""
<< pkt->local_addr_.toText() << "\");" << endl;
cout << " pkt->ifindex_ = " << pkt->ifindex_ << ";" << endl;
cout << " pkt->iface_ = \"" << pkt->iface_ << "\";" << endl;
// TODO it is better to declare an array and then memcpy it to
// packet.
for (int i=0; i< pkt->data_len_; i++) {
cout << " pkt->data_[" << i << "]="
<< (int)(unsigned char)pkt->data_[i] << "; ";
if (!(i%4))
cout << endl;
}
cout << endl;
cout << " return (pkt);" << endl;
cout << "}" << endl << endl;
delete pkt;
}
cout << "---8X-----------------------------------------" << endl;
// never happens. Infinite loop is infinite
delete pkt;
delete ifacemgr;
}
#endif
TEST_F(IfaceMgrTest, basic) {
// checks that IfaceManager can be instantiated
IfaceMgr & ifacemgr = IfaceMgr::instance();
ASSERT_TRUE(&ifacemgr != 0);
}
TEST_F(IfaceMgrTest, ifaceClass) {
// basic tests for Iface inner class
IfaceMgr::Iface* iface = new IfaceMgr::Iface("eth5", 7);
EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
delete iface;
}
// TODO: Implement getPlainMac() test as soon as interface detection
// is implemented.
TEST_F(IfaceMgrTest, getIface) {
cout << "Interface checks. Please ignore socket binding errors." << endl;
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
// interface name, ifindex
IfaceMgr::Iface iface1("lo1", 1);
IfaceMgr::Iface iface2("eth5", 2);
IfaceMgr::Iface iface3("en3", 5);
IfaceMgr::Iface iface4("e1000g0", 3);
// note: real interfaces may be detected as well
ifacemgr->getIfacesLst().push_back(iface1);
ifacemgr->getIfacesLst().push_back(iface2);
ifacemgr->getIfacesLst().push_back(iface3);
ifacemgr->getIfacesLst().push_back(iface4);
cout << "There are " << ifacemgr->getIfacesLst().size()
<< " interfaces." << endl;
for (IfaceMgr::IfaceCollection::iterator iface=ifacemgr->getIfacesLst().begin();
iface != ifacemgr->getIfacesLst().end();
++iface) {
cout << " " << iface->getFullName() << endl;
}
// check that interface can be retrieved by ifindex
IfaceMgr::Iface* tmp = ifacemgr->getIface(5);
// ASSERT_NE(NULL, tmp); is not supported. hmmmm.
ASSERT_TRUE( tmp != NULL );
EXPECT_EQ( "en3", tmp->getName() );
EXPECT_EQ(5, tmp->getIndex());
// check that interface can be retrieved by name
tmp = ifacemgr->getIface("lo1");
ASSERT_TRUE( tmp != NULL );
EXPECT_EQ( "lo1", tmp->getName() );
EXPECT_EQ(1, tmp->getIndex());
// check that non-existing interfaces are not returned
EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi0") );
delete ifacemgr;
}
TEST_F(IfaceMgrTest, detectIfaces) {
// test detects that interfaces can be detected
// there is no code for that now, but interfaces are
// read from file
fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
fakeifaces << "eth0 fe80::1234";
fakeifaces.close();
// this is not usable on systems that don't have eth0
// interfaces. Nevertheless, this fake interface should
// be on list, but if_nametoindex() will fail.
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
ASSERT_TRUE( ifacemgr->getIface("eth0") != NULL );
IfaceMgr::Iface* eth0 = ifacemgr->getIface("eth0");
// there should be one address
IfaceMgr::AddressCollection addrs = eth0->getAddresses();
ASSERT_EQ(1, addrs.size());
IOAddress addr = *addrs.begin();
EXPECT_STREQ( "fe80::1234", addr.toText().c_str() );
delete ifacemgr;
unlink(INTERFACE_FILE);
}
TEST_F(IfaceMgrTest, sockets6) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
IOAddress loAddr("::1");
Pkt6 pkt6(128);
pkt6.iface_ = LOOPBACK;
// bind multicast socket to port 10547
int socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10547);
EXPECT_GT(socket1, 0); // socket > 0
EXPECT_EQ(socket1, ifacemgr->getSocket(pkt6));
// bind unicast socket to port 10548
int socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, 10548);
EXPECT_GT(socket2, 0);
// removed code for binding socket twice to the same address/port
// as it caused problems on some platforms (e.g. Mac OS X)
close(socket1);
close(socket2);
delete ifacemgr;
unlink(INTERFACE_FILE);
}
// TODO: disabled due to other naming on various systems
// (lo in Linux, lo0 in BSD systems)
TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
IOAddress loAddr("::1");
IOAddress mcastAddr("ff02::1:2");
// bind multicast socket to port 10547
int socket1 = ifacemgr->openSocket(LOOPBACK, mcastAddr, 10547);
EXPECT_GT(socket1, 0); // socket > 0
// expect success. This address/port is already bound, but
// we are using SO_REUSEADDR, so we can bind it twice
int socket2 = ifacemgr->openSocket(LOOPBACK, mcastAddr, 10547);
EXPECT_GT(socket2, 0);
// there's no good way to test negative case here.
// we would need non-multicast interface. We will be able
// to iterate thru available interfaces and check if there
// are interfaces without multicast-capable flag.
close(socket1);
close(socket2);
delete ifacemgr;
}
TEST_F(IfaceMgrTest, sendReceive6) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
// let's assume that every supported OS have lo interface
IOAddress loAddr("::1");
int socket1 = 0, socket2 = 0;
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10547);
socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, 10546);
);
boost::shared_ptr<Pkt6> sendPkt(new Pkt6(128) );
// prepare dummy payload
for (int i=0;i<128; i++) {
sendPkt->data_[i] = i;
}
sendPkt->remote_port_ = 10547;
sendPkt->remote_addr_ = IOAddress("::1");
sendPkt->ifindex_ = 1;
sendPkt->iface_ = LOOPBACK;
boost::shared_ptr<Pkt6> rcvPkt;
EXPECT_EQ(true, ifacemgr->send(sendPkt));
rcvPkt = ifacemgr->receive6();
ASSERT_TRUE( rcvPkt ); // received our own packet
// let's check that we received what was sent
EXPECT_EQ(sendPkt->data_len_, rcvPkt->data_len_);
EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0],
rcvPkt->data_len_) );
EXPECT_EQ(sendPkt->remote_addr_.toText(), rcvPkt->remote_addr_.toText());
// since we opened 2 sockets on the same interface and none of them is multicast,
// none is preferred over the other for sending data, so we really should not
// assume the one or the other will always be choosen for sending data. Therefore
// we should accept both values as source ports.
EXPECT_TRUE( (rcvPkt->remote_port_ == 10546) || (rcvPkt->remote_port_ == 10547) );
delete ifacemgr;
unlink(INTERFACE_FILE);
}
TEST_F(IfaceMgrTest, sendReceive4) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
// let's assume that every supported OS have lo interface
IOAddress loAddr("127.0.0.1");
int socket1 = 0, socket2 = 0;
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000 + 1);
);
boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
sendPkt->setLocalAddr(IOAddress("127.0.0.1"));
sendPkt->setLocalPort(DHCP4_SERVER_PORT + 10000 + 1);
sendPkt->setRemotePort(DHCP4_SERVER_PORT + 10000);
sendPkt->setRemoteAddr(IOAddress("127.0.0.1"));
sendPkt->setIndex(1);
sendPkt->setIface(string(LOOPBACK));
sendPkt->setHops(6);
sendPkt->setSecs(42);
sendPkt->setCiaddr(IOAddress("192.0.2.1"));
sendPkt->setSiaddr(IOAddress("192.0.2.2"));
sendPkt->setYiaddr(IOAddress("192.0.2.3"));
sendPkt->setGiaddr(IOAddress("192.0.2.4"));
uint8_t sname[] = "That's just a string that will act as SNAME";
sendPkt->setSname(sname, strlen((const char*)sname));
uint8_t file[] = "/another/string/that/acts/as/a/file_name.txt";
sendPkt->setFile(file, strlen((const char*)file));
ASSERT_NO_THROW(
sendPkt->pack();
);
boost::shared_ptr<Pkt4> rcvPkt;
EXPECT_EQ(true, ifacemgr->send(sendPkt));
rcvPkt = ifacemgr->receive4();
ASSERT_TRUE( rcvPkt ); // received our own packet
ASSERT_NO_THROW(
rcvPkt->unpack();
);
// let's check that we received what was sent
EXPECT_EQ(sendPkt->len(), rcvPkt->len());
2011-12-07 17:45:57 +01:00
EXPECT_EQ("127.0.0.1", rcvPkt->getRemoteAddr().toText());
EXPECT_EQ(sendPkt->getRemotePort(), rcvPkt->getLocalPort());
// now let's check content
EXPECT_EQ(sendPkt->getHops(), rcvPkt->getHops());
EXPECT_EQ(sendPkt->getOp(), rcvPkt->getOp());
EXPECT_EQ(sendPkt->getSecs(), rcvPkt->getSecs());
EXPECT_EQ(sendPkt->getFlags(), rcvPkt->getFlags());
EXPECT_EQ(sendPkt->getCiaddr(), rcvPkt->getCiaddr());
EXPECT_EQ(sendPkt->getSiaddr(), rcvPkt->getSiaddr());
EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr());
EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr());
EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid());
EXPECT_EQ(sendPkt->getType(), rcvPkt->getType());
EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname());
EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile());
EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype());
EXPECT_EQ(sendPkt->getHlen(), rcvPkt->getHlen());
// since we opened 2 sockets on the same interface and none of them is multicast,
// none is preferred over the other for sending data, so we really should not
// assume the one or the other will always be choosen for sending data. We should
// skip checking source port of sent address.
delete ifacemgr;
}
TEST_F(IfaceMgrTest, socket4) {
createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
2011-11-30 18:12:05 +01:00
// Let's assume that every supported OS have lo interface.
IOAddress loAddr("127.0.0.1");
2011-11-30 18:12:05 +01:00
// Use unprivileged port (it's convenient for running tests as non-root).
int socket1 = 0;
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
);
EXPECT_GT(socket1, 0);
Pkt4 pkt(DHCPDISCOVER, 1234);
pkt.setIface(LOOPBACK);
2011-11-30 18:12:05 +01:00
// Expect that we get the socket that we just opened.
EXPECT_EQ(socket1, ifacemgr->getSocket(pkt));
close(socket1);
delete ifacemgr;
unlink(INTERFACE_FILE);
}
// Test the Iface structure itself
TEST_F(IfaceMgrTest, iface) {
IfaceMgr::Iface* iface = NULL;
EXPECT_NO_THROW(
iface = new IfaceMgr::Iface("eth0",1);
);
EXPECT_EQ("eth0", iface->getName());
EXPECT_EQ(1, iface->getIndex());
EXPECT_EQ("eth0/1", iface->getFullName());
// Let's make a copy of this address collection.
IfaceMgr::AddressCollection addrs = iface->getAddresses();
EXPECT_EQ(0, addrs.size());
IOAddress addr1("192.0.2.6");
iface->addAddress(addr1);
addrs = iface->getAddresses();
ASSERT_EQ(1, addrs.size());
EXPECT_EQ("192.0.2.6", addrs.at(0).toText());
// No such address, should return false.
EXPECT_FALSE(iface->delAddress(IOAddress("192.0.8.9")));
// This address is present, delete it!
EXPECT_TRUE(iface->delAddress(IOAddress("192.0.2.6")));
// Not really necessary, previous reference still points to the same
// collection. Let's do it anyway, as test code may serve as example
// usage code as well.
addrs = iface->getAddresses();
EXPECT_EQ(0, addrs.size());
EXPECT_NO_THROW(
delete iface;
);
}
TEST_F(IfaceMgrTest, socketInfo) {
// check that socketinfo for IPv4 socket is functional
IfaceMgr::SocketInfo sock1(7, IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7);
EXPECT_EQ(7, sock1.sockfd_);
EXPECT_EQ("192.0.2.56", sock1.addr_.toText());
EXPECT_EQ(AF_INET, sock1.family_);
EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_);
// check that socketinfo for IPv6 socket is functional
IfaceMgr::SocketInfo sock2(9, IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9);
EXPECT_EQ(9, sock2.sockfd_);
EXPECT_EQ("2001:db8:1::56", sock2.addr_.toText());
EXPECT_EQ(AF_INET6, sock2.family_);
EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock2.port_);
// now let's test if IfaceMgr handles socket info properly
createLoInterfacesTxt();
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
IfaceMgr::Iface* loopback = ifacemgr->getIface(LOOPBACK);
ASSERT_TRUE(loopback);
loopback->addSocket(sock1);
loopback->addSocket(sock2);
Pkt6 pkt6(100);
// pkt6 dos not have interface set yet
EXPECT_THROW(
ifacemgr->getSocket(pkt6),
BadValue
);
// try to send over non-existing interface
pkt6.iface_ = "nosuchinterface45";
EXPECT_THROW(
ifacemgr->getSocket(pkt6),
BadValue
);
// this will work
pkt6.iface_ = LOOPBACK;
EXPECT_EQ(9, ifacemgr->getSocket(pkt6));
bool deleted = false;
EXPECT_NO_THROW(
deleted = ifacemgr->getIface(LOOPBACK)->delSocket(9);
);
EXPECT_EQ(true, deleted);
// it should throw again, there's no usable socket anymore
EXPECT_THROW(
ifacemgr->getSocket(pkt6),
Unexpected
);
// repeat for pkt4
Pkt4 pkt4(DHCPDISCOVER, 1);
// pkt4 does not have interface set yet.
EXPECT_THROW(
ifacemgr->getSocket(pkt4),
BadValue
);
// Try to send over non-existing interface.
pkt4.setIface("nosuchinterface45");
EXPECT_THROW(
ifacemgr->getSocket(pkt4),
BadValue
);
// Socket info is set, packet has well defined interface. It should work.
pkt4.setIface(LOOPBACK);
EXPECT_EQ(7, ifacemgr->getSocket(pkt4));
EXPECT_NO_THROW(
ifacemgr->getIface(LOOPBACK)->delSocket(7);
);
// It should throw again, there's no usable socket anymore.
EXPECT_THROW(
ifacemgr->getSocket(pkt4),
Unexpected
);
delete ifacemgr;
unlink(INTERFACE_FILE);
}
}