diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/bin/dhcp4/tests/dhcp4_test_utils.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-upstream.tar.xz isc-kea-upstream.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/dhcp4/tests/dhcp4_test_utils.cc')
-rw-r--r-- | src/bin/dhcp4/tests/dhcp4_test_utils.cc | 1011 |
1 files changed, 1011 insertions, 0 deletions
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc new file mode 100644 index 0000000..045fe6f --- /dev/null +++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc @@ -0,0 +1,1011 @@ +// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <cc/data.h> +#include <cc/command_interpreter.h> +#include <dhcp4/json_config_parser.h> +#include <dhcp4/tests/dhcp4_test_utils.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option4_addrlst.h> +#include <dhcp/option_int.h> +#include <dhcp/option_int_array.h> +#include <dhcp/option_custom.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcp/tests/pkt_captures.h> +#include <dhcpsrv/cfg_db_access.h> +#include <dhcpsrv/cfg_multi_threading.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/lease.h> +#include <dhcpsrv/lease_mgr.h> +#include <dhcpsrv/lease_mgr_factory.h> +#include <log/logger_support.h> +#include <stats/stats_mgr.h> +#include <sstream> + +using namespace std; +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace dhcp { +namespace test { + +BaseServerTest::BaseServerTest() + : original_datadir_(CfgMgr::instance().getDataDir()) { + CfgMgr::instance().setDataDir(TEST_DATA_BUILDDIR); +} + +BaseServerTest::~BaseServerTest() { + // Remove default lease file. + std::ostringstream s2; + s2 << CfgMgr::instance().getDataDir() << "/kea-leases4.csv"; + static_cast<void>(::remove(s2.str().c_str())); + + // Revert to original data directory. + CfgMgr::instance().setDataDir(original_datadir_); + + // Revert to unit test logging, in case the test reconfigured it. + isc::log::initLogger(); +} + +Dhcpv4SrvTest::Dhcpv4SrvTest() + : rcode_(-1), srv_(0), multi_threading_(false) { + + // Wipe any existing statistics + isc::stats::StatsMgr::instance().removeAll(); + + subnet_ = Subnet4::create(IOAddress("192.0.2.0"), + 24, 1000, 2000, 3000, SubnetID(1)); + pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110"))); + subnet_->addPool(pool_); + + // Add Router option. + Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS)); + opt_routers->setAddress(IOAddress("192.0.2.2")); + subnet_->getCfgOption()->add(opt_routers, false, false, + DHCP4_OPTION_SPACE); + + CfgMgr::instance().clear(); + CfgMgr::instance().setFamily(AF_INET); + CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_); + CfgMgr::instance().commit(); + + LibDHCP::clearRuntimeOptionDefs(); + + // Let's wipe all existing statistics. + isc::stats::StatsMgr::instance().removeAll(); + + // Reset the thread pool. + MultiThreadingMgr::instance().apply(false, 0, 0); +} + +Dhcpv4SrvTest::~Dhcpv4SrvTest() { + // Make sure that we revert to default value + CfgMgr::instance().clear(); + + LibDHCP::clearRuntimeOptionDefs(); + + // Let's wipe all existing statistics. + isc::stats::StatsMgr::instance().removeAll(); + + // Reset the thread pool. + MultiThreadingMgr::instance().apply(false, 0, 0); +} + +void Dhcpv4SrvTest::addPrlOption(Pkt4Ptr& pkt) { + + OptionUint8ArrayPtr option_prl = + OptionUint8ArrayPtr(new OptionUint8Array(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + + // Let's request options that have been configured for the subnet. + option_prl->addValue(DHO_DOMAIN_NAME_SERVERS); + option_prl->addValue(DHO_DOMAIN_NAME); + option_prl->addValue(DHO_LOG_SERVERS); + option_prl->addValue(DHO_COOKIE_SERVERS); + // Let's also request the option that hasn't been configured. In such + // case server should ignore request for this particular option. + option_prl->addValue(DHO_LPR_SERVERS); + // And add 'Parameter Request List' option into the DISCOVER packet. + pkt->addOption(option_prl); +} + +ConstElementPtr +Dhcpv4SrvTest::configure(Dhcpv4Srv& server, ConstElementPtr config) { + ConstElementPtr const status(configureDhcp4Server(server, config)); + + // Simulate the application of MT config such as in ControlledDhcpvXSrv::processConfig(). + CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading()); + + return status; +} + +void +Dhcpv4SrvTest::configureRequestedOptions() { + // dns-servers + Option4AddrLstPtr + option_dns_servers(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS)); + option_dns_servers->addAddress(IOAddress("192.0.2.1")); + option_dns_servers->addAddress(IOAddress("192.0.2.100")); + ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_dns_servers, false, + false, DHCP4_OPTION_SPACE)); + + // domain-name + OptionDefinition def("domain-name", DHO_DOMAIN_NAME, DHCP4_OPTION_SPACE, + OPT_FQDN_TYPE); + OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4)); + option_domain_name->writeFqdn("example.com"); + subnet_->getCfgOption()->add(option_domain_name, false, false, + DHCP4_OPTION_SPACE); + + // log-servers + Option4AddrLstPtr option_log_servers(new Option4AddrLst(DHO_LOG_SERVERS)); + option_log_servers->addAddress(IOAddress("192.0.2.2")); + option_log_servers->addAddress(IOAddress("192.0.2.10")); + ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_log_servers, false, + false, DHCP4_OPTION_SPACE)); + + // cookie-servers + Option4AddrLstPtr option_cookie_servers(new Option4AddrLst(DHO_COOKIE_SERVERS)); + option_cookie_servers->addAddress(IOAddress("192.0.2.1")); + ASSERT_NO_THROW(subnet_->getCfgOption()->add(option_cookie_servers, false, + false, DHCP4_OPTION_SPACE)); +} + +void +Dhcpv4SrvTest::configureServerIdentifier() { + CfgMgr& cfg_mgr = CfgMgr::instance(); + CfgSubnets4Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets4(); + + // Build and add subnet2. + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 1200, 2400, 3600, 2)); + Pool4Ptr pool(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.200"))); + // Add server identifier to the pool. + OptionCustomPtr server_id = makeServerIdOption(IOAddress("192.0.2.254")); + CfgOptionPtr cfg_option = pool->getCfgOption(); + cfg_option->add(server_id, false, false, DHCP4_OPTION_SPACE); + subnet2->addPool(pool); + + // Add a second pool. + pool.reset(new Pool4(IOAddress("192.0.2.201"), IOAddress("192.0.2.220"))); + subnet2->addPool(pool); + + subnets->add(subnet2); + + // Build and add subnet3. + Triplet<uint32_t> unspec; + Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"), 24, unspec, unspec, 3600, 3)); + pool.reset(new Pool4(IOAddress("192.0.3.100"), IOAddress("192.0.3.200"))); + subnet3->addPool(pool); + subnet3->setT1Percent(0.5); + subnet3->setT2Percent(0.75); + subnet3->setCalculateTeeTimes(true); + + // Add server identifier. + server_id = makeServerIdOption(IOAddress("192.0.3.254")); + cfg_option = subnet3->getCfgOption(); + cfg_option->add(server_id, false, false, DHCP4_OPTION_SPACE); + + subnets->add(subnet3); + + // Build and add subnet4. + Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.4.0"), 24, unspec, unspec, 3600, 4)); + pool.reset(new Pool4(IOAddress("192.0.4.100"), IOAddress("192.0.4.200"))); + subnet4->addPool(pool); + subnet4->setCalculateTeeTimes(false); + + subnets->add(subnet4); + + // Build and add subnet5. + Subnet4Ptr subnet5(new Subnet4(IOAddress("192.0.5.0"), 24, unspec, unspec, 3600, 5)); + pool.reset(new Pool4(IOAddress("192.0.5.100"), IOAddress("192.0.5.200"))); + subnet5->addPool(pool); + subnet5->setCalculateTeeTimes(false); + + subnets->add(subnet5); + + CfgOptionPtr options(new CfgOption()); + OptionDescriptor desc(false, false); + desc.option_ = makeServerIdOption(IOAddress("192.0.5.254")); + options->add(desc, DHCP4_OPTION_SPACE); + CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass("foo", ExpressionPtr(), "", true, false, options); + subnet5->requireClientClass("foo"); + + // Build and add subnet6. + Subnet4Ptr subnet6(new Subnet4(IOAddress("192.0.6.0"), 24, unspec, unspec, 3600, 6)); + pool.reset(new Pool4(IOAddress("192.0.6.100"), IOAddress("192.0.6.200"))); + subnet6->addPool(pool); + subnet6->setCalculateTeeTimes(false); + + subnets->add(subnet6); + + options.reset(new CfgOption()); + OptionDescriptor desc_other(false, false); + desc_other.option_ = makeFqdnListOption(); + options->add(desc_other, DHCP4_OPTION_SPACE); + CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass("bar", ExpressionPtr(), "", true, false, options); + subnet6->requireClientClass("bar"); + + // Build and add subnet7. + Subnet4Ptr subnet7(new Subnet4(IOAddress("192.0.7.0"), 24, unspec, unspec, 3600, 7)); + pool.reset(new Pool4(IOAddress("192.0.7.100"), IOAddress("192.0.7.200"))); + subnet7->addPool(pool); + subnet7->setCalculateTeeTimes(false); + + subnets->add(subnet7); + + options.reset(); + CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass("xyz", ExpressionPtr(), "", true, false, options); + subnet7->requireClientClass("xyz"); + + // Build and add a shared-network. + CfgSharedNetworks4Ptr networks = cfg_mgr.getStagingCfg()->getCfgSharedNetworks4(); + SharedNetwork4Ptr network1(new SharedNetwork4("one")); + network1->add(subnet4); + + // Add server identifier. + server_id = makeServerIdOption(IOAddress("192.0.4.254")); + cfg_option = network1->getCfgOption(); + cfg_option->add(server_id, false, false, DHCP4_OPTION_SPACE); + + networks->add(network1); + + // Add a global server identifier. + cfg_option = cfg_mgr.getStagingCfg()->getCfgOption(); + server_id = makeServerIdOption(IOAddress("10.0.0.254")); + cfg_option->add(server_id, false, false, DHCP4_OPTION_SPACE); + + // Commit the config. + cfg_mgr.commit(); +} + +void +Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) { + ASSERT_TRUE(q); + ASSERT_TRUE(a); + + EXPECT_EQ(q->getHops(), a->getHops()); + EXPECT_EQ(q->getIface(), a->getIface()); + EXPECT_EQ(q->getIndex(), a->getIndex()); + EXPECT_EQ(q->getGiaddr(), a->getGiaddr()); + // When processing an incoming packet the remote address + // is copied as a src address, and the source address is + // copied as a remote address to the response. + EXPECT_TRUE(q->getLocalHWAddr() == a->getLocalHWAddr()); + EXPECT_TRUE(q->getRemoteHWAddr() == a->getRemoteHWAddr()); + + // Check that the server identifier is present in the response. + // Presence (or absence) of other options is checked elsewhere. + EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER)); + + // Check that something is offered + EXPECT_NE("0.0.0.0", a->getYiaddr().toText()); +} + +::testing::AssertionResult +Dhcpv4SrvTest::basicOptionsPresent(const Pkt4Ptr& pkt) { + std::ostringstream errmsg; + errmsg << "option missing in the response"; + if (!pkt->getOption(DHO_DOMAIN_NAME)) { + return (::testing::AssertionFailure(::testing::Message() + << "domain-name " << errmsg.str())); + + } else if (!pkt->getOption(DHO_DOMAIN_NAME_SERVERS)) { + return (::testing::AssertionFailure(::testing::Message() + << "dns-servers " << errmsg.str())); + + } else if (!pkt->getOption(DHO_SUBNET_MASK)) { + return (::testing::AssertionFailure(::testing::Message() + << "subnet-mask " << errmsg.str())); + + } else if (!pkt->getOption(DHO_ROUTERS)) { + return (::testing::AssertionFailure(::testing::Message() << "routers " + << errmsg.str())); + + } else if (!pkt->getOption(DHO_DHCP_LEASE_TIME)) { + return (::testing::AssertionFailure(::testing::Message() << + "dhcp-lease-time " << errmsg.str())); + + } + + return (::testing::AssertionSuccess()); +} + +::testing::AssertionResult +Dhcpv4SrvTest::noBasicOptions(const Pkt4Ptr& pkt) { + std::ostringstream errmsg; + errmsg << "option present in the response"; + if (pkt->getOption(DHO_DOMAIN_NAME)) { + return (::testing::AssertionFailure(::testing::Message() + << "domain-name " << errmsg.str())); + + } else if (pkt->getOption(DHO_DOMAIN_NAME_SERVERS)) { + return (::testing::AssertionFailure(::testing::Message() + << "dns-servers " << errmsg.str())); + + } else if (pkt->getOption(DHO_SUBNET_MASK)) { + return (::testing::AssertionFailure(::testing::Message() + << "subnet-mask " << errmsg.str())); + + } else if (pkt->getOption(DHO_ROUTERS)) { + return (::testing::AssertionFailure(::testing::Message() << "routers " + << errmsg.str())); + + } else if (pkt->getOption(DHO_DHCP_LEASE_TIME)) { + return (::testing::AssertionFailure(::testing::Message() + << "dhcp-lease-time " << errmsg.str())); + + } + return (::testing::AssertionSuccess()); +} + +::testing::AssertionResult +Dhcpv4SrvTest::requestedOptionsPresent(const Pkt4Ptr& pkt) { + std::ostringstream errmsg; + errmsg << "option missing in the response"; + if (!pkt->getOption(DHO_LOG_SERVERS)) { + return (::testing::AssertionFailure(::testing::Message() + << "log-servers " << errmsg.str())); + + } else if (!pkt->getOption(DHO_COOKIE_SERVERS)) { + return (::testing::AssertionFailure(::testing::Message() + << "cookie-servers " << errmsg.str())); + + } + return (::testing::AssertionSuccess()); +} + +::testing::AssertionResult +Dhcpv4SrvTest::noRequestedOptions(const Pkt4Ptr& pkt) { + std::ostringstream errmsg; + errmsg << "option present in the response"; + if (pkt->getOption(DHO_LOG_SERVERS)) { + return (::testing::AssertionFailure(::testing::Message() + << "log-servers " << errmsg.str())); + + } else if (pkt->getOption(DHO_COOKIE_SERVERS)) { + return (::testing::AssertionFailure(::testing::Message() + << "cookie-servers " << errmsg.str())); + + } + return (::testing::AssertionSuccess()); +} + +OptionPtr +Dhcpv4SrvTest::generateClientId(size_t size /*= 4*/) { + + OptionBuffer clnt_id(size); + for (size_t i = 0; i < size; i++) { + clnt_id[i] = 100 + i; + } + + client_id_ = ClientIdPtr(new ClientId(clnt_id)); + + return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, + clnt_id.begin(), + clnt_id.begin() + size))); +} + +HWAddrPtr +Dhcpv4SrvTest::generateHWAddr(size_t size /*= 6*/) { + const uint8_t hw_type = 123; // Just a fake number (typically 6=HTYPE_ETHER, see dhcp4.h) + OptionBuffer mac(size); + for (size_t i = 0; i < size; ++i) { + mac[i] = 50 + i; + } + return (HWAddrPtr(new HWAddr(mac, hw_type))); +} + +OptionCustomPtr +Dhcpv4SrvTest::makeServerIdOption(const IOAddress& address) { + OptionDefinitionPtr option_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_SERVER_IDENTIFIER); + OptionCustomPtr server_id(new OptionCustom(*option_def, Option::V4)); + server_id->writeAddress(address); + return (server_id); +} + +OptionPtr +Dhcpv4SrvTest::makeFqdnListOption() { + OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DOMAIN_SEARCH); + + // Prepare buffer holding an array of FQDNs. + const uint8_t fqdn[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + + // Initialize a vector with the FQDN data. + std::vector<uint8_t> fqdn_buf(fqdn, fqdn + sizeof(fqdn)); + + OptionPtr option = def->optionFactory(Option::V4, DHO_DOMAIN_SEARCH, + fqdn_buf.begin(), fqdn_buf.end()); + + return (option); +} + +void +Dhcpv4SrvTest::checkAddressParams(const Pkt4Ptr& rsp, + const Subnet4Ptr subnet, + bool t1_present, + bool t2_present, + uint32_t expected_valid) { + + // Technically inPool implies inRange, but let's be on the safe + // side and check both. + EXPECT_TRUE(subnet->inRange(rsp->getYiaddr())); + EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, rsp->getYiaddr())); + + // Check lease time + OptionUint32Ptr opt = boost::dynamic_pointer_cast< + OptionUint32>(rsp->getOption(DHO_DHCP_LEASE_TIME)); + if (!opt) { + ADD_FAILURE() << "Lease time option missing in response or the" + " option has unexpected type"; + } else if (subnet->getValid().getMin() != subnet->getValid().getMax()) { + EXPECT_GE(opt->getValue(), subnet->getValid().getMin()); + EXPECT_LE(opt->getValue(), subnet->getValid().getMax()); + } else { + EXPECT_EQ(opt->getValue(), subnet->getValid()); + } + + // Check expected value when wanted. + if (opt && expected_valid) { + EXPECT_EQ(opt->getValue(), expected_valid); + } + + // Check T1 timer + opt = boost::dynamic_pointer_cast< + OptionUint32>(rsp->getOption(DHO_DHCP_RENEWAL_TIME)); + if (t1_present) { + ASSERT_TRUE(opt) << "Required T1 option missing or it has" + " an unexpected type"; + EXPECT_EQ(opt->getValue(), subnet->getT1()); + } else { + EXPECT_FALSE(opt); + } + + // Check T2 timer + opt = boost::dynamic_pointer_cast< + OptionUint32>(rsp->getOption(DHO_DHCP_REBINDING_TIME)); + if (t2_present) { + ASSERT_TRUE(opt) << "Required T2 option missing or it has" + " an unexpected type"; + EXPECT_EQ(opt->getValue(), subnet->getT2()); + } else { + EXPECT_FALSE(opt); + } +} + +void +Dhcpv4SrvTest::checkResponse(const Pkt4Ptr& rsp, int expected_message_type, + uint32_t expected_transid) { + ASSERT_TRUE(rsp); + EXPECT_EQ(expected_message_type, + static_cast<int>(rsp->getType())); + EXPECT_EQ(expected_transid, rsp->getTransid()); +} + +Lease4Ptr +Dhcpv4SrvTest::checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id, + const HWAddrPtr&, const IOAddress& expected_addr) { + + ClientIdPtr id; + if (client_id) { + OptionBuffer data = client_id->getData(); + id.reset(new ClientId(data)); + } + + Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr); + if (!lease) { + cout << "Lease for " << expected_addr + << " not found in the database backend."; + return (Lease4Ptr()); + } + + EXPECT_EQ(rsp->getYiaddr(), expected_addr); + + EXPECT_EQ(expected_addr, lease->addr_); + if (client_id) { + EXPECT_TRUE(*lease->client_id_ == *id); + } + EXPECT_EQ(subnet_->getID(), lease->subnet_id_); + + return (lease); +} + +void +Dhcpv4SrvTest::checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) { + // Check that server included its server-id + OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER); + ASSERT_TRUE(opt); + EXPECT_EQ(opt->getType(), expected_srvid->getType() ); + EXPECT_EQ(opt->len(), expected_srvid->len() ); + EXPECT_TRUE(opt->getData() == expected_srvid->getData()); +} + +void +Dhcpv4SrvTest::checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) { + + bool include_clientid = + CfgMgr::instance().getCurrentCfg()->getEchoClientId(); + + // check that server included our own client-id + OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER); + if (include_clientid) { + // Normal mode: echo back (see RFC6842) + ASSERT_TRUE(opt); + EXPECT_EQ(expected_clientid->getType(), opt->getType()); + EXPECT_EQ(expected_clientid->len(), opt->len()); + EXPECT_TRUE(expected_clientid->getData() == opt->getData()); + } else { + // Backward compatibility mode for pre-RFC6842 devices + ASSERT_FALSE(opt); + } +} + +void +Dhcpv4SrvTest::checkServerIdOption(const Pkt4Ptr& packet, const IOAddress& expected_address) { + OptionPtr opt = packet->getOption(DHO_DHCP_SERVER_IDENTIFIER); + ASSERT_TRUE(opt) << "no server-id option"; + + OptionCustomPtr server_id_opt = boost::dynamic_pointer_cast<OptionCustom>(opt); + ASSERT_TRUE(server_id_opt) << "server-id option is not an instance of OptionCustom"; + + EXPECT_EQ(expected_address, server_id_opt->readAddress()); +} + +::testing::AssertionResult +Dhcpv4SrvTest::createPacketFromBuffer(const Pkt4Ptr& src_pkt, + Pkt4Ptr& dst_pkt) { + // Create on-wire format of the packet. If pack() has been called + // on this instance of the packet already, the next call to pack() + // should remove all contents of the output buffer. + try { + src_pkt->pack(); + } catch (const Exception& ex) { + return (::testing::AssertionFailure(::testing::Message() + << "Failed to parse source packet: " + << ex.what())); + } + // Get the output buffer from the source packet. + const util::OutputBuffer& buf = src_pkt->getBuffer(); + // Create a copy of the packet using the output buffer from the source + // packet. + try { + dst_pkt.reset(new Pkt4(static_cast<const uint8_t*>(buf.getData()), + buf.getLength())); + } catch (const Exception& ex) { + return (::testing::AssertionFailure(::testing::Message() + << "Failed to create a" + " destination packet from" + " the buffer: " + << ex.what())); + } + + // The dst_pkt unpack is performed on processPacket by the server. + + return (::testing::AssertionSuccess()); +} + +void +// cppcheck-suppress unusedFunction +Dhcpv4SrvTest::TearDown() { + + CfgMgr::instance().clear(); + + // Close all open sockets. + IfaceMgr::instance().closeSockets(); + + // Some unit tests override the default packet filtering class, used + // by the IfaceMgr. The dummy class, called PktFilterTest, reports the + // capability to directly respond to the clients without IP address + // assigned. This capability is not supported by the default packet + // filtering class: PktFilterInet. Therefore setting the dummy class + // allows to test scenarios, when server responds to the broadcast address + // on client's request, despite having support for direct response. + // The following call restores the use of original packet filtering class + // after the test. + try { + IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet())); + + } catch (const Exception& ex) { + FAIL() << "Failed to restore the default (PktFilterInet) packet filtering" + << " class after the test. Exception has been caught: " + << ex.what(); + } +} + +void +Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) { + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + // Create an instance of the tested class. + boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0)); + + // Initialize the source HW address. + vector<uint8_t> mac(6); + for (uint8_t i = 0; i < 6; ++i) { + mac[i] = i * 10; + } + // Initialized the destination HW address. + vector<uint8_t> dst_mac(6); + for (uint8_t i = 0; i < 6; ++i) { + dst_mac[i] = i * 20; + } + // Create a DHCP message. It will be used to simulate the + // incoming message. + boost::shared_ptr<Pkt4> req(new Pkt4(msg_type, 1234)); + // Create a response message. It will hold a response packet. + // Initially, set it to NULL. + boost::shared_ptr<Pkt4> rsp; + // Set the name of the interface on which packet is received. + req->setIface("eth0"); + // Set the interface index. + req->setIndex(ETH0_INDEX); + // Set the target HW address. This value is normally used to + // construct the data link layer header. + req->setRemoteHWAddr(1, 6, dst_mac); + // Set the HW address. This value is set on DHCP level (in chaddr). + req->setHWAddr(1, 6, mac); + // Set local HW address. It is used to construct the data link layer + // header. + req->setLocalHWAddr(1, 6, mac); + // Set target IP address. + req->setRemoteAddr(IOAddress("192.0.2.55")); + // Set relay address and hops. + req->setGiaddr(IOAddress("192.0.2.10")); + req->setHops(1); + + // We are going to test that certain options are returned + // in the response message when requested using 'Parameter + // Request List' option. Let's configure those options that + // are returned when requested. + configureRequestedOptions(); + + // Create a copy of the original packet by parsing its wire format. + // This simulates the real life scenario when we process the packet + // which was parsed from its wire format. + Pkt4Ptr received; + ASSERT_TRUE(createPacketFromBuffer(req, received)); + received->unpack(); + // Set interface. It is required for the server to generate server id. + received->setIface("eth0"); + received->setIndex(ETH0_INDEX); + if (msg_type == DHCPDISCOVER) { + ASSERT_NO_THROW(rsp = srv->processDiscover(received)); + + // Should return OFFER + ASSERT_TRUE(rsp); + EXPECT_EQ(DHCPOFFER, rsp->getType()); + + } else { + ASSERT_NO_THROW(rsp = srv->processRequest(received)); + + // Should return ACK + ASSERT_TRUE(rsp); + EXPECT_EQ(DHCPACK, rsp->getType()); + + } + + messageCheck(received, rsp); + + // Basic options should be present when we got the lease. + EXPECT_TRUE(basicOptionsPresent(rsp)); + // We did not request any options so these should not be present + // in the RSP. + EXPECT_TRUE(noRequestedOptions(rsp)); + + // Repeat the test but request some options. + // Add 'Parameter Request List' option. + addPrlOption(req); + + ASSERT_TRUE(createPacketFromBuffer(req, received)); + received->unpack(); + ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST)); + + // Set interface. It is required for the server to generate server id. + received->setIface("eth0"); + received->setIndex(ETH0_INDEX); + + if (msg_type == DHCPDISCOVER) { + ASSERT_NO_THROW(rsp = srv->processDiscover(received)); + + // Should return non-NULL packet. + ASSERT_TRUE(rsp); + EXPECT_EQ(DHCPOFFER, rsp->getType()); + + } else { + ASSERT_NO_THROW(rsp = srv->processRequest(received)); + + // Should return non-NULL packet. + ASSERT_TRUE(rsp); + EXPECT_EQ(DHCPACK, rsp->getType()); + } + + // Check that the requested options are returned. + EXPECT_TRUE(basicOptionsPresent(rsp)); + EXPECT_TRUE(requestedOptionsPresent(rsp)); + + // The following part of the test will test that the NAK is sent when + // there is no address pool configured. In the same time, we expect + // that the requested options are not included in NAK message, but that + // they are only included when yiaddr is set to non-zero value. + ASSERT_NO_THROW(subnet_->delPools(Lease::TYPE_V4)); + + // There has been a lease allocated for the particular client. So, + // even though we deleted the subnet, the client would get the + // existing lease (not a NAK). Therefore, we have to change the chaddr + // in the packet so as the existing lease is not returned. + req->setHWAddr(1, 6, std::vector<uint8_t>(2, 6)); + ASSERT_TRUE(createPacketFromBuffer(req, received)); + received->unpack(); + ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST)); + + // Set interface. It is required for the server to generate server id. + received->setIface("eth0"); + received->setIndex(ETH0_INDEX); + + if (msg_type == DHCPDISCOVER) { + ASSERT_NO_THROW(rsp = srv->processDiscover(received)); + // Should return NULL packet. + ASSERT_FALSE(rsp); + + } else { + ASSERT_NO_THROW(rsp = srv->processRequest(received)); + // Should return non-NULL packet. + ASSERT_TRUE(rsp); + // We should get the NAK packet with yiaddr set to 0. + EXPECT_EQ(DHCPNAK, rsp->getType()); + ASSERT_EQ("0.0.0.0", rsp->getYiaddr().toText()); + + // Make sure that none of the requested options is returned in NAK. + // Also options such as Routers or Subnet Mask should not be there, + // because lease hasn't been acquired. + EXPECT_TRUE(noRequestedOptions(rsp)); + EXPECT_TRUE(noBasicOptions(rsp)); + } +} + +void +Dhcpv4SrvTest::buildCfgOptionTest(IOAddress expected_server_id, + Pkt4Ptr& query, + IOAddress requested, + IOAddress server_id) { + OptionDefinitionPtr req_addr_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_DHCP_REQUESTED_ADDRESS); + ASSERT_TRUE(req_addr_def); + + OptionDefinitionPtr sbnsel_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, + DHO_SUBNET_SELECTION); + ASSERT_TRUE(sbnsel_def); + + OptionCustomPtr req_addr(new OptionCustom(*req_addr_def, Option::V4)); + req_addr->writeAddress(requested); + + OptionCustomPtr sbnsel(new OptionCustom(*sbnsel_def, Option::V4)); + sbnsel->writeAddress(requested); + + query->addOption(req_addr); + query->addOption(sbnsel); + query->addOption(makeServerIdOption(server_id)); + + Pkt4Ptr response; + ASSERT_NO_THROW(response = srv_.processRequest(query)); + + checkServerIdOption(response, expected_server_id); + + ASSERT_NO_THROW(query->delOption(DHO_DHCP_REQUESTED_ADDRESS)); + ASSERT_NO_THROW(query->delOption(DHO_SUBNET_SELECTION)); + ASSERT_NO_THROW(query->delOption(DHO_DHCP_SERVER_IDENTIFIER)); +} + +void +Dhcpv4SrvTest::configure(const std::string& config, + const bool commit, + const bool open_sockets, + const bool create_managers, + const bool test, + const LeaseAffinity lease_affinity) { + configure(config, srv_, commit, open_sockets, create_managers, test, + lease_affinity); +} + +void +Dhcpv4SrvTest::configure(const std::string& config, + NakedDhcpv4Srv& srv, + const bool commit, + const bool open_sockets, + const bool create_managers, + const bool test, + const LeaseAffinity lease_affinity) { + setenv("KEA_LFC_EXECUTABLE", KEA_LFC_EXECUTABLE, 1); + MultiThreadingCriticalSection cs; + ConstElementPtr json; + try { + json = parseJSON(config); + } catch (const std::exception& ex){ + // Fatal failure on parsing error + FAIL() << "parsing failure:" + << "config:" << config << std::endl + << "error: " << ex.what(); + } + + ConstElementPtr status; + + // Disable the re-detect flag + disableIfacesReDetect(json); + + // Set up multi-threading + configureMultiThreading(multi_threading_, json); + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json, test)); + ASSERT_TRUE(status); + int rcode; + ConstElementPtr comment = config::parseAnswerText(rcode, status); + ASSERT_EQ(0, rcode) << "configuration failed, test is broken: " + << comment->str(); + + // Use specified lease database backend. + if (create_managers) { + ASSERT_NO_THROW( { + CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess(); + cfg_db->setAppendedParameters("universe=4"); + cfg_db->createManagers(); + } ); + } + + if (lease_affinity == LEASE_AFFINITY_DISABLED) { + auto expiration_cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration(); + expiration_cfg->setFlushReclaimedTimerWaitTime(0); + expiration_cfg->setHoldReclaimedTime(0); + } + + try { + CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->initAllocatorsAfterConfigure(); + } catch (const std::exception& ex) { + ADD_FAILURE() << "Error initializing the allocators after configure: " + << ex.what(); + } + + try { + CfgMultiThreading::apply(CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading()); + } catch (const std::exception& ex) { + ADD_FAILURE() << "Error applying multi threading settings: " + << ex.what(); + } + + if (commit) { + CfgMgr::instance().commit(); + } + + // Opening sockets. + if (open_sockets) { + IfaceMgr::instance().openSockets4(); + } +} + +std::pair<int, std::string> +Dhcpv4SrvTest::configureWithStatus(const std::string& config, NakedDhcpv4Srv& srv, + const bool commit, const int exp_rcode) { + ConstElementPtr json; + try { + json = parseJSON(config); + } catch (const std::exception& ex){ + // Fatal failure on parsing error + + std::stringstream tmp; + tmp << "parsing failure:" + << "config:" << config << std::endl + << "error: " << ex.what(); + ADD_FAILURE() << tmp.str(); + return (std::make_pair(-1, tmp.str())); + } + + ConstElementPtr status; + + // Disable the re-detect flag + disableIfacesReDetect(json); + + // Configure the server and make sure the config is accepted + EXPECT_NO_THROW(status = configureDhcp4Server(srv, json)); + EXPECT_TRUE(status); + if (!status) { + return (make_pair(-1, "configureDhcp4Server didn't return anything")); + } + + int rcode; + ConstElementPtr comment = config::parseAnswerText(rcode, status); + EXPECT_EQ(exp_rcode, rcode) << comment->stringValue(); + + // Use specified lease database backend. + if (rcode == 0) { + EXPECT_NO_THROW( { + CfgDbAccessPtr cfg_db = CfgMgr::instance().getStagingCfg()->getCfgDbAccess(); + cfg_db->setAppendedParameters("universe=4"); + cfg_db->createManagers(); + } ); + if (commit) { + CfgMgr::instance().commit(); + } + } + + return (std::make_pair(rcode, comment->stringValue())); +} + +Dhcpv4Exchange +Dhcpv4SrvTest::createExchange(const Pkt4Ptr& query) { + bool drop = false; + Subnet4Ptr subnet = srv_.selectSubnet(query, drop); + EXPECT_FALSE(drop); + AllocEngine::ClientContext4Ptr context(new AllocEngine::ClientContext4()); + EXPECT_TRUE(srv_.earlyGHRLookup(query, context)); + Dhcpv4Exchange ex(srv_.alloc_engine_, query, context, subnet, drop); + EXPECT_FALSE(context); + EXPECT_FALSE(drop); + return (ex); +} + +void +Dhcpv4SrvTest::pretendReceivingPkt(NakedDhcpv4Srv& srv, const std::string& config, + uint8_t pkt_type, const std::string& stat_name) { + + IfaceMgrTestConfig test_config(true); + IfaceMgr::instance().openSockets4(); + + // Apply the configuration we just received. + configure(config); + + // Let's just use one of the actual captured packets that we have. + Pkt4Ptr pkt = PktCaptures::captureRelayedDiscover(); + + // We just need to tweak it a it, to pretend that its type is as desired. + // Note that when receiving a packet, its on-wire form is stored in data_ + // field. Most methods (including setType()) operates on option objects + // (objects stored in options_ after unpack() is called). Finally, outgoing + // packets are stored in out_buffer_. So we need to go through the full + // unpack/tweak/pack cycle and do repack, i.e. move the output buffer back + // to incoming buffer. + pkt->unpack(); + pkt->setType(pkt_type); // Set message type. + pkt->pack(); + pkt->data_.resize(pkt->getBuffer().getLength()); + // Copy out_buffer_ to data_ to pretend that it's what was just received. + memcpy(&pkt->data_[0], pkt->getBuffer().getData(), pkt->getBuffer().getLength()); + // Clear options so that they can be recreated on unpack. + pkt->options_.clear(); + + // Simulate that we have received that traffic + srv.fakeReceive(pkt); + srv.run(); + + using namespace isc::stats; + StatsMgr& mgr = StatsMgr::instance(); + ObservationPtr pkt4_rcvd = mgr.getObservation("pkt4-received"); + ObservationPtr tested_stat = mgr.getObservation(stat_name); + + // All expected statistics must be present. + ASSERT_TRUE(pkt4_rcvd); + ASSERT_TRUE(tested_stat); + + // They also must have expected values. + EXPECT_EQ(1, pkt4_rcvd->getInteger().first); + EXPECT_EQ(1, tested_stat->getInteger().first); +} + +} // end of isc::dhcp::test namespace +} // end of isc::dhcp namespace +} // end of isc namespace |