summaryrefslogtreecommitdiffstats
path: root/src/bin/perfdhcp/tests/test_control_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/perfdhcp/tests/test_control_unittest.cc')
-rw-r--r--src/bin/perfdhcp/tests/test_control_unittest.cc1971
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);
+}