diff options
Diffstat (limited to 'src/bin/perfdhcp/tests/test_control_unittest.cc')
-rw-r--r-- | src/bin/perfdhcp/tests/test_control_unittest.cc | 1971 |
1 files changed, 1971 insertions, 0 deletions
diff --git a/src/bin/perfdhcp/tests/test_control_unittest.cc b/src/bin/perfdhcp/tests/test_control_unittest.cc new file mode 100644 index 0000000..1838a7b --- /dev/null +++ b/src/bin/perfdhcp/tests/test_control_unittest.cc @@ -0,0 +1,1971 @@ +// Copyright (C) 2012-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 "command_options_helper.h" +#include "../test_control.h" + +#include <asiolink/io_address.h> +#include <exceptions/exceptions.h> +#include <dhcp/dhcp4.h> +#include <dhcp/pkt4.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/option_int.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/foreach.hpp> +#include <boost/format.hpp> + +#include <algorithm> +#include <cstddef> +#include <stdint.h> +#include <string> +#include <vector> +#include <fstream> +#include <gtest/gtest.h> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + +/// \brief FakePerfSocket class that mocks PerfSocket. +/// +/// It stubs send and receive operations and collects statistics. +class FakeTestControlPerfSocket: public BasePerfSocket { +public: + /// \brief Default constructor for FakePerfSocket. + FakeTestControlPerfSocket() : + iface_(boost::make_shared<Iface>("fake", 0)), + sent_cnt_(0), + recv_cnt_(0) {}; + + IfacePtr iface_; ///< Local fake interface. + + int sent_cnt_; ///< Counter of sent packets + int recv_cnt_; ///< Counter of received packets. + + /// \brief Simulate receiving DHCPv4 packet. + virtual dhcp::Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec) override { + (void)timeout_sec; // silence compile 'unused parameter' warning; + (void)timeout_usec; // silence compile 'unused parameter' warning; + recv_cnt_++; + return(dhcp::Pkt4Ptr()); + }; + + /// \brief Simulate receiving DHCPv6 packet. + virtual dhcp::Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec) override { + (void)timeout_sec; // silence compile 'unused parameter' warning; + (void)timeout_usec; // silence compile 'unused parameter' warning; + recv_cnt_++; + return(dhcp::Pkt6Ptr()); + }; + + /// \brief Simulate sending DHCPv4 packet. + virtual bool send(const dhcp::Pkt4Ptr& pkt) override { + sent_cnt_++; + pkt->updateTimestamp(); + sent_pkts4_.push_back(pkt); + return true; + }; + + /// \brief Simulate sending DHCPv6 packet. + virtual bool send(const dhcp::Pkt6Ptr& pkt) override { + sent_cnt_++; + pkt->updateTimestamp(); + sent_pkts6_.push_back(pkt); + return true; + }; + + /// \brief Override getting interface. + virtual IfacePtr getIface() override { return iface_; } + + void reset() { + sent_cnt_ = 0; + recv_cnt_ = 0; + } + + std::vector<dhcp::Pkt4Ptr> sent_pkts4_; /// output v4 packets are stored here + std::vector<dhcp::Pkt6Ptr> sent_pkts6_; /// output v6 packets are stored here +}; + + +/// \brief Test Control class with protected members made public. +/// +/// This class makes protected TestControl class's members public +/// to allow unit testing. +class NakedTestControl: public TestControl { +public: + + /// \brief Incremental transaction id generator. + /// + /// This is incremental transaction id generator. It overrides + /// the default transaction id generator that generates transaction + /// ids using random function. This generator will generate values + /// like: 1,2,3 etc. + class IncrementalGenerator : public TestControl::NumberGenerator { + public: + /// \brief Default constructor. + IncrementalGenerator() : + NumberGenerator(), + transid_(0) { + } + + /// \brief Generate unique transaction id. + /// + /// Generate unique transaction ids incrementally: + /// 1,2,3,4 etc. + /// + /// \return generated transaction id. + virtual uint32_t generate() { + return (++transid_); + } + + /// \brief Return next transaction id value. + uint32_t getNext() const { + return (transid_ + 1); + } + + private: + uint32_t transid_; ///< Last generated transaction id. + }; + + /// \brief Pointer to incremental generator. + typedef boost::shared_ptr<IncrementalGenerator> IncrementalGeneratorPtr; + + using TestControl::createMessageFromReply; + using TestControl::createMessageFromAck; + using TestControl::factoryElapsedTime6; + using TestControl::factoryGeneric; + using TestControl::factoryIana6; + using TestControl::factoryOptionRequestOption6; + using TestControl::factoryRapidCommit6; + using TestControl::factoryRequestList4; + using TestControl::generateClientId; + using TestControl::generateDuid; + using TestControl::generateMacAddress; + using TestControl::getTemplateBuffer; + using TestControl::initPacketTemplates; + using TestControl::processReceivedPacket4; + using TestControl::processReceivedPacket6; + using TestControl::registerOptionFactories; + using TestControl::reset; + using TestControl::sendDiscover4; + using TestControl::sendRequest4; + using TestControl::sendPackets; + using TestControl::sendMultipleMessages4; + using TestControl::sendMultipleMessages6; + using TestControl::sendRequest6; + using TestControl::sendSolicit6; + using TestControl::setDefaults4; + using TestControl::setDefaults6; + using TestControl::socket_; + using TestControl::last_report_; + using TestControl::transid_gen_; + using TestControl::macaddr_gen_; + using TestControl::first_packet_serverid_; + using TestControl::interrupted_; + using TestControl::template_packets_v4_; + using TestControl::template_packets_v6_; + using TestControl::ack_storage_; + using TestControl::sendMessageFromAck; + using TestControl::options_; + using TestControl::stats_mgr_; + + FakeTestControlPerfSocket fake_sock_; + + NakedTestControl(CommandOptions &opt) : TestControl(opt, fake_sock_) { + uint32_t clients_num = opt.getClientsNum() == 0 ? + 1 : opt.getClientsNum(); + setMacAddrGenerator(NumberGeneratorPtr(new TestControl::SequentialGenerator(clients_num))); + }; +}; + + +/// \brief Test Fixture Class +/// +/// This test fixture class is used to perform +/// unit tests on perfdhcp TestControl class. +class TestControlTest : public virtual ::testing::Test +{ +public: + + typedef std::vector<uint8_t> MacAddress; + typedef MacAddress::iterator MacAddressIterator; + + typedef std::vector<uint8_t> Duid; + typedef Duid::iterator DuidIterator; + + /// \brief Default Constructor + TestControlTest() { } + + /// \brief Create packet template file from binary data. + /// + /// Function creates file containing data from the provided buffer + /// in hexadecimal format. The size parameter specifies the maximum + /// size of the file. If total number of hexadecimal digits resulting + /// from buffer size is greater than maximum file size the file is + /// truncated. + /// + /// \param filename template file to be created. + /// \param buffer with binary data to be stored in file. + /// \param size target size of the file. + /// \param invalid_chars inject invalid chars to the template file. + /// \return true if file creation successful. + bool createTemplateFile(const std::string& filename, + const std::vector<uint8_t>& buf, + const size_t size, + const bool invalid_chars = false) const { + std::ofstream temp_file; + temp_file.open(filename.c_str(), ios::out | ios::trunc); + if (!temp_file.is_open()) { + return (false); + } + for (size_t i = 0; i < buf.size(); ++i) { + int first_digit = buf[i] / 16; + int second_digit = buf[i] % 16; + // Insert two spaces between two hexadecimal digits. + // Spaces are allowed in template files. + temp_file << std::string(2, ' '); + if (2 * i + 1 < size) { + if (!invalid_chars) { + temp_file << std::hex << first_digit << second_digit << std::dec; + } else { + temp_file << "XY"; + } + } else if (2 * i < size) { + if (!invalid_chars) { + temp_file << std::hex << first_digit; + } else { + temp_file << "X"; + } + } else { + break; + } + } + temp_file.close(); + return (true); + } + + /// \brief Get full path to a file in testdata directory. + /// + /// \param filename filename being appended to absolute + /// path to testdata directory + /// + /// \return full path to a file in testdata directory. + std::string getFullPath(const std::string& filename) const { + std::ostringstream stream; + stream << TEST_DATA_DIR << "/" << filename; + return (stream.str()); + } + + /// \brief Match requested options in the buffer with given list. + /// + /// This method iterates through options provided in the buffer + /// and matches them with the options specified with first parameter. + /// Options in both vectors may be laid in different order. + /// + /// \param requested_options reference buffer with options. + /// \param buf test buffer with options that will be matched. + /// \return number of options from the buffer matched with options + /// in the reference buffer. + int matchRequestedOptions(const dhcp::OptionBuffer& requested_options, + const dhcp::OptionBuffer& buf) const { + size_t matched_num = 0; + for (size_t i = 0; i < buf.size(); ++i) { + for (size_t j = 0; j < requested_options.size(); ++j) { + if (requested_options[j] == buf[i]) { + // Requested option has been found. + ++matched_num; + } + } + } + return (matched_num); + } + + /// \brief Match requested DHCPv6 options in the buffer with given list. + /// + /// This method iterates through options provided in the buffer and + /// matches them with the options specified with first parameter. + /// Options in both vectors ma be laid in different order. + /// + /// \param requested_options reference buffer with options. + /// \param buf test buffer with options that will be matched. + /// \return number of options from the buffer matched with options in + /// the reference buffer or -1 if error occurred. + int matchRequestedOptions6(const dhcp::OptionBuffer& requested_options, + const dhcp::OptionBuffer& buf) const { + // Sanity check. + if ((requested_options.size() % 2 != 0) || + (buf.size() % 2 != 0)) { + return -1; + } + size_t matched_num = 0; + for (size_t i = 0; i < buf.size(); i += 2) { + for (size_t j = 0; j < requested_options.size(); j += 2) { + uint16_t opt_i = (buf[i + 1] << 8) + (buf[i] & 0xFF); + uint16_t opt_j = (requested_options[j + 1] << 8) + + (requested_options[j] & 0xFF); + if (opt_i == opt_j) { + // Requested option has been found. + ++matched_num; + } + } + } + return (matched_num); + } + + /// \brief Calculate the maximum vectors' mismatch position. + /// + /// This helper function calculates the maximum mismatch position + /// between two vectors (two different DUIDs or MAC addresses). + /// Calculated position is counted from the end of vectors. + /// Calculation is based on number of simulated clients. When number + /// of clients is less than 256 different DUIDs or MAC addresses can + /// can be coded in such a way that they differ on last vector element. + /// If number of clients is between 257 and 65536 they can differ + /// on two last positions so the returned value will be 2 and so on. + /// + /// \param clients_num number of simulated clients + /// \return maximum mismatch position + int unequalOctetPosition(int clients_num) const { + if (!clients_num) { + return (0); + } + clients_num--; + + int cnt = 0; + while (clients_num) { + clients_num >>= 8; + ++cnt; + } + + return (cnt); + } + + /// \brief Test generation of multiple DUIDs + /// + /// This method checks the generation of multiple DUIDs. Number + /// of iterations depends on the number of simulated clients. + /// It is expected that DUID's size is 14 (consists of DUID-LLT + /// HW type field, 4 octets of time value and MAC address). The + /// MAC address can be randomized depending on the number of + /// simulated clients. The DUID-LLT and HW type are expected to + /// be constant. The time value has to be properly calculated + /// as the number of seconds since DUID time epoch. The parts + /// of MAC address has to change if multiple clients are simulated + /// and do not change if single client is simulated. + void testDuid(CommandOptions &opt) const { + int clients_num = opt.getClientsNum(); + // Initialize Test Control class. + NakedTestControl tc(opt); + // The old duid will be holding the previously generated DUID. + // It will be used to compare against the new one. If we have + // multiple clients we want to make sure that duids differ. + uint8_t randomized = 0; + Duid old_duid(tc.generateDuid(randomized)); + Duid new_duid(0); + // total_dist shows the total difference between generated duid. + // It has to be greater than zero if multiple clients are simulated. + size_t total_dist = 0; + // Number of unique DUIDs. + size_t unique_duids = 0; + // Holds the position if the octet on which two DUIDS can be different. + // If number of clients is 256 or less it is last DUID octet (except for + // single client when subsequent DUIDs have to be equal). If number of + // clients is between 257 and 65536 the last two octets can differ etc. + int unequal_pos = unequalOctetPosition(clients_num); + // Keep generated DUIDs in this container. + std::list<std::vector<uint8_t> > duids; + // Perform number of iterations to generate number of DUIDs. + for (int i = 0; i < 10 * clients_num; ++i) { + if (new_duid.empty()) { + new_duid = old_duid; + } else { + std::swap(old_duid, new_duid); + new_duid = tc.generateDuid(randomized); + } + // The DUID-LLT is expected to start with DUID_LLT value + // of 1 and hardware ethernet type equal to 1 (HWETHER_TYPE). + const uint8_t duid_llt_and_hw[4] = { 0x0, 0x1, 0x0, 0x1 }; + // We assume DUID-LLT length 14. This includes 4 octets of + // DUID_LLT value, two octets of hardware type, 4 octets + // of time value and 6 octets of variable link layer (MAC) + // address. + const int duid_llt_size = 14; + ASSERT_EQ(duid_llt_size, new_duid.size()); + // The first four octets do not change. + EXPECT_TRUE(std::equal(new_duid.begin(), new_duid.begin() + 4, + duid_llt_and_hw)); + + // As described in RFC 8415: 'the time value is the time + // that the DUID is generated represented in seconds + // since midnight (UTC), January 1, 2000, modulo 2^32.' + uint32_t duid_time = 0; + // Pick 4 bytes of the time from generated DUID and put them + // in reverse order (in DUID they are stored in network order). + for (int j = 4; j < 8; ++j) { + duid_time |= new_duid[j] << (j - 4); + } + // Calculate the duration since epoch time. + ptime now = microsec_clock::universal_time(); + ptime duid_epoch(from_iso_string("20000101T000000")); + time_period period(duid_epoch, now); + + // Current time is the same or later than time from the DUID because + // DUID had been generated before reference duration was calculated. + EXPECT_GE(period.length().total_seconds(), duid_time); + + // Get the mismatch position (counting from the end) of + // mismatched octet between previously generated DUID + // and current. + std::pair<DuidIterator, DuidIterator> mismatch_pos = + std::mismatch(old_duid.begin(), old_duid.end(), + new_duid.begin()); + size_t mismatch_dist = + std::distance(mismatch_pos.first, old_duid.end()); + // For single client total_dist is expected to be 0 because + // old_duid and new_duid should always match. If we have + // more clients then duids have to differ except the case + // if randomization algorithm generates the same values but + // this would be an error in randomization algorithm. + total_dist += mismatch_dist; + // Mismatch may have occurred on the DUID octet position + // up to calculated earlier unequal_pos. + ASSERT_LE(mismatch_dist, unequal_pos); + // unique will inform if tested DUID is unique. + bool unique = true; + for (std::list<std::vector<uint8_t> >::const_iterator it = + duids.begin(); + it != duids.end(); ++it) { + // DUIDs should be of the same size if we want to compare them. + ASSERT_EQ(new_duid.size(), it->size()); + // Check if DUID is unique. + if (std::equal(new_duid.begin(), new_duid.end(), it->begin())) { + unique = false; + } + } + // Expecting that DUIDs will be unique only when + // first clients-num iterations is performed. + // After that, DUIDs become non unique. + if (unique) { + ++unique_duids; + } + // For number of iterations equal to clients_num,2*clients_num + // 3*clients_num ... we have to have number of unique duids + // equal to clients_num. + if ((i != 0) && (i % clients_num == 0)) { + ASSERT_EQ(clients_num, unique_duids); + } + // Remember generated DUID. + duids.push_back(new_duid); + } + // If we have more than one client at least one mismatch occurred. + if (clients_num < 2) { + EXPECT_EQ(0, total_dist); + } + } + + /// \brief Test DHCPv4 exchanges. + /// + /// Function simulates DHCPv4 exchanges. Function caller specifies + /// number of exchanges to be simulated and number of simulated + /// responses. When number of responses is lower than number of + /// iterations than the difference between them is the number + /// of simulated packet drops. This is useful to test if program + /// exit conditions are handled properly (maximum number of packet + /// drops specified as -D<max-drops> is taken into account). + /// + /// \param iterations_num number of exchanges to simulate. + /// \param receive_num number of received OFFER packets. + /// \param tc test control instance + void testPkt4Exchange(int iterations_num, + int receive_num, + bool use_templates, + NakedTestControl& tc) const { + //int sock_handle = 0; + + // Use templates files to crate packets. + if (use_templates) { + tc.initPacketTemplates(); + tc.getTemplateBuffer(0); + tc.getTemplateBuffer(1); + } + + // Incremental transaction id generator will generate + // predictable values of transaction id for each iteration. + // This is important because we need to simulate responses + // from the server and use the same transaction ids as in + // packets sent by client. + NakedTestControl::IncrementalGeneratorPtr + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + for (int i = 0; i < iterations_num; ++i) { + // Get next transaction id, without actually using it. The same + // id will be used by the TestControl class for DHCPDISCOVER. + uint32_t transid = generator->getNext(); + if (use_templates) { + tc.sendDiscover4(tc.getTemplateBuffer(0)); + } else { + tc.sendDiscover4(); + } + + // Do not simulate responses for packets later + // that specified as receive_num. This simulates + // packet drops. + if (i < receive_num) { + boost::shared_ptr<Pkt4> offer_pkt4(createOfferPkt4(transid)); + tc.processReceivedPacket4(offer_pkt4); + } + } + } + + /// \brief Test DHCPv6 exchanges. + /// + /// Function simulates DHCPv6 exchanges. Function caller specifies + /// number of exchanges to be simulated and number of simulated + /// responses. When number of responses is lower than number of + /// iterations than the difference between them is the number + /// of simulated packet drops. This is useful to test if program + /// exit conditions are handled properly (maximum number of packet + /// drops specified as -D<max-drops> is taken into account). + /// + /// \param iterations_num number of exchanges to simulate. + /// \param receive_num number of received OFFER packets. + /// \param tc test control instance + void testPkt6Exchange(int iterations_num, + int receive_num, + bool use_templates, + NakedTestControl& tc) const { + //int sock_handle = 0; + + // Use templates files to crate packets. + if (use_templates) { + tc.initPacketTemplates(); + tc.getTemplateBuffer(0); + tc.getTemplateBuffer(1); + } + + // Incremental transaction id generator will generate + // predictable values of transaction id for each iteration. + // This is important because we need to simulate responses + // from the server and use the same transaction ids as in + // packets sent by client. + TestControl::NumberGeneratorPtr + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + uint32_t transid = 0; + for (int i = 0; i < iterations_num; ++i) { + // Do not simulate responses for packets later + // that specified as receive_num. This simulates + // packet drops. + if (use_templates) { + tc.sendSolicit6(tc.getTemplateBuffer(0)); + } else { + tc.sendSolicit6(); + } + ++transid; + if (i < receive_num) { + boost::shared_ptr<Pkt6> + advertise_pkt6(createAdvertisePkt6(tc, transid)); + // Receive ADVERTISE and send REQUEST. + tc.processReceivedPacket6(advertise_pkt6); + ++transid; + } + } + } + + /// \brief Test generation of multiple MAC addresses. + /// + /// This method validates generation of multiple MAC addresses. + /// The MAC address can be randomized depending on the number + /// of simulated clients. This test checks if different MAC + /// addresses are generated if number of simulated clients is + /// greater than 1. It also checks if the same MAC addresses is + /// generated if only 1 client is simulated. + void testMacAddress(CommandOptions &opt) const { + int clients_num = opt.getClientsNum(); + // The old_mac will be holding the value of previously generated + // MAC address. We will be comparing the newly generated one with it + // to see if it changes when multiple clients are simulated or if it + // does not change when single client is simulated. + MacAddress old_mac(opt.getMacTemplate()); + // Holds the position if the octet on which two MAC addresses can + // be different. If number of clients is 256 or less it is last MAC + // octet (except for single client when subsequent MAC addresses + // have to be equal). If number of clients is between 257 and 65536 + // the last two octets can differ etc. + int unequal_pos = unequalOctetPosition(clients_num); + // Number of unique MACs. + size_t unique_macs = 0; + // Initialize Test Controller. + NakedTestControl tc(opt); + size_t total_dist = 0; + // Keep generated MACs in this container. + std::list<std::vector<uint8_t> > macs; + // Do many iterations to generate and test MAC address values. + for (int i = 0; i < clients_num * 10; ++i) { + // Generate new MAC address. + uint8_t randomized = 0; + MacAddress new_mac(tc.generateMacAddress(randomized)); + // Get the mismatch position (counting from the end) of + // mismatched octet between previously generated MAC address + // and current. + std::pair<MacAddressIterator, MacAddressIterator> mismatch_pos = + std::mismatch(old_mac.begin(), old_mac.end(), new_mac.begin()); + size_t mismatch_dist = + std::distance(mismatch_pos.first, old_mac.end()); + // For single client total_dist is expected to be 0 because + // old_mac and new_mac should always match. If we have + // more clients then MAC addresses have to differ except + // the case if randomization algorithm generates the same + // values but this would be an error in randomization algorithm. + total_dist += mismatch_dist; + // Mismatch may have occurred on the MAC address's octet position + // up to calculated earlier unequal_pos. + ASSERT_LE(mismatch_dist, unequal_pos); + // unique will inform if tested DUID is unique. + bool unique = true; + for (std::list<std::vector<uint8_t> >::const_iterator it = + macs.begin(); + it != macs.end(); ++it) { + // MACs should be of the same size if we want to compare them. + ASSERT_EQ(new_mac.size(), it->size()); + // Check if MAC is unique. + if (std::equal(new_mac.begin(), new_mac.end(), it->begin())) { + unique = false; + } + } + // Expecting that MACs will be unique only when + // first clients-num iterations is performed. + // After that, MACs become non unique. + if (unique) { + ++unique_macs; + } + // For number of iterations equal to clients_num,2*clients_num + // 3*clients_num ... we have to have number of unique MACs + // equal to clients_num. + if ((i != 0) && (i % clients_num == 0)) { + ASSERT_EQ(clients_num, unique_macs); + } + // Remember generated MAC. + macs.push_back(new_mac); + + } + if (clients_num < 2) { + EXPECT_EQ(total_dist, 0); + } + } + + /// \brief Test sending DHCPv4 renews. + /// + /// This function simulates acquiring 10 leases from the server. Returned + /// DHCPACK messages are cached and used to send renew messages. + /// The maximal number of messages which can be sent is equal to the + /// number of leases acquired (10). This function also checks that an + /// attempt to send more renew messages than the number of leases acquired + /// will fail. + /// + /// \param msg_type A type of the message which is simulated to be sent + /// (DHCPREQUEST in renew state or DHCPRELEASE). + void testSendRenewRelease4(const uint16_t msg_type) { + // Build a command line. Depending on the message type, we will use + // -f<renew-rate> or -F<release-rate> parameter. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -4 -l fake -r 10 "; + s << (msg_type == DHCPREQUEST ? "-f" : "-F"); + s << " 10 -R 10 -L 10067 -n 10 127.0.0.1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator to sequential to control to + // guarantee that transaction ids are predictable. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + // Send a number of DHCPDISCOVER messages. Each generated message will + // be assigned a different transaction id, starting from 1 to 10. + tc.sendPackets(10); + + // Simulate DHCPOFFER responses from the server. Each DHCPOFFER is + // assigned a transaction id from the range of 1 to 10, so as they + // match the transaction ids from the DHCPDISCOVER messages. + for (unsigned i = generator->getNext() - 10; + i < generator->getNext(); ++i) { + Pkt4Ptr offer(createOfferPkt4(i)); + // If DHCPOFFER is matched with the DHCPDISCOVER the call below + // will trigger a corresponding DHCPREQUEST. They will be assigned + // transaction ids from the range from 11 to 20 (the range of + // 1 to 10 has been used by DHCPDISCOVER-DHCPOFFER). + tc.processReceivedPacket4(offer); + } + + // Requests have been sent, so now let's simulate responses from the + // server. Generate corresponding DHCPACK messages with the transaction + // ids from the range from 11 to 20. + for (unsigned i = generator->getNext() - 10; + i < generator->getNext(); ++i) { + Pkt4Ptr ack(createAckPkt4(i)); + // Each DHCPACK packet corresponds to the new lease acquired. Since + // -f<renew-rate> option has been specified, received Reply + // messages are held so as renew messages can be sent for + // existing leases. + tc.processReceivedPacket4(ack); + } + + uint64_t msg_num; + // Try to send 5 messages. It should be successful because 10 + // DHCPREQUEST messages has been received. For each of them we + // should be able to send renewal. + msg_num = tc.sendMultipleMessages4(msg_type, 5); + // Make sure that we have sent 5 messages. + EXPECT_EQ(5, msg_num); + + // Try to do it again. We should still have 5 Reply packets for + // which renews haven't been sent yet. + msg_num = tc.sendMultipleMessages4(msg_type, 5); + EXPECT_EQ(5, msg_num); + + // We used all the DHCPACK packets (we sent renew or release for each of + // them already). Therefore, no further renew messages should be sent + // before we acquire new leases. + msg_num = tc.sendMultipleMessages4(msg_type, 5); + // Make sure that no message has been sent. + EXPECT_EQ(0, msg_num); + } + + /// \brief Test that the DHCPREQUEST message is created correctly and + /// comprises expected values. + /// + /// \param msg_type A type of the message to be tested: + /// DHCPREQUEST in renew state or DHCPRELEASE. + void testCreateRenewRelease4(const uint16_t msg_type) { + // This command line specifies that the Release/Renew messages should + // be sent with the same rate as the Solicit messages. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -4 -l lo -r 10 "; + s << (msg_type == DHCPREQUEST ? "-F" : "-f") << " 10"; + s << " -R 10 -L 10067 -n 10 127.0.0.1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator which will be used by the + // createRenew or createRelease function to generate transaction id. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + Pkt4Ptr ack = createAckPkt4(1); + + // Create DHCPREQUEST from DHCPACK. + Pkt4Ptr msg; + msg = tc.createMessageFromAck(msg_type, ack); + + // Make sure that the DHCPACK has been successfully created and that + // it holds expected data. + ASSERT_TRUE(msg); + EXPECT_EQ("127.0.0.1", msg->getCiaddr().toText()); + + // HW address. + HWAddrPtr hwaddr_ack = ack->getHWAddr(); + ASSERT_TRUE(hwaddr_ack); + HWAddrPtr hwaddr_req = msg->getHWAddr(); + ASSERT_TRUE(hwaddr_req); + EXPECT_TRUE(hwaddr_ack->hwaddr_ == hwaddr_req->hwaddr_); + + // Creating message from null DHCPACK should fail. + EXPECT_THROW(tc.createMessageFromAck(msg_type, Pkt4Ptr()), isc::BadValue); + + // Creating message from DHCPACK holding zero yiaddr should fail. + asiolink::IOAddress yiaddr = ack->getYiaddr(); + ack->setYiaddr(asiolink::IOAddress::IPV4_ZERO_ADDRESS()); + EXPECT_THROW(tc.createMessageFromAck(msg_type, ack), isc::BadValue); + ack->setYiaddr(yiaddr); + } + + /// \brief Test that the DHCPv6 Release or Renew message is created + /// correctly and comprises expected options. + /// + /// \param msg_type A type of the message to be tested: DHCPV6_RELEASE + /// or DHCPV6_RENEW. + void testCreateRenewRelease6(const uint16_t msg_type) { + // This command line specifies that the Release/Renew messages should + // be sent with the same rate as the Solicit messages. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -6 -l lo -r 10 "; + s << (msg_type == DHCPV6_RELEASE ? "-F" : "-f") << " 10 "; + s << "-R 10 -L 10547 -n 10 -e address-and-prefix ::1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator which will be used by the + // createRenew or createRelease function to generate transaction id. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + // Create a Reply packet. The createRelease or createReply function will + // need Reply packet to create a corresponding Release or Reply. + Pkt6Ptr reply = createReplyPkt6(tc, 1); + + Pkt6Ptr msg; + // Check that the message is created. + msg = tc.createMessageFromReply(msg_type, reply); + + ASSERT_TRUE(msg); + // Check that the message type and transaction id is correct. + EXPECT_EQ(msg_type, msg->getType()); + EXPECT_EQ(1, msg->getTransid()); + + // Check that the message has expected options. These are the same for + // Release and Renew. + + // Client Identifier. + OptionPtr opt_clientid = msg->getOption(D6O_CLIENTID); + ASSERT_TRUE(opt_clientid); + EXPECT_TRUE(reply->getOption(D6O_CLIENTID)->getData() == + opt_clientid->getData()); + + // Server identifier + OptionPtr opt_serverid = msg->getOption(D6O_SERVERID); + ASSERT_TRUE(opt_serverid); + EXPECT_TRUE(reply->getOption(D6O_SERVERID)->getData() == + opt_serverid->getData()); + + // IA_NA + OptionPtr opt_ia_na = msg->getOption(D6O_IA_NA); + ASSERT_TRUE(opt_ia_na); + EXPECT_TRUE(reply->getOption(D6O_IA_NA)->getData() == + opt_ia_na->getData()); + + // IA_PD + OptionPtr opt_ia_pd = msg->getOption(D6O_IA_PD); + ASSERT_TRUE(opt_ia_pd); + EXPECT_TRUE(reply->getOption(D6O_IA_PD)->getData() == + opt_ia_pd->getData()); + + // Make sure that exception is thrown if the Reply message is NULL. + EXPECT_THROW(tc.createMessageFromReply(msg_type, Pkt6Ptr()), + isc::BadValue); + + } + + /// \brief Test sending DHCPv6 Releases or Renews. + /// + /// This function simulates acquiring 10 leases from the server. Returned + /// Reply messages are cached and used to send Renew or Release messages. + /// The maximal number of Renew or Release messages which can be sent is + /// equal to the number of leases acquired (10). This function also checks + /// that an attempt to send more Renew or Release messages than the number + /// of leases acquired will fail. + /// + /// \param msg_type A type of the message which is simulated to be sent + /// (DHCPV6_RENEW or DHCPV6_RELEASE). + void testSendRenewRelease6(const uint16_t msg_type) { + // Build a command line. Depending on the message type, we will use + // -f<renew-rate> or -F<release-rate> parameter. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -6 -l fake -r 10 "; + s << (msg_type == DHCPV6_RENEW ? "-f" : "-F"); + s << " 10 -R 10 -L 10547 -n 10 ::1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator to sequential to control to + // guarantee that transaction ids are predictable. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + // Send a number of Solicit messages. Each generated Solicit will be + // assigned a different transaction id, starting from 1 to 10. + tc.sendPackets(10); + + // Simulate Advertise responses from the server. Each advertise is + // assigned a transaction id from the range of 1 to 10, so as they + // match the transaction ids from the Solicit messages. + for (unsigned i = generator->getNext() - 10; + i < generator->getNext(); ++i) { + Pkt6Ptr advertise(createAdvertisePkt6(tc, i)); + // If Advertise is matched with the Solicit the call below will + // trigger a corresponding Request. They will be assigned + // transaction ids from the range from 11 to 20 (the range of + // 1 to 10 has been used by Solicit-Advertise). + tc.processReceivedPacket6(advertise); + } + + // Requests have been sent, so now let's simulate responses from the + // server. Generate corresponding Reply messages with the transaction + // ids from the range from 11 to 20. + for (unsigned i = generator->getNext() - 10; + i < generator->getNext(); ++i) { + Pkt6Ptr reply(createReplyPkt6(tc, i)); + // Each Reply packet corresponds to the new lease acquired. Since + // -f<renew-rate> option has been specified, received Reply + // messages are held so as Renew messages can be sent for + // existing leases. + tc.processReceivedPacket6(reply); + } + + uint64_t msg_num; + // Try to send 5 messages. It should be successful because 10 Reply + // messages has been received. For each of them we should be able to + // send Renew or Release. + msg_num = tc.sendMultipleMessages6(msg_type, 5); + // Make sure that we have sent 5 messages. + EXPECT_EQ(5, msg_num); + + // Try to do it again. We should still have 5 Reply packets for + // which Renews or Releases haven't been sent yet. + msg_num = tc.sendMultipleMessages6(msg_type, 5); + EXPECT_EQ(5, msg_num); + + // We used all the Reply packets (we sent Renew or Release for each of + // them already). Therefore, no further Renew or Release messages should + // be sent before we acquire new leases. + msg_num = tc.sendMultipleMessages6(msg_type, 5); + // Make sure that no message has been sent. + EXPECT_EQ(0, msg_num); + + } + + /// \brief Test counting rejected leases in Solicit-Advertise. + /// + /// This function simulates acquiring 4 leases from the server and + /// rejecting allocating of 6 leases + + void testCountRejectedLeasesSolAdv() { + // Build a command line. + CommandOptions opt; + std::ostringstream s; + s << "perfdhcp -6 -l fake -r 10 -R 10 -L 10547 -n 10 ::1"; + processCmdLine(opt, s.str()); + // Create a test controller class. + NakedTestControl tc(opt); + // Set the transaction id generator to sequential to control to + // guarantee that transaction ids are predictable. + boost::shared_ptr<NakedTestControl::IncrementalGenerator> + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + + // Send a number of Solicit messages. Each generated Solicit will be + // assigned a different transaction id, starting from 1 to 10. + tc.sendPackets(10); + + // Simulate Advertise responses from the server. Each advertise is + // assigned a transaction id from the range of 1 to 6 with incorrect IA + // included in the message + for (uint32_t i = generator->getNext() - 10; i < 7; ++i) { + Pkt6Ptr advertise(createAdvertisePkt6(tc, i, false)); + tc.processReceivedPacket6(advertise); + } + // counter of rejected leases has to be 6 + EXPECT_EQ(tc.stats_mgr_.getRejLeasesNum(ExchangeType::SA), 6); + // Simulate Advertise responses from the server. Each advertise is + // assigned a transaction id from the range of 7 to 10 with correct IA + // included in the message + for (uint32_t i = generator->getNext() - 7; i < 11; ++i) { + Pkt6Ptr advertise(createAdvertisePkt6(tc, i)); + tc.processReceivedPacket6(advertise); + } + // counter of rejected leases can't change at this point + EXPECT_EQ(tc.stats_mgr_.getRejLeasesNum(ExchangeType::SA), 6); + } + + /// \brief Parse command line string with CommandOptions. + /// + /// \param cmdline command line string to be parsed. + /// \throw isc::Unexpected if unexpected error occurred. + /// \throw isc::InvalidParameter if command line is invalid. + void processCmdLine(CommandOptions &opt, const std::string& cmdline) const { + CommandOptionsHelper::process(opt, cmdline); + } + + /// \brief Create DHCPOFFER or DHCPACK packet. + /// + /// \param pkt_type DHCPOFFER or DHCPACK. + /// \param transid Transaction id. + /// + /// \return Instance of the packet. + Pkt4Ptr + createResponsePkt4(const uint8_t pkt_type, + const uint32_t transid) const { + Pkt4Ptr pkt(new Pkt4(pkt_type, transid)); + OptionPtr opt_serverid = Option::factory(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + OptionBuffer(4, 1)); + pkt->setYiaddr(asiolink::IOAddress("127.0.0.1")); + pkt->addOption(opt_serverid); + pkt->updateTimestamp(); + return (pkt); + } + + /// \brief Create DHCPv4 OFFER packet. + /// + /// \param transid transaction id. + /// \return instance of the packet. + Pkt4Ptr + createOfferPkt4(uint32_t transid) const { + return (createResponsePkt4(DHCPOFFER, transid)); + } + + /// \brief Create DHCPACK packet. + /// + /// \param transid transaction id. + /// \return instance of the packet. + Pkt4Ptr + createAckPkt4(const uint32_t transid) const { + return (createResponsePkt4(DHCPACK, transid)); + } + + /// \brief Create DHCPv6 ADVERTISE packet. + /// + /// \param transid transaction id. + /// \return instance of the packet. + Pkt6Ptr + createAdvertisePkt6(NakedTestControl &tc, const uint32_t transid, + const bool validIA = true) const { + boost::shared_ptr<Pkt6> advertise(new Pkt6(DHCPV6_ADVERTISE, transid)); + // Add IA_NA if requested by the client. + if (tc.options_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) { + OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA); + if (validIA) { + OptionPtr iaaddr(new Option6IAAddr(D6O_IAADDR, + isc::asiolink::IOAddress("fe80::abcd"), 300, 500)); + opt_ia_na->addOption(iaaddr); + } + advertise->addOption(opt_ia_na); + } + // Add IA_PD if requested by the client. + if (tc.options_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) { + OptionPtr opt_ia_pd = Option::factory(Option::V6, D6O_IA_PD); + if (validIA) { + OptionPtr iapref(new Option6IAPrefix(D6O_IAPREFIX, + isc::asiolink::IOAddress("fe80::"), 64, 300, 500)); + opt_ia_pd->addOption(iapref); + } + advertise->addOption(opt_ia_pd); + } + OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID)); + uint8_t randomized = 0; + std::vector<uint8_t> duid(tc.generateDuid(randomized)); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + advertise->addOption(opt_serverid); + advertise->addOption(opt_clientid); + advertise->updateTimestamp(); + return (advertise); + } + + Pkt6Ptr + createReplyPkt6(NakedTestControl &tc, const uint32_t transid, + const bool validIA = true) const { + Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, transid)); + // Add IA_NA if requested by the client. + if (tc.options_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) { + OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA); + if (validIA) { + OptionPtr iaaddr(new Option6IAAddr(D6O_IAADDR, + isc::asiolink::IOAddress("fe80::abcd"), 300, 500)); + opt_ia_na->addOption(iaaddr); + } + reply->addOption(opt_ia_na); + } + // Add IA_PD if requested by the client. + if (tc.options_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) { + OptionPtr opt_ia_pd = Option::factory(Option::V6, D6O_IA_PD); + if (validIA) { + OptionPtr iapref(new Option6IAPrefix(D6O_IAPREFIX, + isc::asiolink::IOAddress("fe80::"), 64, 300, 500)); + opt_ia_pd->addOption(iapref); + } + reply->addOption(opt_ia_pd); + } + OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID)); + uint8_t randomized = 0; + std::vector<uint8_t> duid(tc.generateDuid(randomized)); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + reply->addOption(opt_serverid); + reply->addOption(opt_clientid); + reply->updateTimestamp(); + return (reply); + + } + + /// @brief Check presence and content of v4 options 55. + /// + /// \param pkt packet to be checked + /// \param expected_option_requests only these option requests should be + /// found under option 55 in the packet, nothing more, nothing less + void checkOptions55(Pkt4Ptr const& pkt, + vector<uint8_t> const& expected_option_requests) { + // Sanity checks + ASSERT_TRUE(pkt); + OptionPtr const& opt(pkt->getOption(55)); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->getUniverse() == Option::V4); + + // Create the text of the expected option. + string const length(to_string(expected_option_requests.size())); + string const buffer( + TestControl::vector2Hex(expected_option_requests, ":")); + string const expected_option_text(boost::str( + boost::format("type=055, len=%03u: %s") % length % buffer)); + + // Compare. + EXPECT_EQ(opt->toText(), expected_option_text); + } + + /// @brief check if v4 options 200 and 201 are present. + /// + /// The options are expected to have specific format, as if parameters + /// -o 200,abcdef1234, -o 201,00 were passed to the command line. + void checkOptions20x(const Pkt4Ptr& pkt) { + ASSERT_TRUE(pkt); + OptionPtr opt = pkt->getOption(200); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->getUniverse() == Option::V4); + EXPECT_EQ(opt->toText(), "type=200, len=005: ab:cd:ef:12:34"); + + opt = pkt->getOption(201); + ASSERT_TRUE(opt); + EXPECT_EQ(opt->toText(), "type=201, len=001: 00"); + } + + /// @brief check if v6 options 200 and 201 are present. + /// + /// The options are expected to have specific format, as if parameters + /// -o 200,abcdef1234, -o 201,00 were passed to the command line. + void checkOptions20x(const Pkt6Ptr& pkt) { + ASSERT_TRUE(pkt); + OptionPtr opt = pkt->getOption(200); + ASSERT_TRUE(opt); + EXPECT_TRUE(opt->getUniverse() == Option::V6); + EXPECT_EQ(opt->toText(), "type=00200, len=00005: ab:cd:ef:12:34"); + + opt = pkt->getOption(201); + ASSERT_TRUE(opt); + EXPECT_EQ(opt->toText(), "type=00201, len=00001: 00"); + } + +}; + +// This test verifies that the class members are reset to expected values. +TEST_F(TestControlTest, reset) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -4 127.0.0.1"); + NakedTestControl tc(opt); + tc.reset(); + EXPECT_FALSE(tc.last_report_.is_not_a_date_time()); + EXPECT_FALSE(tc.transid_gen_); + EXPECT_FALSE(tc.macaddr_gen_); + EXPECT_TRUE(tc.first_packet_serverid_.empty()); + EXPECT_FALSE(tc.interrupted_); + +} + +// This test verifies that the client id is generated from the HW address. +TEST_F(TestControlTest, generateClientId) { + // Generate HW address. + std::vector<uint8_t> hwaddr; + for (unsigned int i = 0; i < 6; ++i) { + hwaddr.push_back(i); + } + HWAddrPtr hwaddr_ptr(new HWAddr(hwaddr, 5)); + + // Use generated HW address to generate client id. + CommandOptions opt; + processCmdLine(opt, "perfdhcp -4 127.0.0.1"); + NakedTestControl tc(opt); + OptionPtr opt_client_id; + opt_client_id = tc.generateClientId(hwaddr_ptr); + ASSERT_TRUE(opt_client_id); + + // Extract the client id data. + const OptionBuffer& client_id = opt_client_id->getData(); + ASSERT_EQ(7, client_id.size()); + + // Verify that the client identifier is generated correctly. + + // First byte is the HW type. + EXPECT_EQ(5, client_id[0]); + // The rest of the client identifier should be equal to the HW address. + std::vector<uint8_t> sub(client_id.begin() + 1, client_id.end()); + EXPECT_TRUE(hwaddr == sub); +} + +TEST_F(TestControlTest, GenerateDuid) { + // Simple command line that simulates one client only. Always the + // same DUID will be generated. + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l 127.0.0.1 all"); + testDuid(opt); + + // Simulate 50 clients. Different DUID will be generated. + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -R 50 all"); + testDuid(opt); + + // Checks that the random mac address returned by generateDuid + // is in the list of mac addresses in the mac-list.txt data file + std::string mac_list_full_path = getFullPath("mac-list.txt"); + std::ostringstream cmd; + cmd << "perfdhcp -M " << mac_list_full_path << " 127.0.0.1"; + processCmdLine(opt, cmd.str()); + // Initialize Test Controller. + NakedTestControl tc(opt); + uint8_t randomized = 0; + std::vector<uint8_t> generated_duid = tc.generateDuid(randomized); + + // Check that generated_duid is DUID_LL + ASSERT_EQ(10, generated_duid.size()); + DuidPtr duid(new DUID(generated_duid)); + ASSERT_EQ(duid->getType(), DUID::DUID_LL); + + // Make sure it's on the list + const CommandOptions::MacAddrsVector& macs = opt.getMacsFromFile(); + // DUID LL comprises 2 bytes of duid type, 2 bytes of hardware type, + // then 6 bytes of HW address. + vector<uint8_t> mac(6); + std::copy(generated_duid.begin() + 4, generated_duid.begin() + 10, + mac.begin()); + // Check that mac is in macs. + ASSERT_TRUE(std::find(macs.begin(), macs.end(), mac) != macs.end()); +} + +TEST_F(TestControlTest, GenerateMacAddress) { + CommandOptions opt; + // Simulate one client only. Always the same MAC address will be + // generated. + processCmdLine(opt, "perfdhcp -l 127.0.0.1 all"); + testMacAddress(opt); + + // Simulate 50 clients. Different MAC addresses will be generated. + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -R 50 all"); + testMacAddress(opt); + + // Checks that the random mac address returned by generateMacAddress + // is in the list of mac addresses in the mac-list.txt data file + std::string mac_list_full_path = getFullPath("mac-list.txt"); + std::ostringstream cmd; + cmd << "perfdhcp -M " << mac_list_full_path << " 127.0.0.1"; + processCmdLine(opt, cmd.str()); + // Initialize Test Controller. + NakedTestControl tc(opt); + uint8_t randomized = 0; + // Generate MAC address and sanity check its size. + std::vector<uint8_t> mac = tc.generateMacAddress(randomized); + ASSERT_EQ(6, mac.size()); + // Make sure that the generated MAC address belongs to the MAC addresses + // read from a file. + const CommandOptions::MacAddrsVector& macs = opt.getMacsFromFile(); + ASSERT_TRUE(std::find(macs.begin(), macs.end(), mac) != macs.end()); +} + +TEST_F(TestControlTest, Options4) { + using namespace isc::dhcp; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -4 127.0.0.1"); + NakedTestControl tc(opt); + // By default the IP version mode is V4 so there is no need to + // parse command line to override the IP version. Note that + // registerOptionFactories is used for both V4 and V6. + tc.registerOptionFactories(); + // Create option with buffer size equal to 1 and holding DHCPDISCOVER + // message type. + OptionPtr opt_msg_type(Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE, + OptionBuffer(1, DHCPDISCOVER))); + // Validate the option type and universe. + EXPECT_EQ(Option::V4, opt_msg_type->getUniverse()); + EXPECT_EQ(DHO_DHCP_MESSAGE_TYPE, opt_msg_type->getType()); + // Validate the message type from the option we have now created. + uint8_t msg_type = 0; + msg_type = opt_msg_type->getUint8(); + EXPECT_EQ(DHCPDISCOVER, msg_type); + + // Create another option: DHCP_PARAMETER_REQUEST_LIST + OptionPtr + opt_requested_options(Option::factory(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + // Here is a list of options that we are requesting in the + // server's response. + const uint8_t requested_options[] = { + DHO_SUBNET_MASK, + DHO_BROADCAST_ADDRESS, + DHO_TIME_OFFSET, + DHO_ROUTERS, + DHO_DOMAIN_NAME, + DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME + }; + + OptionBuffer + requested_options_ref(requested_options, + requested_options + sizeof(requested_options)); + + // Get the option buffer. It should hold the combination of values + // listed in requested_options array. However their order can be + // different in general so we need to search each value separately. + const OptionBuffer& requested_options_buf = + opt_requested_options->getData(); + EXPECT_EQ(requested_options_ref.size(), requested_options_buf.size()); + size_t matched_num = matchRequestedOptions(requested_options_ref, + requested_options_buf); + // We want exactly the same requested options as listed in + // requested_options array - nothing more or less. + EXPECT_EQ(requested_options_ref.size(), matched_num); +} + +TEST_F(TestControlTest, Options6) { + using namespace isc::dhcp; + CommandOptions opt; + + // Lets override the IP version to test V6 options (-6 parameter) + processCmdLine(opt, "perfdhcp -l lo -6 ::1"); + + NakedTestControl tc(opt); + tc.registerOptionFactories(); + + // Validate the D6O_ELAPSED_TIME option. + OptionPtr opt_elapsed_time(Option::factory(Option::V6, D6O_ELAPSED_TIME)); + // Validate the option type and universe. + EXPECT_EQ(Option::V6, opt_elapsed_time->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt_elapsed_time->getType()); + // The default value of elapsed time is zero. + uint16_t elapsed_time; + elapsed_time = opt_elapsed_time->getUint16(); + EXPECT_EQ(0, elapsed_time); + + // With the factory function we may also specify the actual + // value of elapsed time. Let's make use of std::vector + // constructor to create the option buffer, 2 octets long + // with each octet initialized to 0x1. + size_t elapsed_time_buf_size = 2; + uint8_t elapsed_time_pattern = 0x1; + OptionPtr + opt_elapsed_time2(Option::factory(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(elapsed_time_buf_size, + elapsed_time_pattern))); + + // Any buffer that has size neither equal to 0 nor 2 is considered invalid. + elapsed_time_buf_size = 1; + EXPECT_THROW( + Option::factory(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(elapsed_time_buf_size, elapsed_time_pattern)), + isc::BadValue + ); + + // Validate the option type and universe. + EXPECT_EQ(Option::V6, opt_elapsed_time2->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt_elapsed_time2->getType()); + // Make sure the getUint16 does not throw exception. It wile throw + // buffer is shorter than 2 octets. + elapsed_time = opt_elapsed_time2->getUint16(); + // Check the expected value of elapsed time. + EXPECT_EQ(0x0101, elapsed_time); + + // Validate the D6O_RAPID_COMMIT option. + OptionPtr opt_rapid_commit(Option::factory(Option::V6, D6O_RAPID_COMMIT)); + // Validate the option type and universe. + EXPECT_EQ(Option::V6, opt_rapid_commit->getUniverse()); + EXPECT_EQ(D6O_RAPID_COMMIT, opt_rapid_commit->getType()); + // Rapid commit has no data payload. + EXPECT_THROW(opt_rapid_commit->getUint8(), isc::OutOfRange); + + // Validate the D6O_CLIENTID option. + OptionBuffer duid(opt.getDuidTemplate()); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + EXPECT_EQ(Option::V6, opt_clientid->getUniverse()); + EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType()); + const OptionBuffer& duid2 = opt_clientid->getData(); + ASSERT_EQ(duid.size(), duid2.size()); + // The Duid we set for option is the same we get. + EXPECT_TRUE(std::equal(duid.begin(), duid.end(), duid2.begin())); + + // Validate the D6O_ORO (Option Request Option). + OptionPtr opt_oro(Option::factory(Option::V6, D6O_ORO)); + // Prepare the reference buffer with requested options. + const uint8_t requested_options[] = { + 0, D6O_NAME_SERVERS, + 0, D6O_DOMAIN_SEARCH + }; + // Each option code in ORO is 2 bytes long. We calculate the number of + // requested options by dividing the size of the buffer holding options + // by the size of each individual option. + int requested_options_num = sizeof(requested_options) / sizeof(uint16_t); + OptionBuffer + requested_options_ref(requested_options, + requested_options + sizeof(requested_options)); + // Get the buffer from option. + const OptionBuffer& requested_options_buf = opt_oro->getData(); + // Size of reference buffer and option buffer have to be + // the same for comparison. + EXPECT_EQ(requested_options_ref.size(), requested_options_buf.size()); + // Check if all options in the buffer are matched with reference buffer. + size_t matched_num = matchRequestedOptions6(requested_options_ref, + requested_options_buf); + EXPECT_EQ(requested_options_num, matched_num); + + // Validate the D6O_IA_NA option. + OptionPtr opt_ia_na(Option::factory(Option::V6, D6O_IA_NA)); + EXPECT_EQ(Option::V6, opt_ia_na->getUniverse()); + EXPECT_EQ(D6O_IA_NA, opt_ia_na->getType()); + // Every IA_NA option is expected to start with this sequence. + const uint8_t opt_ia_na_array[] = { + 0, 0, 0, 1, // IAID = 1 + 0, 0, 3600 >> 8, 3600 & 0xff, // T1 = 3600 + 0, 0, 5400 >> 8, 5400 & 0xff, // T2 = 5400 + }; + OptionBuffer opt_ia_na_ref(opt_ia_na_array, + opt_ia_na_array + sizeof(opt_ia_na_array)); + const OptionBuffer& opt_ia_na_buf = opt_ia_na->getData(); + ASSERT_EQ(opt_ia_na_buf.size(), opt_ia_na_ref.size()); + EXPECT_TRUE(std::equal(opt_ia_na_ref.begin(), opt_ia_na_ref.end(), + opt_ia_na_buf.begin())); + + // @todo Add more tests for IA address options. +} + +TEST_F(TestControlTest, Packet4) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -L 10547 all"); + NakedTestControl tc(opt); + uint32_t transid = 123; + boost::shared_ptr<Pkt4> pkt4(new Pkt4(DHCPDISCOVER, transid)); + // Set parameters on outgoing packet. + tc.setDefaults4(pkt4); + // Validate that packet has been setup correctly. + EXPECT_EQ(tc.fake_sock_.iface_->getName(), pkt4->getIface()); + EXPECT_EQ(tc.fake_sock_.ifindex_, pkt4->getIndex()); + EXPECT_EQ(DHCP4_CLIENT_PORT, pkt4->getLocalPort()); + EXPECT_EQ(DHCP4_SERVER_PORT, pkt4->getRemotePort()); + EXPECT_EQ(1, pkt4->getHops()); + EXPECT_EQ(asiolink::IOAddress("255.255.255.255"), + pkt4->getRemoteAddr()); + EXPECT_EQ(asiolink::IOAddress(tc.socket_.addr_), pkt4->getLocalAddr()); + EXPECT_EQ(asiolink::IOAddress(tc.socket_.addr_), pkt4->getGiaddr()); +} + +TEST_F(TestControlTest, Packet6) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -6 -l fake -L 10547 servers"); + NakedTestControl tc(opt); + uint32_t transid = 123; + boost::shared_ptr<Pkt6> pkt6(new Pkt6(DHCPV6_SOLICIT, transid)); + // Set packet's parameters. + tc.setDefaults6(pkt6); + // Validate if parameters have been set correctly. + EXPECT_EQ(tc.fake_sock_.iface_->getName(), pkt6->getIface()); + EXPECT_EQ(tc.socket_.ifindex_, pkt6->getIndex()); + EXPECT_EQ(DHCP6_CLIENT_PORT, pkt6->getLocalPort()); + EXPECT_EQ(DHCP6_SERVER_PORT, pkt6->getRemotePort()); + EXPECT_EQ(tc.socket_.addr_, pkt6->getLocalAddr()); + EXPECT_EQ(asiolink::IOAddress("FF05::1:3"), pkt6->getRemoteAddr()); + // Packet must not be relayed. + EXPECT_TRUE(pkt6->relay_info_.empty()); +} + +TEST_F(TestControlTest, Packet6Relayed) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -6 -l fake -A1 -L 10547 servers"); + NakedTestControl tc(opt); + uint32_t transid = 123; + boost::shared_ptr<Pkt6> pkt6(new Pkt6(DHCPV6_SOLICIT, transid)); + // Set packet's parameters. + tc.setDefaults6(pkt6); + // Validate if parameters have been set correctly. + EXPECT_EQ(tc.fake_sock_.iface_->getName(), pkt6->getIface()); + EXPECT_EQ(tc.socket_.ifindex_, pkt6->getIndex()); + EXPECT_EQ(DHCP6_CLIENT_PORT, pkt6->getLocalPort()); + EXPECT_EQ(DHCP6_SERVER_PORT, pkt6->getRemotePort()); + EXPECT_EQ(tc.socket_.addr_, pkt6->getLocalAddr()); + EXPECT_EQ(asiolink::IOAddress("FF05::1:3"), pkt6->getRemoteAddr()); + // Packet should be relayed. + EXPECT_EQ(pkt6->relay_info_.size(), 1); + EXPECT_EQ(pkt6->relay_info_[0].hop_count_, 0); + EXPECT_EQ(pkt6->relay_info_[0].msg_type_, DHCPV6_RELAY_FORW); + EXPECT_EQ(pkt6->relay_info_[0].linkaddr_, tc.socket_.addr_); + EXPECT_EQ(pkt6->relay_info_[0].peeraddr_, tc.socket_.addr_); +} + +TEST_F(TestControlTest, Packet6RelayedWithRelayOpts) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -6 -l fake -A1 --or 1:32,00000E10 -L 10547 servers"); + NakedTestControl tc(opt); + uint32_t transid = 123; + boost::shared_ptr<Pkt6> pkt6(new Pkt6(DHCPV6_SOLICIT, transid)); + // Set packet's parameters. + tc.setDefaults6(pkt6); + // Validate if parameters have been set correctly. + EXPECT_EQ(tc.fake_sock_.iface_->getName(), pkt6->getIface()); + EXPECT_EQ(tc.socket_.ifindex_, pkt6->getIndex()); + EXPECT_EQ(DHCP6_CLIENT_PORT, pkt6->getLocalPort()); + EXPECT_EQ(DHCP6_SERVER_PORT, pkt6->getRemotePort()); + EXPECT_EQ(tc.socket_.addr_, pkt6->getLocalAddr()); + EXPECT_EQ(asiolink::IOAddress("FF05::1:3"), pkt6->getRemoteAddr()); + // Packet should be relayed. + EXPECT_EQ(pkt6->relay_info_.size(), 1); + EXPECT_EQ(pkt6->relay_info_[0].hop_count_, 0); + EXPECT_EQ(pkt6->relay_info_[0].msg_type_, DHCPV6_RELAY_FORW); + EXPECT_EQ(pkt6->relay_info_[0].linkaddr_, tc.socket_.addr_); + EXPECT_EQ(pkt6->relay_info_[0].peeraddr_, tc.socket_.addr_); + // Checking if relayed option is there. + OptionBuffer opt_data = pkt6->relay_info_[0].options_.find(32)->second->getData(); + EXPECT_EQ(4, opt_data.size()); + EXPECT_EQ("0x00000E10", pkt6->relay_info_[0].options_.find(32)->second->toHexString()); +} + +TEST_F(TestControlTest, Packet4Exchange) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -r 100 -n 10 -R 20 -L 10547 127.0.0.1"); + bool use_templates = false; + NakedTestControl tc(opt); + testPkt4Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Discovery + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RA), 0); +} + +TEST_F(TestControlTest, Packet4ExchangeFromTemplate) { + const int iterations_num = 100; + CommandOptions opt; + + processCmdLine(opt, "perfdhcp -l fake -r 100 -R 20 -n 20 -L 10547" + " -T " + getFullPath("discover-example.hex") + + " -T " + getFullPath("request4-example.hex") + + " 127.0.0.1"); + const int received_num = 10; + bool use_templates = true; + NakedTestControl tc(opt); + testPkt4Exchange(iterations_num, received_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num + received_num); // Discovery + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::DO), received_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RA), received_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RA), 0); +} + +TEST_F(TestControlTest, Packet6Exchange) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -6 -r 100 -n 10 -R 20 -L 10547 ::1"); + bool use_templates = false; + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Solicit + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, Packet6ExchangeFromTemplate) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -6 -r 100 -n 10 -R 20 -L 10547" + " -T " + getFullPath("solicit-example.hex") + + " -T " + getFullPath("request6-example.hex ::1")); + NakedTestControl tc(opt); + + // For the first 3 packets we are simulating responses from server. + // For other packets we don't so packet as 4,5,6 will be dropped and + // then test should be interrupted and actual number of iterations will + // be 6. + const int received_num = 3; + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. All exchanged packets carry + // the IA_NA option to simulate the IPv6 address acquisition and to verify + // that the IA_NA options returned by the server are processed correctly. + bool use_templates = true; + testPkt6Exchange(iterations_num, received_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num + received_num); // Solicit + Advertise + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), received_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), received_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, Packet6ExchangeAddressOnly) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -e address-only" + " -6 -r 100 -n 10 -R 20 -L 10547 ::1"); + // Set number of received packets equal to number of iterations. + // This simulates no packet drops. + bool use_templates = false; + + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. All exchanged packets carry + // the IA_NA option to simulate the IPv6 address acquisition and to verify + // that the IA_NA options returned by the server are processed correctly. + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Solicit + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, Packet6ExchangePrefixDelegation) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -e prefix-only" + " -6 -r 100 -n 10 -R 20 -L 10547 ::1"); + // Set number of received packets equal to number of iterations. + // This simulates no packet drops. + bool use_templates = false; + + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. All exchanged packets carry + // the IA_PD option to simulate the Prefix Delegation and to verify that + // the IA_PD options returned by the server are processed correctly. + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Discovery + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, Packet6ExchangeAddressAndPrefix) { + const int iterations_num = 100; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -e address-and-prefix" + " -6 -r 100 -n 10 -R 20 -L 10547 ::1"); + // Set number of received packets equal to number of iterations. + // This simulates no packet drops. + bool use_templates = false; + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. All exchanged packets carry + // either IA_NA or IA_PD options to simulate the address and prefix + // acquisition with the single message and to verify that the IA_NA + // and IA_PD options returned by the server are processed correctly. + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, use_templates, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Solicit + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); +} + +TEST_F(TestControlTest, PacketTemplates) { + std::vector<uint8_t> template1(256); + std::string file1("test1.hex"); + std::vector<uint8_t> template2(233); + std::string file2("test2.hex"); + for (size_t i = 0; i < template1.size(); ++i) { + template1[i] = static_cast<uint8_t>(random() % 256); + } + for (size_t i = 0; i < template2.size(); ++i) { + template2[i] = static_cast<uint8_t>(random() % 256); + } + // Size of the file is 2 times larger than binary data size. + ASSERT_TRUE(createTemplateFile(file1, template1, template1.size() * 2)); + ASSERT_TRUE(createTemplateFile(file2, template2, template2.size() * 2)); + + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l 127.0.0.1" + " -T " + file1 + " -T " + file2 + " all"); + NakedTestControl tc(opt); + + tc.initPacketTemplates(); + TestControl::TemplateBuffer buf1; + TestControl::TemplateBuffer buf2; + buf1 = tc.getTemplateBuffer(0); + buf2 = tc.getTemplateBuffer(1); + ASSERT_EQ(template1.size(), buf1.size()); + ASSERT_EQ(template2.size(), buf2.size()); + EXPECT_TRUE(std::equal(template1.begin(), template1.end(), buf1.begin())); + EXPECT_TRUE(std::equal(template2.begin(), template2.end(), buf2.begin())); + + // Try to read template file with odd number of digits. + std::string file3("test3.hex"); + // Size of the file is 2 times larger than binary data size and it is always + // even number. Substracting 1 makes file size odd. + ASSERT_TRUE(createTemplateFile(file3, template1, template1.size() * 2 - 1)); + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -T " + file3 + " all"); + EXPECT_THROW(tc.initPacketTemplates(), isc::OutOfRange); + + // Try to read empty file. + std::string file4("test4.hex"); + ASSERT_TRUE(createTemplateFile(file4, template2, 0)); + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -T " + file4 + " all"); + EXPECT_THROW(tc.initPacketTemplates(), isc::OutOfRange); + + // Try reading file with non hexadecimal characters. + std::string file5("test5.hex"); + ASSERT_TRUE(createTemplateFile(file5, template1, template1.size() * 2, true)); + processCmdLine(opt, "perfdhcp -l 127.0.0.1 -T " + file5 + " all"); + EXPECT_THROW(tc.initPacketTemplates(), isc::BadValue); +} + +// This test verifies that DHCPv4 renew (DHCPREQUEST) messages can be +// sent for acquired leases. +TEST_F(TestControlTest, processRenew4) { + testSendRenewRelease4(DHCPREQUEST); +} + +// This test verifies that DHCPv4 release (DHCPRELEASE) messages can be +// sent for acquired leases. +TEST_F(TestControlTest, processRelease4) { + testSendRenewRelease4(DHCPRELEASE); +} + +// This test verifies that DHCPv6 Renew messages can be sent for acquired +// leases. +TEST_F(TestControlTest, processRenew6) { + testSendRenewRelease6(DHCPV6_RENEW); +} + +// This test verifies that DHCPv6 Release messages can be sent for acquired +// leases. +TEST_F(TestControlTest, processRelease6) { + testSendRenewRelease6(DHCPV6_RELEASE); +} + +// This test verifies that DHCPREQUEST is created correctly from the +// DHCPACK message. +TEST_F(TestControlTest, createRenew4) { + testCreateRenewRelease4(DHCPREQUEST); +} + +// This test verifies that DHCPRELEASE is created correctly from the +// DHCPACK message. +TEST_F(TestControlTest, createRelease4) { + testCreateRenewRelease4(DHCPRELEASE); +} + +// This test verifies that the DHCPV6 Renew message is created correctly +// and that it comprises all required options. +TEST_F(TestControlTest, createRenew6) { + testCreateRenewRelease6(DHCPV6_RENEW); +} + +// This test verifies that the DHCPv6 Release message is created correctly +// and that it comprises all required options. +TEST_F(TestControlTest, createRelease6) { + testCreateRenewRelease6(DHCPV6_RELEASE); +} + +// This test verifies that the counter of rejected leases in +// Solicit-Advertise message exchange works correctly +TEST_F(TestControlTest, rejectedLeasesAdv) { + testCountRejectedLeasesSolAdv(); +} + +// Test checks if sendDiscover really includes custom options +TEST_F(TestControlTest, sendDiscoverExtraOpts) { + // Important parameters here: + // -xT - save first packet of each type for templates (useful for packet inspection) + // -o 200,abcdef1234 - send option 200 with hex content: ab:cd:ef:12:34 + // -o 201,00 - send option 201 with hex content: 00 + CommandOptions opt; + processCmdLine(opt, "perfdhcp -4 -l fake -xT -L 10068" + " -o 200,abcdef1234 -o 201,00 -r 1 127.0.0.1"); + + // Create test control and set up some basic defaults. + NakedTestControl tc(opt); + tc.registerOptionFactories(); + NakedTestControl::IncrementalGeneratorPtr gen(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(gen); + + // Make tc send the packet. The first packet of each type is saved in templates. + tc.sendDiscover4(); + + // Let's find the packet and see if it includes the right option. + auto pkt_it = tc.template_packets_v4_.find(DHCPDISCOVER); + ASSERT_TRUE(pkt_it != tc.template_packets_v4_.end()); + + checkOptions20x(pkt_it->second); +} + +// Test checks if regular packet exchange inserts the extra v4 options +// specified on command line. +TEST_F(TestControlTest, Packet4ExchangeExtraOpts) { + // Important parameters here: + // -xT - save first packet of each type for templates (useful for packet inspection) + // -o 200,abcdef1234 - send option 200 with hex content: ab:cd:ef:12:34 + // -o 201,00 - send option 201 with hex content: 00 + const int iterations_num = 1; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -4 -o 200,abcdef1234 -o 201,00 " + "-r 100 -n 10 -R 20 -xT -L 10547 127.0.0.1"); + + NakedTestControl tc(opt); + tc.registerOptionFactories(); + + // Do the actual exchange. + testPkt4Exchange(iterations_num, iterations_num, false, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Discovery + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::DO), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RA), 0); + + // Check if Discover was recored and if it contains options 200 and 201. + auto disc = tc.template_packets_v4_.find(DHCPDISCOVER); + ASSERT_TRUE(disc != tc.template_packets_v4_.end()); + checkOptions20x(disc->second); + + // Check if Request was recored and if it contains options 200 and 201. + auto req = tc.template_packets_v4_.find(DHCPREQUEST); + ASSERT_TRUE(req != tc.template_packets_v4_.end()); + checkOptions20x(req->second); +} + +// Test checks if regular packet exchange inserts the extra v6 options +// specified on command line. +TEST_F(TestControlTest, Packet6ExchangeExtraOpts) { + // Important parameters here: + // -xT - save first packet of each type for templates (useful for packet inspection) + // -o 200,abcdef1234 - send option 200 with hex content: ab:cd:ef:12:34 + // -o 201,00 - send option 201 with hex content: 00 + const int iterations_num = 1; + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake" + " -6 -e address-only" + " -xT -o 200,abcdef1234 -o 201,00 " + " -r 100 -n 10 -R 20 -L 10547 ::1"); + + // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges. + // The test function generates server's responses and passes it to the + // TestControl class methods for processing. + // First packet of each type is recorded as a template packet. The check + // inspects this template to see if the expected options are really there. + NakedTestControl tc(opt); + testPkt6Exchange(iterations_num, iterations_num, false, tc); + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, iterations_num * 2); // Solicit + Request + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::SA), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getSentPacketsNum(ExchangeType::RR), iterations_num); + EXPECT_EQ(tc.stats_mgr_.getRcvdPacketsNum(ExchangeType::RR), 0); + + // Check if Solicit was recorded and if it contains options 200 and 201. + auto sol = tc.template_packets_v6_.find(DHCPV6_SOLICIT); + ASSERT_TRUE(sol != tc.template_packets_v6_.end()); + checkOptions20x(sol->second); + + // Check if Request was recorded and if it contains options 200 and 201. + auto req = tc.template_packets_v6_.find(DHCPV6_REQUEST); + ASSERT_TRUE(req != tc.template_packets_v6_.end()); + checkOptions20x(req->second); +} + +// Test checks if multiple v4 PRL options can be sent. They should be merged +// into a single PRL option by perfdhcp. +TEST_F(TestControlTest, sendDiscoverMultiplePRLs) { + // Important parameters here: + // -o 55,1234 - send option 55 with hex content '1234' + // -o 55,abcd - send option 55 with hex content 'abcd' + CommandOptions opt; + processCmdLine( + opt, "perfdhcp -4 -l fake -o 55,1234 -o 55,abcd -r 1 -xT 127.0.0.1"); + + // Create test control and set up some basic defaults. + NakedTestControl tc(opt); + tc.registerOptionFactories(); + NakedTestControl::IncrementalGeneratorPtr gen( + boost::make_shared<NakedTestControl::IncrementalGenerator>()); + tc.setTransidGenerator(gen); + + // Send the packet. + tc.sendDiscover4(); + + // Let's find the packet and see if it includes the right option. + auto const pkt_it(tc.template_packets_v4_.find(DHCPDISCOVER)); + ASSERT_TRUE(pkt_it != tc.template_packets_v4_.end()); + + checkOptions55(pkt_it->second, + { + // Added to all perfdhcp egress packets by default + DHO_SUBNET_MASK, + DHO_BROADCAST_ADDRESS, + DHO_TIME_OFFSET, + DHO_ROUTERS, + DHO_DOMAIN_NAME, + DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME, + // Explicitly added in this test + 0x12, + 0x34, + 0xab, + 0xcd, + }); +} + +// This test checks if HA failure can be simulated using -y and -Y options with DHCPv4. +TEST_F(TestControlTest, haFailure4) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -l fake -r 1 -n 1 -R 2 -y 10 -Y 0 -L 10547 127.0.0.1"); + NakedTestControl tc(opt); + + tc.sendPackets(1); // Send one packet. It should have secs set to 1. + sleep(1); // wait a second... + tc.sendPackets(1); // and send another packet. This should have secs set to 2. + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, 2); // Make sure the stats are up. + ASSERT_EQ(tc.fake_sock_.sent_pkts4_.size(), 2); // And the packets were captured. + Pkt4Ptr dis1 = tc.fake_sock_.sent_pkts4_[0]; + Pkt4Ptr dis2 = tc.fake_sock_.sent_pkts4_[1]; + ASSERT_TRUE(dis1); + ASSERT_TRUE(dis2); + + EXPECT_EQ(dis1->getSecs(), 1); // Make sure it has secs set to 1. + EXPECT_GT(dis2->getSecs(), 1); // Should be 2, but we want to avoid rare cases when the test + // could fall exactly on the second boundary, so checking for + // greater than 1. +} + +// This test checks if HA failure can be simulated using -y and -Y options with DHCPv6. +TEST_F(TestControlTest, haFailure6) { + CommandOptions opt; + processCmdLine(opt, "perfdhcp -6 -l fake -r 1 -n 1 -R 2 -y 10 -Y 0 -L 10547 all"); + NakedTestControl tc(opt); + + tc.sendPackets(1); // Send one packet. It should have secs set to 1. + sleep(1); // wait a second... + tc.sendPackets(1); // and send another packet. This should have secs set to 2. + + EXPECT_EQ(tc.fake_sock_.sent_cnt_, 2); // Make sure the stats are up. + ASSERT_EQ(tc.fake_sock_.sent_pkts6_.size(), 2); // And the packets were captured. + Pkt6Ptr sol1 = tc.fake_sock_.sent_pkts6_[0]; + Pkt6Ptr sol2 = tc.fake_sock_.sent_pkts6_[1]; + ASSERT_TRUE(sol1); + ASSERT_TRUE(sol2); + OptionUint16Ptr elapsed1(boost::dynamic_pointer_cast<OptionUint16>(sol1->getOption(D6O_ELAPSED_TIME))); + OptionUint16Ptr elapsed2(boost::dynamic_pointer_cast<OptionUint16>(sol2->getOption(D6O_ELAPSED_TIME))); + ASSERT_TRUE(elapsed1); + ASSERT_TRUE(elapsed2); + + EXPECT_EQ(elapsed1->getValue(), 100); + EXPECT_GT(elapsed2->getValue(), 100); +} |