Commit 6f304d9e authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[2984] DHCPv6 hooks tests moved to a separate file.

parent 61363a11
......@@ -49,6 +49,8 @@ TESTS += dhcp6_unittests
dhcp6_unittests_SOURCES = dhcp6_unittests.cc
dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
dhcp6_unittests_SOURCES += hook_unittest.cc
dhcp6_unittests_SOURCES += dhcp6_test_utils.h
dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
dhcp6_unittests_SOURCES += config_parser_unittest.cc
dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
......
This diff is collapsed.
// Copyright (C) 2013 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.
/// @file dhcp6_test_utils.h
///
/// @brief This file contains utility classes used for DHCPv6 server testing
#include <gtest/gtest.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <hooks/hooks_manager.h>
#include <config/ccsession.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcp/pkt6.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_int_array.h>
#include <list>
using namespace isc::dhcp;
using namespace isc::config;
using namespace isc::data;
using namespace isc::hooks;
using namespace isc::asiolink;
using namespace isc::util;
using namespace isc::hooks;
namespace isc {
namespace test {
/// @brief "naked" Dhcpv6Srv class that exposes internal members
class NakedDhcpv6Srv: public isc::dhcp::Dhcpv6Srv {
public:
NakedDhcpv6Srv(uint16_t port) : Dhcpv6Srv(port) {
// Open the "memfile" database for leases
std::string memfile = "type=memfile";
LeaseMgrFactory::create(memfile);
}
/// @brief fakes packet reception
/// @param timeout ignored
///
/// The method receives all packets queued in receive
/// queue, one after another. Once the queue is empty,
/// it initiates the shutdown procedure.
///
/// See fake_received_ field for description
virtual isc::dhcp::Pkt6Ptr receivePacket(int /*timeout*/) {
// If there is anything prepared as fake incoming
// traffic, use it
if (!fake_received_.empty()) {
Pkt6Ptr pkt = fake_received_.front();
fake_received_.pop_front();
return (pkt);
}
// If not, just trigger shutdown and
// return immediately
shutdown();
return (Pkt6Ptr());
}
/// @brief fake packet sending
///
/// Pretend to send a packet, but instead just store
/// it in fake_send_ list where test can later inspect
/// server's response.
virtual void sendPacket(const Pkt6Ptr& pkt) {
fake_sent_.push_back(pkt);
}
/// @brief adds a packet to fake receive queue
///
/// See fake_received_ field for description
void fakeReceive(const Pkt6Ptr& pkt) {
fake_received_.push_back(pkt);
}
virtual ~NakedDhcpv6Srv() {
// Close the lease database
LeaseMgrFactory::destroy();
}
using Dhcpv6Srv::processSolicit;
using Dhcpv6Srv::processRequest;
using Dhcpv6Srv::processRenew;
using Dhcpv6Srv::processRelease;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
using Dhcpv6Srv::sanityCheck;
using Dhcpv6Srv::loadServerID;
using Dhcpv6Srv::writeServerID;
/// @brief packets we pretend to receive
///
/// Instead of setting up sockets on interfaces that change between OSes, it
/// is much easier to fake packet reception. This is a list of packets that
/// we pretend to have received. You can schedule new packets to be received
/// using fakeReceive() and NakedDhcpv6Srv::receivePacket() methods.
std::list<Pkt6Ptr> fake_received_;
std::list<Pkt6Ptr> fake_sent_;
};
static const char* DUID_FILE = "server-id-test.txt";
// test fixture for any tests requiring blank/empty configuration
// serves as base class for additional tests
class NakedDhcpv6SrvTest : public ::testing::Test {
public:
NakedDhcpv6SrvTest() : rcode_(-1) {
// it's ok if that fails. There should not be such a file anyway
unlink(DUID_FILE);
const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
// There must be some interface detected
if (ifaces.empty()) {
// We can't use ASSERT in constructor
ADD_FAILURE() << "No interfaces detected.";
}
valid_iface_ = ifaces.begin()->getName();
}
// Generate IA_NA option with specified parameters
boost::shared_ptr<Option6IA> generateIA(uint32_t iaid, uint32_t t1, uint32_t t2) {
boost::shared_ptr<Option6IA> ia =
boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, iaid));
ia->setT1(t1);
ia->setT2(t2);
return (ia);
}
/// @brief generates interface-id option, based on text
///
/// @param iface_id textual representation of the interface-id content
///
/// @return pointer to the option object
OptionPtr generateInterfaceId(const std::string& iface_id) {
OptionBuffer tmp(iface_id.begin(), iface_id.end());
return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
}
// Generate client-id option
OptionPtr generateClientId(size_t duid_size = 32) {
OptionBuffer clnt_duid(duid_size);
for (int i = 0; i < duid_size; i++) {
clnt_duid[i] = 100 + i;
}
duid_ = DuidPtr(new DUID(clnt_duid));
return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
clnt_duid.begin(),
clnt_duid.begin() + duid_size)));
}
// Checks if server response (ADVERTISE or REPLY) includes proper server-id.
void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
// check that server included its server-id
OptionPtr tmp = rsp->getOption(D6O_SERVERID);
EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
ASSERT_EQ(tmp->len(), expected_srvid->len() );
EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
}
// Checks if server response (ADVERTISE or REPLY) includes proper client-id.
void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
// check that server included our own client-id
OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
ASSERT_TRUE(tmp);
EXPECT_EQ(expected_clientid->getType(), tmp->getType());
ASSERT_EQ(expected_clientid->len(), tmp->len());
// check that returned client-id is valid
EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
}
// Checks if server response is a NAK
void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
uint32_t expected_transid,
uint16_t expected_status_code) {
// Check if we get response at all
checkResponse(rsp, expected_message_type, expected_transid);
// Check that IA_NA was returned
OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
ASSERT_TRUE(option_ia_na);
// check that the status is no address available
boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(option_ia_na);
ASSERT_TRUE(ia);
checkIA_NAStatusCode(ia, expected_status_code);
}
// Checks that server rejected IA_NA, i.e. that it has no addresses and
// that expected status code really appears there. In some limited cases
// (reply to RELEASE) it may be used to verify positive case, where
// IA_NA response is expected to not include address.
//
// Status code indicates type of error encountered (in theory it can also
// indicate success, but servers typically don't send success status
// as this is the default result and it saves bandwidth)
void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
uint16_t expected_status_code) {
// Make sure there is no address assigned.
EXPECT_FALSE(ia->getOption(D6O_IAADDR));
// T1, T2 should be zeroed
EXPECT_EQ(0, ia->getT1());
EXPECT_EQ(0, ia->getT2());
OptionCustomPtr status =
boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
// It is ok to not include status success as this is the default behavior
if (expected_status_code == STATUS_Success && !status) {
return;
}
EXPECT_TRUE(status);
if (status) {
// We don't have dedicated class for status code, so let's just interpret
// first 2 bytes as status. Remainder of the status code option content is
// just a text explanation what went wrong.
EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
status->readInteger<uint16_t>(0));
}
}
void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
OptionCustomPtr status =
boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
// It is ok to not include status success as this is the default behavior
if (expected_status == STATUS_Success && !status) {
return;
}
EXPECT_TRUE(status);
if (status) {
// We don't have dedicated class for status code, so let's just interpret
// first 2 bytes as status. Remainder of the status code option content is
// just a text explanation what went wrong.
EXPECT_EQ(static_cast<uint16_t>(expected_status),
status->readInteger<uint16_t>(0));
}
}
// Basic checks for generated response (message type and transaction-id).
void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
uint32_t expected_transid) {
ASSERT_TRUE(rsp);
EXPECT_EQ(expected_message_type, rsp->getType());
EXPECT_EQ(expected_transid, rsp->getTransid());
}
virtual ~NakedDhcpv6SrvTest() {
// Let's clean up if there is such a file.
unlink(DUID_FILE);
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
"pkt6_receive");
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
"pkt6_send");
HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
"subnet6_select");
};
// A DUID used in most tests (typically as client-id)
DuidPtr duid_;
int rcode_;
ConstElementPtr comment_;
// Name of a valid network interface
std::string valid_iface_;
};
// Provides suport for tests against a preconfigured subnet6
// extends upon NakedDhcp6SrvTest
class Dhcpv6SrvTest : public NakedDhcpv6SrvTest {
public:
/// Name of the server-id file (used in server-id tests)
// these are empty for now, but let's keep them around
Dhcpv6SrvTest() {
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
2000, 3000, 4000));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
subnet_->addPool(pool_);
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet_);
}
// Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
// It returns IAADDR option for each chaining with checkIAAddr method.
boost::shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
uint32_t expected_t1, uint32_t expected_t2) {
OptionPtr tmp = rsp->getOption(D6O_IA_NA);
// Can't use ASSERT_TRUE() in method that returns something
if (!tmp) {
ADD_FAILURE() << "IA_NA option not present in response";
return (boost::shared_ptr<Option6IAAddr>());
}
boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
if (!ia) {
ADD_FAILURE() << "IA_NA cannot convert option ptr to Option6";
return (boost::shared_ptr<Option6IAAddr>());
}
EXPECT_EQ(expected_iaid, ia->getIAID());
EXPECT_EQ(expected_t1, ia->getT1());
EXPECT_EQ(expected_t2, ia->getT2());
tmp = ia->getOption(D6O_IAADDR);
boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
return (addr);
}
// Check that generated IAADDR option contains expected address
// and lifetime values match the configured subnet
void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
const IOAddress& expected_addr,
uint32_t /* expected_preferred */,
uint32_t /* expected_valid */) {
// Check that the assigned address is indeed from the configured pool.
// Note that when comparing addresses, we compare the textual
// representation. IOAddress does not support being streamed to
// an ostream, which means it can't be used in EXPECT_EQ.
EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
EXPECT_EQ(addr->getValid(), subnet_->getValid());
}
// Checks if the lease sent to client is present in the database
// and is valid when checked agasint the configured subnet
Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
boost::shared_ptr<Option6IAAddr> addr) {
boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_na);
Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(addr->getAddress());
if (!lease) {
std::cout << "Lease for " << addr->getAddress().toText()
<< " not found in the database backend.";
return (Lease6Ptr());
}
EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
EXPECT_TRUE(*lease->duid_ == *duid);
EXPECT_EQ(ia->getIAID(), lease->iaid_);
EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
return (lease);
}
~Dhcpv6SrvTest() {
CfgMgr::instance().deleteSubnets6();
};
// A subnet used in most tests
Subnet6Ptr subnet_;
// A pool used in most tests
Pool6Ptr pool_;
};
}; // end of isc::test namespace
}; // end of isc namespace
This diff is collapsed.
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