Commit 272122f2 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[2994] Unit-tests implemented for first 3 hooks

parent d3c480a5
......@@ -16,6 +16,7 @@
#include <sstream>
#include <asiolink/io_address.h>
#include <config/ccsession.h>
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/option.h>
......@@ -26,12 +27,15 @@
#include <dhcp/pkt_filter_inet.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/config_parser.h>
#include <hooks/server_hooks.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/utils.h>
#include <gtest/gtest.h>
#include <hooks/server_hooks.h>
#include <hooks/hooks_manager.h>
#include <boost/scoped_ptr.hpp>
......@@ -43,7 +47,9 @@
using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::asiolink;
using namespace isc::hooks;
namespace {
......@@ -80,6 +86,59 @@ public:
: Dhcpv4Srv(port, "type=memfile", false, false) {
}
/// @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 Pkt4Ptr receivePacket(int /*timeout*/) {
// If there is anything prepared as fake incoming
// traffic, use it
if (!fake_received_.empty()) {
Pkt4Ptr pkt = fake_received_.front();
fake_received_.pop_front();
return (pkt);
}
// If not, just trigger shutdown and
// return immediately
shutdown();
return (Pkt4Ptr());
}
/// @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 Pkt4Ptr& pkt) {
fake_sent_.push_back(pkt);
}
/// @brief adds a packet to fake receive queue
///
/// See fake_received_ field for description
void fakeReceive(const Pkt4Ptr& pkt) {
fake_received_.push_back(pkt);
}
virtual ~NakedDhcpv4Srv() {
}
/// @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 NakedDhcpv4Srv::receivePacket() methods.
list<Pkt4Ptr> fake_received_;
list<Pkt4Ptr> fake_sent_;
using Dhcpv4Srv::adjustRemoteAddr;
using Dhcpv4Srv::processDiscover;
using Dhcpv4Srv::processRequest;
......@@ -154,6 +213,16 @@ public:
// it's ok if that fails. There should not be such a file anyway
unlink(SRVID_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();
}
virtual ~Dhcpv4SrvTest() {
......@@ -565,6 +634,13 @@ public:
/// @brief A client-id used in most tests
ClientIdPtr client_id_;
int rcode_;
ConstElementPtr comment_;
// Name of a valid network interface
string valid_iface_;
};
// Sanity check. Verifies that both Dhcpv4Srv and its derived
......@@ -1539,4 +1615,709 @@ TEST_F(Dhcpv4SrvTest, ServerID) {
EXPECT_EQ(srvid_text, text);
}
// Checks if hooks are registered properly.
TEST_F(Dhcpv4SrvTest, Hooks) {
NakedDhcpv4Srv srv(0);
// check if appropriate hooks are registered
int hook_index_pkt4_received = -1;
int hook_index_select_subnet = -1;
int hook_index_pkt4_send = -1;
// check if appropriate indexes are set
EXPECT_NO_THROW(hook_index_pkt4_received = ServerHooks::getServerHooks()
.getIndex("pkt4_receive"));
EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
.getIndex("subnet4_select"));
EXPECT_NO_THROW(hook_index_pkt4_send = ServerHooks::getServerHooks()
.getIndex("pkt4_send"));
EXPECT_TRUE(hook_index_pkt4_received > 0);
EXPECT_TRUE(hook_index_select_subnet > 0);
EXPECT_TRUE(hook_index_pkt4_send > 0);
}
// a dummy MAC address
const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
// A dummy MAC address, padded with 0s
const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 };
// Let's use some creative test content here (128 chars + \0)
const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit. Proin mollis placerat metus, at "
"lacinia orci ornare vitae. Mauris amet.";
// Yet another type of test content (64 chars + \0)
const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit posuere.";
/// @brief a class dedicated to Hooks testing in DHCPv4 server
///
/// This class has a number of static members, because each non-static
/// method has implicit 'this' parameter, so it does not match callout
/// signature and couldn't be registered. Furthermore, static methods
/// can't modify non-static members (for obvious reasons), so many
/// fields are declared static. It is still better to keep them as
/// one class rather than unrelated collection of global objects.
class HooksDhcpv4SrvTest : public Dhcpv4SrvTest {
public:
/// @brief creates Dhcpv4Srv and prepares buffers for callouts
HooksDhcpv4SrvTest() {
// Allocate new DHCPv6 Server
srv_ = new NakedDhcpv4Srv(0);
// clear static buffers
resetCalloutBuffers();
}
/// @brief destructor (deletes Dhcpv4Srv)
virtual ~HooksDhcpv4SrvTest() {
delete srv_;
}
/// @brief creates an option with specified option code
///
/// This method is static, because it is used from callouts
/// that do not have a pointer to HooksDhcpv4SSrvTest object
///
/// @param option_code code of option to be created
///
/// @return pointer to create option object
static OptionPtr createOption(uint16_t option_code) {
char payload[] = {
0xa, 0xb, 0xc, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14
};
OptionBuffer tmp(payload, payload + sizeof(payload));
return OptionPtr(new Option(Option::V4, option_code, tmp));
}
/// @brief Generates test packet.
///
/// Allocates and generates on-wire buffer that represents test packet, with all
/// fixed fields set to non-zero values. Content is not always reasonable.
///
/// See generateTestPacket1() function that returns exactly the same packet as
/// Pkt4 object.
///
/// @return pointer to allocated Pkt4 object
// Returns a vector containing a DHCPv4 packet header.
Pkt4Ptr
generateSimpleDiscover() {
// That is only part of the header. It contains all "short" fields,
// larger fields are constructed separately.
uint8_t hdr[] = {
1, 6, 6, 13, // op, htype, hlen, hops,
0x12, 0x34, 0x56, 0x78, // transaction-id
0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags
192, 0, 2, 1, // ciaddr
1, 2, 3, 4, // yiaddr
192, 0, 2, 255, // siaddr
255, 255, 255, 255, // giaddr
};
// Initialize the vector with the header fields defined above.
vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
// Append the large header fields.
copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
// Should now have all the header, so check. The "static_cast" is used
// to get round an odd bug whereby the linker appears not to find the
// definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
return (Pkt4Ptr(new Pkt4(DHCPDISCOVER, 12345)));
}
/// test callback that stores received callout name and pkt6 value
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
pkt4_receive_callout(CalloutHandle& callout_handle) {
callback_name_ = string("pkt4_receive");
callout_handle.getArgument("query4", callback_pkt4_);
callback_argument_names_ = callout_handle.getArgumentNames();
return (0);
}
/// test callback that changes client-id value
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
pkt4_receive_change_clientid(CalloutHandle& callout_handle) {
Pkt4Ptr pkt;
callout_handle.getArgument("query4", pkt);
// get rid of the old client-id
pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
// add a new option
pkt->addOption(createOption(DHO_DHCP_CLIENT_IDENTIFIER));
// carry on as usual
return pkt4_receive_callout(callout_handle);
}
/// test callback that deletes client-id
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
pkt4_receive_delete_clientid(CalloutHandle& callout_handle) {
Pkt4Ptr pkt;
callout_handle.getArgument("query4", pkt);
// get rid of the old client-id
pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
// carry on as usual
return pkt4_receive_callout(callout_handle);
}
/// test callback that sets skip flag
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
pkt4_receive_skip(CalloutHandle& callout_handle) {
Pkt4Ptr pkt;
callout_handle.getArgument("query4", pkt);
callout_handle.setSkip(true);
// carry on as usual
return pkt4_receive_callout(callout_handle);
}
/// Test callback that stores received callout name and pkt4 value
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
pkt4_send_callout(CalloutHandle& callout_handle) {
callback_name_ = string("pkt4_send");
callout_handle.getArgument("response4", callback_pkt4_);
callback_argument_names_ = callout_handle.getArgumentNames();
return (0);
}
// Test callback that changes server-id
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
pkt4_send_change_serverid(CalloutHandle& callout_handle) {
Pkt4Ptr pkt;
callout_handle.getArgument("response4", pkt);
// get rid of the old server-id
pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER);
// add a new option
pkt->addOption(createOption(DHO_DHCP_SERVER_IDENTIFIER));
// carry on as usual
return pkt4_send_callout(callout_handle);
}
/// test callback that deletes server-id
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
pkt4_send_delete_serverid(CalloutHandle& callout_handle) {
Pkt4Ptr pkt;
callout_handle.getArgument("response4", pkt);
// get rid of the old client-id
pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER);
// carry on as usual
return pkt4_send_callout(callout_handle);
}
/// Test callback that sets skip flag
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
pkt4_send_skip(CalloutHandle& callout_handle) {
Pkt4Ptr pkt;
callout_handle.getArgument("response4", pkt);
callout_handle.setSkip(true);
// carry on as usual
return pkt4_send_callout(callout_handle);
}
/// Test callback that stores received callout name and subnet4 values
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
subnet4_select_callout(CalloutHandle& callout_handle) {
callback_name_ = string("subnet4_select");
callout_handle.getArgument("query4", callback_pkt4_);
callout_handle.getArgument("subnet4", callback_subnet4_);
callout_handle.getArgument("subnet4collection", callback_subnet4collection_);
callback_argument_names_ = callout_handle.getArgumentNames();
return (0);
}
/// Test callback that picks the other subnet if possible.
/// @param callout_handle handle passed by the hooks framework
/// @return always 0
static int
subnet4_select_different_subnet_callout(CalloutHandle& callout_handle) {
// Call the basic calllout to record all passed values
subnet4_select_callout(callout_handle);
Subnet4Collection subnets;
Subnet4Ptr subnet;
callout_handle.getArgument("subnet4", subnet);
callout_handle.getArgument("subnet4collection", subnets);
// Let's change to a different subnet
if (subnets.size() > 1) {
subnet = subnets[1]; // Let's pick the other subnet
callout_handle.setArgument("subnet4", subnet);
}
return (0);
}
/// resets buffers used to store data received by callouts
void resetCalloutBuffers() {
callback_name_ = string("");
callback_pkt4_.reset();
callback_subnet4_.reset();
callback_subnet4collection_ = NULL;
callback_argument_names_.clear();
}
/// pointer to Dhcpv4Srv that is used in tests
NakedDhcpv4Srv* srv_;
// The following fields are used in testing pkt4_receive_callout
/// String name of the received callout
static string callback_name_;
/// Pkt4 structure returned in the callout
static Pkt4Ptr callback_pkt4_;
/// Pointer to a subnet received by callout
static Subnet4Ptr callback_subnet4_;
/// A list of all available subnets (received by callout)
static const Subnet4Collection* callback_subnet4collection_;
/// A list of all received arguments
static vector<string> callback_argument_names_;
};
// The following fields are used in testing pkt4_receive_callout.
// See fields description in the class for details
string HooksDhcpv4SrvTest::callback_name_;
Pkt4Ptr HooksDhcpv4SrvTest::callback_pkt4_;
Subnet4Ptr HooksDhcpv4SrvTest::callback_subnet4_;
const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_;
vector<string> HooksDhcpv4SrvTest::callback_argument_names_;
// Checks if callouts installed on pkt4_received are indeed called and the
// all necessary parameters are passed.
//
// Note that the test name does not follow test naming convention,
// but the proper hook name is "pkt4_receive".
TEST_F(HooksDhcpv4SrvTest, simple_pkt4_receive) {
// Install pkt4_receive_callout
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"pkt4_receive", pkt4_receive_callout));
// Let's create a simple DISCOVER
Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
// Simulate that we have received that traffic
srv_->fakeReceive(sol);
// Server will now process to run its normal loop, but instead of calling
// IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered pkt4_receive callback.
srv_->run();
// check that the callback called is indeed the one we installed
EXPECT_EQ("pkt4_receive", callback_name_);
// check that pkt4 argument passing was successful and returned proper value
EXPECT_TRUE(callback_pkt4_.get() == sol.get());
// Check that all expected parameters are there
vector<string> expected_argument_names;
expected_argument_names.push_back(string("query4"));
EXPECT_TRUE(expected_argument_names == callback_argument_names_);
}
// Checks if callouts installed on pkt4_received is able to change
// the values and the parameters are indeed used by the server.
TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_receive) {
// Install pkt4_receive_callout
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"pkt4_receive", pkt4_receive_change_clientid));
// Let's create a simple DISCOVER
Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
// Simulate that we have received that traffic
srv_->fakeReceive(sol);
// Server will now process to run its normal loop, but instead of calling
// IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered pkt4_receive callback.
srv_->run();
// check that the server did send a reposonce
ASSERT_EQ(1, srv_->fake_sent_.size());
// Make sure that we received a response
Pkt4Ptr adv = srv_->fake_sent_.front();
ASSERT_TRUE(adv);
// Get client-id...
OptionPtr clientid = adv->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
// ... and check if it is the modified value
OptionPtr expected = createOption(DHO_DHCP_CLIENT_IDENTIFIER);
EXPECT_TRUE(clientid->equal(expected));
}
// Checks if callouts installed on pkt4_received is able to delete
// existing options and that change impacts server processing (mandatory
// client-id option is deleted, so the packet is expected to be dropped)
TEST_F(HooksDhcpv4SrvTest, deleteClientId_pkt4_receive) {
// Install pkt4_receive_callout
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"pkt4_receive", pkt4_receive_delete_clientid));
// Let's create a simple DISCOVER
Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
// Simulate that we have received that traffic
srv_->fakeReceive(sol);
// Server will now process to run its normal loop, but instead of calling
// IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered pkt4_receive callback.
srv_->run();
// Check that the server dropped the packet and did not send a response
ASSERT_EQ(0, srv_->fake_sent_.size());
}
// Checks if callouts installed on pkt4_received is able to set skip flag that
// will cause the server to not process the packet (drop), even though it is valid.
TEST_F(HooksDhcpv4SrvTest, skip_pkt4_receive) {
// Install pkt4_receive_callout
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"pkt4_receive", pkt4_receive_skip));
// Let's create a simple DISCOVER
Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
// Simulate that we have received that traffic
srv_->fakeReceive(sol);
// Server will now process to run its normal loop, but instead of calling
// IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered pkt4_receive callback.
srv_->run();
// check that the server dropped the packet and did not produce any response
ASSERT_EQ(0, srv_->fake_sent_.size());
}
// Checks if callouts installed on pkt4_send are indeed called and the
// all necessary parameters are passed.
TEST_F(HooksDhcpv4SrvTest, simple_pkt4_send) {
// Install pkt4_receive_callout
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
"pkt4_send", pkt4_send_callout));
// Let's create a simple DISCOVER
Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
// Simulate that we have received that traffic
srv_->fakeReceive(sol);
// Server will now process to run its normal loop, but instead of calling
// IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered pkt4_receive callback.
srv_->run();
// Check that the callback called is indeed the one we installed
EXPECT_EQ("pkt4_send", callback_name_);
// Check that there is one packet sent
ASSERT_EQ(1, srv_->fake_sent_.size());
Pkt4Ptr adv = srv_->fake_sent_.front();
// Check that pkt4 argument passing was successful and returned proper value
EXPECT_TRUE(callback_pkt4_.get() == adv.get());
// Check that all expected parameters are there
vector<string> expected_argument_names;
expected_argument_names.push_back(string("response4"));
EXPECT_TRUE(expected_argument_names == callback_argument_names_);
}
// Checks if callouts installed on pkt4_send is able to change
// the values and the packet sent contains those changes
TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_send) {
// Install pkt4_receive_callout
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(