mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 14:05:33 +00:00
[2984] DHCPv6 hooks tests moved to a separate file.
This commit is contained in:
@@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
392
src/bin/dhcp6/tests/dhcp6_test_utils.h
Normal file
392
src/bin/dhcp6/tests/dhcp6_test_utils.h
Normal file
@@ -0,0 +1,392 @@
|
||||
// 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
|
736
src/bin/dhcp6/tests/hook_unittest.cc
Normal file
736
src/bin/dhcp6/tests/hook_unittest.cc
Normal file
@@ -0,0 +1,736 @@
|
||||
// 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.
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/dhcp6.h>
|
||||
#include <dhcp/duid.h>
|
||||
#include <dhcp/option.h>
|
||||
#include <dhcp/option_custom.h>
|
||||
#include <dhcp/option6_addrlst.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp6/config_parser.h>
|
||||
#include <dhcp/dhcp6.h>
|
||||
#include <dhcpsrv/cfgmgr.h>
|
||||
#include <dhcpsrv/lease_mgr.h>
|
||||
#include <dhcpsrv/lease_mgr_factory.h>
|
||||
#include <dhcpsrv/utils.h>
|
||||
#include <util/buffer.h>
|
||||
#include <util/range_utilities.h>
|
||||
#include <hooks/server_hooks.h>
|
||||
|
||||
#include <dhcp6/tests/dhcp6_test_utils.h>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <unistd.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
using namespace isc;
|
||||
using namespace isc::test;
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::util;
|
||||
using namespace isc::hooks;
|
||||
using namespace std;
|
||||
|
||||
// namespace has to be named, because friends are defined in Dhcpv6Srv class
|
||||
// Maybe it should be isc::test?
|
||||
namespace {
|
||||
|
||||
// Checks if hooks are implemented properly.
|
||||
TEST_F(Dhcpv6SrvTest, Hooks) {
|
||||
NakedDhcpv6Srv srv(0);
|
||||
|
||||
// check if appropriate hooks are registered
|
||||
int hook_index_pkt6_received = -1;
|
||||
int hook_index_select_subnet = -1;
|
||||
int hook_index_pkt6_send = -1;
|
||||
|
||||
// check if appropriate indexes are set
|
||||
EXPECT_NO_THROW(hook_index_pkt6_received = ServerHooks::getServerHooks()
|
||||
.getIndex("pkt6_receive"));
|
||||
EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
|
||||
.getIndex("subnet6_select"));
|
||||
EXPECT_NO_THROW(hook_index_pkt6_send = ServerHooks::getServerHooks()
|
||||
.getIndex("pkt6_send"));
|
||||
|
||||
EXPECT_TRUE(hook_index_pkt6_received > 0);
|
||||
EXPECT_TRUE(hook_index_select_subnet > 0);
|
||||
EXPECT_TRUE(hook_index_pkt6_send > 0);
|
||||
}
|
||||
|
||||
// This function returns buffer for very simple Solicit
|
||||
Pkt6* captureSimpleSolicit() {
|
||||
Pkt6* pkt;
|
||||
uint8_t data[] = {
|
||||
1, // type 1 = SOLICIT
|
||||
0xca, 0xfe, 0x01, // trans-id = 0xcafe01
|
||||
0, 1, // option type 1 (client-id)
|
||||
0, 10, // option lenth 10
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // DUID
|
||||
0, 3, // option type 3 (IA_NA)
|
||||
0, 12, // option length 12
|
||||
0, 0, 0, 1, // iaid = 1
|
||||
0, 0, 0, 0, // T1 = 0
|
||||
0, 0, 0, 0 // T2 = 0
|
||||
};
|
||||
|
||||
pkt = new Pkt6(data, sizeof(data));
|
||||
pkt->setRemotePort(546);
|
||||
pkt->setRemoteAddr(IOAddress("fe80::1"));
|
||||
pkt->setLocalPort(0);
|
||||
pkt->setLocalAddr(IOAddress("ff02::1:2"));
|
||||
pkt->setIndex(2);
|
||||
pkt->setIface("eth0");
|
||||
|
||||
return (pkt);
|
||||
}
|
||||
|
||||
/// @brief a class dedicated to Hooks testing in DHCPv6 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 HooksDhcpv6SrvTest : public Dhcpv6SrvTest {
|
||||
|
||||
public:
|
||||
|
||||
/// @brief creates Dhcpv6Srv and prepares buffers for callouts
|
||||
HooksDhcpv6SrvTest() {
|
||||
|
||||
// Allocate new DHCPv6 Server
|
||||
srv_ = new NakedDhcpv6Srv(0);
|
||||
|
||||
// clear static buffers
|
||||
resetCalloutBuffers();
|
||||
}
|
||||
|
||||
/// @brief destructor (deletes Dhcpv6Srv)
|
||||
~HooksDhcpv6SrvTest() {
|
||||
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 HooksDhcpv6SSrvTest 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::V6, option_code, tmp));
|
||||
}
|
||||
|
||||
/// test callback that stores received callout name and pkt6 value
|
||||
/// @param callout_handle handle passed by the hooks framework
|
||||
/// @return always 0
|
||||
static int
|
||||
pkt6_receive_callout(CalloutHandle& callout_handle) {
|
||||
callback_name_ = string("pkt6_receive");
|
||||
|
||||
callout_handle.getArgument("query6", callback_pkt6_);
|
||||
|
||||
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
|
||||
pkt6_receive_change_clientid(CalloutHandle& callout_handle) {
|
||||
|
||||
Pkt6Ptr pkt;
|
||||
callout_handle.getArgument("query6", pkt);
|
||||
|
||||
// get rid of the old client-id
|
||||
pkt->delOption(D6O_CLIENTID);
|
||||
|
||||
// add a new option
|
||||
pkt->addOption(createOption(D6O_CLIENTID));
|
||||
|
||||
// carry on as usual
|
||||
return pkt6_receive_callout(callout_handle);
|
||||
}
|
||||
|
||||
/// test callback that deletes client-id
|
||||
/// @param callout_handle handle passed by the hooks framework
|
||||
/// @return always 0
|
||||
static int
|
||||
pkt6_receive_delete_clientid(CalloutHandle& callout_handle) {
|
||||
|
||||
Pkt6Ptr pkt;
|
||||
callout_handle.getArgument("query6", pkt);
|
||||
|
||||
// get rid of the old client-id
|
||||
pkt->delOption(D6O_CLIENTID);
|
||||
|
||||
// carry on as usual
|
||||
return pkt6_receive_callout(callout_handle);
|
||||
}
|
||||
|
||||
/// test callback that sets skip flag
|
||||
/// @param callout_handle handle passed by the hooks framework
|
||||
/// @return always 0
|
||||
static int
|
||||
pkt6_receive_skip(CalloutHandle& callout_handle) {
|
||||
|
||||
Pkt6Ptr pkt;
|
||||
callout_handle.getArgument("query6", pkt);
|
||||
|
||||
callout_handle.setSkip(true);
|
||||
|
||||
// carry on as usual
|
||||
return pkt6_receive_callout(callout_handle);
|
||||
}
|
||||
|
||||
/// Test callback that stores received callout name and pkt6 value
|
||||
/// @param callout_handle handle passed by the hooks framework
|
||||
/// @return always 0
|
||||
static int
|
||||
pkt6_send_callout(CalloutHandle& callout_handle) {
|
||||
callback_name_ = string("pkt6_send");
|
||||
|
||||
callout_handle.getArgument("response6", callback_pkt6_);
|
||||
|
||||
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
|
||||
pkt6_send_change_serverid(CalloutHandle& callout_handle) {
|
||||
|
||||
Pkt6Ptr pkt;
|
||||
callout_handle.getArgument("response6", pkt);
|
||||
|
||||
// get rid of the old server-id
|
||||
pkt->delOption(D6O_SERVERID);
|
||||
|
||||
// add a new option
|
||||
pkt->addOption(createOption(D6O_SERVERID));
|
||||
|
||||
// carry on as usual
|
||||
return pkt6_send_callout(callout_handle);
|
||||
}
|
||||
|
||||
/// test callback that deletes server-id
|
||||
/// @param callout_handle handle passed by the hooks framework
|
||||
/// @return always 0
|
||||
static int
|
||||
pkt6_send_delete_serverid(CalloutHandle& callout_handle) {
|
||||
|
||||
Pkt6Ptr pkt;
|
||||
callout_handle.getArgument("response6", pkt);
|
||||
|
||||
// get rid of the old client-id
|
||||
pkt->delOption(D6O_SERVERID);
|
||||
|
||||
// carry on as usual
|
||||
return pkt6_send_callout(callout_handle);
|
||||
}
|
||||
|
||||
/// Test callback that sets skip flag
|
||||
/// @param callout_handle handle passed by the hooks framework
|
||||
/// @return always 0
|
||||
static int
|
||||
pkt6_send_skip(CalloutHandle& callout_handle) {
|
||||
|
||||
Pkt6Ptr pkt;
|
||||
callout_handle.getArgument("response6", pkt);
|
||||
|
||||
callout_handle.setSkip(true);
|
||||
|
||||
// carry on as usual
|
||||
return pkt6_send_callout(callout_handle);
|
||||
}
|
||||
|
||||
/// Test callback that stores received callout name and subnet6 values
|
||||
/// @param callout_handle handle passed by the hooks framework
|
||||
/// @return always 0
|
||||
static int
|
||||
subnet6_select_callout(CalloutHandle& callout_handle) {
|
||||
callback_name_ = string("subnet6_select");
|
||||
|
||||
callout_handle.getArgument("query6", callback_pkt6_);
|
||||
callout_handle.getArgument("subnet6", callback_subnet6_);
|
||||
callout_handle.getArgument("subnet6collection", callback_subnet6collection_);
|
||||
|
||||
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
|
||||
subnet6_select_different_subnet_callout(CalloutHandle& callout_handle) {
|
||||
|
||||
// Call the basic calllout to record all passed values
|
||||
subnet6_select_callout(callout_handle);
|
||||
|
||||
const Subnet6Collection* subnets;
|
||||
Subnet6Ptr subnet;
|
||||
callout_handle.getArgument("subnet6", subnet);
|
||||
callout_handle.getArgument("subnet6collection", subnets);
|
||||
|
||||
// Let's change to a different subnet
|
||||
if (subnets->size() > 1) {
|
||||
subnet = (*subnets)[1]; // Let's pick the other subnet
|
||||
callout_handle.setArgument("subnet6", subnet);
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/// resets buffers used to store data received by callouts
|
||||
void resetCalloutBuffers() {
|
||||
callback_name_ = string("");
|
||||
callback_pkt6_.reset();
|
||||
callback_subnet6_.reset();
|
||||
callback_subnet6collection_ = NULL;
|
||||
callback_argument_names_.clear();
|
||||
}
|
||||
|
||||
/// pointer to Dhcpv6Srv that is used in tests
|
||||
NakedDhcpv6Srv* srv_;
|
||||
|
||||
// The following fields are used in testing pkt6_receive_callout
|
||||
|
||||
/// String name of the received callout
|
||||
static string callback_name_;
|
||||
|
||||
/// Pkt6 structure returned in the callout
|
||||
static Pkt6Ptr callback_pkt6_;
|
||||
|
||||
/// Pointer to a subnet received by callout
|
||||
static Subnet6Ptr callback_subnet6_;
|
||||
|
||||
/// A list of all available subnets (received by callout)
|
||||
static const Subnet6Collection* callback_subnet6collection_;
|
||||
|
||||
/// A list of all received arguments
|
||||
static vector<string> callback_argument_names_;
|
||||
};
|
||||
|
||||
// The following fields are used in testing pkt6_receive_callout.
|
||||
// See fields description in the class for details
|
||||
string HooksDhcpv6SrvTest::callback_name_;
|
||||
Pkt6Ptr HooksDhcpv6SrvTest::callback_pkt6_;
|
||||
Subnet6Ptr HooksDhcpv6SrvTest::callback_subnet6_;
|
||||
const Subnet6Collection* HooksDhcpv6SrvTest::callback_subnet6collection_;
|
||||
vector<string> HooksDhcpv6SrvTest::callback_argument_names_;
|
||||
|
||||
|
||||
// Checks if callouts installed on pkt6_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 "pkt6_receive".
|
||||
TEST_F(HooksDhcpv6SrvTest, simple_pkt6_receive) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"pkt6_receive", pkt6_receive_callout));
|
||||
|
||||
// Let's create a simple SOLICIT
|
||||
Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
|
||||
|
||||
// Simulate that we have received that traffic
|
||||
srv_->fakeReceive(sol);
|
||||
|
||||
// Server will now process to run its normal loop, but instead of calling
|
||||
// IfaceMgr::receive6(), it will read all packets from the list set by
|
||||
// fakeReceive()
|
||||
// In particular, it should call registered pkt6_receive callback.
|
||||
srv_->run();
|
||||
|
||||
// check that the callback called is indeed the one we installed
|
||||
EXPECT_EQ("pkt6_receive", callback_name_);
|
||||
|
||||
// check that pkt6 argument passing was successful and returned proper value
|
||||
EXPECT_TRUE(callback_pkt6_.get() == sol.get());
|
||||
|
||||
// Check that all expected parameters are there
|
||||
vector<string> expected_argument_names;
|
||||
expected_argument_names.push_back(string("query6"));
|
||||
|
||||
EXPECT_TRUE(expected_argument_names == callback_argument_names_);
|
||||
}
|
||||
|
||||
// Checks if callouts installed on pkt6_received is able to change
|
||||
// the values and the parameters are indeed used by the server.
|
||||
TEST_F(HooksDhcpv6SrvTest, valueChange_pkt6_receive) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"pkt6_receive", pkt6_receive_change_clientid));
|
||||
|
||||
// Let's create a simple SOLICIT
|
||||
Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
|
||||
|
||||
// Simulate that we have received that traffic
|
||||
srv_->fakeReceive(sol);
|
||||
|
||||
// Server will now process to run its normal loop, but instead of calling
|
||||
// IfaceMgr::receive6(), it will read all packets from the list set by
|
||||
// fakeReceive()
|
||||
// In particular, it should call registered pkt6_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
|
||||
Pkt6Ptr adv = srv_->fake_sent_.front();
|
||||
ASSERT_TRUE(adv);
|
||||
|
||||
// Get client-id...
|
||||
OptionPtr clientid = adv->getOption(D6O_CLIENTID);
|
||||
|
||||
// ... and check if it is the modified value
|
||||
OptionPtr expected = createOption(D6O_CLIENTID);
|
||||
EXPECT_TRUE(clientid->equal(expected));
|
||||
}
|
||||
|
||||
// Checks if callouts installed on pkt6_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(HooksDhcpv6SrvTest, deleteClientId_pkt6_receive) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"pkt6_receive", pkt6_receive_delete_clientid));
|
||||
|
||||
// Let's create a simple SOLICIT
|
||||
Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
|
||||
|
||||
// Simulate that we have received that traffic
|
||||
srv_->fakeReceive(sol);
|
||||
|
||||
// Server will now process to run its normal loop, but instead of calling
|
||||
// IfaceMgr::receive6(), it will read all packets from the list set by
|
||||
// fakeReceive()
|
||||
// In particular, it should call registered pkt6_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 pkt6_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(HooksDhcpv6SrvTest, skip_pkt6_receive) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"pkt6_receive", pkt6_receive_skip));
|
||||
|
||||
// Let's create a simple SOLICIT
|
||||
Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
|
||||
|
||||
// Simulate that we have received that traffic
|
||||
srv_->fakeReceive(sol);
|
||||
|
||||
// Server will now process to run its normal loop, but instead of calling
|
||||
// IfaceMgr::receive6(), it will read all packets from the list set by
|
||||
// fakeReceive()
|
||||
// In particular, it should call registered pkt6_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 pkt6_send are indeed called and the
|
||||
// all necessary parameters are passed.
|
||||
TEST_F(HooksDhcpv6SrvTest, simple_pkt6_send) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"pkt6_send", pkt6_send_callout));
|
||||
|
||||
// Let's create a simple SOLICIT
|
||||
Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
|
||||
|
||||
// Simulate that we have received that traffic
|
||||
srv_->fakeReceive(sol);
|
||||
|
||||
// Server will now process to run its normal loop, but instead of calling
|
||||
// IfaceMgr::receive6(), it will read all packets from the list set by
|
||||
// fakeReceive()
|
||||
// In particular, it should call registered pkt6_receive callback.
|
||||
srv_->run();
|
||||
|
||||
// Check that the callback called is indeed the one we installed
|
||||
EXPECT_EQ("pkt6_send", callback_name_);
|
||||
|
||||
// Check that there is one packet sent
|
||||
ASSERT_EQ(1, srv_->fake_sent_.size());
|
||||
Pkt6Ptr adv = srv_->fake_sent_.front();
|
||||
|
||||
// Check that pkt6 argument passing was successful and returned proper value
|
||||
EXPECT_TRUE(callback_pkt6_.get() == adv.get());
|
||||
|
||||
// Check that all expected parameters are there
|
||||
vector<string> expected_argument_names;
|
||||
expected_argument_names.push_back(string("response6"));
|
||||
EXPECT_TRUE(expected_argument_names == callback_argument_names_);
|
||||
}
|
||||
|
||||
// Checks if callouts installed on pkt6_send is able to change
|
||||
// the values and the packet sent contains those changes
|
||||
TEST_F(HooksDhcpv6SrvTest, valueChange_pkt6_send) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"pkt6_send", pkt6_send_change_serverid));
|
||||
|
||||
// Let's create a simple SOLICIT
|
||||
Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
|
||||
|
||||
// Simulate that we have received that traffic
|
||||
srv_->fakeReceive(sol);
|
||||
|
||||
// Server will now process to run its normal loop, but instead of calling
|
||||
// IfaceMgr::receive6(), it will read all packets from the list set by
|
||||
// fakeReceive()
|
||||
// In particular, it should call registered pkt6_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
|
||||
Pkt6Ptr adv = srv_->fake_sent_.front();
|
||||
ASSERT_TRUE(adv);
|
||||
|
||||
// Get client-id...
|
||||
OptionPtr clientid = adv->getOption(D6O_SERVERID);
|
||||
|
||||
// ... and check if it is the modified value
|
||||
OptionPtr expected = createOption(D6O_SERVERID);
|
||||
EXPECT_TRUE(clientid->equal(expected));
|
||||
}
|
||||
|
||||
// Checks if callouts installed on pkt6_send is able to delete
|
||||
// existing options and that server applies those changes. In particular,
|
||||
// we are trying to send a packet without server-id. The packet should
|
||||
// be sent
|
||||
TEST_F(HooksDhcpv6SrvTest, deleteServerId_pkt6_send) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"pkt6_send", pkt6_send_delete_serverid));
|
||||
|
||||
// Let's create a simple SOLICIT
|
||||
Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
|
||||
|
||||
// Simulate that we have received that traffic
|
||||
srv_->fakeReceive(sol);
|
||||
|
||||
// Server will now process to run its normal loop, but instead of calling
|
||||
// IfaceMgr::receive6(), it will read all packets from the list set by
|
||||
// fakeReceive()
|
||||
// In particular, it should call registered pkt6_receive callback.
|
||||
srv_->run();
|
||||
|
||||
// Check that the server indeed sent a malformed ADVERTISE
|
||||
ASSERT_EQ(1, srv_->fake_sent_.size());
|
||||
|
||||
// Get that ADVERTISE
|
||||
Pkt6Ptr adv = srv_->fake_sent_.front();
|
||||
ASSERT_TRUE(adv);
|
||||
|
||||
// Make sure that it does not have server-id
|
||||
EXPECT_FALSE(adv->getOption(D6O_SERVERID));
|
||||
}
|
||||
|
||||
// Checks if callouts installed on pkt6_skip is able to set skip flag that
|
||||
// will cause the server to not process the packet (drop), even though it is valid.
|
||||
TEST_F(HooksDhcpv6SrvTest, skip_pkt6_send) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"pkt6_send", pkt6_send_skip));
|
||||
|
||||
// Let's create a simple REQUEST
|
||||
Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
|
||||
|
||||
// Simulate that we have received that traffic
|
||||
srv_->fakeReceive(sol);
|
||||
|
||||
// Server will now process to run its normal loop, but instead of calling
|
||||
// IfaceMgr::receive6(), it will read all packets from the list set by
|
||||
// fakeReceive()
|
||||
// In particular, it should call registered pkt6_receive callback.
|
||||
srv_->run();
|
||||
|
||||
// check that the server dropped the packet and did not produce any response
|
||||
ASSERT_EQ(0, srv_->fake_sent_.size());
|
||||
}
|
||||
|
||||
// This test checks if subnet6_select callout is triggered and reports
|
||||
// valid parameters
|
||||
TEST_F(HooksDhcpv6SrvTest, subnet6_select) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"subnet6_select", subnet6_select_callout));
|
||||
|
||||
// Configure 2 subnets, both directly reachable over local interface
|
||||
// (let's not complicate the matter with relays)
|
||||
string config = "{ \"interfaces\": [ \"*\" ],"
|
||||
"\"preferred-lifetime\": 3000,"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet6\": [ { "
|
||||
" \"pool\": [ \"2001:db8:1::/64\" ],"
|
||||
" \"subnet\": \"2001:db8:1::/48\", "
|
||||
" \"interface\": \"" + valid_iface_ + "\" "
|
||||
" }, {"
|
||||
" \"pool\": [ \"2001:db8:2::/64\" ],"
|
||||
" \"subnet\": \"2001:db8:2::/48\" "
|
||||
" } ],"
|
||||
"\"valid-lifetime\": 4000 }";
|
||||
|
||||
ElementPtr json = Element::fromJSON(config);
|
||||
ConstElementPtr status;
|
||||
|
||||
// Configure the server and make sure the config is accepted
|
||||
EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
|
||||
ASSERT_TRUE(status);
|
||||
comment_ = parseAnswer(rcode_, status);
|
||||
ASSERT_EQ(0, rcode_);
|
||||
|
||||
// Prepare solicit packet. Server should select first subnet for it
|
||||
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
|
||||
sol->setRemoteAddr(IOAddress("fe80::abcd"));
|
||||
sol->setIface(valid_iface_);
|
||||
sol->addOption(generateIA(234, 1500, 3000));
|
||||
OptionPtr clientid = generateClientId();
|
||||
sol->addOption(clientid);
|
||||
|
||||
// Pass it to the server and get an advertise
|
||||
Pkt6Ptr adv = srv_->processSolicit(sol);
|
||||
|
||||
// check if we get response at all
|
||||
ASSERT_TRUE(adv);
|
||||
|
||||
// Check that the callback called is indeed the one we installed
|
||||
EXPECT_EQ("subnet6_select", callback_name_);
|
||||
|
||||
// Check that pkt6 argument passing was successful and returned proper value
|
||||
EXPECT_TRUE(callback_pkt6_.get() == sol.get());
|
||||
|
||||
const Subnet6Collection* exp_subnets = CfgMgr::instance().getSubnets6();
|
||||
|
||||
// The server is supposed to pick the first subnet, because of matching
|
||||
// interface. Check that the value is reported properly.
|
||||
ASSERT_TRUE(callback_subnet6_);
|
||||
EXPECT_EQ(callback_subnet6_.get(), exp_subnets->front().get());
|
||||
|
||||
// Server is supposed to report two subnets
|
||||
ASSERT_EQ(exp_subnets->size(), callback_subnet6collection_->size());
|
||||
|
||||
// Compare that the available subnets are reported as expected
|
||||
EXPECT_TRUE((*exp_subnets)[0].get() == (*callback_subnet6collection_)[0].get());
|
||||
EXPECT_TRUE((*exp_subnets)[1].get() == (*callback_subnet6collection_)[1].get());
|
||||
}
|
||||
|
||||
// This test checks if callout installed on subnet6_select hook point can pick
|
||||
// a different subnet.
|
||||
TEST_F(HooksDhcpv6SrvTest, subnet_select_change) {
|
||||
|
||||
// Install pkt6_receive_callout
|
||||
EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
|
||||
"subnet6_select", subnet6_select_different_subnet_callout));
|
||||
|
||||
// Configure 2 subnets, both directly reachable over local interface
|
||||
// (let's not complicate the matter with relays)
|
||||
string config = "{ \"interfaces\": [ \"*\" ],"
|
||||
"\"preferred-lifetime\": 3000,"
|
||||
"\"rebind-timer\": 2000, "
|
||||
"\"renew-timer\": 1000, "
|
||||
"\"subnet6\": [ { "
|
||||
" \"pool\": [ \"2001:db8:1::/64\" ],"
|
||||
" \"subnet\": \"2001:db8:1::/48\", "
|
||||
" \"interface\": \"" + valid_iface_ + "\" "
|
||||
" }, {"
|
||||
" \"pool\": [ \"2001:db8:2::/64\" ],"
|
||||
" \"subnet\": \"2001:db8:2::/48\" "
|
||||
" } ],"
|
||||
"\"valid-lifetime\": 4000 }";
|
||||
|
||||
ElementPtr json = Element::fromJSON(config);
|
||||
ConstElementPtr status;
|
||||
|
||||
// Configure the server and make sure the config is accepted
|
||||
EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
|
||||
ASSERT_TRUE(status);
|
||||
comment_ = parseAnswer(rcode_, status);
|
||||
ASSERT_EQ(0, rcode_);
|
||||
|
||||
// Prepare solicit packet. Server should select first subnet for it
|
||||
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
|
||||
sol->setRemoteAddr(IOAddress("fe80::abcd"));
|
||||
sol->setIface(valid_iface_);
|
||||
sol->addOption(generateIA(234, 1500, 3000));
|
||||
OptionPtr clientid = generateClientId();
|
||||
sol->addOption(clientid);
|
||||
|
||||
// Pass it to the server and get an advertise
|
||||
Pkt6Ptr adv = srv_->processSolicit(sol);
|
||||
|
||||
// check if we get response at all
|
||||
ASSERT_TRUE(adv);
|
||||
|
||||
// The response should have an address from second pool, so let's check it
|
||||
OptionPtr tmp = adv->getOption(D6O_IA_NA);
|
||||
ASSERT_TRUE(tmp);
|
||||
boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
|
||||
ASSERT_TRUE(ia);
|
||||
tmp = ia->getOption(D6O_IAADDR);
|
||||
ASSERT_TRUE(tmp);
|
||||
boost::shared_ptr<Option6IAAddr> addr_opt =
|
||||
boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
|
||||
ASSERT_TRUE(addr_opt);
|
||||
|
||||
// Get all subnets and use second subnet for verification
|
||||
const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
|
||||
ASSERT_EQ(2, subnets->size());
|
||||
|
||||
// Advertised address must belong to the second pool (in subnet's range,
|
||||
// in dynamic pool)
|
||||
EXPECT_TRUE((*subnets)[1]->inRange(addr_opt->getAddress()));
|
||||
EXPECT_TRUE((*subnets)[1]->inPool(addr_opt->getAddress()));
|
||||
}
|
||||
|
||||
} // end of anonymous namespace
|
Reference in New Issue
Block a user