diff options
Diffstat (limited to 'src/bin/perfdhcp/test_control.cc')
-rw-r--r-- | src/bin/perfdhcp/test_control.cc | 1942 |
1 files changed, 1942 insertions, 0 deletions
diff --git a/src/bin/perfdhcp/test_control.cc b/src/bin/perfdhcp/test_control.cc new file mode 100644 index 0000000..c15153b --- /dev/null +++ b/src/bin/perfdhcp/test_control.cc @@ -0,0 +1,1942 @@ +// 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 <perfdhcp/test_control.h> +#include <perfdhcp/receiver.h> +#include <perfdhcp/command_options.h> +#include <perfdhcp/perf_pkt4.h> +#include <perfdhcp/perf_pkt6.h> + +#include <exceptions/exceptions.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/dhcp4.h> +#include <dhcp/option6_ia.h> +#include <dhcp/option6_iaaddr.h> +#include <dhcp/option6_iaprefix.h> +#include <dhcp/option_int.h> +#include <util/unittests/check_valgrind.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <algorithm> +#include <fstream> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <unistd.h> +#include <signal.h> +#include <sstream> +#include <sys/wait.h> + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace isc { +namespace perfdhcp { + +bool TestControl::interrupted_ = false; + +bool +TestControl::waitToExit() { + uint32_t const wait_time = options_.getExitWaitTime(); + + // If we care and not all packets are in yet + if (wait_time && !haveAllPacketsBeenReceived()) { + const ptime now = microsec_clock::universal_time(); + + // Init the end time if it hasn't started yet + if (exit_time_.is_not_a_date_time()) { + exit_time_ = now + time_duration(microseconds(wait_time)); + } + + // If we're not at end time yet, return true + return (now < exit_time_); + } + + // No need to wait, return false; + return (false); +} + +bool +TestControl::haveAllPacketsBeenReceived() const { + const uint8_t& ipversion = options_.getIpVersion(); + const std::vector<int>& num_request = options_.getNumRequests(); + const size_t& num_request_size = num_request.size(); + + if (num_request_size == 0) { + return false; + } + + uint32_t responses = 0; + uint32_t requests = num_request[0]; + if (num_request_size >= 2) { + requests += num_request[1]; + } + + if (ipversion == 4) { + responses = stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) + + stats_mgr_.getRcvdPacketsNum(ExchangeType::RA); + } else { + responses = stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) + + stats_mgr_.getRcvdPacketsNum(ExchangeType::RR); + } + + return (responses == requests); +} + +void +TestControl::cleanCachedPackets() { + // When Renews are not sent, Reply packets are not cached so there + // is nothing to do. + if (options_.getRenewRate() == 0) { + return; + } + + static boost::posix_time::ptime last_clean = + microsec_clock::universal_time(); + + // Check how much time has passed since last cleanup. + time_period time_since_clean(last_clean, + microsec_clock::universal_time()); + // Cleanup every 1 second. + if (time_since_clean.length().total_seconds() >= 1) { + // Calculate how many cached packets to remove. Actually we could + // just leave enough packets to handle Renews for 1 second but + // since we want to randomize leases to be renewed so leave 5 + // times more packets to randomize from. + /// @todo The cache size might be controlled from the command line. + if (reply_storage_.size() > 5 * options_.getRenewRate()) { + reply_storage_.clear(reply_storage_.size() - + 5 * options_.getRenewRate()); + } + // Remember when we performed a cleanup for the last time. + // We want to do the next cleanup not earlier than in one second. + last_clean = microsec_clock::universal_time(); + } +} + +void +TestControl::copyIaOptions(const Pkt6Ptr& pkt_from, Pkt6Ptr& pkt_to) { + if (!pkt_from || !pkt_to) { + isc_throw(BadValue, "NULL pointers must not be specified as arguments" + " for the copyIaOptions function"); + } + // IA_NA + if (options_.getLeaseType() + .includes(CommandOptions::LeaseType::ADDRESS)) { + OptionPtr option = pkt_from->getOption(D6O_IA_NA); + if (!option) { + isc_throw(NotFound, "IA_NA option not found in the" + " server's response"); + } + pkt_to->addOption(option); + } + // IA_PD + if (options_.getLeaseType() + .includes(CommandOptions::LeaseType::PREFIX)) { + OptionPtr option = pkt_from->getOption(D6O_IA_PD); + if (!option) { + isc_throw(NotFound, "IA_PD option not found in the" + " server's response"); + } + pkt_to->addOption(option); + } +} + +std::string +TestControl::byte2Hex(const uint8_t b) { + const int b1 = b / 16; + const int b0 = b % 16; + ostringstream stream; + stream << std::hex << b1 << b0 << std::dec; + return (stream.str()); +} + +Pkt4Ptr +TestControl::createMessageFromAck(const uint16_t msg_type, + const dhcp::Pkt4Ptr& ack) { + // Restrict messages to Release and Renew. + if (msg_type != DHCPREQUEST && msg_type != DHCPRELEASE) { + isc_throw(isc::BadValue, "invalid message type " << msg_type + << " to be created from Reply, expected DHCPREQUEST or" + " DHCPRELEASE"); + } + + // Get the string representation of the message - to be used for error + // logging purposes. + auto msg_type_str = [=]() -> const char* { + return (msg_type == DHCPREQUEST ? "Request" : "Release"); + }; + + if (!ack) { + isc_throw(isc::BadValue, "Unable to create " + << msg_type_str() + << " from a null DHCPACK message"); + } else if (ack->getYiaddr().isV4Zero()) { + isc_throw(isc::BadValue, + "Unable to create " + << msg_type_str() + << " from a DHCPACK message containing yiaddr of 0"); + } + Pkt4Ptr msg(new Pkt4(msg_type, generateTransid())); + msg->setCiaddr(ack->getYiaddr()); + msg->setHWAddr(ack->getHWAddr()); + msg->addOption(generateClientId(msg->getHWAddr())); + if (msg_type == DHCPRELEASE) { + // RFC 2132: DHCPRELEASE MUST include server ID. + if (options_.isUseFirst()) { + // Honor the '-1' flag if it exists. + if (first_packet_serverid_.empty()) { + isc_throw(isc::BadValue, + "Unable to create " + << msg_type_str() + << "from the first packet which lacks the server " + "identifier option"); + } + msg->addOption(Option::factory(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + first_packet_serverid_)); + } else { + // Otherwise take it from the DHCPACK message. + OptionPtr server_identifier( + ack->getOption(DHO_DHCP_SERVER_IDENTIFIER)); + if (!server_identifier) { + isc_throw(isc::BadValue, + "Unable to create " + << msg_type_str() + << "from a DHCPACK message without the server " + "identifier option"); + } + msg->addOption(server_identifier); + } + } + return (msg); +} + +Pkt6Ptr +TestControl::createMessageFromReply(const uint16_t msg_type, + const dhcp::Pkt6Ptr& reply) { + // Restrict messages to Release and Renew. + if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) { + isc_throw(isc::BadValue, "invalid message type " << msg_type + << " to be created from Reply, expected DHCPV6_RENEW or" + " DHCPV6_RELEASE"); + } + + // Get the string representation of the message - to be used for error + // logging purposes. + auto msg_type_str = [=]() -> const char* { + return (msg_type == DHCPV6_RENEW ? "Renew" : "Release"); + }; + + // Reply message must be specified. + if (!reply) { + isc_throw(isc::BadValue, "Unable to create " << msg_type_str() + << " message from the Reply message because the instance of" + " the Reply message is NULL"); + } + + Pkt6Ptr msg(new Pkt6(msg_type, generateTransid())); + // Client id. + OptionPtr opt_clientid = reply->getOption(D6O_CLIENTID); + if (!opt_clientid) { + isc_throw(isc::Unexpected, "failed to create " << msg_type_str() + << " message because client id option has not been found" + " in the Reply message"); + } + msg->addOption(opt_clientid); + // Server id. + OptionPtr opt_serverid = reply->getOption(D6O_SERVERID); + if (!opt_serverid) { + isc_throw(isc::Unexpected, "failed to create " << msg_type_str() + << " because server id option has not been found in the" + " Reply message"); + } + msg->addOption(opt_serverid); + copyIaOptions(reply, msg); + return (msg); +} + +OptionPtr +TestControl::factoryElapsedTime6(Option::Universe, uint16_t, + const OptionBuffer& buf) { + if (buf.size() == 2) { + return (OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME, buf))); + } else if (buf.size() == 0) { + return (OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(2, 0)))); + } + isc_throw(isc::BadValue, + "elapsed time option buffer size has to be 0 or 2"); +} + +OptionPtr +TestControl::factoryGeneric(Option::Universe u, uint16_t type, + const OptionBuffer& buf) { + OptionPtr opt(new Option(u, type, buf)); + return (opt); +} + +OptionPtr +TestControl::factoryIana6(Option::Universe, uint16_t, + const OptionBuffer& buf) { + /// @todo allow different values of T1, T2 and IAID. + const uint8_t buf_array[] = { + 0, 0, 0, 1, // IAID = 1 + 0, 0, 3600 >> 8, 3600 & 0xff, // T1 = 3600 + 0, 0, 5400 >> 8, 5400 & 0xff, // T2 = 5400 + }; + OptionBuffer buf_ia_na(buf_array, buf_array + sizeof(buf_array)); + for (size_t i = 0; i < buf.size(); ++i) { + buf_ia_na.push_back(buf[i]); + } + return (OptionPtr(new Option(Option::V6, D6O_IA_NA, buf_ia_na))); +} + +OptionPtr +TestControl::factoryIapd6(Option::Universe, uint16_t, + const OptionBuffer& buf) { + /// @todo allow different values of T1, T2 and IAID. + static const uint8_t buf_array[] = { + 0, 0, 0, 1, // IAID = 1 + 0, 0, 3600 >> 8, 3600 & 0xff, // T1 = 3600 + 0, 0, 5400 >> 8, 5400 & 0xff, // T2 = 5400 + }; + OptionBuffer buf_ia_pd(buf_array, buf_array + sizeof(buf_array)); + // Append sub-options to IA_PD. + buf_ia_pd.insert(buf_ia_pd.end(), buf.begin(), buf.end()); + return (OptionPtr(new Option(Option::V6, D6O_IA_PD, buf_ia_pd))); +} + + +OptionPtr +TestControl::factoryRapidCommit6(Option::Universe, uint16_t, + const OptionBuffer&) { + return (OptionPtr(new Option(Option::V6, D6O_RAPID_COMMIT, OptionBuffer()))); +} + +OptionPtr +TestControl::factoryOptionRequestOption6(Option::Universe, + uint16_t, + const OptionBuffer&) { + const uint8_t buf_array[] = { + 0, D6O_NAME_SERVERS, + 0, D6O_DOMAIN_SEARCH, + }; + OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array)); + return (OptionPtr(new Option(Option::V6, D6O_ORO, buf_with_options))); +} + + +OptionPtr +TestControl::factoryRequestList4(Option::Universe u, + uint16_t type, + const OptionBuffer& buf) { + const uint8_t buf_array[] = { + DHO_SUBNET_MASK, + DHO_BROADCAST_ADDRESS, + DHO_TIME_OFFSET, + DHO_ROUTERS, + DHO_DOMAIN_NAME, + DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME + }; + + OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array)); + OptionPtr opt(new Option(u, type, buf)); + opt->setData(buf_with_options.begin(), buf_with_options.end()); + return (opt); +} + +std::vector<uint8_t> +TestControl::generateMacAddress(uint8_t& randomized) { + const CommandOptions::MacAddrsVector& macs = options_.getMacsFromFile(); + // if we are using the -M option return a random one from the list... + if (macs.size() > 0) { + uint16_t r = number_generator_(); + if (r >= macs.size()) { + r = 0; + } + return macs[r]; + + } else { + // ... otherwise use the standard behavior + uint32_t clients_num = options_.getClientsNum(); + if (clients_num < 2) { + return (options_.getMacTemplate()); + } + // Get the base MAC address. We are going to randomize part of it. + std::vector<uint8_t> mac_addr(options_.getMacTemplate()); + if (mac_addr.size() != HW_ETHER_LEN) { + isc_throw(BadValue, "invalid MAC address template specified"); + } + uint32_t r = macaddr_gen_->generate(); + randomized = 0; + // Randomize MAC address octets. + for (std::vector<uint8_t>::iterator it = mac_addr.end() - 1; + it >= mac_addr.begin(); + --it) { + // Add the random value to the current octet. + (*it) += r; + ++randomized; + if (r < 256) { + // If we are here it means that there is no sense + // to randomize the remaining octets of MAC address + // because the following bytes of random value + // are zero and it will have no effect. + break; + } + // Randomize the next octet with the following + // byte of random value. + r >>= 8; + } + return (mac_addr); + } +} + +OptionPtr +TestControl::generateClientId(const dhcp::HWAddrPtr& hwaddr) const { + std::vector<uint8_t> client_id(1, static_cast<uint8_t>(hwaddr->htype_)); + client_id.insert(client_id.end(), hwaddr->hwaddr_.begin(), + hwaddr->hwaddr_.end()); + return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER, + client_id))); +} + +std::vector<uint8_t> +TestControl::generateDuid(uint8_t& randomized) { + std::vector<uint8_t> mac_addr(generateMacAddress(randomized)); + const CommandOptions::MacAddrsVector& macs = options_.getMacsFromFile(); + // pick a random mac address if we are using option -M.. + if (macs.size() > 0) { + uint16_t r = number_generator_(); + if (r >= macs.size()) { + r = 0; + } + std::vector<uint8_t> mac = macs[r]; + // DUID_LL is in this format + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | 3 | hardware type (16 bits) | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // . . + // . link-layer address (variable length) . + // . . + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + // No C++11 so initializer list support, building a vector<uint8_t> is a + // pain... + uint8_t duid_ll[] = {0, 3, 0, 1, 0, 0, 0, 0, 0, 0}; + // copy duid_ll array into the vector + std::vector<uint8_t> duid(duid_ll, + duid_ll + sizeof(duid_ll) / sizeof(duid_ll[0])); + // put the mac address bytes at the end + std::copy(mac.begin(), mac.end(), duid.begin() + 4); + return (duid); + } else { + uint32_t clients_num = options_.getClientsNum(); + if ((clients_num == 0) || (clients_num == 1)) { + return (options_.getDuidTemplate()); + } + // Get the base DUID. We are going to randomize part of it. + std::vector<uint8_t> duid(options_.getDuidTemplate()); + /// @todo: add support for DUIDs of different sizes. + duid.resize(duid.size()); + std::copy(mac_addr.begin(), mac_addr.end(), + duid.begin() + duid.size() - mac_addr.size()); + return (duid); + } +} + +int +TestControl::getElapsedTimeOffset() const { + int elp_offset = options_.getIpVersion() == 4 ? + DHCPV4_ELAPSED_TIME_OFFSET : DHCPV6_ELAPSED_TIME_OFFSET; + if (options_.getElapsedTimeOffset() > 0) { + elp_offset = options_.getElapsedTimeOffset(); + } + return (elp_offset); +} + +template<class T> +uint32_t +TestControl::getElapsedTime(const T& pkt1, const T& pkt2) { + using namespace boost::posix_time; + ptime pkt1_time = pkt1->getTimestamp(); + ptime pkt2_time = pkt2->getTimestamp(); + if (pkt1_time.is_not_a_date_time() || + pkt2_time.is_not_a_date_time()) { + isc_throw(InvalidOperation, "packet timestamp not set");; + } + time_period elapsed_period(pkt1_time, pkt2_time); + return (elapsed_period.is_null() ? 0 : + elapsed_period.length().total_milliseconds()); +} + +int +TestControl::getRandomOffset(const int arg_idx) const { + int rand_offset = options_.getIpVersion() == 4 ? + DHCPV4_RANDOMIZATION_OFFSET : DHCPV6_RANDOMIZATION_OFFSET; + if (options_.getRandomOffset().size() > arg_idx) { + rand_offset = options_.getRandomOffset()[arg_idx]; + } + return (rand_offset); +} + +int +TestControl::getRequestedIpOffset() const { + int rip_offset = options_.getIpVersion() == 4 ? + DHCPV4_REQUESTED_IP_OFFSET : DHCPV6_IA_NA_OFFSET; + if (options_.getRequestedIpOffset() > 0) { + rip_offset = options_.getRequestedIpOffset(); + } + return (rip_offset); +} + +int +TestControl::getServerIdOffset() const { + int srvid_offset = options_.getIpVersion() == 4 ? + DHCPV4_SERVERID_OFFSET : DHCPV6_SERVERID_OFFSET; + if (options_.getServerIdOffset() > 0) { + srvid_offset = options_.getServerIdOffset(); + } + return (srvid_offset); +} + +TestControl::TemplateBuffer +TestControl::getTemplateBuffer(const size_t idx) const { + if (template_buffers_.size() > idx) { + return (template_buffers_[idx]); + } + isc_throw(OutOfRange, "invalid buffer index"); +} + +int +TestControl::getTransactionIdOffset(const int arg_idx) const { + int xid_offset = options_.getIpVersion() == 4 ? + DHCPV4_TRANSID_OFFSET : DHCPV6_TRANSID_OFFSET; + if (options_.getTransactionIdOffset().size() > arg_idx) { + xid_offset = options_.getTransactionIdOffset()[arg_idx]; + } + return (xid_offset); +} + +void +TestControl::handleChild(int) { + int status = 0; + while (wait3(&status, WNOHANG, NULL) > 0) { + // continue + } +} + +void +TestControl::handleInterrupt(int) { + interrupted_ = true; +} + +void +TestControl::initPacketTemplates() { + template_packets_v4_.clear(); + template_packets_v6_.clear(); + template_buffers_.clear(); + std::vector<std::string> template_files = options_.getTemplateFiles(); + for (std::vector<std::string>::const_iterator it = template_files.begin(); + it != template_files.end(); ++it) { + readPacketTemplate(*it); + } +} + +void +TestControl::sendPackets(const uint64_t packets_num, + const bool preload /* = false */) { + for (uint64_t i = packets_num; i > 0; --i) { + if (options_.getIpVersion() == 4) { + // No template packets means that no -T option was specified. + // We have to build packets ourselves. + if (template_buffers_.empty()) { + sendDiscover4(preload); + } else { + /// @todo add defines for packet type index that can be + /// used to access template_buffers_. + sendDiscover4(template_buffers_[0], preload); + } + } else { + // No template packets means that no -T option was specified. + // We have to build packets ourselves. + if (template_buffers_.empty()) { + sendSolicit6(preload); + } else { + /// @todo add defines for packet type index that can be + /// used to access template_buffers_. + sendSolicit6(template_buffers_[0], preload); + } + } + } +} + +uint64_t +TestControl::sendMultipleMessages4(const uint32_t msg_type, + const uint64_t msg_num) { + for (uint64_t i = 0; i < msg_num; ++i) { + if (!sendMessageFromAck(msg_type)) { + return (i); + } + } + return (msg_num); +} + +uint64_t +TestControl::sendMultipleMessages6(const uint32_t msg_type, + const uint64_t msg_num) { + for (uint64_t i = 0; i < msg_num; ++i) { + if (!sendMessageFromReply(msg_type)) { + return (i); + } + } + return (msg_num); +} + +void +TestControl::printDiagnostics() const { + if (options_.testDiags('a')) { + // Print all command line parameters. + options_.printCommandLine(); + // Print MAC and DUID. + std::cout << "Set MAC to " << vector2Hex(options_.getMacTemplate(), "::") + << std::endl; + if (options_.getDuidTemplate().size() > 0) { + std::cout << "Set DUID to " << vector2Hex(options_.getDuidTemplate()) << std::endl; + } + } +} + +void +TestControl::printTemplate(const uint8_t packet_type) const { + std::string hex_buf; + int arg_idx = 0; + if (options_.getIpVersion() == 4) { + if (packet_type == DHCPREQUEST) { + arg_idx = 1; + } + std::map<uint8_t, dhcp::Pkt4Ptr>::const_iterator pkt_it = + template_packets_v4_.find(packet_type); + if ((pkt_it != template_packets_v4_.end()) && + pkt_it->second) { + const util::OutputBuffer& out_buf(pkt_it->second->getBuffer()); + const char* out_buf_data = + static_cast<const char*>(out_buf.getData()); + std::vector<uint8_t> buf(out_buf_data, out_buf_data + out_buf.getLength()); + hex_buf = vector2Hex(buf); + } + } else if (options_.getIpVersion() == 6) { + if (packet_type == DHCPV6_REQUEST) { + arg_idx = 1; + } + std::map<uint8_t, dhcp::Pkt6Ptr>::const_iterator pkt_it = + template_packets_v6_.find(packet_type); + if (pkt_it != template_packets_v6_.end() && + pkt_it->second) { + const util::OutputBuffer& out_buf(pkt_it->second->getBuffer()); + const char* out_buf_data = + static_cast<const char*>(out_buf.getData()); + std::vector<uint8_t> buf(out_buf_data, out_buf_data + out_buf.getLength()); + hex_buf = vector2Hex(buf); + } + } + std::cout << "xid-offset=" << getTransactionIdOffset(arg_idx) << std::endl; + std::cout << "random-offset=" << getRandomOffset(arg_idx) << std::endl; + if (arg_idx > 0) { + std::cout << "srvid-offset=" << getServerIdOffset() << std::endl; + std::cout << "time-offset=" << getElapsedTimeOffset() << std::endl; + std::cout << "ip-offset=" << getRequestedIpOffset() << std::endl; + } + + std::cout << "contents: " << std::endl; + int line_len = 32; + int i = 0; + while (line_len == 32) { + if (hex_buf.length() - i < 32) { + line_len = hex_buf.length() - i; + }; + if (line_len > 0) { + std::cout << setfill('0') << setw(4) << std::hex << i << std::dec + << " " << hex_buf.substr(i, line_len) << std::endl; + } + i += 32; + } + std::cout << std::endl; +} + +void +TestControl::printTemplates() const { + if (options_.getIpVersion() == 4) { + printTemplate(DHCPDISCOVER); + printTemplate(DHCPREQUEST); + } else if (options_.getIpVersion() == 6) { + printTemplate(DHCPV6_SOLICIT); + printTemplate(DHCPV6_REQUEST); + } +} + +void +TestControl::printRate() const { + double rate = 0; + std::string exchange_name = "4-way exchanges"; + ExchangeType xchg_type = ExchangeType::DO; + if (options_.getIpVersion() == 4) { + xchg_type = + options_.getExchangeMode() == CommandOptions::DO_SA ? + ExchangeType::DO : ExchangeType::RA; + if (xchg_type == ExchangeType::DO) { + exchange_name = "DISCOVER-OFFER"; + } + } else if (options_.getIpVersion() == 6) { + xchg_type = + options_.getExchangeMode() == CommandOptions::DO_SA ? + ExchangeType::SA : ExchangeType::RR; + if (xchg_type == ExchangeType::SA) { + exchange_name = options_.isRapidCommit() ? "Solicit-Reply" : + "Solicit-Advertise"; + } + } + double duration = + stats_mgr_.getTestPeriod().length().total_nanoseconds() / 1e9; + rate = stats_mgr_.getRcvdPacketsNum(xchg_type) / duration; + std::ostringstream s; + s << "***Rate statistics***" << std::endl; + s << "Rate: " << rate << " " << exchange_name << "/second"; + if (options_.getRate() > 0) { + s << ", expected rate: " << options_.getRate() << std::endl; + } + + std::cout << s.str() << std::endl; + + std::cout <<"***Malformed Packets***" << std::endl + << "Malformed packets: " << ExchangeStats::malformed_pkts_ + << std::endl; +} + +void +TestControl::printIntermediateStats() { + int delay = options_.getReportDelay(); + ptime now = microsec_clock::universal_time(); + time_period time_since_report(last_report_, now); + if (time_since_report.length().total_seconds() >= delay) { + stats_mgr_.printIntermediateStats(options_.getCleanReport(), + options_.getCleanReportSeparator()); + last_report_ = now; + } +} + +void +TestControl::printStats() const { + printRate(); + stats_mgr_.printStats(); + if (options_.testDiags('i')) { + stats_mgr_.printCustomCounters(); + } +} + +std::string +TestControl::vector2Hex(const std::vector<uint8_t>& vec, + const std::string& separator /* = "" */) { + std::ostringstream stream; + for (std::vector<uint8_t>::const_iterator it = vec.begin(); + it != vec.end(); + ++it) { + if (it == vec.begin()) { + stream << byte2Hex(*it); + } else { + stream << separator << byte2Hex(*it); + } + } + return (stream.str()); +} + +void +TestControl::readPacketTemplate(const std::string& file_name) { + std::ifstream temp_file; + temp_file.open(file_name.c_str(), ios::in | ios::binary | ios::ate); + if (!temp_file.is_open()) { + isc_throw(BadValue, "unable to open template file " << file_name); + } + // Read template file contents. + std::streampos temp_size = temp_file.tellg(); + if (temp_size == std::streampos(0)) { + temp_file.close(); + isc_throw(OutOfRange, "the template file " << file_name << " is empty"); + } + temp_file.seekg(0, ios::beg); + std::vector<char> file_contents(temp_size); + temp_file.read(&file_contents[0], temp_size); + temp_file.close(); + // Spaces are allowed so we have to strip the contents + // from them. In the same time we want to make sure that + // apart from spaces the file contains hexadecimal digits + // only. + std::vector<char> hex_digits; + for (size_t i = 0; i < file_contents.size(); ++i) { + if (isxdigit(file_contents[i])) { + hex_digits.push_back(file_contents[i]); + } else if (!isxdigit(file_contents[i]) && + !isspace(file_contents[i])) { + isc_throw(BadValue, "'" << file_contents[i] << "' is not a" + " hexadecimal digit"); + } + } + // Expect even number of digits. + if (hex_digits.size() % 2 != 0) { + isc_throw(OutOfRange, "odd number of digits in template file"); + } else if (hex_digits.empty()) { + isc_throw(OutOfRange, "template file " << file_name << " is empty"); + } + std::vector<uint8_t> binary_stream; + for (size_t i = 0; i < hex_digits.size(); i += 2) { + stringstream s; + s << "0x" << hex_digits[i] << hex_digits[i+1]; + int b; + s >> std::hex >> b; + binary_stream.push_back(static_cast<uint8_t>(b)); + } + template_buffers_.push_back(binary_stream); +} + +void +TestControl::processReceivedPacket4(const Pkt4Ptr& pkt4) { + if (pkt4->getType() == DHCPOFFER) { + PktPtr pkt = stats_mgr_.passRcvdPacket(ExchangeType::DO, pkt4); + address4Uniqueness(pkt4, ExchangeType::DO); + Pkt4Ptr discover_pkt4(boost::dynamic_pointer_cast<Pkt4>(pkt)); + CommandOptions::ExchangeMode xchg_mode = options_.getExchangeMode(); + if ((xchg_mode == CommandOptions::DORA_SARR) && discover_pkt4) { + if (template_buffers_.size() < 2) { + sendRequest4(discover_pkt4, pkt4); + } else { + /// @todo add defines for packet type index that can be + /// used to access template_buffers_. + sendRequest4(template_buffers_[1], discover_pkt4, pkt4); + } + } + } else if (pkt4->getType() == DHCPACK) { + // If received message is DHCPACK, we have to check if this is + // a response to 4-way exchange. We'll match this packet with + // a DHCPREQUEST sent as part of the 4-way exchanges. + if (stats_mgr_.passRcvdPacket(ExchangeType::RA, pkt4)) { + address4Uniqueness(pkt4, ExchangeType::RA); + // The DHCPACK belongs to DHCPREQUEST-DHCPACK exchange type. + // So, we may need to keep this DHCPACK in the storage if renews. + // Note that, DHCPACK messages hold the information about + // leases assigned. We use this information to renew. + if (stats_mgr_.hasExchangeStats(ExchangeType::RNA) || + stats_mgr_.hasExchangeStats(ExchangeType::RLA)) { + // Renew or release messages are sent, because StatsMgr has the + // specific exchange type specified. Let's append the DHCPACK + // message to a storage. + ack_storage_.append(pkt4); + } + // The DHCPACK message is not a server's response to the DHCPREQUEST + // message sent within the 4-way exchange. It may be a response to a + // renewal. In this case we first check if StatsMgr has exchange type + // for renew specified, and if it has, if there is a corresponding + // renew message for the received DHCPACK. + } else if (stats_mgr_.hasExchangeStats(ExchangeType::RNA)) { + stats_mgr_.passRcvdPacket(ExchangeType::RNA, pkt4); + } + } +} + +void +TestControl::address6Uniqueness(const Pkt6Ptr& pkt6, ExchangeType xchg_type) { + // check if received address is unique + if (options_.getAddrUnique()) { + std::set<std::string> current; + // addresses were already checked in validateIA + // we can safely assume that those are correct + for (const auto& opt : pkt6->options_) { + switch (opt.second->getType()) { + case D6O_IA_PD: { + // add address and check if it has not been already assigned + // addresses should be unique cross options of the packet + auto ret = current.emplace(boost::dynamic_pointer_cast< + Option6IAPrefix>(opt.second->getOption(D6O_IAPREFIX))->getAddress().toText()); + if (!ret.second) { + stats_mgr_.updateNonUniqueAddrNum(xchg_type); + } + break; + } + case D6O_IA_NA: { + // add address and check if it has not been already assigned + // addresses should be unique cross options of the packet + auto ret = current.emplace(boost::dynamic_pointer_cast< + Option6IAAddr>(opt.second->getOption(D6O_IAADDR))->getAddress().toText()); + if (!ret.second) { + stats_mgr_.updateNonUniqueAddrNum(xchg_type); + } + break; + } + default: + break; + } + } + // addresses should be unique cross packets + addUniqeAddr(current, xchg_type); + } +} + +void +TestControl::address4Uniqueness(const Pkt4Ptr& pkt4, ExchangeType xchg_type) { + // check if received address is unique + if (options_.getAddrUnique()) { + // addresses were already checked in validateIA + // we can safely assume that those are correct + std::set<std::string> current; + current.insert(pkt4->getYiaddr().toText()); + // addresses should be unique cross packets + addUniqeAddr(current, xchg_type); + } +} + +bool +TestControl::validateIA(const Pkt6Ptr& pkt6) { + // check if iaaddr exists - if it does, we can continue sending request + // if not we will update statistics about rejected leases + // @todo it's checking just one iaaddress option for now it's ok + // but when perfdhcp will be extended to create message with multiple IA + // this will have to be iterate on: + // OptionCollection ias = pkt6->getOptions(D6O_IA_NA); + Option6IAPrefixPtr iapref; + Option6IAAddrPtr iaaddr; + if (pkt6->getOption(D6O_IA_PD)) { + iapref = boost::dynamic_pointer_cast< + Option6IAPrefix>(pkt6->getOption(D6O_IA_PD)->getOption(D6O_IAPREFIX)); + } + if (pkt6->getOption(D6O_IA_NA)) { + iaaddr = boost::dynamic_pointer_cast< + Option6IAAddr>(pkt6->getOption(D6O_IA_NA)->getOption(D6O_IAADDR)); + } + + bool address_and_prefix = options_.getLeaseType().includes( + CommandOptions::LeaseType::ADDRESS_AND_PREFIX); + bool prefix_only = options_.getLeaseType().includes( + CommandOptions::LeaseType::PREFIX); + bool address_only = options_.getLeaseType().includes( + CommandOptions::LeaseType::ADDRESS); + if ((address_and_prefix && iapref && iaaddr) || + (prefix_only && iapref && !address_and_prefix) || + (address_only && iaaddr && !address_and_prefix)) { + return true; + } else { + return false; + } +} + +void +TestControl::processReceivedPacket6(const Pkt6Ptr& pkt6) { + uint8_t packet_type = pkt6->getType(); + if (packet_type == DHCPV6_ADVERTISE) { + PktPtr pkt = stats_mgr_.passRcvdPacket(ExchangeType::SA, pkt6); + Pkt6Ptr solicit_pkt6(boost::dynamic_pointer_cast<Pkt6>(pkt)); + CommandOptions::ExchangeMode xchg_mode = options_.getExchangeMode(); + if ((xchg_mode == CommandOptions::DORA_SARR) && solicit_pkt6) { + if (validateIA(pkt6)) { + // if address is correct - check uniqueness + address6Uniqueness(pkt6, ExchangeType::SA); + if (template_buffers_.size() < 2) { + sendRequest6(pkt6); + } else { + /// @todo add defines for packet type index that can be + /// used to access template_buffers_. + sendRequest6(template_buffers_[1], pkt6); + } + } else { + stats_mgr_.updateRejLeases(ExchangeType::SA); + } + } + } else if (packet_type == DHCPV6_REPLY) { + // If the received message is Reply, we have to find out which exchange + // type the Reply message belongs to. It is doable by matching the Reply + // transaction id with the transaction id of the sent Request, Renew + // or Release. First we start with the Request. + if (stats_mgr_.passRcvdPacket(ExchangeType::RR, pkt6)) { + // The Reply belongs to Request-Reply exchange type. So, we may need + // to keep this Reply in the storage if Renews or/and Releases are + // being sent. Note that, Reply messages hold the information about + // leases assigned. We use this information to construct Renew and + // Release messages. + if (validateIA(pkt6)) { + // if address is correct - check uniqueness + address6Uniqueness(pkt6, ExchangeType::RR); + // check if there is correct IA to continue with Renew/Release + if (stats_mgr_.hasExchangeStats(ExchangeType::RN) || + stats_mgr_.hasExchangeStats(ExchangeType::RL)) { + // Renew or Release messages are sent, because StatsMgr has the + // specific exchange type specified. Let's append the Reply + // message to a storage. + reply_storage_.append(pkt6); + } + } else { + stats_mgr_.updateRejLeases(ExchangeType::RR); + } + // The Reply message is not a server's response to the Request message + // sent within the 4-way exchange. It may be a response to the Renew + // or Release message. In the if clause we first check if StatsMgr + // has exchange type for Renew specified, and if it has, if there is + // a corresponding Renew message for the received Reply. If not, + // we check that StatsMgr has exchange type for Release specified, + // as possibly the Reply has been sent in response to Release. + } else if (!(stats_mgr_.hasExchangeStats(ExchangeType::RN) && + stats_mgr_.passRcvdPacket(ExchangeType::RN, pkt6)) && + stats_mgr_.hasExchangeStats(ExchangeType::RL)) { + // At this point, it is only possible that the Reply has been sent + // in response to a Release. Try to match the Reply with Release. + stats_mgr_.passRcvdPacket(ExchangeType::RL, pkt6); + } + } +} + +unsigned int +TestControl::consumeReceivedPackets() { + unsigned int pkt_count = 0; + PktPtr pkt; + while ((pkt = receiver_.getPkt())) { + pkt_count += 1; + if (options_.getIpVersion() == 4) { + Pkt4Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4>(pkt); + processReceivedPacket4(pkt4); + } else { + Pkt6Ptr pkt6 = boost::dynamic_pointer_cast<Pkt6>(pkt); + processReceivedPacket6(pkt6); + } + } + return pkt_count; +} +void +TestControl::registerOptionFactories4() const { + static bool factories_registered = false; + if (!factories_registered) { + // DHCP_MESSAGE_TYPE option factory. + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_MESSAGE_TYPE, + &TestControl::factoryGeneric); + // DHCP_SERVER_IDENTIFIER option factory. + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + &TestControl::factoryGeneric); + // DHCP_PARAMETER_REQUEST_LIST option factory. + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST, + &TestControl::factoryRequestList4); + } + factories_registered = true; +} + +void +TestControl::registerOptionFactories6() const { + static bool factories_registered = false; + if (!factories_registered) { + // D6O_ELAPSED_TIME + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_ELAPSED_TIME, + &TestControl::factoryElapsedTime6); + // D6O_RAPID_COMMIT + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_RAPID_COMMIT, + &TestControl::factoryRapidCommit6); + // D6O_ORO (option request option) factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_ORO, + &TestControl::factoryOptionRequestOption6); + // D6O_CLIENTID option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_CLIENTID, + &TestControl::factoryGeneric); + // D6O_SERVERID option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_SERVERID, + &TestControl::factoryGeneric); + // D6O_IA_NA option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_IA_NA, + &TestControl::factoryIana6); + + // D6O_IA_PD option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_IA_PD, + &TestControl::factoryIapd6); + + + } + factories_registered = true; +} + +void +TestControl::registerOptionFactories() const { + switch(options_.getIpVersion()) { + case 4: + registerOptionFactories4(); + break; + case 6: + registerOptionFactories6(); + break; + default: + isc_throw(InvalidOperation, "command line options have to be parsed " + "before DHCP option factories can be registered"); + } +} + +void +TestControl::reset() { + transid_gen_.reset(); + last_report_ = microsec_clock::universal_time(); + // Actual generators will have to be set later on because we need to + // get command line parameters first. + setTransidGenerator(NumberGeneratorPtr()); + setMacAddrGenerator(NumberGeneratorPtr()); + first_packet_serverid_.clear(); + interrupted_ = false; +} + +TestControl::TestControl(CommandOptions& options, BasePerfSocket &socket) : + exit_time_(not_a_date_time), + number_generator_(0, options.getMacsFromFile().size()), + socket_(socket), + receiver_(socket, options.isSingleThreaded(), options.getIpVersion()), + stats_mgr_(options), + options_(options) +{ + // Reset singleton state before test starts. + reset(); + + // Ip version is not set ONLY in case the command options + // were not parsed. This surely means that parse() function + // was not called prior to starting the test. This is fatal + // error. + if (options_.getIpVersion() == 0) { + isc_throw(InvalidOperation, + "command options must be parsed before running a test"); + } else if (options_.getIpVersion() == 4) { + // Turn off packet queueing. + IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, data::ElementPtr()); + setTransidGenerator(NumberGeneratorPtr(new SequentialGenerator())); + } else { + // Turn off packet queueing. + IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ElementPtr()); + setTransidGenerator(NumberGeneratorPtr(new SequentialGenerator(0x00FFFFFF))); + } + + uint32_t clients_num = options_.getClientsNum() == 0 ? + 1 : options_.getClientsNum(); + setMacAddrGenerator(NumberGeneratorPtr(new SequentialGenerator(clients_num))); + + // Diagnostics are command line options mainly. + printDiagnostics(); + // Option factories have to be registered. + registerOptionFactories(); + // Initialize packet templates. + initPacketTemplates(); + // Initialize randomization seed. + if (options_.isSeeded()) { + srandom(options_.getSeed()); + } else { + // Seed with current time. + time_period duration(from_iso_string("20111231T235959"), + microsec_clock::universal_time()); + srandom(duration.length().total_seconds() + + duration.length().fractional_seconds()); + } + // If user interrupts the program we will exit gracefully. + signal(SIGINT, TestControl::handleInterrupt); +} + +void +TestControl::runWrapped(bool do_stop /*= false */) const { + if (!options_.getWrapped().empty()) { + pid_t pid = 0; + signal(SIGCHLD, handleChild); + pid = fork(); + if (pid < 0) { + isc_throw(Unexpected, "unable to fork"); + } else if (pid == 0) { + execlp(options_.getWrapped().c_str(), do_stop ? "stop" : "start", (void*)0); + } + } +} + +void +TestControl::saveFirstPacket(const Pkt4Ptr& pkt) { + if (options_.testDiags('T')) { + if (template_packets_v4_.find(pkt->getType()) == template_packets_v4_.end()) { + template_packets_v4_[pkt->getType()] = pkt; + } + } +} + +void +TestControl::saveFirstPacket(const Pkt6Ptr& pkt) { + if (options_.testDiags('T')) { + if (template_packets_v6_.find(pkt->getType()) == template_packets_v6_.end()) { + template_packets_v6_[pkt->getType()] = pkt; + } + } +} + +void +TestControl::sendDiscover4(const bool preload /*= false*/) { + // Generate the MAC address to be passed in the packet. + uint8_t randomized = 0; + std::vector<uint8_t> mac_address = generateMacAddress(randomized); + // Generate transaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, transid)); + if (!pkt4) { + isc_throw(Unexpected, "failed to create DISCOVER packet"); + } + + // Delete the default Message Type option set by Pkt4 + pkt4->delOption(DHO_DHCP_MESSAGE_TYPE); + + // Set options: DHCP_MESSAGE_TYPE and DHCP_PARAMETER_REQUEST_LIST + OptionBuffer buf_msg_type; + buf_msg_type.push_back(DHCPDISCOVER); + pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE, + buf_msg_type)); + pkt4->addOption(Option::factory(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + + + // Set client's and server's ports as well as server's address, + // and local (relay) address. + setDefaults4(pkt4); + + // Set hardware address + pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address); + + // Set client identifier + pkt4->addOption(generateClientId(pkt4->getHWAddr())); + + // Check if we need to simulate HA failures by pretending no responses were received. + // The DHCP protocol signals that by increasing secs field (seconds since the configuration attempt started). + if (options_.getIncreaseElapsedTime() && + stats_mgr_.getTestPeriod().length().total_seconds() >= options_.getWaitForElapsedTime() && + stats_mgr_.getTestPeriod().length().total_seconds() < options_.getWaitForElapsedTime() + + options_.getIncreaseElapsedTime()) { + + // Keep increasing elapsed time. The value should start increasing steadily. + uint32_t val = stats_mgr_.getTestPeriod().length().total_seconds() - options_.getWaitForElapsedTime() + 1; + if (val > 65535) { + val = 65535; + } + pkt4->setSecs(static_cast<uint16_t>(val)); + } + + // Add any extra options that user may have specified. + addExtraOpts(pkt4); + + pkt4->pack(); + socket_.send(pkt4); + if (!preload) { + stats_mgr_.passSentPacket(ExchangeType::DO, pkt4); + } + + saveFirstPacket(pkt4); +} + +void +TestControl::sendDiscover4(const std::vector<uint8_t>& template_buf, + const bool preload /* = false */) { + // Get the first argument if multiple the same arguments specified + // in the command line. First one refers to DISCOVER packets. + const uint8_t arg_idx = 0; + // Generate the MAC address to be passed in the packet. + uint8_t randomized = 0; + std::vector<uint8_t> mac_address = generateMacAddress(randomized); + // Generate transaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + // Get transaction id offset. + size_t transid_offset = getTransactionIdOffset(arg_idx); + // Get randomization offset. + // We need to go back by HW_ETHER_LEN (MAC address length) + // because this offset points to last octet of MAC address. + size_t rand_offset = getRandomOffset(arg_idx) - HW_ETHER_LEN + 1; + // Create temporary buffer with template contents. We will + // modify this temporary buffer but we don't want to modify + // the original template. + std::vector<uint8_t> in_buf(template_buf.begin(), + template_buf.end()); + // Check if we are not going out of bounds. + if (rand_offset + HW_ETHER_LEN > in_buf.size()) { + isc_throw(OutOfRange, "randomization offset is out of bounds"); + } + PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(), + transid_offset, + transid)); + + // Replace MAC address in the template with actual MAC address. + pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end()); + // Create a packet from the temporary buffer. + setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4)); + // Pack the input packet buffer to output buffer so as it can + // be sent to server. + pkt4->rawPack(); + socket_.send(boost::static_pointer_cast<Pkt4>(pkt4)); + if (!preload) { + // Update packet stats. + stats_mgr_.passSentPacket(ExchangeType::DO, + boost::static_pointer_cast<Pkt4>(pkt4)); + } + saveFirstPacket(pkt4); +} + +bool +TestControl::sendMessageFromAck(const uint16_t msg_type) { + // We only permit Request or Release messages to be sent using this + // function. + if (msg_type != DHCPREQUEST && msg_type != DHCPRELEASE) { + isc_throw(isc::BadValue, + "invalid message type " + << msg_type + << " to be sent, expected DHCPREQUEST or DHCPRELEASE"); + } + + // Get one of the recorded DHCPACK messages. + Pkt4Ptr ack = ack_storage_.getRandom(); + if (!ack) { + return (false); + } + + // Create message of the specified type. + Pkt4Ptr msg = createMessageFromAck(msg_type, ack); + setDefaults4(msg); + + // Override relay address + msg->setGiaddr(ack->getGiaddr()); + + // Add any extra options that user may have specified. + addExtraOpts(msg); + + // Pack it. + msg->pack(); + + // And send it. + socket_.send(msg); + address4Uniqueness(msg, ExchangeType::RLA); + stats_mgr_.passSentPacket((msg_type == DHCPREQUEST ? ExchangeType::RNA : + ExchangeType::RLA), + msg); + return (true); +} + + +bool +TestControl::sendMessageFromReply(const uint16_t msg_type) { + // We only permit Release or Renew messages to be sent using this function. + if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) { + isc_throw(isc::BadValue, "invalid message type " << msg_type + << " to be sent, expected DHCPV6_RENEW or DHCPV6_RELEASE"); + } + + // Get one of the recorded DHCPV6_OFFER messages. + Pkt6Ptr reply = reply_storage_.getRandom(); + if (!reply) { + return (false); + } + // Prepare the message of the specified type. + Pkt6Ptr msg = createMessageFromReply(msg_type, reply); + setDefaults6(msg); + + // Add any extra options that user may have specified. + addExtraOpts(msg); + + // Pack it. + msg->pack(); + + // And send it. + socket_.send(msg); + address6Uniqueness(msg, ExchangeType::RL); + stats_mgr_.passSentPacket((msg_type == DHCPV6_RENEW ? ExchangeType::RN + : ExchangeType::RL), msg); + return (true); +} + +void +TestControl::sendRequest4(const dhcp::Pkt4Ptr& discover_pkt4, + const dhcp::Pkt4Ptr& offer_pkt4) { + // Use the same transaction id as the one used in the discovery packet. + const uint32_t transid = discover_pkt4->getTransid(); + Pkt4Ptr pkt4(new Pkt4(DHCPREQUEST, transid)); + + // Use first flags indicates that we want to use the server + // id captured in first packet. + if (options_.isUseFirst() && + (first_packet_serverid_.size() > 0)) { + pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_SERVER_IDENTIFIER, + first_packet_serverid_)); + } else { + OptionPtr opt_serverid = + offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER); + if (!opt_serverid) { + isc_throw(BadValue, "there is no SERVER_IDENTIFIER option " + << "in OFFER message"); + } + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) == 1) { + first_packet_serverid_ = opt_serverid->getData(); + } + pkt4->addOption(opt_serverid); + } + + /// Set client address. + asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr(); + if (!yiaddr.isV4()) { + isc_throw(BadValue, "the YIADDR returned in OFFER packet is not " + " IPv4 address"); + } + OptionPtr opt_requested_address = + OptionPtr(new Option(Option::V4, DHO_DHCP_REQUESTED_ADDRESS, + OptionBuffer())); + opt_requested_address->setUint32(yiaddr.toUint32()); + pkt4->addOption(opt_requested_address); + OptionPtr opt_parameter_list = + Option::factory(Option::V4, DHO_DHCP_PARAMETER_REQUEST_LIST); + pkt4->addOption(opt_parameter_list); + // Set client's and server's ports as well as server's address + setDefaults4(pkt4); + // Override relay address + pkt4->setGiaddr(offer_pkt4->getGiaddr()); + // Add any extra options that user may have specified. + addExtraOpts(pkt4); + + // Set hardware address + pkt4->setHWAddr(offer_pkt4->getHWAddr()); + // Set client id. + pkt4->addOption(generateClientId(pkt4->getHWAddr())); + // Set elapsed time. + uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4); + pkt4->setSecs(static_cast<uint16_t>(elapsed_time / 1000)); + // Prepare on wire data to send. + pkt4->pack(); + socket_.send(pkt4); + stats_mgr_.passSentPacket(ExchangeType::RA, pkt4); + saveFirstPacket(pkt4); +} + +void +TestControl::sendRequest4(const std::vector<uint8_t>& template_buf, + const dhcp::Pkt4Ptr& discover_pkt4, + const dhcp::Pkt4Ptr& offer_pkt4) { + // Get the second argument if multiple the same arguments specified + // in the command line. Second one refers to REQUEST packets. + const uint8_t arg_idx = 1; + // Use the same transaction id as the one used in the discovery packet. + const uint32_t transid = discover_pkt4->getTransid(); + // Get transaction id offset. + size_t transid_offset = getTransactionIdOffset(arg_idx); + // Get the offset of MAC's last octet. + // We need to go back by HW_ETHER_LEN (MAC address length) + // because this offset points to last octet of MAC address. + size_t rand_offset = getRandomOffset(arg_idx) - HW_ETHER_LEN + 1; + // Create temporary buffer from the template. + std::vector<uint8_t> in_buf(template_buf.begin(), + template_buf.end()); + // Check if given randomization offset is not out of bounds. + if (rand_offset + HW_ETHER_LEN > in_buf.size()) { + isc_throw(OutOfRange, "randomization offset is out of bounds"); + } + + // Create packet from the temporary buffer. + PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(), + transid_offset, + transid)); + + // Set hardware address from OFFER packet received. + HWAddrPtr hwaddr = offer_pkt4->getHWAddr(); + std::vector<uint8_t> mac_address(HW_ETHER_LEN, 0); + uint8_t hw_len = hwaddr->hwaddr_.size(); + if (hw_len != 0) { + memcpy(&mac_address[0], &hwaddr->hwaddr_[0], + hw_len > HW_ETHER_LEN ? HW_ETHER_LEN : hw_len); + } + pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end()); + + // Set elapsed time. + size_t elp_offset = getElapsedTimeOffset(); + uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4); + pkt4->writeValueAt<uint16_t>(elp_offset, + static_cast<uint16_t>(elapsed_time / 1000)); + + // Get the actual server id offset. + size_t sid_offset = getServerIdOffset(); + // Use first flags indicates that we want to use the server + // id captured in first packet. + if (options_.isUseFirst() && + (first_packet_serverid_.size() > 0)) { + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + first_packet_serverid_, + sid_offset)); + pkt4->addOption(opt_serverid); + } else { + // Copy the contents of server identifier received in + // OFFER packet to put this into REQUEST. + OptionPtr opt_serverid_offer = + offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER); + if (!opt_serverid_offer) { + isc_throw(BadValue, "there is no SERVER_IDENTIFIER option " + << "in OFFER message"); + } + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + opt_serverid_offer->getData(), + sid_offset)); + pkt4->addOption(opt_serverid); + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) == 1) { + first_packet_serverid_ = opt_serverid_offer->getData(); + } + } + + /// Set client address. + asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr(); + if (!yiaddr.isV4()) { + isc_throw(BadValue, "the YIADDR returned in OFFER packet is not " + " IPv4 address"); + } + + // Get the actual offset of requested ip. + size_t rip_offset = getRequestedIpOffset(); + // Place requested IP option at specified position (rip_offset). + boost::shared_ptr<LocalizedOption> + opt_requested_ip(new LocalizedOption(Option::V4, + DHO_DHCP_REQUESTED_ADDRESS, + OptionBuffer(), + rip_offset)); + // The IOAddress is convertible to uint32_t and returns exactly what we need. + opt_requested_ip->setUint32(yiaddr.toUint32()); + pkt4->addOption(opt_requested_ip); + + setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4)); + + // Add any extra options that user may have specified. + addExtraOpts(pkt4); + + // Prepare on-wire data. + pkt4->rawPack(); + socket_.send(boost::static_pointer_cast<Pkt4>(pkt4)); + // Update packet stats. + stats_mgr_.passSentPacket(ExchangeType::RA, + boost::static_pointer_cast<Pkt4>(pkt4)); + saveFirstPacket(pkt4); +} + +void +TestControl::sendRequest6(const Pkt6Ptr& advertise_pkt6) { + const uint32_t transid = generateTransid(); + Pkt6Ptr pkt6(new Pkt6(DHCPV6_REQUEST, transid)); + // Set elapsed time. + OptionPtr opt_elapsed_time = + Option::factory(Option::V6, D6O_ELAPSED_TIME); + pkt6->addOption(opt_elapsed_time); + // Set client id. + OptionPtr opt_clientid = advertise_pkt6->getOption(D6O_CLIENTID); + if (!opt_clientid) { + isc_throw(Unexpected, "client id not found in received packet"); + } + pkt6->addOption(opt_clientid); + + // Use first flags indicates that we want to use the server + // id captured in first packet. + if (options_.isUseFirst() && + (first_packet_serverid_.size() > 0)) { + pkt6->addOption(Option::factory(Option::V6, D6O_SERVERID, + first_packet_serverid_)); + } else { + OptionPtr opt_serverid = advertise_pkt6->getOption(D6O_SERVERID); + if (!opt_serverid) { + isc_throw(Unexpected, "server id not found in received packet"); + } + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) == 1) { + first_packet_serverid_ = opt_serverid->getData(); + } + pkt6->addOption(opt_serverid); + } + + // Copy IA_NA or IA_PD option from the Advertise message to the Request + // message being sent to the server. This will throw exception if the + // option to be copied is not found. Note that this function will copy + // one of IA_NA or IA_PD options, depending on the lease-type value + // specified in the command line. + copyIaOptions(advertise_pkt6, pkt6); + + // Set default packet data. + setDefaults6(pkt6); + + // Add any extra options that user may have specified. + addExtraOpts(pkt6); + + // Prepare on-wire data. + pkt6->pack(); + socket_.send(pkt6); + stats_mgr_.passSentPacket(ExchangeType::RR, pkt6); + saveFirstPacket(pkt6); +} + +void +TestControl::sendRequest6(const std::vector<uint8_t>& template_buf, + const Pkt6Ptr& advertise_pkt6) { + // Get the second argument if multiple the same arguments specified + // in the command line. Second one refers to REQUEST packets. + const uint8_t arg_idx = 1; + // Generate transaction id. + const uint32_t transid = generateTransid(); + // Get transaction id offset. + size_t transid_offset = getTransactionIdOffset(arg_idx); + PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(), + transid_offset, transid)); + // Set elapsed time. + size_t elp_offset = getElapsedTimeOffset(); + boost::shared_ptr<LocalizedOption> + opt_elapsed_time(new LocalizedOption(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(), elp_offset)); + pkt6->addOption(opt_elapsed_time); + + // Get the actual server id offset. + size_t sid_offset = getServerIdOffset(); + // Use first flags indicates that we want to use the server + // id captured in first packet. + if (options_.isUseFirst() && + (first_packet_serverid_.size() > 0)) { + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V6, + D6O_SERVERID, + first_packet_serverid_, + sid_offset)); + pkt6->addOption(opt_serverid); + + } else { + // Copy the contents of server identifier received in + // ADVERTISE packet to put this into REQUEST. + OptionPtr opt_serverid_advertise = + advertise_pkt6->getOption(D6O_SERVERID); + if (!opt_serverid_advertise) { + isc_throw(BadValue, "there is no SERVERID option " + << "in ADVERTISE message"); + } + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V6, + D6O_SERVERID, + opt_serverid_advertise->getData(), + sid_offset)); + pkt6->addOption(opt_serverid); + if (stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) == 1) { + first_packet_serverid_ = opt_serverid_advertise->getData(); + } + } + // Set IA_NA + OptionPtr opt_ia_na_advertise = advertise_pkt6->getOption(D6O_IA_NA); + if (!opt_ia_na_advertise) { + isc_throw(Unexpected, "DHCPv6 IA_NA option not found in received " + "packet"); + } + size_t addr_offset = getRequestedIpOffset(); + boost::shared_ptr<LocalizedOption> + opt_ia_na(new LocalizedOption(Option::V6, D6O_IA_NA, opt_ia_na_advertise->getData(), addr_offset)); + if (!opt_ia_na->valid()) { + isc_throw(BadValue, "Option IA_NA in advertise packet is invalid"); + } + pkt6->addOption(opt_ia_na); + // Set server id. + OptionPtr opt_serverid_advertise = advertise_pkt6->getOption(D6O_SERVERID); + if (!opt_serverid_advertise) { + isc_throw(Unexpected, "DHCPV6 SERVERID option not found in received " + "packet"); + } + size_t srvid_offset = getServerIdOffset(); + boost::shared_ptr<LocalizedOption> + opt_serverid(new LocalizedOption(Option::V6, D6O_SERVERID, + opt_serverid_advertise->getData(), + srvid_offset)); + pkt6->addOption(opt_serverid); + // Get randomization offset. + size_t rand_offset = getRandomOffset(arg_idx); + OptionPtr opt_clientid_advertise = advertise_pkt6->getOption(D6O_CLIENTID); + if (!opt_clientid_advertise) { + isc_throw(Unexpected, "DHCPV6 CLIENTID option not found in received packet"); + } + rand_offset -= (opt_clientid_advertise->len() - 1); + // Set client id. + boost::shared_ptr<LocalizedOption> + opt_clientid(new LocalizedOption(Option::V6, D6O_CLIENTID, + opt_clientid_advertise->getData(), + rand_offset)); + pkt6->addOption(opt_clientid); + // Set default packet data. + setDefaults6(pkt6); + + // Add any extra options that user may have specified. + addExtraOpts(pkt6); + + // Prepare on wire data. + pkt6->rawPack(); + // Send packet. + socket_.send(pkt6); + // Update packet stats. + stats_mgr_.passSentPacket(ExchangeType::RR, pkt6); + + // When 'T' diagnostics flag is specified it means that user requested + // printing packet contents. It will be just one (first) packet which + // contents will be printed. Here we check if this packet has been already + // collected. If it hasn't we save this packet so as we can print its + // contents when test is finished. + if (options_.testDiags('T') && + (template_packets_v6_.find(DHCPV6_REQUEST) == template_packets_v6_.end())) { + template_packets_v6_[DHCPV6_REQUEST] = pkt6; + } +} + +void +TestControl::sendSolicit6(const bool preload /*= false*/) { + // Generate DUID to be passed to the packet + uint8_t randomized = 0; + std::vector<uint8_t> duid = generateDuid(randomized); + // Generate transaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, transid)); + if (!pkt6) { + isc_throw(Unexpected, "failed to create SOLICIT packet"); + } + + // Check if we need to simulate HA failures by pretending no responses were received. + // The DHCPv6 protocol signals that by increasing the elapsed option field. Note it is in 1/100 of a second. + if (options_.getIncreaseElapsedTime() && + stats_mgr_.getTestPeriod().length().total_seconds() >= options_.getWaitForElapsedTime() && + stats_mgr_.getTestPeriod().length().total_seconds() < options_.getWaitForElapsedTime() + + options_.getIncreaseElapsedTime()) { + + + // Keep increasing elapsed time. The value should start increasing steadily. + uint32_t val = (stats_mgr_.getTestPeriod().length().total_seconds() - options_.getWaitForElapsedTime() + 1)*100; + if (val > 65535) { + val = 65535; + } + OptionPtr elapsed(new OptionInt<uint16_t>(Option::V6, D6O_ELAPSED_TIME, val)); + pkt6->addOption(elapsed); + } else { + pkt6->addOption(Option::factory(Option::V6, D6O_ELAPSED_TIME)); + } + + if (options_.isRapidCommit()) { + pkt6->addOption(Option::factory(Option::V6, D6O_RAPID_COMMIT)); + } + pkt6->addOption(Option::factory(Option::V6, D6O_CLIENTID, duid)); + pkt6->addOption(Option::factory(Option::V6, D6O_ORO)); + + + // Depending on the lease-type option specified, we should request + // IPv6 address (with IA_NA) or IPv6 prefix (IA_PD) or both. + + // IA_NA + if (options_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) { + pkt6->addOption(Option::factory(Option::V6, D6O_IA_NA)); + } + // IA_PD + if (options_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) { + pkt6->addOption(Option::factory(Option::V6, D6O_IA_PD)); + } + + setDefaults6(pkt6); + + // Add any extra options that user may have specified. + addExtraOpts(pkt6); + + pkt6->pack(); + socket_.send(pkt6); + if (!preload) { + stats_mgr_.passSentPacket(ExchangeType::SA, pkt6); + } + + saveFirstPacket(pkt6); +} + +void +TestControl::sendSolicit6(const std::vector<uint8_t>& template_buf, + const bool preload /*= false*/) { + const int arg_idx = 0; + // Get transaction id offset. + size_t transid_offset = getTransactionIdOffset(arg_idx); + // Generate transaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + // Create packet. + PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(), + transid_offset, transid)); + if (!pkt6) { + isc_throw(Unexpected, "failed to create SOLICIT packet"); + } + size_t rand_offset = getRandomOffset(arg_idx); + // randomized will pick number of bytes randomized so we can + // just use part of the generated duid and substitute a few bytes + /// in template. + uint8_t randomized = 0; + std::vector<uint8_t> duid = generateDuid(randomized); + if (rand_offset > template_buf.size()) { + isc_throw(OutOfRange, "randomization offset is out of bounds"); + } + // Store random part of the DUID into the packet. + pkt6->writeAt(rand_offset - randomized + 1, + duid.end() - randomized, duid.end()); + + // Prepare on-wire data. + pkt6->rawPack(); + setDefaults6(pkt6); + + // Add any extra options that user may have specified. + addExtraOpts(pkt6); + + // Send solicit packet. + socket_.send(pkt6); + if (!preload) { + // Update packet stats. + stats_mgr_.passSentPacket(ExchangeType::SA, pkt6); + } + saveFirstPacket(pkt6); +} + + +void +TestControl::setDefaults4(const Pkt4Ptr& pkt) { + // Interface name. + IfacePtr iface = socket_.getIface(); + if (iface == NULL) { + isc_throw(BadValue, "unable to find interface with given index"); + } + pkt->setIface(iface->getName()); + // Interface index. + pkt->setIndex(socket_.ifindex_); + // Local client's port (68) + pkt->setLocalPort(DHCP4_CLIENT_PORT); + // Server's port (67) + if (options_.getRemotePort()) { + pkt->setRemotePort(options_.getRemotePort()); + } else { + pkt->setRemotePort(DHCP4_SERVER_PORT); + } + // The remote server's name or IP. + pkt->setRemoteAddr(IOAddress(options_.getServerName())); + // Set local address. + pkt->setLocalAddr(IOAddress(socket_.addr_)); + // Set relay (GIADDR) address to local address if multiple + // subnet mode is not enabled + if (!options_.checkMultiSubnet()) { + pkt->setGiaddr(IOAddress(socket_.addr_)); + } else { + pkt->setGiaddr(IOAddress(options_.getRandRelayAddr())); + } + // Pretend that we have one relay (which is us). + pkt->setHops(1); +} + +void +TestControl::setDefaults6(const Pkt6Ptr& pkt) { + // Interface name. + IfacePtr iface = socket_.getIface(); + if (iface == NULL) { + isc_throw(BadValue, "unable to find interface with given index"); + } + pkt->setIface(iface->getName()); + // Interface index. + pkt->setIndex(socket_.ifindex_); + // Local client's port (547) + pkt->setLocalPort(DHCP6_CLIENT_PORT); + // Server's port (548) + if (options_.getRemotePort()) { + pkt->setRemotePort(options_.getRemotePort()); + } else { + pkt->setRemotePort(DHCP6_SERVER_PORT); + } + // Set local address. + pkt->setLocalAddr(socket_.addr_); + // The remote server's name or IP. + pkt->setRemoteAddr(IOAddress(options_.getServerName())); + + // only act as a relay agent when told so. + /// @todo: support more level of encapsulation, at the moment we only support + /// one, via -A1 option. + if (options_.isUseRelayedV6()) { + Pkt6::RelayInfo relay_info; + relay_info.msg_type_ = DHCPV6_RELAY_FORW; + relay_info.hop_count_ = 0; + if (options_.checkMultiSubnet()) { + relay_info.linkaddr_ = IOAddress(options_.getRandRelayAddr()); + } else { + relay_info.linkaddr_ = IOAddress(socket_.addr_); + } + relay_info.peeraddr_ = IOAddress(socket_.addr_); + relay_info.options_.insert(options_.getRelayOpts().begin(), options_.getRelayOpts().end()); + pkt->addRelayInfo(relay_info); + } +} + +namespace { + +static OptionBuffer const concatenateBuffers(OptionBuffer const& a, + OptionBuffer const& b) { + OptionBuffer result; + result.insert(result.end(), a.begin(), a.end()); + result.insert(result.end(), b.begin(), b.end()); + return result; +} + +static void mergeOptionIntoPacket(Pkt4Ptr const& packet, + OptionPtr const& extra_option) { + uint16_t const code(extra_option->getType()); + // If option already exists... + OptionPtr const& option(packet->getOption(code)); + if (option) { + switch (code) { + // List here all the options for which we want to concatenate buffers. + case DHO_DHCP_PARAMETER_REQUEST_LIST: + packet->delOption(code); + packet->addOption(boost::make_shared<Option>( + Option::V4, code, + concatenateBuffers(option->getData(), + extra_option->getData()))); + return; + default: + // For all others, add option as usual, it will result in "Option + // already present in this message" error. + break; + } + } + packet->addOption(extra_option); +} + +} // namespace + +void +TestControl::addExtraOpts(const Pkt4Ptr& pkt) { + // Add all extra options that the user may have specified. + const dhcp::OptionCollection& extra_opts = options_.getExtraOpts(); + for (auto entry : extra_opts) { + mergeOptionIntoPacket(pkt, entry.second); + } +} + +void +TestControl::addExtraOpts(const Pkt6Ptr& pkt) { + // Add all extra options that the user may have specified. + const dhcp::OptionCollection& extra_opts = options_.getExtraOpts(); + for (auto entry : extra_opts) { + pkt->addOption(entry.second); + } +} + +} // namespace perfdhcp +} // namespace isc |