BIND 10 master, updated. 8d56105742f3043ed4b561f26241f3e4331f51dc Merge branch 'trac1954'
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon May 28 13:43:32 UTC 2012
The branch, master has been updated
via 8d56105742f3043ed4b561f26241f3e4331f51dc (commit)
via 7401fd9fbbe95b310165bd34da5b27359caf1c66 (commit)
via a3f8c40ba785618482a17a3ee2cf8edc65e37d73 (commit)
via 3f32b68d6f2d0f02af6cebac046a604751ad4dcc (commit)
via c65a54347a7bceee54531a6a81209d1ec6ceb82b (commit)
via d81f573764fe2df0006527145e439893872e6c69 (commit)
via 190b91b0701773f162424423789ee1d12fbc3260 (commit)
via d6f64f5a6b1c524e6b6fd91151df7dee98add719 (commit)
via a5171dfc73c99aaa7d8191d309d9808dca19b49b (commit)
via 6fb56baf17fbb2d8e666bfd7c16138f2a3ae5a44 (commit)
via 38d2dadf3b24367b9d967d23fe700899ddd752f4 (commit)
via 7e245132c9d7e2e49a23ebf738ca5d9d229dda30 (commit)
via ff8a54c66f7c0fadcceb18aa6b6d33c0cfbd615b (commit)
via d18d92e4e6a83f370258801c2c05caf15cdb6322 (commit)
via 35a0237e8d8263e32717c64f1b16b7bf59990ba7 (commit)
via 434ca15428a07aad9012b140724cb6ee1cd971a7 (commit)
via b04764bc6d95ffa182555d626b2dee91344e958a (commit)
via 57346e7268b5406e87e8e46b7015fed601813120 (commit)
via a7557c12f65bb21145a117df4770863737d4acee (commit)
from cd77eba438268ed3624a8d1c6b859baf84d993e5 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 8d56105742f3043ed4b561f26241f3e4331f51dc
Merge: cd77eba 7401fd9
Author: Marcin Siodelski <marcin at isc.org>
Date: Mon May 28 15:35:04 2012 +0200
Merge branch 'trac1954'
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 1 +
tests/tools/perfdhcp/Makefile.am | 22 +-
tests/tools/perfdhcp/command_options.cc | 681 ++++++++++++++++++++
tests/tools/perfdhcp/command_options.h | 412 ++++++++++++
tests/tools/perfdhcp/{perfdhcp.c => perfdhcp.cc} | 21 +-
.../{badpacket => perfdhcp}/tests/Makefile.am | 5 +-
.../perfdhcp/tests/command_options_unittest.cc | 454 +++++++++++++
.../{badpacket => perfdhcp}/tests/run_unittests.cc | 0
8 files changed, 1583 insertions(+), 13 deletions(-)
create mode 100644 tests/tools/perfdhcp/command_options.cc
create mode 100644 tests/tools/perfdhcp/command_options.h
copy tests/tools/perfdhcp/{perfdhcp.c => perfdhcp.cc} (99%)
copy tests/tools/{badpacket => perfdhcp}/tests/Makefile.am (74%)
create mode 100644 tests/tools/perfdhcp/tests/command_options_unittest.cc
copy tests/tools/{badpacket => perfdhcp}/tests/run_unittests.cc (100%)
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 566b74d..6935431 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1122,6 +1122,7 @@ AC_CONFIG_FILES([Makefile
tests/tools/badpacket/Makefile
tests/tools/badpacket/tests/Makefile
tests/tools/perfdhcp/Makefile
+ tests/tools/perfdhcp/tests/Makefile
dns++.pc
])
AC_OUTPUT([doc/version.ent
diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am
index bbad595..685ff1b 100644
--- a/tests/tools/perfdhcp/Makefile.am
+++ b/tests/tools/perfdhcp/Makefile.am
@@ -1,4 +1,8 @@
-SUBDIRS = .
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -8,5 +12,19 @@ if USE_STATIC_LINK
AM_LDFLAGS += -static
endif
+# We have to suppress warnings because we are compiling C code with CXX
+# We have to do this to link with new C++ pieces of code
+perfdhcp_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_CLANGPP
+perfdhcp_CXXFLAGS += -Wno-error
+else
+if USE_GXX
+perfdhcp_CXXFLAGS += -Wno-write-strings
+endif
+endif
+
pkglibexec_PROGRAMS = perfdhcp
-perfdhcp_SOURCES = perfdhcp.c
+perfdhcp_SOURCES = perfdhcp.cc
+perfdhcp_SOURCES += command_options.cc command_options.h
+
+perfdhcp_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc
new file mode 100644
index 0000000..7b62076
--- /dev/null
+++ b/tests/tools/perfdhcp/command_options.cc
@@ -0,0 +1,681 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "exceptions/exceptions.h"
+
+#include "command_options.h"
+
+using namespace std;
+using namespace isc;
+
+namespace isc {
+namespace perfdhcp {
+
+CommandOptions&
+CommandOptions::instance() {
+ static CommandOptions options;
+ return (options);
+}
+
+void
+CommandOptions::reset() {
+ // Default mac address used in DHCP messages
+ // if -b mac=<mac-address> was not specified
+ uint8_t mac[6] = { 0x0, 0xC, 0x1, 0x2, 0x3, 0x4 };
+
+ // Default packet drop time if -D<drop-time> parameter
+ // was not specified
+ double dt[2] = { 1., 1. };
+
+ // We don't use constructor initialization list because we
+ // will need to reset all members many times to perform unit tests
+ ipversion_ = 0;
+ exchange_mode_ = DORA_SARR;
+ rate_ = 0;
+ report_delay_ = 0;
+ clients_num_ = 0;
+ mac_prefix_.assign(mac, mac + 6);
+ base_.resize(0);
+ num_request_.resize(0);
+ period_ = 0;
+ drop_time_set_ = 0;
+ drop_time_.assign(dt, dt + 2);
+ max_drop_.clear();
+ max_pdrop_.clear();
+ localname_.clear();
+ is_interface_ = false;
+ preload_ = 0;
+ aggressivity_ = 1;
+ local_port_ = 0;
+ seeded_ = false;
+ seed_ = 0;
+ broadcast_ = false;
+ rapid_commit_ = false;
+ use_first_ = false;
+ template_file_.clear();
+ rnd_offset_.clear();
+ xid_offset_.clear();
+ elp_offset_ = -1;
+ sid_offset_ = -1;
+ rip_offset_ = -1;
+ diags_.clear();
+ wrapped_.clear();
+ server_name_.clear();
+}
+
+void
+CommandOptions::parse(int argc, char** const argv) {
+ // Reset internal variables used by getopt
+ // to eliminate undefined behavior when
+ // parsing different command lines multiple times
+ optind = 1;
+ opterr = 0;
+
+ // Reset values of class members
+ reset();
+
+ initialize(argc, argv);
+ validate();
+}
+
+void
+CommandOptions::initialize(int argc, char** argv) {
+ char opt = 0; // Subsequent options returned by getopt()
+ std::string drop_arg; // Value of -D<value>argument
+ size_t percent_loc = 0; // Location of % sign in -D<value>
+ double drop_percent = 0; // % value (1..100) in -D<value%>
+ int num_drops = 0; // Max number of drops specified in -D<value>
+ int num_req = 0; // Max number of dropped requests in -n<max-drops>
+ int offset_arg = 0; // Temporary variable holding offset arguments
+ std::string sarg; // Temporary variable for string args
+
+ // In this section we collect argument values from command line
+ // they will be tuned and validated elsewhere
+ while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:")) != -1) {
+ switch (opt) {
+ case 'v':
+ version();
+ return;
+
+ case '1':
+ use_first_ = true;
+ break;
+
+ case '4':
+ check(ipversion_ == 6, "IP version already set to 6");
+ ipversion_ = 4;
+ break;
+
+ case '6':
+ check(ipversion_ == 4, "IP version already set to 4");
+ ipversion_ = 6;
+ break;
+
+ case 'a':
+ aggressivity_ = positiveInteger("value of aggressivity: -a<value> must be a positive integer");
+ break;
+
+ case 'b':
+ check(base_.size() > 3, "-b<value> already specified, unexpected occurence of 5th -b<value>");
+ base_.push_back(optarg);
+ decodeBase(base_.back());
+ break;
+
+ case 'B':
+ broadcast_ = true;
+ break;
+
+ case 'c':
+ rapid_commit_ = true;
+ break;
+
+ case 'd':
+ check(drop_time_set_ > 1, "maximum number of drops already specified, "
+ "unexpected 3rd occurence of -d<value>");
+ try {
+ drop_time_[drop_time_set_] = boost::lexical_cast<double>(optarg);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter,
+ "value of drop time: -d<value> must be positive number");
+ }
+ check(drop_time_[drop_time_set_] <= 0., "drop-time must be a positive number");
+ drop_time_set_ = true;
+ break;
+
+ case 'D':
+ drop_arg = std::string(optarg);
+ percent_loc = drop_arg.find('%');
+ check(max_pdrop_.size() > 1 || max_drop_.size() > 1, "values of maximum drops: -D<value> already "
+ "specified, unexpected 3rd occurence of -D,value>");
+ if ((percent_loc) != std::string::npos) {
+ try {
+ drop_percent = boost::lexical_cast<double>(drop_arg.substr(0, percent_loc));
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter,
+ "value of drop percentage: -D<value%> must be 0..100");
+ }
+ check((drop_percent <= 0) || (drop_percent >= 100),
+ "value of drop percentage: -D<value%> must be 0..100");
+ max_pdrop_.push_back(drop_percent);
+ } else {
+ num_drops = positiveInteger("value of max drops number: -d<value> must be a positive integer");
+ max_drop_.push_back(num_drops);
+ }
+ break;
+
+ case 'E':
+ elp_offset_ = nonNegativeInteger("value of time-offset: -E<value> must not be a negative integer");
+ break;
+
+ case 'h':
+ usage();
+ return;
+
+ case 'i':
+ exchange_mode_ = DO_SA;
+ break;
+
+ case 'I':
+ rip_offset_ = positiveInteger("value of ip address offset: -I<value> must be a positive integer");
+ break;
+
+ case 'l':
+ localname_ = std::string(optarg);
+ break;
+
+ case 'L':
+ local_port_ = nonNegativeInteger("value of local port: -L<value> must not be a negative integer");
+ check(local_port_ > static_cast<int>(std::numeric_limits<uint16_t>::max()),
+ "local-port must be lower than " +
+ boost::lexical_cast<std::string>(std::numeric_limits<uint16_t>::max()));
+ break;
+
+ case 'n':
+ num_req = positiveInteger("value of num-request: -n<value> must be a positive integer");
+ if (num_request_.size() >= 2) {
+ isc_throw(isc::InvalidParameter,"value of maximum number of requests: -n<value> "
+ "already specified, unexpected 3rd occurence of -n<value>");
+ }
+ num_request_.push_back(num_req);
+ break;
+
+ case 'O':
+ if (rnd_offset_.size() < 2) {
+ offset_arg = positiveInteger("value of random offset: -O<value> must be greater than 3");
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "random offsets already specified, unexpected 3rd occurence of -O<value>");
+ }
+ check(offset_arg < 3, "value of random random-offset: -O<value> must be greater than 3 ");
+ rnd_offset_.push_back(offset_arg);
+ break;
+
+ case 'p':
+ period_ = positiveInteger("value of test period: -p<value> must be a positive integer");
+ break;
+
+ case 'P':
+ preload_ = nonNegativeInteger("number of preload packets: -P<value> must not be "
+ "a negative integer");
+ break;
+
+ case 'r':
+ rate_ = positiveInteger("value of rate: -r<value> must be a positive integer");
+ break;
+
+ case 'R':
+ initClientsNum();
+ break;
+
+ case 's':
+ seed_ = static_cast<unsigned int>
+ (nonNegativeInteger("value of seed: -s <seed> must be non-negative integer"));
+ seeded_ = seed_ > 0 ? true : false;
+ break;
+
+ case 'S':
+ sid_offset_ = positiveInteger("value of server id offset: -S<value> must be a positive integer");
+ break;
+
+ case 't':
+ report_delay_ = positiveInteger("value of report delay: -t<value> must be a positive integer");
+ break;
+
+ case 'T':
+ if (template_file_.size() < 2) {
+ sarg = nonEmptyString("template file name not specified, expected -T<filename>");
+ template_file_.push_back(sarg);
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "template files are already specified, unexpected 3rd -T<filename> occurence");
+ }
+ break;
+
+ case 'w':
+ wrapped_ = nonEmptyString("command for wrapped mode: -w<command> must be specified");
+ break;
+
+ case 'x':
+ diags_ = nonEmptyString("value of diagnostics selectors: -x<value> must be specified");
+ break;
+
+ case 'X':
+ if (xid_offset_.size() < 2) {
+ offset_arg = positiveInteger("value of transaction id: -X<value> must be a positive integer");
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "transaction ids already specified, unexpected 3rd -X<value> occurence");
+ }
+ xid_offset_.push_back(offset_arg);
+ break;
+
+ default:
+ isc_throw(isc::InvalidParameter, "unknown command line option");
+ }
+ }
+
+ // If the IP version was not specified in the
+ // command line, assume IPv4.
+ if (ipversion_ == 0) {
+ ipversion_ = 4;
+ }
+
+ // If template packet files specified for both DISCOVER/SOLICIT
+ // and REQUEST/REPLY exchanges make sure we have transaction id
+ // and random duid offsets for both exchanges. We will duplicate
+ // value specified as -X<value> and -R<value> for second
+ // exchange if user did not specified otherwise.
+ if (template_file_.size() > 1) {
+ if (xid_offset_.size() == 1) {
+ xid_offset_.push_back(xid_offset_[0]);
+ }
+ if (rnd_offset_.size() == 1) {
+ rnd_offset_.push_back(rnd_offset_[0]);
+ }
+ }
+
+ // Get server argument
+ // NoteFF02::1:2 and FF02::1:3 are defined in RFC3315 as
+ // All_DHCP_Relay_Agents_and_Servers and All_DHCP_Servers
+ // addresses
+ check(optind < argc -1, "extra arguments?");
+ if (optind == argc - 1) {
+ server_name_ = argv[optind];
+ // Decode special cases
+ if ((ipversion_ == 4) && (server_name_.compare("all") == 0)) {
+ broadcast_ = 1;
+ // 255.255.255.255 is IPv4 broadcast address
+ server_name_ = "255.255.255.255";
+ } else if ((ipversion_ == 6) && (server_name_.compare("all") == 0)) {
+ server_name_ = "FF02::1:2";
+ } else if ((ipversion_ == 6) && (server_name_.compare("servers") == 0)) {
+ server_name_ = "FF05::1:3";
+ }
+ }
+
+ // TODO handle -l option with IfaceManager when it is created
+}
+
+void
+CommandOptions::initClientsNum() {
+ const std::string errmsg = "value of -R <value> must be non-negative integer";
+
+ // Declare clients_num as as 64-bit signed value to
+ // be able to detect negative values provided
+ // by user. We would not detect negative values
+ // if we casted directly to unsigned value.
+ long long clients_num = 0;
+ try {
+ clients_num = boost::lexical_cast<long long>(optarg);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter, errmsg.c_str());
+ }
+ check(clients_num < 0, errmsg);
+ try {
+ clients_num_ = boost::lexical_cast<uint32_t>(optarg);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(isc::InvalidParameter, errmsg);
+ }
+}
+
+void
+CommandOptions::decodeBase(const std::string& base) {
+ std::string b(base);
+ boost::algorithm::to_lower(b);
+
+ // Currently we only support mac and duid
+ if ((b.substr(0, 4) == "mac=") || (b.substr(0, 6) == "ether=")) {
+ decodeMac(b);
+ } else if (b.substr(0, 5) == "duid=") {
+ decodeDuid(b);
+ } else {
+ isc_throw(isc::InvalidParameter,
+ "base value not provided as -b<value>, expected -b mac=<mac> or -b duid=<duid>");
+ }
+}
+
+void
+CommandOptions::decodeMac(const std::string& base) {
+ // Strip string from mac=
+ size_t found = base.find('=');
+ static const char* errmsg = "expected -b<base> format for mac address is -b mac=00::0C::01::02::03::04";
+ check(found == std::string::npos, errmsg);
+
+ // Decode mac address to vector of uint8_t
+ std::istringstream s1(base.substr(found + 1));
+ std::string token;
+ mac_prefix_.clear();
+ // Get pieces of MAC address separated with : (or even ::)
+ while (std::getline(s1, token, ':')) {
+ unsigned int ui = 0;
+ // Convert token to byte value using std::istringstream
+ if (token.length() > 0) {
+ try {
+ // Do actual conversion
+ ui = convertHexString(token);
+ } catch (isc::InvalidParameter&) {
+ isc_throw(isc::InvalidParameter,
+ "invalid characters in MAC provided");
+
+ }
+ // If conversion succeeded store byte value
+ mac_prefix_.push_back(ui);
+ }
+ }
+ // MAC address must consist of 6 octets, otherwise it is invalid
+ check(mac_prefix_.size() != 6, errmsg);
+}
+
+void
+CommandOptions::decodeDuid(const std::string& base) {
+ // Strip argument from duid=
+ size_t found = base.find('=');
+ check(found == std::string::npos, "expected -b<base> format for duid is -b duid=<duid>");
+ std::string b = base.substr(found + 1);
+
+ // DUID must have even number of digits and must not be longer than 64 bytes
+ check(b.length() & 1, "odd number of hexadecimal digits in duid");
+ check(b.length() > 128, "duid too large");
+ check(b.length() == 0, "no duid specified");
+
+ // Turn pairs of hexadecimal digits into vector of octets
+ for (int i = 0; i < b.length(); i += 2) {
+ unsigned int ui = 0;
+ try {
+ // Do actual conversion
+ ui = convertHexString(b.substr(i, 2));
+ } catch (isc::InvalidParameter&) {
+ isc_throw(isc::InvalidParameter,
+ "invalid characters in DUID provided, exepected hex digits");
+ }
+ duid_prefix_.push_back(static_cast<uint8_t>(ui));
+ }
+}
+
+uint8_t
+CommandOptions::convertHexString(const std::string& text) const {
+ unsigned int ui = 0;
+ // First, check if we are dealing with hexadecimal digits only
+ for (int i = 0; i < text.length(); ++i) {
+ if (!std::isxdigit(text[i])) {
+ isc_throw(isc::InvalidParameter,
+ "The following digit: " << text[i] << " in "
+ << text << "is not hexadecimal");
+ }
+ }
+ // If we are here, we have valid string to convert to octet
+ std::istringstream text_stream(text);
+ text_stream >> std::hex >> ui >> std::dec;
+ // Check if for some reason we have overflow - this should never happen!
+ if (ui > 0xFF) {
+ isc_throw(isc::InvalidParameter, "Can't convert more than two hex digits to byte");
+ }
+ return ui;
+}
+
+void
+CommandOptions::validate() const {
+ check((getIpVersion() != 4) && (isBroadcast() != 0),
+ "-B is not compatible with IPv6 (-6)");
+ check((getIpVersion() != 6) && (isRapidCommit() != 0),
+ "-6 (IPv6) must be set to use -c");
+ check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
+ "second -n<num-request> is not compatible with -i");
+ check((getExchangeMode() == DO_SA) && (getDropTime()[1] != 1.),
+ "second -d<drop-time> is not compatible with -i");
+ check((getExchangeMode() == DO_SA) &&
+ ((getMaxDrop().size() > 1) || (getMaxDropPercentage().size() > 1)),
+ "second -D<max-drop> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (isUseFirst()),
+ "-1 is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getTemplateFiles().size() > 1),
+ "second -T<template-file> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getTransactionIdOffset().size() > 1),
+ "second -X<xid-offset> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getRandomOffset().size() > 1),
+ "second -O<random-offset is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getElapsedTimeOffset() >= 0),
+ "-E<time-offset> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getServerIdOffset() >= 0),
+ "-S<srvid-offset> is not compatible with -i\n");
+ check((getExchangeMode() == DO_SA) && (getRequestedIpOffset() >= 0),
+ "-I<ip-offset> is not compatible with -i\n");
+ check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
+ "-i must be set to use -c\n");
+ check((getRate() == 0) && (getReportDelay() != 0),
+ "-r<rate> must be set to use -t<report>\n");
+ check((getRate() == 0) && (getNumRequests().size() > 0),
+ "-r<rate> must be set to use -n<num-request>\n");
+ check((getRate() == 0) && (getPeriod() != 0),
+ "-r<rate> must be set to use -p<test-period>\n");
+ check((getRate() == 0) &&
+ ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
+ "-r<rate> must be set to use -D<max-drop>\n");
+ check((getTemplateFiles().size() < getTransactionIdOffset().size()),
+ "-T<template-file> must be set to use -X<xid-offset>\n");
+ check((getTemplateFiles().size() < getRandomOffset().size()),
+ "-T<template-file> must be set to use -O<random-offset>\n");
+ check((getTemplateFiles().size() < 2) && (getElapsedTimeOffset() >= 0),
+ "second/request -T<template-file> must be set to use -E<time-offset>\n");
+ check((getTemplateFiles().size() < 2) && (getServerIdOffset() >= 0),
+ "second/request -T<template-file> must be set to "
+ "use -S<srvid-offset>\n");
+ check((getTemplateFiles().size() < 2) && (getRequestedIpOffset() >= 0),
+ "second/request -T<template-file> must be set to "
+ "use -I<ip-offset>\n");
+
+}
+
+void
+CommandOptions::check(bool condition, const std::string& errmsg) const {
+ // The same could have been done with macro or just if statement but
+ // we prefer functions to macros here
+ if (condition) {
+ isc_throw(isc::InvalidParameter, errmsg);
+ }
+}
+
+int
+CommandOptions::positiveInteger(const std::string& errmsg) const {
+ try {
+ int value = boost::lexical_cast<int>(optarg);
+ check(value <= 0, errmsg);
+ return (value);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(InvalidParameter, errmsg);
+ }
+}
+
+int
+CommandOptions::nonNegativeInteger(const std::string& errmsg) const {
+ try {
+ int value = boost::lexical_cast<int>(optarg);
+ check(value < 0, errmsg);
+ return (value);
+ } catch (boost::bad_lexical_cast&) {
+ isc_throw(InvalidParameter, errmsg);
+ }
+}
+
+std::string
+CommandOptions::nonEmptyString(const std::string& errmsg) const {
+ std::string sarg = optarg;
+ if (sarg.length() == 0) {
+ isc_throw(isc::InvalidParameter, errmsg);
+ }
+ return sarg;
+}
+
+void
+CommandOptions::usage() const {
+ fprintf(stdout, "%s",
+"perfdhcp [-hv] [-4|-6] [-r<rate>] [-t<report>] [-R<range>] [-b<base>]\n"
+" [-n<num-request>] [-p<test-period>] [-d<drop-time>] [-D<max-drop>]\n"
+" [-l<local-addr|interface>] [-P<preload>] [-a<aggressivity>]\n"
+" [-L<local-port>] [-s<seed>] [-i] [-B] [-c] [-1]\n"
+" [-T<template-file>] [-X<xid-offset>] [-O<random-offset]\n"
+" [-E<time-offset>] [-S<srvid-offset>] [-I<ip-offset>]\n"
+" [-x<diagnostic-selector>] [-w<wrapped>] [server]\n"
+"\n"
+"The [server] argument is the name/address of the DHCP server to\n"
+"contact. For DHCPv4 operation, exchanges are initiated by\n"
+"transmitting a DHCP DISCOVER to this address.\n"
+"\n"
+"For DHCPv6 operation, exchanges are initiated by transmitting a DHCP\n"
+"SOLICIT to this address. In the DHCPv6 case, the special name 'all'\n"
+"can be used to refer to All_DHCP_Relay_Agents_and_Servers (the\n"
+"multicast address FF02::1:2), or the special name 'servers' to refer\n"
+"to All_DHCP_Servers (the multicast address FF05::1:3). The [server]\n"
+"argument is optional only in the case that -l is used to specify an\n"
+"interface, in which case [server] defaults to 'all'.\n"
+"\n"
+"The default is to perform a single 4-way exchange, effectively pinging\n"
+"the server.\n"
+"The -r option is used to set up a performance test, without\n"
+"it exchanges are initiated as fast as possible.\n"
+"\n"
+"Options:\n"
+"-1: Take the server-ID option from the first received message.\n"
+"-4: DHCPv4 operation (default). This is incompatible with the -6 option.\n"
+"-6: DHCPv6 operation. This is incompatible with the -4 option.\n"
+"-a<aggressivity>: When the target sending rate is not yet reached,\n"
+" control how many exchanges are initiated before the next pause.\n"
+"-b<base>: The base mac, duid, IP, etc, used to simulate different\n"
+" clients. This can be specified multiple times, each instance is\n"
+" in the <type>=<value> form, for instance:\n"
+" (and default) mac=00:0c:01:02:03:04.\n"
+"-d<drop-time>: Specify the time after which a request is treated as\n"
+" having been lost. The value is given in seconds and may contain a\n"
+" fractional component. The default is 1 second.\n"
+"-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
+" elapsed-time option in the (second/request) template.\n"
+" The value 0 disables it.\n"
+"-h: Print this help.\n"
+"-i: Do only the initial part of an exchange: DO or SA, depending on\n"
+" whether -6 is given.\n"
+"-I<ip-offset>: Offset of the (DHCPv4) IP address in the requested-IP\n"
+" option / (DHCPv6) IA_NA option in the (second/request) template.\n"
+"-l<local-addr|interface>: For DHCPv4 operation, specify the local\n"
+" hostname/address to use when communicating with the server. By\n"
+" default, the interface address through which traffic would\n"
+" normally be routed to the server is used.\n"
+" For DHCPv6 operation, specify the name of the network interface\n"
+" via which exchanges are initiated.\n"
+"-L<local-port>: Specify the local port to use\n"
+" (the value 0 means to use the default).\n"
+"-O<random-offset>: Offset of the last octet to randomize in the template.\n"
+"-P<preload>: Initiate first <preload> exchanges back to back at startup.\n"
+"-r<rate>: Initiate <rate> DORA/SARR (or if -i is given, DO/SA)\n"
+" exchanges per second. A periodic report is generated showing the\n"
+" number of exchanges which were not completed, as well as the\n"
+" average response latency. The program continues until\n"
+" interrupted, at which point a final report is generated.\n"
+"-R<range>: Specify how many different clients are used. With 1\n"
+" (the default), all requests seem to come from the same client.\n"
+"-s<seed>: Specify the seed for randomization, making it repeatable.\n"
+"-S<srvid-offset>: Offset of the server-ID option in the\n"
+" (second/request) template.\n"
+"-T<template-file>: The name of a file containing the template to use\n"
+" as a stream of hexadecimal digits.\n"
+"-v: Report the version number of this program.\n"
+"-w<wrapped>: Command to call with start/stop at the beginning/end of\n"
+" the program.\n"
+"-x<diagnostic-selector>: Include extended diagnostics in the output.\n"
+" <diagnostic-selector> is a string of single-keywords specifying\n"
+" the operations for which verbose output is desired. The selector\n"
+" keyletters are:\n"
+" * 'a': print the decoded command line arguments\n"
+" * 'e': print the exit reason\n"
+" * 'i': print rate processing details\n"
+" * 'r': print randomization details\n"
+" * 's': print first server-id\n"
+" * 't': when finished, print timers of all successful exchanges\n"
+" * 'T': when finished, print templates\n"
+"-X<xid-offset>: Transaction ID (aka. xid) offset in the template.\n"
+"\n"
+"DHCPv4 only options:\n"
+"-B: Force broadcast handling.\n"
+"\n"
+"DHCPv6 only options:\n"
+"-c: Add a rapid commit option (exchanges will be SA).\n"
+"\n"
+"The remaining options are used only in conjunction with -r:\n"
+"\n"
+"-D<max-drop>: Abort the test if more than <max-drop> requests have\n"
+" been dropped. Use -D0 to abort if even a single request has been\n"
+" dropped. If <max-drop> includes the suffix '%', it specifies a\n"
+" maximum percentage of requests that may be dropped before abort.\n"
+" In this case, testing of the threshold begins after 10 requests\n"
+" have been expected to be received.\n"
+"-n<num-request>: Initiate <num-request> transactions. No report is\n"
+" generated until all transactions have been initiated/waited-for,\n"
+" after which a report is generated and the program terminates.\n"
+"-p<test-period>: Send requests for the given test period, which is\n"
+" specified in the same manner as -d. This can be used as an\n"
+" alternative to -n, or both options can be given, in which case the\n"
+" testing is completed when either limit is reached.\n"
+"-t<report>: Delay in seconds between two periodic reports.\n"
+"\n"
+"Errors:\n"
+"- tooshort: received a too short message\n"
+"- orphans: received a message which doesn't match an exchange\n"
+" (duplicate, late or not related)\n"
+"- locallimit: reached to local system limits when sending a message.\n"
+"\n"
+"Exit status:\n"
+"The exit status is:\n"
+"0 on complete success.\n"
+"1 for a general error.\n"
+"2 if an error is found in the command line arguments.\n"
+"3 if there are no general failures in operation, but one or more\n"
+" exchanges are not successfully completed.\n");
+}
+
+void
+CommandOptions::version() const {
+ fprintf(stdout, "version 0.01\n");
+}
+
+
+} // namespace perfdhcp
+} // namespace isc
diff --git a/tests/tools/perfdhcp/command_options.h b/tests/tools/perfdhcp/command_options.h
new file mode 100644
index 0000000..033d29a
--- /dev/null
+++ b/tests/tools/perfdhcp/command_options.h
@@ -0,0 +1,412 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __COMMAND_OPTIONS_H
+#define __COMMAND_OPTIONS_H
+
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Command Options
+///
+/// This class is responsible for parsing the command-line and storing the
+/// specified options.
+///
+class CommandOptions : public boost::noncopyable {
+public:
+ /// 2-way (cmd line param -i) or 4-way exchanges
+ enum ExchangeMode {
+ DO_SA,
+ DORA_SARR
+ };
+
+ /// CommandOptions is a singleton class. This method returns reference
+ /// to its sole instance.
+ ///
+ /// \return the only existing instance of command options
+ static CommandOptions& instance();
+
+ /// \brief Reset to defaults
+ ///
+ /// Reset data members to default values. This is specifically
+ /// useful when unit tests are performed using different
+ /// command line options.
+ void reset();
+
+ /// \brief Parse command line
+ ///
+ /// Parses the command line and stores the selected options
+ /// in class data members.
+ ///
+ /// \param argc Argument count passed to main().
+ /// \param argv Argument value array passed to main().
+ /// \throws isc::InvalidParameter if parse fails
+ void parse(int argc, char** const argv);
+
+ /// \brief Returns IP version
+ ///
+ /// \return IP version to be used
+ uint8_t getIpVersion() const { return ipversion_; }
+
+ /// \brief Returns packet exchange mode
+ ///
+ /// \return packet exchange mode
+ ExchangeMode getExchangeMode() const { return exchange_mode_; }
+
+ /// \brief Returns echange rate
+ ///
+ /// \return exchange rate per second
+ int getRate() const { return rate_; }
+
+ /// \brief Returns delay between two performance reports
+ ///
+ /// \return delay between two consecutive performance reports
+ int getReportDelay() const { return report_delay_; }
+
+ /// \brief Returns number of simulated clients
+ ///
+ /// \return number of simulated clients
+ uint32_t getClientsNum() const { return clients_num_; }
+
+ /// \brief Returns MAC address prefix
+ ///
+ /// \ return MAC address prefix to simulate different clients
+ std::vector<uint8_t> getMacPrefix() const { return mac_prefix_; }
+
+ /// \brief Returns DUID prefix
+ ///
+ /// \return DUID prefix to simulate different clients
+ std::vector<uint8_t> getDuidPrefix() const { return duid_prefix_; }
+
+ /// \brief Returns base values
+ ///
+ /// \return all base values specified
+ std::vector<std::string> getBase() const { return base_; }
+
+ /// \brief Returns maximum number of exchanges
+ ///
+ /// \return number of exchange requests before test is aborted
+ std::vector<int> getNumRequests() const { return num_request_; }
+
+ /// \brief Returns test period
+ ///
+ /// \return test period before it is aborted
+ int getPeriod() const { return period_; }
+
+ /// \brief Returns drop time
+ ///
+ /// The method returns maximum time elapsed from
+ /// sending the packet before it is assumed dropped.
+ ///
+ /// \return return time before request is assumed dropped
+ std::vector<double> getDropTime() const { return drop_time_; }
+
+ /// \brief Returns maximum drops number
+ ///
+ /// Returns maximum number of packet drops before
+ /// aborting a test.
+ ///
+ /// \return maximum number of dropped requests
+ std::vector<int> getMaxDrop() const { return max_drop_; }
+
+ /// \brief Returns maximal percentage of drops
+ ///
+ /// Returns maximal percentage of packet drops
+ /// before aborting a test.
+ ///
+ /// \return maximum percentage of lost requests
+ std::vector<double> getMaxDropPercentage() const { return max_pdrop_; }
+
+ /// \brief Returns local address or interface name
+ ///
+ /// \return local address or interface name
+ std::string getLocalName() const { return localname_; }
+
+ /// \brief Checks if interface name was used
+ ///
+ /// The method checks if interface name was used
+ /// rather than address.
+ ///
+ /// \return true if interface name was used
+ bool isInterface() const { return is_interface_; }
+
+ /// \brief Returns number of preload exchanges
+ ///
+ /// \return number of preload exchanges
+ int getPreload() const { return preload_; }
+
+ /// \brief Returns aggressivity value
+ ///
+ /// \return aggressivity value
+ int getAggressivity() const { return aggressivity_; }
+
+ /// \brief Returns local port number
+ ///
+ /// \return local port number
+ int getLocalPort() const { return local_port_; }
+
+ /// \brief Checks if seed provided
+ ///
+ /// \return true if seed was provided
+ bool isSeeded() const { return seeded_; }
+
+ /// \brief Returns radom seed
+ ///
+ /// \return random seed
+ uint32_t getSeed() const { return seed_; }
+
+ /// \brief Checks if broadcast address is to be used
+ ///
+ /// \return true if broadcast address is to be used
+ bool isBroadcast() const { return broadcast_; }
+
+ /// \brief Check if rapid commit option used
+ ///
+ /// \return true if rapid commit option is used
+ bool isRapidCommit() const { return rapid_commit_; }
+
+ /// \brief Check if server-ID to be taken from first package
+ ///
+ /// \return true if server-iD to be taken from first package
+ bool isUseFirst() const { return use_first_; }
+
+ /// \brief Returns template file names
+ ///
+ /// \return template file names
+ std::vector<std::string> getTemplateFiles() const { return template_file_; }
+
+ /// brief Returns template offsets for xid
+ ///
+ /// \return template offsets for xid
+ std::vector<int> getTransactionIdOffset() const { return xid_offset_; }
+
+ /// \brief Returns template offsets for rnd
+ ///
+ /// \return template offsets for rnd
+ std::vector<int> getRandomOffset() const { return rnd_offset_; }
+
+ /// \brief Returns template offset for elapsed time
+ ///
+ /// \return template offset for elapsed time
+ int getElapsedTimeOffset() const { return elp_offset_; }
+
+ /// \brief Returns template offset for server-ID
+ ///
+ /// \return template offset for server-ID
+ int getServerIdOffset() const { return sid_offset_; }
+
+ /// \brief Returns template offset for requested IP
+ ///
+ /// \return template offset for requested IP
+ int getRequestedIpOffset() const { return rip_offset_; }
+
+ /// \brief Returns diagnostic selectors
+ ///
+ /// \return diagnostics selector
+ std::string getDiags() const { return diags_; }
+
+ /// \brief Returns wrapped command
+ ///
+ /// \return wrapped command (start/stop)
+ std::string getWrapped() const { return wrapped_; }
+
+ /// \brief Returns server name
+ ///
+ /// \return server name
+ std::string getServerName() const { return server_name_; }
+
+ /// \brief Print usage
+ ///
+ /// Prints perfdhcp usage
+ void usage() const;
+
+ /// \brief Print program version
+ ///
+ /// Prints perfdhcp version
+ void version() const;
+
+private:
+
+ /// \brief Default Constructor
+ ///
+ /// Private constructor as this is a singleton class.
+ /// Use CommandOptions::instance() to get instance of it.
+ CommandOptions() {
+ reset();
+ }
+
+ /// \brief Initializes class members based command line
+ ///
+ /// Reads each command line parameter and sets class member values
+ ///
+ /// \param argc Argument count passed to main().
+ /// \param argv Argument value array passed to main().
+ /// \throws isc::InvalidParameter if command line options initialization fails
+ void initialize(int argc, char** argv);
+
+ /// \brief Validates initialized options
+ ///
+ /// \throws isc::InvalidParameter if command line validation fails
+ void validate() const;
+
+ /// \brief Throws !InvalidParameter exception if condition is true
+ ///
+ /// Convenience function that throws an InvalidParameter exception if
+ /// the condition argument is true
+ ///
+ /// \param condition Condition to be checked
+ /// \param errmsg Error message in exception
+ /// \throws isc::InvalidParameter if condition argument true
+ inline void check(bool condition, const std::string& errmsg) const;
+
+ /// \brief Casts command line argument to positive integer
+ ///
+ /// \param errmsg Error message if lexical cast fails
+ /// \throw InvalidParameter if lexical cast fails
+ int positiveInteger(const std::string& errmsg) const;
+
+ /// \brief Casts command line argument to non-negative integer
+ ///
+ /// \param errmsg Error message if lexical cast fails
+ /// \throw InvalidParameter if lexical cast fails
+ int nonNegativeInteger(const std::string& errmsg) const;
+
+ /// \brief Returns command line string if it is not empty
+ ///
+ /// \param errmsg Error message if string is empty
+ /// \throw InvalidParameter if string is empty
+ std::string nonEmptyString(const std::string& errmsg) const;
+
+ /// \brief Set number of clients
+ ///
+ /// Interprets the getopt() "opt" global variable as the number of clients
+ /// (a non-negative number). This value is specified by the "-R" switch.
+ ///
+ /// \throw InvalidParameter if -R<value> is wrong
+ void initClientsNum();
+
+ /// \brief Decodes base provided with -b<base>
+ ///
+ /// Function decodes argument of -b switch, which
+ /// specifies a base value used to generate unique
+ /// mac or duid values in packets sent to system
+ /// under test.
+ /// The following forms of switch arguments are supported:
+ /// - -b mac=00:01:02:03:04:05
+ /// - -b duid=0F1234 (duid can be up to 128 hex digits)
+ // Function will decode 00:01:02:03:04:05 and/or
+ /// 0F1234 respectively and initialize mac_prefix_
+ /// and/or duid_prefix_ members
+ ///
+ /// \param base Base in string format
+ /// \throws isc::InvalidParameter if base is invalid
+ void decodeBase(const std::string& base);
+
+ /// \brief Decodes base MAC address provided with -b<base>
+ ///
+ /// Function decodes parameter given as -b mac=00:01:02:03:04:05
+ /// The function will decode 00:01:02:03:04:05 initialize mac_prefix_
+ /// class member.
+ /// Provided MAC address is for example only
+ ///
+ /// \param base Base string given as -b mac=00:01:02:03:04:05
+ /// \throws isc::InvalidParameter if mac address is invalid
+ void decodeMac(const std::string& base);
+
+ /// \brief Decodes base DUID provided with -b<base>
+ ///
+ /// Function decodes parameter given as -b duid=0F1234
+ /// The function will decode 0F1234 and initialize duid_prefix_
+ /// class member.
+ /// Provided DUID is for example only.
+ ///
+ /// \param base Base string given as -b duid=0F1234
+ /// \throws isc::InvalidParameter if DUID is invalid
+ void decodeDuid(const std::string& base);
+
+ /// \brief Converts two-digit hexadecimal string to a byte
+ ///
+ /// \param hex_text Hexadecimal string e.g. AF
+ /// \throw isc::InvalidParameter if string does not represent hex byte
+ uint8_t convertHexString(const std::string& hex_text) const;
+
+ uint8_t ipversion_; ///< IP protocol version to be used, expected values are:
+ ///< 4 for IPv4 and 6 for IPv6, default value 0 means "not set"
+ ExchangeMode exchange_mode_; ///< Packet exchange mode (e.g. DORA/SARR)
+ int rate_; ///< Rate in exchange per second
+ int report_delay_; ///< Delay between generation of two consecutive
+ ///< performance reports
+ uint32_t clients_num_; ///< Number of simulated clients (aka randomization range).
+ std::vector<uint8_t> mac_prefix_; ///< MAC address prefix used to generate unique DUIDs
+ ///< for simulated clients.
+ std::vector<uint8_t> duid_prefix_; ///< DUID prefix used to generate unique DUIDs for
+ ///< simulated clients
+ std::vector<std::string> base_; ///< Collection of base values specified with -b<value>
+ ///< options. Supported "bases" are mac=<mac> and duid=<duid>
+ std::vector<int> num_request_; ///< Number of 2 or 4-way exchanges to perform
+ int period_; ///< Test period in seconds
+ uint8_t drop_time_set_; ///< Indicates number of -d<value> parameters specified by user.
+ ///< If this value goes above 2, command line parsing fails.
+ std::vector<double> drop_time_; ///< Time to elapse before request is lost. The fisrt value of
+ ///< two-element vector refers to DO/SA exchanges,
+ ///< second value refers to RA/RR. Default values are { 1, 1 }
+ std::vector<int> max_drop_; ///< Maximum number of drops request before aborting test.
+ ///< First value of two-element vector specifies maximum
+ ///< number of drops for DO/SA exchange, second value
+ ///< specifies maximum number of drops for RA/RR.
+ std::vector<double> max_pdrop_; ///< Maximal percentage of lost requests before aborting test.
+ ///< First value of two-element vector specifies percentage for
+ ///< DO/SA exchanges, second value for RA/RR.
+ std::string localname_; ///< Local address or interface specified with -l<value> option.
+ bool is_interface_; ///< Indicates that specified value with -l<value> is
+ ///< rather interface (not address)
+ int preload_; ///< Number of preload packets. Preload packets are used to
+ ///< initiate communication with server before doing performance
+ ///< measurements.
+ int aggressivity_; ///< Number of exchanges sent before next pause.
+ int local_port_; ///< Local port number (host endian)
+ bool seeded_; ///< Indicates that randomization seed was provided.
+ uint32_t seed_; ///< Randomization seed.
+ bool broadcast_; ///< Indicates that we use broadcast address.
+ bool rapid_commit_; ///< Indicates that we do rapid commit option.
+ bool use_first_; ///< Indicates that we take server id from first received packet.
+ std::vector<std::string> template_file_; ///< Packet template file names. These files store template packets
+ ///< that are used for initiating echanges. Template packets
+ ///< read from files are later tuned with variable data.
+ std::vector<int> xid_offset_; ///< Offset of transaction id in template files. First vector
+ ///< element points to offset for DISCOVER/SOLICIT messages,
+ ///< second element points to trasaction id offset for
+ ///< REQUEST messages
+ std::vector<int> rnd_offset_; ///< Random value offset in templates. Random value offset
+ ///< points to last octet of DUID. Up to 4 last octets of
+ ///< DUID are randomized to simulate differnt clients.
+ int elp_offset_; ///< Offset of elapsed time option in template packet.
+ int sid_offset_; ///< Offset of server id option in template packet.
+ int rip_offset_; ///< Offset of requested ip data in template packet/
+ std::string diags_; ///< String representing diagnostic selectors specified
+ ///< by user with -x<value>.
+ std::string wrapped_; ///< Wrapped command specified as -w<value>. Expected
+ ///< values are start and stop.
+ std::string server_name_; ///< Server name specified as last argument of command line.
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __COMMAND_OPTIONS_H
diff --git a/tests/tools/perfdhcp/perfdhcp.cc b/tests/tools/perfdhcp/perfdhcp.cc
new file mode 100644
index 0000000..414db24
--- /dev/null
+++ b/tests/tools/perfdhcp/perfdhcp.cc
@@ -0,0 +1,3566 @@
+/*
+ * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#ifndef HAVE_GETIFADDRS
+
+/*
+ * Solaris 10 does not have the getifaddrs() function available (although it
+ * is present on Solaris 11 and later). For the present we will not implement
+ * a replacement (as we do with clock_gettime) as the implementation is
+ * relatively complex. Just output a message saying that the utility is not
+ * supported on this operating system.
+ */
+
+#include <stdio.h>
+
+int
+main(const int argc, char* const argv[])
+{
+ fprintf(stderr, "perfdhcp is not supported on this version of the operating system\n");
+ return (1);
+}
+
+#else
+
+/* getifaddrs() present, so the code should compile */
+
+#define __STDC_LIMIT_MACROS
+
+#ifdef __linux__
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#endif
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <math.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef HAVE_PSELECT
+
+#include <assert.h>
+
+/* Platforms such as OpenBSD don't provide a pselect(), so we use our
+ own implementation for this testcase, which wraps around select() and
+ hence doesn't implement the high precision timer. This implementation
+ is fine for our purpose. */
+
+static int
+pselect (int nfds, fd_set *readfds, fd_set *writefds,
+ fd_set *exceptfds, const struct timespec *timeout,
+ const sigset_t *sigmask)
+{
+ struct timeval my_timeout;
+
+ /* Our particular usage of pselect() doesn't use these fields. */
+ assert(writefds == NULL);
+ assert(exceptfds == NULL);
+ assert(sigmask == NULL);
+
+ my_timeout.tv_sec = timeout->tv_sec;
+ my_timeout.tv_usec = timeout->tv_nsec / 1000;
+
+ return (select(nfds, readfds, writefds, exceptfds, &my_timeout));
+}
+
+#endif /* !HAVE_PSELECT */
+
+/* DHCPv4 defines (to be moved/shared) */
+
+#define DHCP_OFF_OPCODE 0
+#define DHCP_OFF_HTYPE 1
+#define DHCP_OFF_HLEN 2
+#define DHCP_OFF_HOPS 3
+#define DHCP_OFF_XID 4
+#define DHCP_OFF_SECS 8
+#define DHCP_OFF_FLAGS 10
+#define DHCP_OFF_CIADDR 12
+#define DHCP_OFF_YIADDR 16
+#define DHCP_OFF_SADDR 20
+#define DHCP_OFF_GIADDR 24
+#define DHCP_OFF_CHADDR 28
+#define DHCP_OFF_SNAME 44
+#define DHCP_OFF_FILE 108
+#define DHCP_OFF_COOKIE 236
+#define DHCP_OFF_OPTIONS 240
+
+#define BOOTP_OP_REQUEST 1
+#define BOOTP_OP_REPLY 2
+#define BOOTP_MIN_LEN 300
+
+#define DHCP_OP_DISCOVER 1
+#define DHCP_OP_OFFER 2
+#define DHCP_OP_REQUEST 3
+#define DHCP_OP_DECLINE 4
+#define DHCP_OP_ACK 5
+#define DHCP_OP_NAK 6
+#define DHCP_OP_RELEASE 7
+#define DHCP_OP_INFORM 8
+
+#define DHCP_HTYPE_ETHER 1
+
+#define DHCP_OPT_PAD 0
+#define DHCP_OPT_SUBNET_MASK 1
+#define DHCP_OPT_TIME_OFFSET 2
+#define DHCP_OPT_ROUTERS 3
+#define DHCP_OPT_DNS_SERVERS 6
+#define DHCP_OPT_HOST_NAME 12
+#define DHCP_OPT_DOMAIN_NAME 15
+#define DHCP_OPT_BROADCAST 28
+#define DHCP_OPT_DHCP_ADDRESS 50
+#define DHCP_OPT_DHCP_LEASE 51
+#define DHCP_OPT_DHCP_MSGTYPE 53
+#define DHCP_OPT_DHCP_SRVID 54
+#define DHCP_OPT_DHCP_PRL 55
+#define DHCP_OPT_END 255
+
+#define DHCP_OPTLEN_SRVID 6
+
+/* DHCPv6 defines (to be moved/shared) */
+
+#define DHCP6_OFF_MSGTYP 0
+#define DHCP6_OFF_XID 1
+#define DHCP6_OFF_OPTIONS 4
+
+#define DHCP6_OP_SOLICIT 1
+#define DHCP6_OP_ADVERTISE 2
+#define DHCP6_OP_REQUEST 3
+#define DHCP6_OP_REPLY 7
+
+#define DHCP6_OPT_CLIENTID 1
+#define DHCP6_OPT_SERVERID 2
+#define DHCP6_OPT_IA_NA 3
+#define DHCP6_OPT_ORO 6
+#define DHCP6_OPT_ELAPSED_TIME 8
+#define DHCP6_OPT_STATUS_CODE 13
+#define DHCP6_OPT_RAPID_COMMIT 14
+#define DHCP6_OPT_NAME_SERVERS 23
+#define DHCP6_OPT_DOMAIN_SEARCH 24
+
+#define DHCP6_ST_SUCCESS 0
+#define DHCP6_ST_NOADDRSAVAIL 2
+
+#define DHCP6_DUID_LLT 1
+#define DHCP6_DUID_EPOCH 946684800
+
+/* tail queue macros (from FreeBSD 8.2 /sys/sys/queue.h, to be moved/shared) */
+
+#define ISC_TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; \
+ struct type **tqh_last; \
+}
+
+#define ISC_TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; \
+ struct type **tqe_prev; \
+}
+
+#define ISC_TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define ISC_TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define ISC_TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define ISC_TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define ISC_TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+#define ISC_TAILQ_INIT(head) do { \
+ ISC_TAILQ_FIRST((head)) = NULL; \
+ (head)->tqh_last = &ISC_TAILQ_FIRST((head)); \
+} while (0)
+
+#define ISC_TAILQ_INSERT_HEAD(head, elm, field) do { \
+ ISC_TAILQ_NEXT((elm), field) = ISC_TAILQ_FIRST((head)); \
+ if (!ISC_TAILQ_EMPTY((head))) \
+ ISC_TAILQ_FIRST((head))->field.tqe_prev = \
+ &ISC_TAILQ_NEXT((elm), field); \
+ else \
+ (head)->tqh_last = &ISC_TAILQ_NEXT((elm), field); \
+ ISC_TAILQ_FIRST((head)) = (elm); \
+ (elm)->field.tqe_prev = &ISC_TAILQ_FIRST((head)); \
+} while (0)
+
+#define ISC_TAILQ_INSERT_TAIL(head, elm, field) do { \
+ ISC_TAILQ_NEXT((elm), field) = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &ISC_TAILQ_NEXT((elm), field); \
+} while (0)
+
+#define ISC_TAILQ_REMOVE(head, elm, field) do { \
+ if ((ISC_TAILQ_NEXT((elm), field)) != NULL) \
+ ISC_TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = ISC_TAILQ_NEXT((elm), field); \
+} while (0)
+
+#define ISC_TAILQ_FOREACH(var, head, field) \
+ for ((var) = ISC_TAILQ_FIRST((head)); \
+ (var); \
+ (var) = ISC_TAILQ_NEXT((var), field))
+
+#define ISC_TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = ISC_TAILQ_FIRST((head)); \
+ (var) && ((tvar) = ISC_TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+/*
+ * Data structures
+ */
+
+/*
+ * exchange:
+ * - per exchange values:
+ * * order (for debugging)
+ * * xid (even/odd for 4 packet exchanges)
+ * * random (for debugging)
+ * * time-stamps
+ * * server ID (for 3rd packet)
+ * * IA_NA (for IPv6 3rd packet)
+ *
+ * sent/rcvd global chains, "next to be received" on entry cache,
+ * and hash table for xid -> data structure fast matching
+ * (using the assumption collisions are unlikely, cf birthday problem)
+ */
+
+struct exchange { /* per exchange structure */
+ ISC_TAILQ_ENTRY(exchange) gchain; /* global chaining */
+ ISC_TAILQ_ENTRY(exchange) hchain; /* hash table chaining */
+ uint64_t order0, order2; /* number of this exchange */
+ uint32_t xid; /* transaction ID */
+ uint32_t rnd; /* random part */
+ struct timespec ts0, ts1, ts2, ts3; /* timespecs */
+ uint8_t *sid; /* server ID */
+ size_t sidlen; /* server ID length */
+ uint8_t *iana; /* (IPv6) IA_NA */
+ size_t ianalen; /* (IPv6) IA_NA length */
+};
+struct exchange *xnext0, *xnext2; /* next to be received */
+ISC_TAILQ_HEAD(xlist, exchange); /* exchange list */
+struct xlist xsent0, xsent2, xrcvd0, xrcvd2; /* sent and received lists */
+uint64_t xscount0, xscount2; /* sent counters */
+uint64_t xrcount0, xrcount2; /* received counters */
+caddr_t exchanges0, exchanges2; /* hash tables */
+uint32_t hashsize0, hashsize2; /* hash table sizes */
+
+/*
+ * statictics counters and accumulators
+ */
+
+uint64_t tooshort, orphans, locallimit; /* error counters */
+uint64_t latesent, compsend, latercvd; /* rate stats */
+uint64_t multrcvd, shortwait, collected[2]; /* rate stats (cont) */
+double dmin0 = 999999999., dmin2 = 999999999.; /* minimum delays */
+double dmax0 = 0., dmax2 = 0.; /* maximum delays */
+double dsum0 = 0., dsum2 = 0.; /* delay sums */
+double dsumsq0 = 0., dsumsq2 = 0.; /* square delay sums */
+
+/*
+ * command line parameters
+ */
+
+int ipversion = 0; /* IP version */
+int simple; /* DO/SA in place of DORR/SARR */
+int rate; /* rate in exchange per second */
+int report; /* delay between two reports */
+uint32_t range; /* randomization range */
+uint32_t maxrandom; /* maximum random value */
+int basecnt; /* base count */
+char *base[4]; /* bases */
+int gotnumreq; /* numreq[0] was set */
+int numreq[2]; /* number of exchange */
+int period; /* test period */
+int gotlosttime; /* losttime[0] was set */
+double losttime[2] = {1., 1.}; /* time after a request is lost */
+int gotmaxdrop; /* max{p}drop[0] was set */
+int maxdrop[2]; /* maximum number of lost requests */
+double maxpdrop[2] = { 0., 0.}; /* maximum percentage */
+char *localname; /* local address or interface */
+int isinterface; /* interface vs local address */
+int preload; /* preload exchanges */
+int aggressivity = 1; /* back to back exchanges */
+int localport; /* local port number (host endian) */
+int seeded; /* is a seed provided */
+unsigned int seed; /* randomization seed */
+int isbroadcast; /* use broadcast */
+int rapidcommit; /* add rapid commit option */
+int usefirst; /* where to take the server-ID */
+char *templatefile[2]; /* template file name */
+int xidoffset[2] = {-1, -1}; /* template offsets (xid)*/
+int rndoffset[2] = {-1, -1}; /* template offsets (random) */
+int elpoffset = -1; /* template offset (elapsed time) */
+int sidoffset = -1; /* template offset (server ID) */
+int ripoffset = -1; /* template offset (requested IP) */
+char *diags; /* diagnostic selectors */
+char *wrapped; /* wrapped command */
+char *servername; /* server */
+
+/*
+ * global variables
+ */
+
+struct sockaddr_storage localaddr; /* local socket address */
+struct sockaddr_storage serveraddr; /* server socket address */
+
+int sock; /* socket descriptor */
+int interrupted, fatal; /* to finish flags */
+
+uint8_t obuf[4096], ibuf[4096]; /* I/O buffers */
+char tbuf[8200]; /* template buffer */
+
+struct timespec boot; /* the date of boot */
+struct timespec last; /* the date of last send */
+struct timespec due; /* the date of next send */
+struct timespec dreport; /* the date of next reporting */
+struct timespec finished; /* the date of finish */
+
+uint8_t *gsrvid; /* global server id */
+size_t gsrvidlen; /* and its length */
+uint8_t gsrvidbuf[64]; /* and its storage */
+
+/* MAC address */
+uint8_t mac_prefix[6] = { 0x00, 0x0c, 0x01, 0x02, 0x03, 0x04 };
+
+/* DUID prefix */
+uint8_t *duid_prefix;
+int duid_length;
+
+/* magic cookie for BOOTP/DHCPv4 */
+uint8_t dhcp_cookie[4] = { 0x63, 0x82, 0x53, 0x63 };
+
+/*
+ * templates
+ *
+ * note: the only hard point is what are the offsets:
+ * - xid_discover4 and xid_request4: first of the 4 octet long
+ * transaction ID (default DHCP_OFF_XID = 4)
+ * - random_discover4 and random_request4: last of the 6 octet long
+ * MAC address (default DHCP_OFF_CHADDR + 6 = 28 + 6)
+ * - elapsed_request4: first of the 2 octet long secs field
+ * (default DHCP_OFF_SECS = 8, 0 means disabled)
+ * - serverid_request4: first of the 6 octet long server ID option
+ * (no default, required)
+ * - reqaddr_request4: first of the 4 octet long requested IP address
+ * option content (i.e., the address itself, btw OFFER yiaddr)
+ * (no default, required)
+ * - xid_solicit6 and xid_request6: first of the 3 octet long
+ * transaction ID (default DHCP6_OFF_XID = 1)
+ * - random_solicit6 and random_request6: last of the DUID in the
+ * client ID option (no default, required when rate is set)
+ * - elapsed_request6: first of the 2 octet long content of
+ * the option elapsed time option (no default, 0 means disabled)
+ * - serverid_request6: position where the variable length server ID
+ * option is inserted (no default, required, set to length means append)
+ * - reqaddr_request6: position where of the variable length requested
+ * IP address option is inserted (no default, required, set to
+ * length means append)
+ */
+
+size_t length_discover4;
+uint8_t template_discover4[4096];
+size_t xid_discover4;
+size_t random_discover4;
+size_t length_request4;
+uint8_t template_request4[4096];
+size_t xid_request4;
+size_t elapsed_request4;
+size_t random_request4;
+size_t serverid_request4;
+size_t reqaddr_request4;
+size_t length_solicit6;
+uint8_t template_solicit6[4096];
+size_t xid_solicit6;
+size_t random_solicit6;
+size_t length_request6;
+uint8_t template_request6[4096];
+size_t xid_request6;
+size_t elapsed_request6;
+size_t random_request6;
+size_t serverid_request6;
+size_t reqaddr_request6;
+
+
+// use definition of CLOCK_REALTIME (or lack of thereof) as an indicator
+// if the code is being compiled or Linux (or somewhere else)
+// Perhaps this should be based on OS_LINUX define?
+
+#if !defined (CLOCK_REALTIME)
+#define CLOCK_REALTIME 0
+
+/// @brief clock_gettime implementation for non-Linux systems
+///
+/// This implementation lacks nanosecond resolution. It is intended
+/// to be used on non-Linux systems that does not provide clock_gettime
+/// implementation.
+///
+/// @param clockid ignored (kept for Linux prototype compatibility)
+/// @param tp timespec structure
+///
+/// @return always zero (kept for compatibility reasons)
+int clock_gettime(int clockid, struct timespec *tp) {
+
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ tp->tv_sec = tv.tv_sec;
+ tp->tv_nsec = tv.tv_usec*1000;
+
+ return (0);
+}
+
+#endif
+
+/*
+ * initialize data structures handling exchanges
+ */
+
+void
+inits(void)
+{
+ struct xlist *bucket;
+ caddr_t p;
+ size_t len, i;
+
+ ISC_TAILQ_INIT(&xsent0);
+ ISC_TAILQ_INIT(&xsent2);
+ ISC_TAILQ_INIT(&xrcvd0);
+ ISC_TAILQ_INIT(&xrcvd2);
+
+ /// compute hashsizes
+ hashsize0 = 1024;
+ len = sizeof(*bucket) * hashsize0;
+ exchanges0 = (caddr_t)malloc(len);
+ if (exchanges0 == NULL) {
+ perror("malloc(exchanges0)");
+ exit(1);
+ }
+ for (i = 0, p = exchanges0; i < hashsize0; i++, p += sizeof(*bucket)) {
+ bucket = (struct xlist *) p;
+ ISC_TAILQ_INIT(bucket);
+ }
+ if (simple != 0)
+ return;
+ hashsize2 = 1024;
+ len = sizeof(*bucket) * hashsize2;
+ exchanges2 = (caddr_t)malloc(len);
+ if (exchanges2 == NULL) {
+ perror("malloc(exchanges2)");
+ exit(1);
+ }
+ for (i = 0, p = exchanges2; i < hashsize2; i++, p += sizeof(*bucket)) {
+ bucket = (struct xlist *) p;
+ ISC_TAILQ_INIT(bucket);
+ }
+}
+
+/*
+ * randomize the value of the given field:
+ * - offset of the field
+ * - random seed (used as it when suitable)
+ * - returns the random value which was used
+ */
+
+uint32_t
+randomize(size_t offset, uint32_t r)
+{
+ uint32_t v;
+
+ if (range == 0)
+ return 0;
+ if (range == UINT32_MAX)
+ return r;
+ if (maxrandom != 0)
+ while (r >= maxrandom)
+ r = (uint32_t) random();
+ r %= range + 1;
+ v = r;
+ v += obuf[offset];
+ obuf[offset] = v;
+ if (v < 256)
+ return r;
+ v >>= 8;
+ v += obuf[offset - 1];
+ obuf[offset - 1] = v;
+ if (v < 256)
+ return r;
+ v >>= 8;
+ v += obuf[offset - 2];
+ obuf[offset - 2] = v;
+ if (v < 256)
+ return r;
+ v >>= 8;
+ v += obuf[offset - 3];
+ obuf[offset - 3] = v;
+ return r;
+}
+
+/*
+ * receive a reply (4th packet), shared between IPv4 and IPv6:
+ * - transaction ID xid
+ * - receiving time-stamp now
+ * called from receive[46]() when the xid is odd
+ */
+
+void
+receive_reply(uint32_t xid, struct timespec *now)
+{
+ struct exchange *x, *t;
+ struct xlist *bucket;
+ uint32_t hash;
+ int checklost;
+ double delta;
+
+ /* bucket is needed even when the next cache matches */
+ hash = (xid >> 1) & (hashsize2 - 1);
+ bucket = (struct xlist *) (exchanges2 + hash * sizeof(*bucket));
+ /* try the 'next to be received' cache */
+ if ((xnext2 != NULL) && (xnext2->xid == xid)) {
+ x = xnext2;
+ goto found;
+ }
+ /* usually the lost probability is low for request/reply */
+ checklost = 1;
+ /* look for the exchange */
+ ISC_TAILQ_FOREACH_SAFE(x, bucket, hchain, t) {
+ double waited;
+
+ if (x->xid == xid)
+ goto found;
+ if (checklost <= 0)
+ continue;
+ checklost = 0;
+ /* check for a timed-out exchange */
+ waited = now->tv_sec - x->ts2.tv_sec;
+ waited += (now->tv_nsec - x->ts2.tv_nsec) / 1e9;
+ if (waited < losttime[1])
+ continue;
+ /* garbage collect timed-out exchange */
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent2, x, gchain);
+ free(x);
+ collected[1] += 1;
+ }
+ /* no match? very late or not for us */
+ orphans++;
+ return;
+
+ /* got it: update stats and move to the received queue */
+ found:
+ xrcount2++;
+ x->ts3 = *now;
+ delta = x->ts3.tv_sec - x->ts2.tv_sec;
+ delta += (x->ts3.tv_nsec - x->ts2.tv_nsec) / 1e9;
+ if (delta < dmin2)
+ dmin2 = delta;
+ if (delta > dmax2)
+ dmax2 = delta;
+ dsum2 += delta;
+ dsumsq2 += delta * delta;
+ xnext2 = ISC_TAILQ_NEXT(x, gchain);
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent2, x, gchain);
+ ISC_TAILQ_INSERT_TAIL(&xrcvd2, x, gchain);
+}
+
+/*
+ * get the DHCPv4 socket descriptor
+ * (the only complexity is broadcast enabling: there is no easy way to
+ * recognize broadcast addresses, so the command line -B flag)
+ */
+
+void
+getsock4(void)
+{
+ int ret;
+
+ /* update local port */
+ if (localport != 0) {
+ uint16_t lp = htons((uint16_t) localport);
+
+ ((struct sockaddr_in *) &localaddr)->sin_port = lp;
+ }
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0) {
+ perror("socket");
+ exit(1);
+ }
+ ret = bind(sock,
+ (struct sockaddr *) &localaddr,
+ sizeof(struct sockaddr_in));
+ if (ret < 0) {
+ perror("bind");
+ exit(1);
+ }
+ /* enable broadcast if needed or required */
+ if (isbroadcast != 0) {
+ int on = 1;
+
+ ret = setsockopt(sock,
+ SOL_SOCKET, SO_BROADCAST,
+ &on, sizeof(on));
+ if (ret < 0) {
+ perror("setsockopt(SO_BROADCAST)");
+ exit(1);
+ }
+ }
+}
+
+/*
+ * build a DHCPv4 DISCOVER from a relay template
+ * (implicit parameters are the local (giaddr) and MAC addresses (chaddr))
+ * (assume the link is Ethernet)
+ */
+
+void
+build_template_discover4(void)
+{
+ uint8_t *p = template_discover4;
+
+ length_discover4 = BOOTP_MIN_LEN;
+ xid_discover4 = DHCP_OFF_XID;
+ random_discover4 = DHCP_OFF_CHADDR + 6;
+ /* opcode */
+ p[DHCP_OFF_OPCODE] = BOOTP_OP_REQUEST;
+ /* hardware address type */
+ p[DHCP_OFF_HTYPE] = DHCP_HTYPE_ETHER;
+ /* hardware address length */
+ p[DHCP_OFF_HLEN] = 6;
+ /* hops */
+ p[DHCP_OFF_HOPS] = 1;
+ /* gateway address */
+ memcpy(p + DHCP_OFF_GIADDR,
+ &((struct sockaddr_in *) &localaddr)->sin_addr,
+ 4);
+ /* hardware address */
+ memcpy(p + DHCP_OFF_CHADDR, mac_prefix, 6);
+ /* cookie */
+ memcpy(p + DHCP_OFF_COOKIE, dhcp_cookie, 4);
+ /* options */
+ p += DHCP_OFF_OPTIONS;
+ /* inline DHCP message type */
+ *p++ = DHCP_OPT_DHCP_MSGTYPE;
+ *p++ = 1;
+ *p++ = DHCP_OP_DISCOVER;
+ /* inline DHCP parameter request list (default) */
+ *p++ = DHCP_OPT_DHCP_PRL;
+ *p++ = 7;
+ *p++ = DHCP_OPT_SUBNET_MASK;
+ *p++ = DHCP_OPT_BROADCAST;
+ *p++ = DHCP_OPT_TIME_OFFSET;
+ *p++ = DHCP_OPT_ROUTERS;
+ *p++ = DHCP_OPT_DOMAIN_NAME;
+ *p++ = DHCP_OPT_DNS_SERVERS;
+ *p++ = DHCP_OPT_HOST_NAME;
+ /* end */
+ *p = DHCP_OPT_END;
+}
+
+/*
+ * get a DHCPv4 client/relay first packet (usually a DISCOVER) template
+ * from the file given in the command line (-T<template-file>)
+ * and xid/rnd offsets (-X<xid-offset> and -O<random-offset>)
+ */
+
+void
+get_template_discover4(void)
+{
+ uint8_t *p = template_discover4;
+ int fd, cc, i, j;
+
+ fd = open(templatefile[0], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open(%s): %s\n",
+ templatefile[0], strerror(errno));
+ exit(2);
+ }
+ cc = read(fd, tbuf, sizeof(tbuf));
+ (void) close(fd);
+ if (cc < 0) {
+ fprintf(stderr, "read(%s): %s\n",
+ templatefile[0], strerror(errno));
+ exit(1);
+ }
+ if (cc < 100) {
+ fprintf(stderr, "file '%s' too short\n", templatefile[0]);
+ exit(2);
+ }
+ if (cc > 8193) {
+ fprintf(stderr,"file '%s' too large\n", templatefile[0]);
+ exit(2);
+ }
+ j = 0;
+ for (i = 0; i < cc; i++) {
+ if (isspace((int) tbuf[i]))
+ continue;
+ if (!isxdigit((int) tbuf[i])) {
+ fprintf(stderr,
+ "illegal char[%d]='%c' in file '%s'\n",
+ i, (int) tbuf[i], templatefile[0]);
+ exit(2);
+ }
+ tbuf[j] = tbuf[i];
+ j++;
+ }
+ cc = j;
+ if ((cc & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in file '%s'\n",
+ templatefile[0]);
+ exit(2);
+ }
+ length_discover4 = cc >> 1;
+ for (i = 0; i < cc; i += 2)
+ (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
+ if (xidoffset[0] >= 0)
+ xid_discover4 = (size_t) xidoffset[0];
+ else
+ xid_discover4 = DHCP_OFF_XID;
+ if (xid_discover4 + 4 > length_discover4) {
+ fprintf(stderr,
+ "xid (at %zu) outside the template (length %zu)?\n",
+ xid_discover4, length_discover4);
+ exit(2);
+ }
+ if (rndoffset[0] >= 0)
+ random_discover4 = (size_t) rndoffset[0];
+ else
+ random_discover4 = DHCP_OFF_CHADDR + 6;
+ if (random_discover4 > length_discover4) {
+ fprintf(stderr,
+ "random (at %zu) outside the template (length %zu)?\n",
+ random_discover4, length_discover4);
+ exit(2);
+ }
+}
+
+/*
+ * build a DHCPv4 REQUEST from a relay template
+ * (implicit parameters are the local (giaddr) and MAC addresses (chaddr))
+ * (assume the link is Ethernet)
+ */
+
+void
+build_template_request4(void)
+{
+ uint8_t *p = template_request4;
+
+ length_request4 = BOOTP_MIN_LEN;
+ xid_request4 = DHCP_OFF_XID;
+ elapsed_request4 = DHCP_OFF_SECS;
+ random_request4 = DHCP_OFF_CHADDR + 6;
+ /* opcode */
+ p[DHCP_OFF_OPCODE] = BOOTP_OP_REQUEST;
+ /* hardware address type */
+ p[DHCP_OFF_HTYPE] = DHCP_HTYPE_ETHER;
+ /* hardware address length */
+ p[DHCP_OFF_HLEN] = 6;
+ /* hops */
+ p[DHCP_OFF_HOPS] = 1;
+ /* gateway address */
+ memcpy(p + DHCP_OFF_GIADDR,
+ &((struct sockaddr_in *) &localaddr)->sin_addr,
+ 4);
+ /* hardware address */
+ memcpy(p + DHCP_OFF_CHADDR, mac_prefix, 6);
+ /* cookie */
+ memcpy(p + DHCP_OFF_COOKIE, dhcp_cookie, 4);
+ /* options */
+ p += DHCP_OFF_OPTIONS;
+ /* inline DHCP message type */
+ *p++ = DHCP_OPT_DHCP_MSGTYPE;
+ *p++ = 1;
+ *p++ = DHCP_OP_REQUEST;
+ /* place for DHCP server id (option) */
+ serverid_request4 = p - template_request4;
+ p += DHCP_OPTLEN_SRVID;
+ /* place for DHCP requested IP address (address) */
+ *p++ = DHCP_OPT_DHCP_ADDRESS;
+ *p++ = 4;
+ reqaddr_request4 = p - template_request4;
+ p += 4;
+ /* inline DHCP parameter request list (default) */
+ *p++ = DHCP_OPT_DHCP_PRL;
+ *p++ = 7;
+ *p++ = DHCP_OPT_SUBNET_MASK;
+ *p++ = DHCP_OPT_BROADCAST;
+ *p++ = DHCP_OPT_TIME_OFFSET;
+ *p++ = DHCP_OPT_ROUTERS;
+ *p++ = DHCP_OPT_DOMAIN_NAME;
+ *p++ = DHCP_OPT_DNS_SERVERS;
+ *p++ = DHCP_OPT_HOST_NAME;
+ /* end */
+ *p = DHCP_OPT_END;
+}
+
+/*
+ * get a DHCPv4 client/relay third packet (usually a REQUEST) template
+ * from the file given in the command line (-T<template-file>)
+ * and offsets (-X,-O,-E,-S,-I).
+ */
+
+void
+get_template_request4(void)
+{
+ uint8_t *p = template_request4;
+ int fd, cc, i, j;
+
+ fd = open(templatefile[1], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open(%s): %s\n",
+ templatefile[1], strerror(errno));
+ exit(2);
+ }
+ cc = read(fd, tbuf, sizeof(tbuf));
+ (void) close(fd);
+ if (cc < 0) {
+ fprintf(stderr, "read(%s): %s\n",
+ templatefile[1], strerror(errno));
+ exit(1);
+ }
+ if (cc < 100) {
+ fprintf(stderr, "file '%s' too short\n", templatefile[1]);
+ exit(2);
+ }
+ if (cc > 8193) {
+ fprintf(stderr,"file '%s' too large\n", templatefile[1]);
+ exit(2);
+ }
+ j = 0;
+ for (i = 0; i < cc; i++) {
+ if (isspace((int) tbuf[i]))
+ continue;
+ if (!isxdigit((int) tbuf[i])) {
+ fprintf(stderr,
+ "illegal char[%d]='%c' in file '%s'\n",
+ i, (int) tbuf[i], templatefile[1]);
+ exit(2);
+ }
+ tbuf[j] = tbuf[i];
+ j++;
+ }
+ cc = j;
+ if ((cc & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in file '%s'\n",
+ templatefile[1]);
+ exit(2);
+ }
+ length_request4 = cc >> 1;
+ for (i = 0; i < cc; i += 2)
+ (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
+ if (xidoffset[1] >= 0)
+ xid_request4 = (size_t) xidoffset[1];
+ else
+ xid_request4 = DHCP_OFF_XID;
+ if (xid_request4 + 4 > length_request4) {
+ fprintf(stderr,
+ "xid (at %zu) outside the template (length %zu)?\n",
+ xid_request4, length_request4);
+ exit(2);
+ }
+ if (rndoffset[1] >= 0)
+ random_request4 = (size_t) rndoffset[1];
+ else
+ random_request4 = DHCP_OFF_CHADDR + 6;
+ if (random_request4 > length_request4) {
+ fprintf(stderr,
+ "random (at %zu) outside the template (length %zu)?\n",
+ random_request4, length_request4);
+ exit(2);
+ }
+ if (elpoffset >= 0)
+ elapsed_request4 = (size_t) elpoffset;
+ else
+ elapsed_request4 = DHCP_OFF_SECS;
+ if (elapsed_request4 + 2 > length_request4) {
+ fprintf(stderr,
+ "secs (at %zu) outside the template (length %zu)?\n",
+ elapsed_request4, length_request4);
+ exit(2);
+ }
+ serverid_request4 = (size_t) sidoffset;
+ if (serverid_request4 + 6 > length_request4) {
+ fprintf(stderr,
+ "server-id option (at %zu) outside the template "
+ "(length %zu)?\n",
+ serverid_request4, length_request4);
+ exit(2);
+ }
+ reqaddr_request4 = (size_t) ripoffset;
+ if (reqaddr_request4 + 4 > length_request4) {
+ fprintf(stderr,
+ "requested-ip-address option (at %zu) outside "
+ "the template (length %zu)?\n",
+ reqaddr_request4, length_request4);
+ exit(2);
+ }
+}
+
+/*
+ * send the DHCPv4 REQUEST third packet
+ * (the transaction ID is odd)
+ * (TODO: check for errors in the OFFER)
+ */
+
+void
+send_request4(struct exchange *x0)
+{
+ struct exchange *x;
+ struct xlist *bucket;
+ uint32_t hash;
+ ssize_t ret;
+
+ x = (struct exchange *) malloc(sizeof(*x));
+ if (x == NULL) {
+ locallimit++;
+ perror("send2");
+ return;
+ }
+
+ memcpy(x, x0, sizeof(*x));
+ x->order2 = xscount2++;
+ x->xid |= 1;
+ hash = x->xid >> 1;
+
+ ISC_TAILQ_INSERT_TAIL(&xsent2, x, gchain);
+ hash &= hashsize2 - 1;
+ bucket = (struct xlist *) (exchanges2 + hash * sizeof(*bucket));
+ ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
+
+ memcpy(obuf, template_request4, length_request4);
+ /* xid */
+ memcpy(obuf + xid_request4, &x->xid, 4);
+ /* random */
+ randomize(random_request4, x->rnd);
+ /* secs */
+ if (elapsed_request4 > 0) {
+ int secs;
+
+ secs = x->ts1.tv_sec - x->ts0.tv_sec;
+ if (x->ts1.tv_nsec < x->ts0.tv_nsec)
+ secs += 1;
+ if (secs > 0) {
+ obuf[elapsed_request4] = secs >> 8;
+ obuf[elapsed_request4 + 1] = secs & 0xff;
+ }
+ }
+ /* server ID */
+ memcpy(obuf + serverid_request4, x->sid, x->sidlen);
+ /* requested IP address */
+ memcpy(obuf + reqaddr_request4, ibuf + DHCP_OFF_YIADDR, 4);
+
+ /* timestamp */
+ ret = clock_gettime(CLOCK_REALTIME, &x->ts2);
+ if (ret < 0) {
+ perror("clock_gettime(send2)");
+ fatal = 1;
+ return;
+ }
+ ret = sendto(sock, obuf, length_request4, 0,
+ (struct sockaddr *) &serveraddr,
+ sizeof(struct sockaddr_in));
+ if (ret >= 0)
+ return;
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK) ||
+ (errno == ENOBUFS) || (errno == ENOMEM))
+ locallimit++;
+ perror("send2");
+}
+
+/*
+ * send the DHCPv4 DISCOVER first packet
+ * (for 4-exchange, the transaction ID xid is even)
+ */
+
+int
+send4(void)
+{
+ struct exchange *x;
+ struct xlist *bucket;
+ uint32_t hash;
+ ssize_t ret;
+
+ x = (struct exchange *) malloc(sizeof(*x));
+ if (x == NULL)
+ return -ENOMEM;
+
+ memset(x, 0, sizeof(*x));
+ x->order0 = xscount0++;
+ hash = x->rnd = (uint32_t) random();
+ if (simple == 0)
+ x->xid = hash << 1;
+ else
+ x->xid = hash;
+
+ ISC_TAILQ_INSERT_TAIL(&xsent0, x, gchain);
+ hash &= hashsize0 - 1;
+ bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
+ ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
+
+ memcpy(obuf, template_discover4, length_discover4);
+ /* xid */
+ memcpy(obuf + xid_discover4, &x->xid, 4);
+ /* random */
+ x->rnd = randomize(random_discover4, x->rnd);
+ /* timestamp */
+ ret = clock_gettime(CLOCK_REALTIME, &last);
+ if (ret < 0) {
+ perror("clock_gettime(send)");
+ fatal = 1;
+ return -errno;
+ }
+ x->ts0 = last;
+ errno = 0;
+ ret = sendto(sock, obuf, length_discover4, 0,
+ (struct sockaddr *) &serveraddr,
+ sizeof(struct sockaddr_in));
+ if (ret == (ssize_t) length_discover4)
+ return 0;
+ return -errno;
+}
+
+/*
+ * scan a DHCPv4 OFFER to get its server-id option
+ */
+
+int
+scan_for_srvid4(struct exchange *x, size_t cc)
+{
+ size_t off = DHCP_OFF_OPTIONS;
+
+ for (;;) {
+ if (off + DHCP_OPTLEN_SRVID > cc) {
+ fprintf(stderr, "truncated\n");
+ return -1;
+ }
+ if (ibuf[off] == DHCP_OPT_DHCP_SRVID)
+ break;
+ if (ibuf[off] == DHCP_OPT_END) {
+ fprintf(stderr, "server-id not found\n");
+ return -1;
+ }
+ if (ibuf[off] == DHCP_OPT_PAD) {
+ off++;
+ continue;
+ }
+ off += 2 + ibuf[off + 1];
+ }
+ /* check length */
+ if (ibuf[off + 1] != DHCP_OPTLEN_SRVID - 2) {
+ fprintf(stderr,
+ "bad server-id length (%hhu)\n",
+ ibuf[off + 1]);
+ return -1;
+ }
+ /* cache it in the global variables when required and not yet done */
+ if ((usefirst != 0) && (gsrvid == NULL)) {
+ memcpy(gsrvidbuf, ibuf + off, DHCP_OPTLEN_SRVID);
+ gsrvid = gsrvidbuf;
+ gsrvidlen = DHCP_OPTLEN_SRVID;
+ }
+ x->sid = ibuf + off;
+ x->sidlen = DHCP_OPTLEN_SRVID;
+ return 0;
+}
+
+/*
+ * receive a DHCPv4 packet
+ */
+
+void
+receive4(void)
+{
+ struct exchange *x, *t;
+ struct xlist *bucket;
+ struct timespec now;
+ ssize_t cc;
+ uint32_t xid, hash;
+ int checklost = 0;
+ double delta;
+
+ cc = recv(sock, ibuf, sizeof(ibuf), 0);
+ if (cc < 0) {
+ if ((errno == EAGAIN) ||
+ (errno == EWOULDBLOCK) ||
+ (errno == EINTR))
+ return;
+ perror("recv");
+ fatal = 1;
+ return;
+ }
+ /* enforce a reasonable length */
+ if (cc < BOOTP_MIN_LEN) {
+ tooshort++;
+ return;
+ }
+ /* must be a BOOTP REPLY */
+ if (ibuf[DHCP_OFF_OPCODE] != BOOTP_OP_REPLY)
+ return;
+ if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
+ perror("clock_gettime(receive)");
+ fatal = 1;
+ return;
+ }
+ memcpy(&xid, ibuf + xid_discover4, 4);
+ /* 4-packet exchange even/odd xid */
+ if (simple == 0) {
+ if ((xid & 1) != 0) {
+ receive_reply(xid, &now);
+ return;
+ }
+ hash = (xid >> 1) & (hashsize0 - 1);
+ } else
+ hash = xid & (hashsize0 - 1);
+ /* now it is the second packet, get the bucket which is needed */
+ bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
+ /* try the 'next to be received' cache */
+ if ((xnext0 != NULL) && (xnext0->xid == xid)) {
+ x = xnext0;
+ goto found;
+ }
+ /* if the rate is not illimited, garbage collect up to 3
+ timed-out exchanges */
+ if (rate != 0)
+ checklost = 3;
+ /* look for the exchange */
+ ISC_TAILQ_FOREACH_SAFE(x, bucket, hchain, t) {
+ double waited;
+
+ if (x->xid == xid)
+ goto found;
+ if (checklost <= 0)
+ continue;
+ /* check for a timed-out exchange */
+ waited = now.tv_sec - x->ts0.tv_sec;
+ waited += (now.tv_nsec - x->ts0.tv_nsec) / 1e9;
+ if (waited < losttime[0]) {
+ checklost = 0;
+ continue;
+ }
+ /* garbage collect timed-out exchange */
+ checklost--;
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent0, x, gchain);
+ free(x);
+ collected[0] += 1;
+ }
+ /* no match? very late or not for us */
+ orphans++;
+ return;
+
+ /* got it: update stats and move to the received queue */
+ found:
+ xrcount0++;
+ x->ts1 = now;
+ delta = x->ts1.tv_sec - x->ts0.tv_sec;
+ delta += (x->ts1.tv_nsec - x->ts0.tv_nsec) / 1e9;
+ if (delta < dmin0)
+ dmin0 = delta;
+ if (delta > dmax0)
+ dmax0 = delta;
+ dsum0 += delta;
+ dsumsq0 += delta * delta;
+ xnext0 = ISC_TAILQ_NEXT(x, gchain);
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent0, x, gchain);
+ ISC_TAILQ_INSERT_TAIL(&xrcvd0, x, gchain);
+ /* if the exchange is not finished, go to the second part */
+ if (simple == 0) {
+ int ret = 0;
+
+ /* the server-ID option is needed */
+ if ((usefirst != 0) && (gsrvid != NULL)) {
+ x->sid = gsrvid;
+ x->sidlen = gsrvidlen;
+ } else
+ ret = scan_for_srvid4(x, cc);
+ if (ret >= 0)
+ send_request4(x);
+ }
+}
+
+/*
+ * get the DHCPv6 socket descriptor
+ */
+
+void
+getsock6(void)
+{
+ struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &serveraddr;
+ int ret;
+
+ /* update local port */
+ if (localport != 0) {
+ uint16_t lp = htons((uint16_t) localport);
+
+ ((struct sockaddr_in6 *) &localaddr)->sin6_port = lp;
+ }
+ sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0) {
+ perror("socket");
+ exit(1);
+ }
+ ret = bind(sock,
+ (struct sockaddr *) &localaddr,
+ sizeof(struct sockaddr_in6));
+ if (ret < 0) {
+ perror("Failed to bind v6 socket to local-link address");
+ exit(1);
+ }
+ /* perform the multicast stuff when the destination is multicast */
+ if (IN6_IS_ADDR_MULTICAST(&s6->sin6_addr)) {
+ int hops = 1;
+
+ ret = setsockopt(sock,
+ IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ &hops, sizeof(hops));
+ if (ret < 0) {
+ perror("setsockopt(IPV6_MULTICAST_HOPS)");
+ exit(1);
+ }
+ }
+ if (isinterface && IN6_IS_ADDR_MULTICAST(&s6->sin6_addr)) {
+ unsigned idx = if_nametoindex(localname);
+
+ if (idx == 0) {
+ fprintf(stderr,
+ "if_nametoindex(%s) failed\n",
+ localname);
+ exit(1);
+ }
+ ret = setsockopt(sock,
+ IPPROTO_IPV6, IPV6_MULTICAST_IF,
+ &idx, sizeof(idx));
+ if (ret < 0) {
+ perror("setsockopt(IPV6_MULTICAST_IF)");
+ exit(1);
+ }
+ }
+}
+
+/*
+ * build a DHCPv6 SOLICIT template
+ * (implicit parameter is the DUID, don't assume an Ethernet link)
+ */
+
+void
+build_template_solicit6(void)
+{
+ uint8_t *p = template_solicit6;
+
+ xid_solicit6 = DHCP6_OFF_XID;
+ /* message type */
+ p[DHCP6_OFF_MSGTYP] = DHCP6_OP_SOLICIT;
+ /* options */
+ p += DHCP6_OFF_OPTIONS;
+ /* elapsed time */
+ p[1] = DHCP6_OPT_ELAPSED_TIME;
+ p[3] = 2;
+ p += 6;
+ /* rapid commit */
+ if (rapidcommit != 0) {
+ p[1] = DHCP6_OPT_RAPID_COMMIT;
+ p += 4;
+ }
+ /* client ID */
+ p[1] = DHCP6_OPT_CLIENTID;
+ p[3] = duid_length;
+ memcpy(p + 4, duid_prefix, duid_length);
+ p += 4 + duid_length;
+ random_solicit6 = p - template_solicit6;
+ /* option request option */
+ p[1] = DHCP6_OPT_ORO;
+ p[3] = 4;
+ p[5] = DHCP6_OPT_NAME_SERVERS;
+ p[7] = DHCP6_OPT_DOMAIN_SEARCH;
+ p += 8;
+ /* IA_NA (IAID = 1, T1 = 3600, T2 = 5400) */
+ p[1] = DHCP6_OPT_IA_NA;
+ p[3] = 12;
+ p[7] = 1;
+ p[10] = 3600 >> 8;
+ p[11] = 3600 & 0xff;
+ p[14] = 5400 >> 8;
+ p[15] = 5400 & 0xff;
+ p += 16;
+ /* set length */
+ length_solicit6 = p - template_solicit6;
+}
+
+/*
+ * get a DHCPv6 first packet (usually a SOLICIT) template
+ * from the file given in the command line (-T<template-file>)
+ * and xid/rnd offsets (-X<xid-offset> and -O<random-offset>)
+ */
+
+void
+get_template_solicit6(void)
+{
+ uint8_t *p = template_solicit6;
+ int fd, cc, i, j;
+
+ fd = open(templatefile[0], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open(%s): %s\n",
+ templatefile[0], strerror(errno));
+ exit(2);
+ }
+ cc = read(fd, tbuf, sizeof(tbuf));
+ (void) close(fd);
+ if (cc < 0) {
+ fprintf(stderr, "read(%s): %s\n",
+ templatefile[0], strerror(errno));
+ exit(1);
+ }
+ if (cc < 10) {
+ fprintf(stderr, "file '%s' too short\n", templatefile[0]);
+ exit(2);
+ }
+ if (cc > 8193) {
+ fprintf(stderr,"file '%s' too large\n", templatefile[0]);
+ exit(2);
+ }
+ j = 0;
+ for (i = 0; i < cc; i++) {
+ if (isspace((int) tbuf[i]))
+ continue;
+ if (!isxdigit((int) tbuf[i])) {
+ fprintf(stderr,
+ "illegal char[%d]='%c' in file '%s'\n",
+ i, (int) tbuf[i], templatefile[0]);
+ exit(2);
+ }
+ tbuf[j] = tbuf[i];
+ j++;
+ }
+ cc = j;
+ if ((cc & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in file '%s'\n",
+ templatefile[0]);
+ exit(2);
+ }
+ length_solicit6 = cc >> 1;
+ for (i = 0; i < cc; i += 2)
+ (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
+ if (xidoffset[0] >= 0)
+ xid_solicit6 = (size_t) xidoffset[0];
+ else
+ xid_solicit6 = DHCP6_OFF_XID;
+ if (xid_solicit6 + 3 > length_solicit6) {
+ fprintf(stderr,
+ "xid (at %zu) is outside the template (length %zu)?\n",
+ xid_solicit6, length_solicit6);
+ exit(2);
+ }
+ if (rndoffset[0] >= 0)
+ random_solicit6 = (size_t) rndoffset[0];
+ else
+ random_solicit6 = 0;
+ if (random_solicit6 > length_solicit6) {
+ fprintf(stderr,
+ "random (at %zu) outside the template (length %zu)?\n",
+ random_solicit6, length_solicit6);
+ exit(2);
+ }
+}
+
+/*
+ * build a DHCPv6 REQUEST template
+ * (implicit parameter is the DUID, don't assume an Ethernet link)
+ */
+
+void
+build_template_request6(void)
+{
+ uint8_t *p = template_request6;
+
+ xid_request6 = DHCP6_OFF_XID;
+ /* message type */
+ p[DHCP6_OFF_MSGTYP] = DHCP6_OP_REQUEST;
+ /* options */
+ p += DHCP6_OFF_OPTIONS;
+ /* elapsed time */
+ p[1] = DHCP6_OPT_ELAPSED_TIME;
+ p[3] = 2;
+ p += 4;
+ elapsed_request6 = p - template_request6;
+ p += 2;
+ /* client ID */
+ p[1] = DHCP6_OPT_CLIENTID;
+ p[3] = duid_length;
+ memcpy(p + 4, duid_prefix, duid_length);
+ p += 4 + duid_length;
+ random_request6 = p - template_request6;
+ /* option request option */
+ p[1] = DHCP6_OPT_ORO;
+ p[3] = 4;
+ p[5] = DHCP6_OPT_NAME_SERVERS;
+ p[7] = DHCP6_OPT_DOMAIN_SEARCH;
+ p += 8;
+ /* server ID and IA_NA */
+ serverid_request6 = p - template_request6;
+ reqaddr_request6 = p - template_request6;
+ /* set length */
+ length_request6 = p - template_request6;
+}
+
+/*
+ * get a DHCPv6 third packet (usually a REQUEST) template
+ * from the file given in the command line (-T<template-file>)
+ * and offsets (-X,-O,-E,-S,-I).
+ */
+
+void
+get_template_request6(void)
+{
+ uint8_t *p = template_request6;
+ int fd, cc, i, j;
+
+ fd = open(templatefile[1], O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open(%s): %s\n",
+ templatefile[1], strerror(errno));
+ exit(2);
+ }
+ cc = read(fd, tbuf, sizeof(tbuf));
+ (void) close(fd);
+ if (cc < 0) {
+ fprintf(stderr, "read(%s): %s\n",
+ templatefile[1], strerror(errno));
+ exit(1);
+ }
+ if (cc < 10) {
+ fprintf(stderr, "file '%s' too short\n", templatefile[1]);
+ exit(2);
+ }
+ if (cc > 8193) {
+ fprintf(stderr,"file '%s' too large\n", templatefile[1]);
+ exit(2);
+ }
+ j = 0;
+ for (i = 0; i < cc; i++) {
+ if (isspace((int) tbuf[i]))
+ continue;
+ if (!isxdigit((int) tbuf[i])) {
+ fprintf(stderr,
+ "illegal char[%d]='%c' in file '%s'\n",
+ i, (int) tbuf[i], templatefile[1]);
+ exit(2);
+ }
+ tbuf[j] = tbuf[i];
+ j++;
+ }
+ cc = j;
+ if ((cc & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in file '%s'\n",
+ templatefile[1]);
+ exit(2);
+ }
+ length_request6 = cc >> 1;
+ for (i = 0; i < cc; i += 2)
+ (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
+ if (xidoffset[1] >= 0)
+ xid_request6 = (size_t) xidoffset[1];
+ else
+ xid_request6 = DHCP6_OFF_XID;
+ if (xid_request6 + 3 > length_request6) {
+ fprintf(stderr,
+ "xid (at %zu) is outside the template (length %zu)?\n",
+ xid_request6, length_request6);
+ exit(2);
+ }
+ if (rndoffset[1] >= 0)
+ random_request6 = (size_t) rndoffset[1];
+ else
+ random_request6 = 0;
+ if (random_request6 > length_request6) {
+ fprintf(stderr,
+ "random (at %zu) outside the template (length %zu)?\n",
+ random_request6, length_request6);
+ exit(2);
+ }
+ if (elpoffset >= 0)
+ elapsed_request6 = (size_t) elpoffset;
+ if (elapsed_request6 + 2 > length_request6) {
+ fprintf(stderr,
+ "secs (at %zu) outside the template (length %zu)?\n",
+ elapsed_request6, length_request6);
+ exit(2);
+ }
+ serverid_request6 = (size_t) sidoffset;
+ if (serverid_request6 > length_request6) {
+ fprintf(stderr,
+ "server-id option (at %zu) outside the template "
+ "(length %zu)?\n",
+ serverid_request6, length_request6);
+ exit(2);
+ }
+ reqaddr_request6 = (size_t) ripoffset;
+ if (reqaddr_request6 > length_request6) {
+ fprintf(stderr,
+ "requested-ip-address option (at %zu) outside "
+ "the template (length %zu)?\n",
+ reqaddr_request6, length_request6);
+ exit(2);
+ }
+}
+
+/*
+ * send the DHCPv6 REQUEST third packet
+ * (the transaction ID is odd)
+ * (TODO: check for errors in the ADVERTISE)
+ */
+
+void
+send_request6(struct exchange *x0)
+{
+ struct exchange *x;
+ struct xlist *bucket;
+ size_t len;
+ uint32_t hash;
+ ssize_t ret;
+
+ x = (struct exchange *) malloc(sizeof(*x));
+ if (x == NULL) {
+ locallimit++;
+ perror("send2");
+ return;
+ }
+
+ memcpy(x, x0, sizeof(*x));
+ x->order2 = xscount2++;
+ x->xid |= 1;
+ hash = x->xid >> 1;
+
+ ISC_TAILQ_INSERT_TAIL(&xsent2, x, gchain);
+ hash &= hashsize2 - 1;
+ bucket = (struct xlist *) (exchanges2 + hash * sizeof(*bucket));
+ ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
+
+ len = length_request6;
+ memcpy(obuf, template_request6, len);
+ /* xid */
+ obuf[xid_request6] = x->xid >> 16;
+ obuf[xid_request6 + 1] = x->xid >> 8;
+ obuf[xid_request6 + 2] = x->xid;
+ /* random */
+ randomize(random_request6, x->rnd);
+ /* elapsed time */
+ if (elapsed_request6 > 0) {
+ int et;
+
+ et = (x->ts1.tv_sec - x->ts0.tv_sec) * 100;
+ et += (x->ts1.tv_nsec - x->ts0.tv_nsec) / 10000000;
+ if (et > 65535) {
+ obuf[elapsed_request6] = 0xff;
+ obuf[elapsed_request6 + 1] = 0xff;
+ } else if (et > 0) {
+ obuf[elapsed_request6] = et >> 8;
+ obuf[elapsed_request6 + 1] = et & 0xff;
+ }
+ }
+ /* server ID */
+ if (serverid_request6 < length_request6)
+ memmove(obuf + serverid_request6 + x->sidlen,
+ obuf + serverid_request6,
+ x->sidlen);
+ memcpy(obuf + serverid_request6, x->sid, x->sidlen);
+ len += x->sidlen;
+ /* IA_NA */
+ if (reqaddr_request6 < serverid_request6) {
+ memmove(obuf + reqaddr_request6 + x->ianalen,
+ obuf + reqaddr_request6,
+ x->ianalen);
+ memcpy(obuf + reqaddr_request6, x->iana, x->ianalen);
+ } else if (reqaddr_request6 < length_request6) {
+ memmove(obuf + reqaddr_request6 + x->sidlen + x->ianalen,
+ obuf + reqaddr_request6 + x->sidlen,
+ x->ianalen);
+ memcpy(obuf + reqaddr_request6+ x->sidlen,
+ x->iana,
+ x->ianalen);
+ } else
+ memcpy(obuf + len, x->iana, x->ianalen);
+ len += x->ianalen;
+
+ /* timestamp */
+ ret = clock_gettime(CLOCK_REALTIME, &x->ts2);
+ if (ret < 0) {
+ perror("clock_gettime(send2)");
+ fatal = 1;
+ return;
+ }
+ ret = sendto(sock, obuf, len, 0,
+ (struct sockaddr *) &serveraddr,
+ sizeof(struct sockaddr_in6));
+ if (ret >= 0)
+ return;
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK) ||
+ (errno == ENOBUFS) || (errno == ENOMEM))
+ locallimit++;
+ perror("send2");
+}
+
+/*
+ * send the DHCPv6 SOLICIT first packet
+ * (for 4-exchange, the transaction ID xid is even)
+ */
+
+int
+send6(void)
+{
+ struct exchange *x;
+ struct xlist *bucket;
+ uint32_t hash;
+ ssize_t ret;
+
+ x = (struct exchange *) malloc(sizeof(*x));
+ if (x == NULL)
+ return -ENOMEM;
+
+ memset(x, 0, sizeof(*x));
+ x->order0 = xscount0++;
+ hash = x->rnd = (uint32_t) random();
+ if (simple == 0)
+ x->xid = (hash << 1) & 0x00ffffff;
+ else
+ x->xid = hash & 0x00ffffff;
+
+ ISC_TAILQ_INSERT_TAIL(&xsent0, x, gchain);
+ hash &= hashsize0 - 1;
+ bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
+ ISC_TAILQ_INSERT_TAIL(bucket, x, hchain);
+
+ memcpy(obuf, template_solicit6, length_solicit6);
+ /* xid */
+ obuf[xid_solicit6] = x->xid >> 16;
+ obuf[xid_solicit6 + 1] = x->xid >> 8;
+ obuf[xid_solicit6 + 2] = x->xid;
+ /* random */
+ x->rnd = randomize(random_solicit6, x->rnd);
+
+ /* timestamp */
+ ret = clock_gettime(CLOCK_REALTIME, &last);
+ if (ret < 0) {
+ perror("clock_gettime(send)");
+ fatal = 1;
+ return -errno;
+ }
+ x->ts0 = last;
+ errno = 0;
+ ret = sendto(sock, obuf, length_solicit6, 0,
+ (struct sockaddr *) &serveraddr,
+ sizeof(struct sockaddr_in6));
+ if (ret == (ssize_t) length_solicit6)
+ return 0;
+ return -errno;
+}
+
+/*
+ * scan a DHCPv6 ADVERTISE to get its server-id option
+ */
+
+int
+scan_for_srvid6(struct exchange *x, size_t cc)
+{
+ size_t off = DHCP6_OFF_OPTIONS, len;
+
+ for (;;) {
+ if (off + 4 > cc) {
+ fprintf(stderr, "server-id not found\n");
+ return -1;
+ }
+ if ((ibuf[off] == 0) && (ibuf[off + 1] == DHCP6_OPT_SERVERID))
+ break;
+ off += 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
+ }
+ len = 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
+ /* cache it in the global variables when required and not yet done */
+ if ((usefirst != 0) && (gsrvid == NULL)) {
+ if (len <= sizeof(gsrvidbuf)) {
+ memcpy(gsrvidbuf, ibuf + off, len);
+ gsrvid = gsrvidbuf;
+ gsrvidlen = len;
+ } else {
+ gsrvid = (uint8_t *) malloc(len);
+ if (gsrvid == NULL) {
+ perror("malloc(gsrvid");
+ return -1;
+ }
+ memcpy(gsrvid, ibuf + off, len);
+ gsrvidlen = len;
+ }
+ }
+ x->sid = ibuf + off;
+ x->sidlen = len;
+ return 0;
+}
+
+/*
+ * scan a DHCPv6 ADVERTISE to get its IA_NA option
+ * (TODO: check for errors)
+ */
+
+int
+scan_for_ia_na(struct exchange *x, size_t cc)
+{
+ size_t off = DHCP6_OFF_OPTIONS, len;
+
+ for (;;) {
+ if (off + 4 > cc) {
+ fprintf(stderr, "ia-na not found\n");
+ return -1;
+ }
+ if ((ibuf[off] == 0) && (ibuf[off + 1] == DHCP6_OPT_IA_NA))
+ break;
+ off += 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
+ }
+ len = 4 + (ibuf[off + 2] << 8) + ibuf[off + 3];
+ x->iana = ibuf + off;
+ x->ianalen = len;
+ return 0;
+}
+
+/*
+ * receive a DHCPv6 packet
+ */
+
+void
+receive6(void)
+{
+ struct exchange *x, *t;
+ struct xlist *bucket;
+ struct timespec now;
+ ssize_t cc;
+ uint32_t xid, hash;
+ int checklost = 0;
+ double delta;
+
+ cc = recv(sock, ibuf, sizeof(ibuf), 0);
+ if (cc < 0) {
+ if ((errno == EAGAIN) ||
+ (errno == EWOULDBLOCK) ||
+ (errno == EINTR))
+ return;
+ perror("recv");
+ fatal = 1;
+ return;
+ }
+ /* enforce a reasonable length */
+ if (cc < 22) {
+ tooshort++;
+ return;
+ }
+ if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
+ perror("clock_gettime(receive)");
+ fatal = 1;
+ return;
+ }
+ xid = ibuf[xid_solicit6] << 16;
+ xid |= ibuf[xid_solicit6 + 1] << 8;
+ xid |= ibuf[xid_solicit6 + 2];
+ /* 4-packet exchange even/odd xid */
+ if (simple == 0) {
+ if ((xid & 1) != 0) {
+ receive_reply(xid, &now);
+ return;
+ }
+ hash = (xid >> 1) & (hashsize0 - 1);
+ } else
+ hash = xid & (hashsize0 - 1);
+ /* now it is the second packet, get the bucket which is needed */
+ bucket = (struct xlist *) (exchanges0 + hash * sizeof(*bucket));
+ /* try the 'next to be received' cache */
+ if ((xnext0 != NULL) && (xnext0->xid == xid)) {
+ x = xnext0;
+ goto found;
+ }
+ /* if the rate is not illimited, garbage collect up to 3
+ timed-out exchanges */
+ if (rate != 0)
+ checklost = 3;
+ /* look for the exchange */
+ ISC_TAILQ_FOREACH_SAFE(x, bucket, hchain, t) {
+ double waited;
+
+ if (x->xid == xid)
+ goto found;
+ if (checklost <= 0)
+ continue;
+ /* check for a timed-out exchange */
+ waited = now.tv_sec - x->ts0.tv_sec;
+ waited += (now.tv_nsec - x->ts0.tv_nsec) / 1e9;
+ if (waited < losttime[0]) {
+ checklost = 0;
+ continue;
+ }
+ /* garbage collect timed-out exchange */
+ checklost--;
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent0, x, gchain);
+ free(x);
+ collected[0] += 1;
+ }
+ /* no match? very late or not for us */
+ orphans++;
+ return;
+
+ /* got it: update stats and move to the received queue */
+ found:
+ xrcount0++;
+ x->ts1 = now;
+ delta = x->ts1.tv_sec - x->ts0.tv_sec;
+ delta += (x->ts1.tv_nsec - x->ts0.tv_nsec) / 1e9;
+ if (delta < dmin0)
+ dmin0 = delta;
+ if (delta > dmax0)
+ dmax0 = delta;
+ dsum0 += delta;
+ dsumsq0 += delta * delta;
+ xnext0 = ISC_TAILQ_NEXT(x, gchain);
+ ISC_TAILQ_REMOVE(bucket, x, hchain);
+ ISC_TAILQ_REMOVE(&xsent0, x, gchain);
+ ISC_TAILQ_INSERT_TAIL(&xrcvd0, x, gchain);
+ /* if the exchange is not finished, go to the second part */
+ if (simple == 0) {
+ int ret = 0;
+
+ /* the server-ID option is needed */
+ if ((usefirst != 0) && (gsrvid != NULL)) {
+ x->sid = gsrvid;
+ x->sidlen = gsrvidlen;
+ } else
+ ret = scan_for_srvid6(x, cc);
+ /* and the IA_NA option too */
+ if (ret >= 0)
+ ret = scan_for_ia_na(x, cc);
+ if (ret >= 0)
+ send_request6(x);
+ }
+}
+
+/*
+ * decode a base command line parameter
+ * (currently only MAC address and DUID are supported)
+ */
+
+void
+decodebase(void)
+{
+ char *b0 = base[basecnt];
+
+ /* MAC address (alias Ethernet address) */
+ if ((strncasecmp(b0, "mac=", 4) == 0) ||
+ (strncasecmp(b0, "ether=", 6) == 0)) {
+ char *b;
+
+ b = strchr(b0, '=') + 1;
+ if (sscanf(b, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+ &mac_prefix[0], &mac_prefix[1],
+ &mac_prefix[2], &mac_prefix[3],
+ &mac_prefix[4], &mac_prefix[5]) == 6) {
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL))
+ printf("set MAC to %s\n", b);
+ return;
+ }
+ fprintf(stderr,
+ "expected base in '%*s=xx:xx:xx:xx:xx:xx' format\n",
+ (int) (b - b0 - 1), b0);
+ exit(2);
+ }
+ /* DUID */
+ if (strncasecmp(b0, "duid=", 5) == 0) {
+ char *b;
+ size_t i, l;
+
+ if (duid_prefix != NULL) {
+ fprintf(stderr, "duid was already set?\n");
+ exit(2);
+ }
+ b = strchr(b0, '=') + 1;
+ l = 0;
+ while (*b != '\0') {
+ if (!isxdigit((int) *b)) {
+ fprintf(stderr,
+ "illegal char '%c' in duid\n",
+ (int) *b);
+ exit(2);
+ }
+ b++;
+ l++;
+ }
+ if ((l & 1) != 0) {
+ fprintf(stderr,
+ "odd number of hexadecimal digits in duid\n");
+ exit(2);
+ }
+ l /= 2;
+ if (l > 64) {
+ fprintf(stderr, "duid too large\n");
+ exit(2);
+ }
+ duid_prefix = (uint8_t *) malloc(l);
+ if (duid_prefix == NULL) {
+ perror("malloc(duid)");
+ exit(1);
+ }
+ b = strchr(b0, '=') + 1;
+ for (i = 0; i < l; i++, b += 2)
+ (void) sscanf(b, "%02hhx", &duid_prefix[i]);
+ duid_length = l;
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
+ b = strchr(b0, '=') + 1;
+ printf("set DUID[%d] to %s\n",
+ duid_length, b);
+ }
+ return;
+ }
+ /* other */
+ fprintf(stderr, "not yet supported base '%s'\n", b0);
+ exit(2);
+}
+
+/*
+ * get the server socket address from the command line:
+ * - flags: inherited from main, 0 or AI_NUMERICHOST (for literals)
+ */
+
+void
+getserveraddr(const int flags)
+{
+ struct addrinfo hints, *res;
+ char *service;
+ int ret;
+
+ memset(&hints, 0, sizeof(hints));
+ if (ipversion == 4) {
+ hints.ai_family = AF_INET;
+ service = "67";
+ } else {
+ hints.ai_family = AF_INET6;
+ service = "547";
+ }
+ hints.ai_socktype = SOCK_DGRAM;
+
+ hints.ai_flags = AI_NUMERICSERV | flags;
+#if defined(AI_ADDRCONFIG)
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ hints.ai_protocol = IPPROTO_UDP;
+
+ ret = getaddrinfo(servername, service, &hints, &res);
+ if (ret != 0) {
+ fprintf(stderr, "bad server=%s: %s\n",
+ servername, gai_strerror(ret));
+ exit(2);
+ }
+ if (res->ai_next != NULL) {
+ fprintf(stderr, "ambiguous server=%s\n", servername);
+ exit(2);
+ }
+ memcpy(&serveraddr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+}
+
+/*
+ * get the interface from the command line:
+ * - name of the interface
+ * - socket address to fill
+ * (in IPv6, get the first link-local address)
+ */
+
+void
+getinterface(const char *name, struct sockaddr_storage *ss)
+{
+ struct ifaddrs *ifaddr, *ifa;
+ int ret;
+ size_t len;
+
+ ret = getifaddrs(&ifaddr);
+ if (ret < 0) {
+ perror("getifaddrs");
+ exit(1);
+ }
+
+ for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_name == NULL)
+ continue;
+ if (strcmp(ifa->ifa_name, name) != 0)
+ continue;
+ if ((ifa->ifa_flags & IFF_UP) == 0)
+ continue;
+ if ((ifa->ifa_flags & IFF_MULTICAST) == 0)
+ continue;
+ if (ifa->ifa_addr == NULL)
+ continue;
+ if ((ipversion == 4) &&
+ (ifa->ifa_addr->sa_family != AF_INET))
+ continue;
+ if (ipversion == 6) {
+ struct sockaddr_in6 *a6;
+
+ a6 = (struct sockaddr_in6 *) ifa->ifa_addr;
+ if (a6->sin6_family != AF_INET6)
+ continue;
+ if (!IN6_IS_ADDR_LINKLOCAL(&a6->sin6_addr))
+ continue;
+ }
+ if (ipversion == 4)
+ len = sizeof(struct sockaddr_in);
+ else
+ len = sizeof(struct sockaddr_in6);
+ memcpy(ss, ifa->ifa_addr, len);
+ /* fill the server port */
+ if (ipversion == 4)
+ ((struct sockaddr_in *) ss)->sin_port = htons(67);
+ else
+ ((struct sockaddr_in6 *) ss)->sin6_port = htons(546);
+ return;
+ }
+ fprintf(stderr, "can't find interface %s\n", name);
+ exit(1);
+}
+
+/*
+ * get the local socket address from the command line
+ * (if it doesn't work, try an interface name)
+ */
+
+void
+getlocaladdr(void)
+{
+ struct addrinfo hints, *res;
+ char *service;
+ int ret;
+
+ memset(&hints, 0, sizeof(hints));
+ if (ipversion == 4) {
+ hints.ai_family = AF_INET;
+ service = "67";
+ } else {
+ hints.ai_family = AF_INET6;
+ service = "546";
+ }
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_NUMERICSERV;
+#if defined(AI_ADDRCONFIG)
+ hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ hints.ai_protocol = IPPROTO_UDP;
+
+ ret = getaddrinfo(localname, service, &hints, &res);
+ if ((ret == EAI_NONAME)
+#ifdef EAI_NODATA
+ || (ret == EAI_NODATA)
+#endif
+ ) {
+ isinterface = 1;
+ getinterface(localname, &localaddr);
+ return;
+ }
+ if (ret != 0) {
+ fprintf(stderr,
+ "bad -l<local-addr=%s>: %s\n",
+ localname,
+ gai_strerror(ret));
+ exit(2);
+ }
+ /* refuse multiple addresses */
+ if (res->ai_next != NULL) {
+ fprintf(stderr,
+ "ambiguous -l<local-addr=%s>\n",
+ localname);
+ exit(2);
+ }
+ memcpy(&localaddr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ return;
+}
+
+/*
+ * get the local socket address from the server one
+ */
+
+void
+getlocal(void)
+{
+ int ret, s;
+ socklen_t l;
+
+ if (ipversion == 4) {
+ l = sizeof(struct sockaddr_in);
+ s = socket(PF_INET, SOCK_DGRAM, 0);
+ } else {
+ l = sizeof(struct sockaddr_in6);
+ s = socket(PF_INET6, SOCK_DGRAM, 0);
+ }
+ if (s < 0) {
+ perror("socket");
+ exit(1);
+ }
+ if ((ipversion == 4) && (isbroadcast != 0)) {
+ int on = 1;
+
+ ret = setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
+ if (ret < 0) {
+ perror("setsockopt(SO_BROADCAST)");
+ exit(1);
+ }
+ }
+ ret = connect(s, (struct sockaddr *) &serveraddr, l);
+ if (ret < 0) {
+ perror("connect");
+ exit(1);
+ }
+ l = sizeof(localaddr);
+ ret = getsockname(s, (struct sockaddr *) &localaddr, &l);
+ if (ret < 0) {
+ perror("getsockname");
+ exit(1);
+ }
+ (void) close(s);
+ /* fill the local port */
+ if (ipversion == 4)
+ ((struct sockaddr_in *) &localaddr)->sin_port = htons(67);
+ else
+ ((struct sockaddr_in6 *) &localaddr)->sin6_port = htons(546);
+}
+
+/*
+ * intermediate reporting
+ * (note: an in-transit packet can be reported as dropped)
+ */
+
+void
+reporting(void)
+{
+ dreport.tv_sec += report;
+
+ if (xscount2 == 0) {
+ printf("sent: %llu, received: %llu (drops: %lld)",
+ (unsigned long long) xscount0,
+ (unsigned long long) xrcount0,
+ (long long) (xscount0 - xrcount0));
+ if (!ISC_TAILQ_EMPTY(&xrcvd0)) {
+ double avg;
+
+ avg = dsum0 / xrcount0;
+ printf(" average: %.3f ms", avg * 1e3);
+ }
+ } else {
+ printf("sent: %llu/%llu received: %llu/%llu "
+ "(drops: %lld/%lld)",
+ (unsigned long long) xscount0,
+ (unsigned long long) xscount2,
+ (unsigned long long) xrcount0,
+ (unsigned long long) xrcount2,
+ (long long) (xscount0 - xrcount0),
+ (long long) (xscount2 - xrcount2));
+ if (!ISC_TAILQ_EMPTY(&xrcvd0)) {
+ double avg0, avg2;
+
+ avg0 = dsum0 / xrcount0;
+ if (xrcount2 != 0)
+ avg2 = dsum2 / xrcount2;
+ else
+ avg2 = 0.;
+ printf(" average: %.3f/%.3f ms",
+ avg0 * 1e3, avg2 * 1e3);
+ }
+ }
+ printf("\n");
+}
+
+/*
+ * SIGCHLD handler
+ */
+
+void
+reapchild(int sig)
+{
+ // Do something with variables to avoid unused-variable warning
+ int status;
+ status = sig;
+ status = 0;
+ while (wait3(&status, WNOHANG, NULL) > 0)
+ /* continue */;
+}
+
+/*
+ * SIGINT handler
+ */
+
+void
+interrupt(int sig)
+{
+ // Do something to avoid unused-variable warning
+ int s = sig;
+ s = 1;
+ interrupted = s;
+}
+
+/*
+ * '-v' handler
+ */
+
+void
+version(void)
+{
+ fprintf(stderr, "version 0.01\n");
+}
+
+/*
+ * usage (from the wiki)
+ */
+
+void
+usage(void)
+{
+ fprintf(stderr, "%s",
+"perfdhcp [-hv] [-4|-6] [-r<rate>] [-t<report>] [-R<range>] [-b<base>]\n"
+" [-n<num-request>] [-p<test-period>] [-d<drop-time>] [-D<max-drop>]\n"
+" [-l<local-addr|interface>] [-P<preload>] [-a<aggressivity>]\n"
+" [-L<local-port>] [-s<seed>] [-i] [-B] [-c] [-1]\n"
+" [-T<template-file>] [-X<xid-offset>] [-O<random-offset]\n"
+" [-E<time-offset>] [-S<srvid-offset>] [-I<ip-offset>]\n"
+" [-x<diagnostic-selector>] [-w<wrapped>] [server]\n"
+"\f\n"
+"The [server] argument is the name/address of the DHCP server to\n"
+"contact. For DHCPv4 operation, exchanges are initiated by\n"
+"transmitting a DHCP DISCOVER to this address.\n"
+"\n"
+"For DHCPv6 operation, exchanges are initiated by transmitting a DHCP\n"
+"SOLICIT to this address. In the DHCPv6 case, the special name 'all'\n"
+"can be used to refer to All_DHCP_Relay_Agents_and_Servers (the\n"
+"multicast address FF02::1:2), or the special name 'servers' to refer\n"
+"to All_DHCP_Servers (the multicast address FF05::1:3). The [server]\n"
+"argument is optional only in the case that -l is used to specify an\n"
+"interface, in which case [server] defaults to 'all'.\n"
+"\n"
+"The default is to perform a single 4-way exchange, effectively pinging\n"
+"the server.\n"
+"The -r option is used to set up a performance test, without\n"
+"it exchanges are initiated as fast as possible.\n"
+"\n"
+"Options:\n"
+"-1: Take the server-ID option from the first received message.\n"
+"-4: DHCPv4 operation (default). This is incompatible with the -6 option.\n"
+"-6: DHCPv6 operation. This is incompatible with the -4 option.\n"
+"-a<aggressivity>: When the target sending rate is not yet reached,\n"
+" control how many exchanges are initiated before the next pause.\n"
+"-b<base>: The base MAC, DUID, IP, etc, used to simulate different\n"
+" clients. This can be specified multiple times, each instance is\n"
+" in the <type>=<value> form, for instance:\n"
+" (and default) MAC=00:0c:01:02:03:04.\n"
+"-d<drop-time>: Specify the time after which a request is treated as\n"
+" having been lost. The value is given in seconds and may contain a\n"
+" fractional component. The default is 1 second.\n"
+"-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
+" elapsed-time option in the (second/request) template.\n"
+" The value 0 disables it.\n"
+"-h: Print this help.\n"
+"-i: Do only the initial part of an exchange: DO or SA, depending on\n"
+" whether -6 is given.\n"
+"-I<ip-offset>: Offset of the (DHCPv4) IP address in the requested-IP\n"
+" option / (DHCPv6) IA_NA option in the (second/request) template.\n"
+"-l<local-addr|interface>: For DHCPv4 operation, specify the local\n"
+" hostname/address to use when communicating with the server. By\n"
+" default, the interface address through which traffic would\n"
+" normally be routed to the server is used.\n"
+" For DHCPv6 operation, specify the name of the network interface\n"
+" via which exchanges are initiated.\n"
+"-L<local-port>: Specify the local port to use\n"
+" (the value 0 means to use the default).\n"
+"-O<random-offset>: Offset of the last octet to randomize in the template.\n"
+"-P<preload>: Initiate first <preload> exchanges back to back at startup.\n"
+"-r<rate>: Initiate <rate> DORA/SARR (or if -i is given, DO/SA)\n"
+" exchanges per second. A periodic report is generated showing the\n"
+" number of exchanges which were not completed, as well as the\n"
+" average response latency. The program continues until\n"
+" interrupted, at which point a final report is generated.\n"
+"-R<range>: Specify how many different clients are used. With 1\n"
+" (the default), all requests seem to come from the same client.\n"
+"-s<seed>: Specify the seed for randomization, making it repeatable.\n"
+"-S<srvid-offset>: Offset of the server-ID option in the\n"
+" (second/request) template.\n"
+"-T<template-file>: The name of a file containing the template to use\n"
+" as a stream of hexadecimal digits.\n"
+"-v: Report the version number of this program.\n"
+"-w<wrapped>: Command to call with start/stop at the beginning/end of\n"
+" the program.\n"
+"-x<diagnostic-selector>: Include extended diagnostics in the output.\n"
+" <diagnostic-selector> is a string of single-keywords specifying\n"
+" the operations for which verbose output is desired. The selector\n"
+" keyletters are:\n"
+" * 'a': print the decoded command line arguments\n"
+" * 'e': print the exit reason\n"
+" * 'i': print rate processing details\n"
+" * 'r': print randomization details\n"
+" * 's': print first server-id\n"
+" * 't': when finished, print timers of all successful exchanges\n"
+" * 'T': when finished, print templates\n"
+"-X<xid-offset>: Transaction ID (aka. xid) offset in the template.\n"
+"\n"
+"DHCPv4 only options:\n"
+"-B: Force broadcast handling.\n"
+"\n"
+"DHCPv6 only options:\n"
+"-c: Add a rapid commit option (exchanges will be SA).\n"
+"\n"
+"The remaining options are used only in conjunction with -r:\n"
+"\n"
+"-D<max-drop>: Abort the test if more than <max-drop> requests have\n"
+" been dropped. Use -D0 to abort if even a single request has been\n"
+" dropped. If <max-drop> includes the suffix '%', it specifies a\n"
+" maximum percentage of requests that may be dropped before abort.\n"
+" In this case, testing of the threshold begins after 10 requests\n"
+" have been expected to be received.\n"
+"-n<num-request>: Initiate <num-request> transactions. No report is\n"
+" generated until all transactions have been initiated/waited-for,\n"
+" after which a report is generated and the program terminates.\n"
+"-p<test-period>: Send requests for the given test period, which is\n"
+" specified in the same manner as -d. This can be used as an\n"
+" alternative to -n, or both options can be given, in which case the\n"
+" testing is completed when either limit is reached.\n"
+"-t<report>: Delay in seconds between two periodic reports.\n"
+"\n"
+"Errors:\n"
+"- tooshort: received a too short message\n"
+"- orphans: received a message which doesn't match an exchange\n"
+" (duplicate, late or not related)\n"
+"- locallimit: reached to local system limits when sending a message.\n"
+"\n"
+"Exit status:\n"
+"The exit status is:\n"
+"0 on complete success.\n"
+"1 for a general error.\n"
+"2 if an error is found in the command line arguments.\n"
+"3 if there are no general failures in operation, but one or more\n"
+" exchanges are not successfully completed.\n");
+}
+
+/*
+ * main function / entry point
+ */
+
+int
+main(const int argc, char * const argv[])
+{
+ int opt, flags = 0, ret, i;
+ long long r;
+ char *pc;
+ extern char *optarg;
+ extern int optind;
+
+#define OPTIONS "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:"
+
+ /* decode options */
+ while ((opt = getopt(argc, argv, OPTIONS)) != -1)
+ switch (opt) {
+ case 'h':
+ usage();
+ exit(0);
+
+ case 'v':
+ version();
+ exit(0);
+
+ case '4':
+ if (ipversion == 6) {
+ fprintf(stderr, "IP version already set to 6\n");
+ usage();
+ exit(2);
+ }
+ ipversion = 4;
+ break;
+
+ case '6':
+ if (ipversion == 4) {
+ fprintf(stderr, "IP version already set to 4\n");
+ usage();
+ exit(2);
+ }
+ ipversion = 6;
+ break;
+
+ case 'r':
+ rate = atoi(optarg);
+ if (rate <= 0) {
+ fprintf(stderr, "rate must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 't':
+ report = atoi(optarg);
+ if (report <= 0) {
+ fprintf(stderr, "report must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'R':
+ r = atoll(optarg);
+ if (r < 0) {
+ fprintf(stderr,
+ "range must not be a negative integer\n");
+ usage();
+ exit(2);
+ }
+ range = (uint32_t) r;
+ if ((range != 0) && (range != UINT32_MAX)) {
+ uint32_t s = range + 1;
+ uint64_t b = UINT32_MAX + 1, m;
+
+ m = (b / s) * s;
+ if (m == b)
+ maxrandom = 0;
+ else
+ maxrandom = (uint32_t) m;
+ }
+ break;
+
+ case 'b':
+ if (basecnt > 3) {
+ fprintf(stderr, "too many bases\n");
+ usage();
+ exit(2);
+ }
+ base[basecnt] = optarg;
+ decodebase();
+ basecnt++;
+ break;
+
+ case 'n':
+ numreq[gotnumreq] = atoi(optarg);
+ if (numreq[gotnumreq] <= 0) {
+ fprintf(stderr,
+ "num-request must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ gotnumreq = 1;
+ break;
+
+ case 'p':
+ period = atoi(optarg);
+ if (period <= 0) {
+ fprintf(stderr,
+ "test-period must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'd':
+ losttime[gotlosttime] = atof(optarg);
+ if (losttime[gotlosttime] <= 0.) {
+ fprintf(stderr,
+ "drop-time must be a positive number\n");
+ usage();
+ exit(2);
+ }
+ gotlosttime = 1;
+ break;
+
+ case 'D':
+ pc = strchr(optarg, '%');
+ if (pc != NULL) {
+ *pc = '\0';
+ maxpdrop[gotmaxdrop] = atof(optarg);
+ if ((maxpdrop[gotmaxdrop] <= 0) ||
+ (maxpdrop[gotmaxdrop] >= 100)) {
+ fprintf(stderr,
+ "invalid drop-time percentage\n");
+ usage();
+ exit(2);
+ }
+ gotmaxdrop = 1;
+ break;
+ }
+ maxdrop[gotmaxdrop] = atoi(optarg);
+ if (maxdrop[gotmaxdrop] <= 0) {
+ fprintf(stderr,
+ "max-drop must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ gotmaxdrop = 1;
+ break;
+
+ case 'l':
+ localname = optarg;
+ break;
+
+ case 'P':
+ preload = atoi(optarg);
+ if (preload < 0) {
+ fprintf(stderr,
+ "preload must not be a negative integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'a':
+ aggressivity = atoi(optarg);
+ if (aggressivity <= 0) {
+ fprintf(stderr,
+ "aggressivity must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'L':
+ localport = atoi(optarg);
+ if (localport < 0) {
+ fprintf(stderr,
+ "local-port must not be a negative integer\n");
+ usage();
+ exit(2);
+ }
+ if (localport > (int) UINT16_MAX) {
+ fprintf(stderr,
+ "local-port must be lower than %d\n",
+ (int) UINT16_MAX);
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 's':
+ seeded = 1;
+ seed = (unsigned int) atol(optarg);
+ break;
+
+ case 'i':
+ simple = 1;
+ break;
+
+ case 'B':
+ isbroadcast = 1;
+ break;
+
+ case 'c':
+ rapidcommit = 1;
+ break;
+
+ case '1':
+ usefirst = 1;
+ break;
+
+ case 'T':
+ if (templatefile[0] != NULL) {
+ if (templatefile[1] != NULL) {
+ fprintf(stderr,
+ "template-files are already set\n");
+ usage();
+ exit(2);
+ }
+ templatefile[1] = optarg;
+ } else
+ templatefile[0] = optarg;
+ break;
+
+ case 'X':
+ if (xidoffset[0] >= 0)
+ i = 1;
+ else
+ i = 0;
+ xidoffset[i] = atoi(optarg);
+ if (xidoffset[i] <= 0) {
+ fprintf(stderr,
+ "xid-offset must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'O':
+ if (rndoffset[0] >= 0)
+ i = 1;
+ else
+ i = 0;
+ rndoffset[i] = atoi(optarg);
+ if (rndoffset[i] < 3) {
+ fprintf(stderr,
+ "random-offset must be greater than 3\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'E':
+ elpoffset = atoi(optarg);
+ if (elpoffset < 0) {
+ fprintf(stderr,
+ "time-offset must not be a "
+ "negative integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'S':
+ sidoffset = atoi(optarg);
+ if (sidoffset <= 0) {
+ fprintf(stderr,
+ "srvid-offset must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'I':
+ ripoffset = atoi(optarg);
+ if (ripoffset <= 0) {
+ fprintf(stderr,
+ "ip-offset must be a positive integer\n");
+ usage();
+ exit(2);
+ }
+ break;
+
+ case 'x':
+ diags = optarg;
+ break;
+
+ case 'w':
+ wrapped = optarg;
+ break;
+
+ default:
+ usage();
+ exit(2);
+ }
+
+ /* adjust some global variables */
+ if (ipversion == 0)
+ ipversion = 4;
+ if (templatefile[1] != NULL) {
+ if (xidoffset[1] < 0)
+ xidoffset[1] = xidoffset[0];
+ if (rndoffset[1] < 0)
+ rndoffset[1] = rndoffset[0];
+ }
+
+ /* when required, print the internal view of the command line */
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
+ printf("IPv%d", ipversion);
+ if (simple != 0) {
+ if (ipversion == 4)
+ printf(" DO only");
+ else
+ printf(" SA only");
+ }
+ if (rate != 0)
+ printf(" rate=%d", rate);
+ if (report != 0)
+ printf(" report=%d", report);
+ if (range != 0) {
+ if (strchr(diags, 'r') != NULL)
+ printf(" range=0..%d [0x%x]",
+ range,
+ (unsigned int) maxrandom);
+ else
+ printf(" range=0..%d", range);
+ }
+ if (basecnt != 0)
+ for (i = 0; i < basecnt; i++)
+ printf(" base[%d]='%s'", i, base[i]);
+ if (gotnumreq != 0)
+ printf(" num-request=%d,%d", numreq[0], numreq[1]);
+ if (period != 0)
+ printf(" test-period=%d", period);
+ printf(" drop-time=%g,%g", losttime[0], losttime[1]);
+ if ((maxdrop[0] != 0) || (maxdrop[1] != 0))
+ printf(" max-drop=%d,%d", maxdrop[0], maxdrop[1]);
+ if ((maxpdrop[0] != 0.) || (maxpdrop[1] != 0.))
+ printf(" max-drop=%2.2f%%,%2.2f%%",
+ maxpdrop[0], maxpdrop[1]);
+ if (preload != 0)
+ printf(" preload=%d", preload);
+ printf(" aggressivity=%d", aggressivity);
+ if (localport != 0)
+ printf(" local-port=%d", localport);
+ if (seeded)
+ printf(" seed=%u", seed);
+ if (isbroadcast != 0)
+ printf(" broadcast");
+ if (rapidcommit != 0)
+ printf(" rapid-commit");
+ if (usefirst != 0)
+ printf(" use-first");
+ if ((templatefile[0] != NULL) && (templatefile[1] == NULL))
+ printf(" template-file='%s'", templatefile[0]);
+ else if (templatefile[1] != NULL)
+ printf(" template-file='%s','%s'",
+ templatefile[0], templatefile[1]);
+ if ((xidoffset[0] >= 0) && (xidoffset[1] < 0))
+ printf(" xid-offset=%d", xidoffset[0]);
+ else if (xidoffset[1] >= 0)
+ printf(" xid-offset=%d,%d",
+ xidoffset[0], xidoffset[1]);
+ if ((rndoffset[0] >= 0) && (rndoffset[1] < 0))
+ printf(" xid-offset=%d", rndoffset[0]);
+ else if (rndoffset[1] >= 0)
+ printf(" xid-offset=%d,%d",
+ rndoffset[0], rndoffset[1]);
+ if (elpoffset >= 0)
+ printf(" time-offset=%d", elpoffset);
+ if (sidoffset >= 0)
+ printf(" srvid-offset=%d", sidoffset);
+ if (ripoffset >= 0)
+ printf(" ip-offset=%d", ripoffset);
+ printf(" diagnotic-selectors='%s'", diags);
+ if (wrapped != NULL)
+ printf(" wrapped='%s'", wrapped);
+ printf("\n");
+ }
+
+ /* check DHCPv4 only options */
+ if ((ipversion != 4) && (isbroadcast != 0)) {
+ fprintf(stderr, "-b is not compatible with IPv6 (-6)\n");
+ usage();
+ exit(2);
+ }
+
+ /* check DHCPv6 only options */
+ if ((ipversion != 6) && (rapidcommit != 0)) {
+ fprintf(stderr, "-6 (IPv6) must be set to use -c\n");
+ usage();
+ exit(2);
+ }
+
+ /* check 4-packet (aka not simple) mode options */
+ if ((simple != 0) && (numreq[1] != 0)) {
+ fprintf(stderr,
+ "second -n<num-request> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (losttime[1] != 1.)) {
+ fprintf(stderr,
+ "second -d<drop-time> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) &&
+ ((maxdrop[1] != 0) || (maxpdrop[1] != 0.))) {
+ fprintf(stderr,
+ "second -D<max-drop> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (usefirst != 0)) {
+ fprintf(stderr,
+ "-1 is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (templatefile[1] != NULL)) {
+ fprintf(stderr,
+ "second -T<template-file> is not "
+ "compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (xidoffset[1] >= 0)) {
+ fprintf(stderr,
+ "second -X<xid-offset> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (rndoffset[1] >= 0)) {
+ fprintf(stderr,
+ "second -O<random-offset is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (elpoffset >= 0)) {
+ fprintf(stderr,
+ "-E<time-offset> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (sidoffset >= 0)) {
+ fprintf(stderr,
+ "-S<srvid-offset> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+ if ((simple != 0) && (ripoffset >= 0)) {
+ fprintf(stderr,
+ "-I<ip-offset> is not compatible with -i\n");
+ usage();
+ exit(2);
+ }
+
+
+ /* check simple mode options */
+ if ((simple == 0) && (rapidcommit != 0)) {
+ fprintf(stderr, "-i must be set to use -c\n");
+ usage();
+ exit(2);
+ }
+
+ /* check rate '-r' options */
+ if ((rate == 0) && (report != 0)) {
+ fprintf(stderr,
+ "-r<rate> must be set to use -t<report>\n");
+ usage();
+ exit(2);
+ }
+ if ((rate == 0) && ((numreq[0] != 0) || (numreq[1] != 0))) {
+ fprintf(stderr,
+ "-r<rate> must be set to use -n<num-request>\n");
+ usage();
+ exit(2);
+ }
+ if ((rate == 0) && (period != 0)) {
+ fprintf(stderr,
+ "-r<rate> must be set to use -p<test-period>\n");
+ usage();
+ exit(2);
+ }
+ if ((rate == 0) &&
+ ((maxdrop[0] != 0) || (maxdrop[1] != 0) ||
+ (maxpdrop[0] != 0.) || (maxpdrop[1] != 0.))) {
+ fprintf(stderr,
+ "-r<rate> must be set to use -D<max-drop>\n");
+ usage();
+ exit(2);
+ }
+
+ /* check (first) template file options */
+ if ((templatefile[0] == NULL) && (xidoffset[0] >= 0)) {
+ fprintf(stderr,
+ "-T<template-file> must be set to "
+ "use -X<xid-offset>\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[0] == NULL) && (rndoffset[0] >= 0)) {
+ fprintf(stderr,
+ "-T<template-file> must be set to "
+ "use -O<random-offset>\n");
+ usage();
+ exit(2);
+ }
+
+ /* check (second) template file options */
+ if ((templatefile[1] == NULL) && (elpoffset >= 0)) {
+ fprintf(stderr,
+ "second/request -T<template-file> must be set to "
+ "use -E<time-offset>\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[1] == NULL) && (sidoffset >= 0)) {
+ fprintf(stderr,
+ "second/request -T<template-file> must be set to "
+ "use -S<srvid-offset>\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[1] == NULL) && (ripoffset >= 0)) {
+ fprintf(stderr,
+ "second/request -T<template-file> must be set to "
+ "use -I<ip-offset>\n");
+ usage();
+ exit(2);
+ }
+
+ /* check various template file(s) and other condition(s) options */
+ if ((templatefile[0] != NULL) && (range > 0) && (rndoffset[0] < 0)) {
+ fprintf(stderr,
+ "-O<random-offset> must be set when "
+ "-T<template-file> and -R<range> are used\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[1] != NULL) && (sidoffset < 0)) {
+ fprintf(stderr,
+ "-S<srvid-offset> must be set when second "
+ "-T<template-file> is used\n");
+ usage();
+ exit(2);
+ }
+ if ((templatefile[1] != NULL) && (ripoffset < 0)) {
+ fprintf(stderr,
+ "-I<ip-offset> must be set when second "
+ "-T<template-file> is used\n");
+ usage();
+ exit(2);
+ }
+
+ /* get the server argument */
+ if (optind < argc - 1) {
+ fprintf(stderr, "extra arguments?\n");
+ usage();
+ exit(2);
+ }
+ if (optind == argc - 1) {
+ servername = argv[optind];
+ /* decode special cases */
+ if ((ipversion == 4) &&
+ (strcmp(servername, "all") == 0)) {
+ flags = AI_NUMERICHOST;
+ isbroadcast = 1;
+ servername = "255.255.255.255";
+ } else if ((ipversion == 6) &&
+ (strcmp(servername, "all") == 0)) {
+ flags = AI_NUMERICHOST;
+ servername = "FF02::1:2";
+ } else if ((ipversion == 6) &&
+ (strcmp(servername, "servers") == 0)) {
+ flags = AI_NUMERICHOST;
+ servername = "FF05::1:3";
+ }
+ }
+
+ /* handle the local '-l' address/interface */
+ if (localname != NULL) {
+ /* given */
+ getlocaladdr();
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
+ if (isinterface)
+ printf("interface='%s'\n", localname);
+ else
+ printf("local-addr='%s'\n", localname);
+ }
+ /* get the not given server from it */
+ if (servername == NULL) {
+ if (isinterface && (ipversion == 4)) {
+ flags = AI_NUMERICHOST;
+ isbroadcast = 1;
+ servername = "255.255.255.255";
+ } else if (isinterface && (ipversion == 6)) {
+ flags = AI_NUMERICHOST;
+ servername = "FF02::1:2";
+ } else {
+ fprintf(stderr,
+ "without an interface "
+ "server is required\n");
+ usage();
+ exit(2);
+ }
+ }
+ } else if (servername == NULL) {
+ fprintf(stderr, "without an interface server is required\n");
+ usage();
+ exit(2);
+ }
+ /* get the server socket address */
+ getserveraddr(flags);
+ /* finish local/server socket address stuff and print it */
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL))
+ printf("server='%s'\n", servername);
+ if (localname == NULL)
+ getlocal();
+ if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
+ char addr[NI_MAXHOST];
+
+ ret = getnameinfo((struct sockaddr *) &localaddr,
+ sizeof(localaddr),
+ addr,
+ NI_MAXHOST,
+ NULL,
+ 0,
+ NI_NUMERICHOST);
+ if (ret != 0) {
+ fprintf(stderr,
+ "can't get the local address: %s\n",
+ gai_strerror(ret));
+ exit(1);
+ }
+ printf("local address='%s'\n", addr);
+ }
+
+ /* initialize exchange structures */
+ inits();
+
+ /* get the socket descriptor and template(s) */
+ if (ipversion == 4) {
+ getsock4();
+ if (templatefile[0] == NULL)
+ build_template_discover4();
+ else
+ get_template_discover4();
+ if (simple == 0) {
+ if (templatefile[1] == NULL)
+ build_template_request4();
+ else
+ get_template_request4();
+ }
+ } else {
+ getsock6();
+ if (duid_prefix != NULL) {
+ if (templatefile[0] == NULL)
+ build_template_solicit6();
+ else
+ get_template_solicit6();
+ if (simple == 0) {
+ if (templatefile[1] == NULL)
+ build_template_request6();
+ else
+ get_template_request6();
+ }
+ }
+ }
+ /* sanity check */
+ if ((unsigned) sock > FD_SETSIZE) {
+ fprintf(stderr, "socket descriptor (%d) too large?!\n", sock);
+ exit(1);
+ }
+ /* make the socket descriptor not blocking */
+ flags = fcntl(sock, F_GETFL, 0);
+ if (flags < 0) {
+ perror("fcntl(F_GETFL)");
+ exit(1);
+ }
+ if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
+ perror("fcntl(F_SETFL)");
+ exit(1);
+ }
+
+ /* wrapped start */
+ if (wrapped != NULL) {
+ pid_t pid;
+
+ (void) signal(SIGCHLD, reapchild);
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ } else if (pid == 0)
+ (void) execlp(wrapped, "start", (char *) NULL);
+ }
+
+ /* boot is done! */
+ if (clock_gettime(CLOCK_REALTIME, &boot) < 0) {
+ perror("clock_gettime(boot)");
+ exit(1);
+ }
+
+ /* compute the next intermediate reporting date */
+ if (report != 0) {
+ dreport.tv_sec = boot.tv_sec + report;
+ dreport.tv_nsec = boot.tv_nsec;
+ }
+
+ /* compute the DUID (the current date is needed) */
+ if ((ipversion == 6) && (duid_prefix == NULL)) {
+ uint32_t curdate;
+
+ duid_length = 14;
+ duid_prefix = (uint8_t *) malloc(duid_length);
+ if (duid_prefix == NULL) {
+ perror("malloc(duid)");
+ exit(1);
+ }
+ duid_prefix[0] = DHCP6_DUID_LLT >> 8;
+ duid_prefix[1] = DHCP6_DUID_LLT;
+ duid_prefix[2] = DHCP_HTYPE_ETHER >> 8;
+ duid_prefix[3] = DHCP_HTYPE_ETHER;
+ curdate = htonl(boot.tv_sec - DHCP6_DUID_EPOCH);
+ memcpy(duid_prefix + 4, &curdate, 4);
+ memcpy(duid_prefix + 8, mac_prefix, 6);
+ /* the DUID is in template(s) */
+ if (templatefile[0] == NULL)
+ build_template_solicit6();
+ else
+ get_template_solicit6();
+ if (simple == 0) {
+ if (templatefile[1] == NULL)
+ build_template_request6();
+ else
+ get_template_request6();
+ }
+ }
+
+ /* seed the random generator */
+ if (seeded == 0)
+ seed = (unsigned int) (boot.tv_sec + boot.tv_nsec);
+ srandom(seed);
+
+ /* preload the server with at least one packet */
+ compsend = preload + 1;
+ for (i = 0; i <= preload; i++) {
+ if (ipversion == 4)
+ ret = send4();
+ else
+ ret = send6();
+ if (ret < 0) {
+ /* failure at the first packet is fatal */
+ if (i == 0) {
+ fprintf(stderr,
+ "initial send failed: %s\n",
+ strerror(-ret));
+ exit(1);
+ }
+ if ((errno == EAGAIN) ||
+ (errno == EWOULDBLOCK) ||
+ (errno == ENOBUFS) ||
+ (errno == ENOMEM))
+ locallimit++;
+ fprintf(stderr, "preload send: %s\n", strerror(-ret));
+ break;
+ }
+ }
+
+ /* required only before the interrupted flag check */
+ (void) signal(SIGINT, interrupt);
+
+ /* main loop */
+ for (;;) {
+ struct timespec now, ts;
+ fd_set rfds;
+
+ /* immediate loop exit conditions */
+ if (interrupted) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("interrupted\n");
+ break;
+ }
+ if (fatal) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("got a fatal error\n");
+ break;
+ }
+
+ /* get the date and use it */
+ if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
+ perror("clock_gettime(now)");
+ fatal = 1;
+ continue;
+ }
+ if ((period != 0) &&
+ ((boot.tv_sec + period < now.tv_sec) ||
+ ((boot.tv_sec + period == now.tv_sec) &&
+ (boot.tv_nsec < now.tv_nsec)))) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached test-period\n");
+ break;
+ }
+ if ((report != 0) &&
+ ((dreport.tv_sec < now.tv_sec) ||
+ ((dreport.tv_sec == now.tv_sec) &&
+ (dreport.tv_nsec < now.tv_nsec))))
+ reporting();
+
+ /* compute the delay for the next send */
+ due = last;
+ if (rate == 1)
+ due.tv_sec += 1;
+ else if (rate != 0)
+ due.tv_nsec += 1010000000 / rate;
+ else
+ due.tv_nsec += 1;
+ while (due.tv_nsec >= 1000000000) {
+ due.tv_sec += 1;
+ due.tv_nsec -= 1000000000;
+ }
+ ts = due;
+ ts.tv_sec -= now.tv_sec;
+ ts.tv_nsec -= now.tv_nsec;
+ while (ts.tv_nsec < 0) {
+ ts.tv_sec -= 1;
+ ts.tv_nsec += 1000000000;
+ }
+ /* the send was already due? */
+ if (ts.tv_sec < 0) {
+ ts.tv_sec = ts.tv_nsec = 0;
+ latesent++;
+ }
+
+ /* pselect() */
+ FD_ZERO(&rfds);
+ FD_SET(sock, &rfds);
+ ret = pselect(sock + 1, &rfds, NULL, NULL, &ts, NULL);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("pselect");
+ fatal = 1;
+ continue;
+ }
+
+ /* packet(s) to receive */
+ while (ret == 1) {
+ if (ipversion == 4)
+ receive4();
+ else
+ receive6();
+ if (recv(sock, ibuf, sizeof(ibuf), MSG_PEEK) <= 0)
+ ret = 0;
+ else
+ multrcvd++;
+ }
+ if (fatal)
+ continue;
+
+ /* check receive loop exit conditions */
+ if ((numreq[0] != 0) && ((int) xscount0 >= numreq[0])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached num-request0\n");
+ break;
+ }
+ if ((numreq[1] != 0) && ((int) xscount2 >= numreq[1])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached num-request2\n");
+ break;
+ }
+ if ((maxdrop[0] != 0) &&
+ ((int) (xscount0 - xrcount0) > maxdrop[0])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached max-drop%s (absolute)\n",
+ simple != 0 ? "" : "0");
+ break;
+ }
+ if ((maxdrop[1] != 0) &&
+ ((int) (xscount2 - xrcount2) > maxdrop[1])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached max-drop2 (absolute)\n");
+ break;
+ }
+ if ((maxpdrop[0] != 0.) &&
+ (xscount0 > 10) &&
+ (((100. * (xscount0 - xrcount0)) / xscount0)
+ > maxpdrop[0])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached max-drop%s (percent)\n",
+ simple != 0 ? "" : "0");
+ break;
+ }
+ if ((maxpdrop[1] != 0.) &&
+ (xscount2 > 10) &&
+ (((100. * (xscount2 - xrcount2)) / xscount2)
+ > maxpdrop[1])) {
+ if ((diags != NULL) && (strchr(diags, 'e') != NULL))
+ printf("reached max-drop2 (percent)\n");
+ break;
+ }
+
+ /* compute how many packets to send */
+ if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
+ perror("clock_gettime(now2)");
+ fatal = 1;
+ continue;
+ }
+ if ((now.tv_sec > due.tv_sec) ||
+ ((now.tv_sec == due.tv_sec) &&
+ (now.tv_nsec >= due.tv_nsec))) {
+ double tosend;
+
+ if (rate != 0) {
+ tosend = (now.tv_nsec - due.tv_nsec) / 1e9;
+ tosend += now.tv_sec - due.tv_sec;
+ tosend *= rate;
+ tosend += 1;
+ if (tosend > (double) aggressivity)
+ i = aggressivity;
+ else
+ i = (int) tosend;
+ } else
+ i = aggressivity;
+ compsend += i;
+ /* send packets */
+ for (;;) {
+ if (ipversion == 4)
+ ret = send4();
+ else
+ ret = send6();
+ if (ret < 0) {
+ if ((errno == EAGAIN) ||
+ (errno == EWOULDBLOCK) ||
+ (errno == ENOBUFS) ||
+ (errno == ENOMEM))
+ locallimit++;
+ fprintf(stderr,
+ "send: %s\n", strerror(-ret));
+ break;
+ }
+ i--;
+ if (i == 0)
+ break;
+ /* check for late packets to receive */
+ if (recv(sock, ibuf, sizeof(ibuf),
+ MSG_PEEK) > 0) {
+ latercvd++;
+ if (ipversion == 4)
+ receive4();
+ else
+ receive6();
+ }
+ }
+ } else
+ /* there was no packet to send */
+ shortwait++;
+ }
+
+ /* after main loop: finished */
+ if (clock_gettime(CLOCK_REALTIME, &finished) < 0)
+ perror("clock_gettime(finished)");
+
+ /* wrapped stop */
+ if (wrapped != NULL) {
+ pid_t pid;
+
+ pid = fork();
+ if (pid == 0)
+ (void) execlp(wrapped, "stop", (char *) NULL);
+ }
+
+ /* main statictics */
+ if (xscount2 == 0)
+ printf("sent: %llu, received: %llu (drops: %lld)\n",
+ (unsigned long long) xscount0,
+ (unsigned long long) xrcount0,
+ (long long) (xscount0 - xrcount0));
+ else
+ printf("sent: %llu/%llu, received: %llu/%llu "
+ "(drops: %lld/%lld)\n",
+ (unsigned long long) xscount0,
+ (unsigned long long) xscount2,
+ (unsigned long long) xrcount0,
+ (unsigned long long) xrcount2,
+ (long long) (xscount0 - xrcount0),
+ (long long) (xscount2 - xrcount2));
+ printf("tooshort: %llu, orphans: %llu, local limits: %llu\n",
+ (unsigned long long) tooshort,
+ (unsigned long long) orphans,
+ (unsigned long long) locallimit);
+
+ /* print the rate */
+ if (finished.tv_sec != 0) {
+ double dall, erate;
+
+ dall = (finished.tv_nsec - boot.tv_nsec) / 1e9;
+ dall += finished.tv_sec - boot.tv_sec;
+ erate = xrcount0 / dall;
+ if (rate != 0)
+ printf("rate: %f (expected %d)\n", erate, rate);
+ else
+ printf("rate: %f\n", erate);
+ }
+
+ /* rate processing instrumentation */
+ if ((diags != NULL) && (strchr(diags, 'i') != NULL)) {
+ printf("latesent: %llu, compsend: %llu, shortwait: %llu\n"
+ "multrcvd: %llu, latercvd: %llu, collected:%llu/%llu\n",
+ (unsigned long long) latesent,
+ (unsigned long long) compsend,
+ (unsigned long long) shortwait,
+ (unsigned long long) multrcvd,
+ (unsigned long long) latercvd,
+ (unsigned long long) collected[0],
+ (unsigned long long) collected[1]);
+ }
+
+ /* round-time trip statistics */
+ if (xrcount2 != 0) {
+ double avg0, avg2, stddev0, stddev2;
+
+ avg0 = dsum0 / xrcount0;
+ avg2 = dsum2 / xrcount2;
+ stddev0 = sqrt(dsumsq0 / xrcount0 - avg0 * avg0);
+ stddev2 = sqrt(dsumsq2 / xrcount2 - avg2 * avg2);
+ printf("RTT0: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
+ dmin0 * 1e3, avg0 * 1e3, dmax0 * 1e3, stddev0 * 1e3);
+ printf("RTT2: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
+ dmin2 * 1e3, avg2 * 1e3, dmax2 * 1e3, stddev2 * 1e3);
+ } else if (xrcount0 != 0) {
+ double avg, stddev;
+
+ avg = dsum0 / xrcount0;
+ stddev = sqrt(dsumsq0 / xrcount0 - avg * avg);
+ printf("RTT%s: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
+ simple != 0 ? "" : "0",
+ dmin0 * 1e3, avg * 1e3, dmax0 * 1e3, stddev * 1e3);
+ }
+
+ /* (first) server-ID option content */
+ if ((diags != NULL) && (strchr(diags, 's') != NULL) &&
+ !ISC_TAILQ_EMPTY(&xrcvd0)) {
+ struct exchange *x;
+ size_t n;
+
+ printf("server-id: ");
+ x = ISC_TAILQ_FIRST(&xrcvd0);
+ if (ipversion == 4)
+ n = 2;
+ else
+ n = 4;
+ for (; n < x->sidlen; n++)
+ printf("%02hhx", x->sid[n]);
+ printf("\n");
+ }
+
+ /* all time-stamps */
+ if ((diags != NULL) && (strchr(diags, 't') != NULL) &&
+ !ISC_TAILQ_EMPTY(&xrcvd0)) {
+ struct exchange *x;
+
+ printf("\n\n");
+ ISC_TAILQ_FOREACH(x, &xrcvd0, gchain)
+ printf("%ld.%09ld %ld.%09ld\n",
+ (long) x->ts0.tv_sec, x->ts0.tv_nsec,
+ (long) x->ts1.tv_sec, x->ts1.tv_nsec);
+
+ }
+ if ((diags != NULL) && (strchr(diags, 't') != NULL) &&
+ !ISC_TAILQ_EMPTY(&xrcvd2)) {
+ struct exchange *x;
+
+ printf("--\n");
+ ISC_TAILQ_FOREACH(x, &xrcvd2, gchain)
+ printf("%ld.%09ld %ld.%09ld %ld.%09ld %ld.%09ld\n",
+ (long) x->ts0.tv_sec, x->ts0.tv_nsec,
+ (long) x->ts1.tv_sec, x->ts1.tv_nsec,
+ (long) x->ts2.tv_sec, x->ts2.tv_nsec,
+ (long) x->ts3.tv_sec, x->ts3.tv_nsec);
+
+ }
+ if ((diags != NULL) && (strchr(diags, 't') != NULL) &&
+ !ISC_TAILQ_EMPTY(&xrcvd0))
+ printf("\n\n");
+
+ /* template(s) */
+ if ((diags != NULL) && (strchr(diags, 'T') != NULL)) {
+ size_t n;
+
+ if (ipversion == 4) {
+ printf("length = %zu\n", length_discover4);
+ printf("xid offset = %d\n", DHCP_OFF_XID);
+ printf("xid length = 4\n");
+ printf("random offset = %zu\n", random_discover4);
+ printf("content:\n");
+ for (n = 0; n < length_discover4; n++) {
+ printf("%s%02hhx",
+ (n & 15) == 0 ? "" : " ",
+ template_discover4[n]);
+ if ((n & 15) == 15)
+ printf("\n");
+ }
+ if ((n & 15) != 15)
+ printf("\n");
+ if (simple != 0)
+ goto doneT;
+ printf("--\n");
+ printf("length = %zu\n", length_request4);
+ printf("xid offset = %d\n", DHCP_OFF_XID);
+ printf("xid length = 4\n");
+ printf("random offset = %zu\n", random_request4);
+ if (elapsed_request4 > 0)
+ printf("secs offset = %zu\n",
+ elapsed_request4);
+ printf("server-id offset = %zu\n", serverid_request4);
+ printf("server-id length = %d\n", DHCP_OPTLEN_SRVID);
+ printf("content:\n");
+ printf("requested-ip-address offset = %zu\n",
+ reqaddr_request4);
+ printf("requested-ip-address length = %d\n", 4);
+ for (n = 0; n < length_request4; n++) {
+ printf("%s%02hhx",
+ (n & 15) == 0 ? "" : " ",
+ template_request4[n]);
+ if ((n & 15) == 15)
+ printf("\n");
+ }
+ printf("\n");
+ } else {
+ printf("length = %zu\n", length_solicit6);
+ printf("xid offset = %d\n", DHCP6_OFF_XID);
+ printf("xid length = 3\n");
+ printf("random offset = %zu\n", random_solicit6);
+ for (n = 0; n < length_solicit6; n++) {
+ printf("%s%02hhx",
+ (n & 15) == 0 ? "" : " ",
+ template_solicit6[n]);
+ if ((n & 15) == 15)
+ printf("\n");
+ }
+ if ((n & 15) != 15)
+ printf("\n");
+ if (simple != 0)
+ goto doneT;
+ printf("--\n");
+ printf("length = %zu\n", length_request6);
+ printf("xid offset = %d\n", DHCP_OFF_XID);
+ printf("xid length = 4\n");
+ printf("random offset = %zu\n", random_request6);
+ if (elapsed_request6 > 0)
+ printf("secs offset = %zu\n",
+ elapsed_request6);
+ printf("server-id offset = %zu\n", serverid_request6);
+ printf("content:\n");
+ printf("requested-ip-address offset = %zu\n",
+ reqaddr_request6);
+ for (n = 0; n < length_request6; n++) {
+ printf("%s%02hhx",
+ (n & 15) == 0 ? "" : " ",
+ template_request6[n]);
+ if ((n & 15) == 15)
+ printf("\n");
+ }
+ printf("\n");
+ }
+ }
+ doneT:
+
+ /* compute the exit code (and exit) */
+ if (fatal)
+ exit(1);
+ else if ((xscount0 == xrcount0) && (xscount2 == xrcount2))
+ exit(0);
+ else
+ exit(3);
+}
+
+#endif /* HAVE_GETIFADDRS */
diff --git a/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am
new file mode 100644
index 0000000..c94ecba
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/Makefile.am
@@ -0,0 +1,29 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += command_options_unittest.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/tests/tools/perfdhcp/tests/command_options_unittest.cc b/tests/tools/perfdhcp/tests/command_options_unittest.cc
new file mode 100644
index 0000000..c92edd0
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/command_options_unittest.cc
@@ -0,0 +1,454 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <cstddef>
+#include <stdint.h>
+#include <string>
+#include <gtest/gtest.h>
+
+#include "../command_options.h"
+
+#include "exceptions/exceptions.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::perfdhcp;
+
+/// \brief Test Fixture Class
+///
+/// This test fixture class is used to perform
+/// unit tests on perfdhcp CommandOptions class.
+class CommandOptionsTest : public virtual ::testing::Test
+{
+public:
+ /// \brief Default Constructor
+ CommandOptionsTest() { }
+
+protected:
+ /// \brief Parse command line and cleanup
+ ///
+ /// The method tokenizes command line to array of C-strings,
+ /// parses arguments using CommandOptions class to set
+ /// its data members and de-allocates array of C-strings.
+ ///
+ /// \param cmdline Command line to parse
+ /// \throws std::bad allocation if tokenization failed
+ void process(const std::string& cmdline) {
+ CommandOptions& opt = CommandOptions::instance();
+ int argc = 0;
+ char** argv = tokenizeString(cmdline, &argc);
+ opt.reset();
+ opt.parse(argc, argv);
+ for(int i = 0; i < argc; ++i) {
+ free(argv[i]);
+ argv[i] = NULL;
+ }
+ free(argv);
+ }
+
+ /// \brief Check default initialized values
+ ///
+ /// Check if initialized values are correct
+ void checkDefaults() {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp");
+ EXPECT_EQ(4, opt.getIpVersion());
+ EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
+ EXPECT_EQ(0, opt.getRate());
+ EXPECT_EQ(0, opt.getReportDelay());
+ EXPECT_EQ(0, opt.getClientsNum());
+
+ // default mac
+ uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 };
+ std::vector<uint8_t> v1 = opt.getMacPrefix();
+ ASSERT_EQ(6, v1.size());
+ EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+
+ EXPECT_EQ(0, opt.getBase().size());
+ EXPECT_EQ(0, opt.getNumRequests().size());
+ EXPECT_EQ(0, opt.getPeriod());
+ for (int i = 0; i < opt.getDropTime().size(); ++i) {
+ EXPECT_DOUBLE_EQ(1, opt.getDropTime()[i]);
+ }
+ ASSERT_EQ(opt.getMaxDrop().size(), opt.getMaxDropPercentage().size());
+ for (int i = 0; i < opt.getMaxDrop().size(); ++i) {
+ EXPECT_EQ(0, opt.getMaxDrop()[i]);
+ EXPECT_EQ(0, opt.getMaxDropPercentage()[i]);
+ }
+ EXPECT_EQ("", opt.getLocalName());
+ EXPECT_FALSE(opt.isInterface());
+ EXPECT_EQ(0, opt.getPreload());
+ EXPECT_EQ(1, opt.getAggressivity());
+ EXPECT_EQ(0, opt.getLocalPort());
+ EXPECT_FALSE(opt.isSeeded());
+ EXPECT_EQ(0, opt.getSeed());
+ EXPECT_FALSE(opt.isBroadcast());
+ EXPECT_FALSE(opt.isRapidCommit());
+ EXPECT_FALSE(opt.isUseFirst());
+ EXPECT_EQ(0, opt.getTemplateFiles().size());
+ EXPECT_EQ(0, opt.getTransactionIdOffset().size());
+ EXPECT_EQ(0, opt.getRandomOffset().size());
+ EXPECT_GT(0, opt.getElapsedTimeOffset());
+ EXPECT_GT(0, opt.getServerIdOffset());
+ EXPECT_GT(0, opt.getRequestedIpOffset());
+ EXPECT_EQ("", opt.getDiags());
+ EXPECT_EQ("", opt.getWrapped());
+ EXPECT_EQ("", opt.getServerName());
+ }
+
+ /// \brief Split string to array of C-strings
+ ///
+ /// \param s String to split (tokenize)
+ /// \param num Number of tokens returned
+ /// \return array of C-strings (tokens)
+ char** tokenizeString(const std::string& text_to_split, int* num) const {
+ char** results = NULL;
+ // Tokenization with std streams
+ std::stringstream text_stream(text_to_split);
+ // Iterators to be used for tokenization
+ std::istream_iterator<std::string> text_iterator(text_stream);
+ std::istream_iterator<std::string> text_end;
+ // Tokenize string (space is a separator) using begin and end iteratos
+ std::vector<std::string> tokens(text_iterator, text_end);
+
+ if (tokens.size() > 0) {
+ // Allocate array of C-strings where we will store tokens
+ results = static_cast<char**>(malloc(tokens.size() * sizeof(char*)));
+ if (results == NULL) {
+ throw std::bad_alloc();
+ }
+ // Store tokens in C-strings array
+ for (int i = 0; i < tokens.size(); ++i) {
+ char* cs = static_cast<char*>(malloc(tokens[i].length() + 1));
+ strcpy(cs, tokens[i].c_str());
+ results[i] = cs;
+ }
+ // Return number of tokens to calling function
+ if (num != NULL) {
+ *num = tokens.size();
+ }
+ }
+ return results;
+ }
+
+};
+
+TEST_F(CommandOptionsTest, Defaults) {
+ process("perfdhcp");
+ checkDefaults();
+}
+
+TEST_F(CommandOptionsTest, UseFirst) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -1 -B -l ethx");
+ EXPECT_TRUE(opt.isUseFirst());
+}
+TEST_F(CommandOptionsTest, IpVersion) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -6 -l ethx -c -i");
+ EXPECT_EQ(6, opt.getIpVersion());
+ EXPECT_EQ("ethx", opt.getLocalName());
+ EXPECT_TRUE(opt.isRapidCommit());
+ EXPECT_FALSE(opt.isBroadcast());
+ process("perfdhcp -4 -B -l ethx");
+ EXPECT_EQ(4, opt.getIpVersion());
+ EXPECT_TRUE(opt.isBroadcast());
+ EXPECT_FALSE(opt.isRapidCommit());
+
+ // Negative test cases
+ // -4 and -6 must not coexist
+ EXPECT_THROW(process("perfdhcp -4 -6 -l ethx"), isc::InvalidParameter);
+ // -6 and -B must not coexist
+ EXPECT_THROW(process("perfdhcp -6 -B -l ethx"), isc::InvalidParameter);
+ // -c and -4 (default) must not coexist
+ EXPECT_THROW(process("perfdhcp -c -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Rate) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -4 -r 10 -l ethx");
+ EXPECT_EQ(10, opt.getRate());
+
+ // Negative test cases
+ // Rate must not be 0
+ EXPECT_THROW(process("perfdhcp -4 -r 0 -l ethx"), isc::InvalidParameter);
+ // -r must be specified to use -n, -p and -D
+ EXPECT_THROW(process("perfdhcp -6 -t 5 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -p 120 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -D 1400 -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ReportDelay) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -r 100 -t 17 -l ethx");
+ EXPECT_EQ(17, opt.getReportDelay());
+
+ // Negative test cases
+ // -t must be positive integer
+ EXPECT_THROW(process("perfdhcp -r 10 -t -8 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -r 10 -t 0 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -r 10 -t s -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ClientsNum) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -R 200 -l ethx");
+ EXPECT_EQ(200, opt.getClientsNum());
+ process("perfdhcp -R 0 -l ethx");
+ EXPECT_EQ(0, opt.getClientsNum());
+
+ // Negative test cases
+ // Number of clients must be non-negative integer
+ EXPECT_THROW(process("perfdhcp -R -5 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -R gs -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Base) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -6 -b MAC=10::20::30::40::50::60 -l ethx -b duiD=1AB7F5670901FF");
+ uint8_t mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60 };
+ uint8_t duid[7] = { 0x1A, 0xB7, 0xF5, 0x67, 0x09, 0x01, 0xFF };
+
+ // Test Mac
+ std::vector<uint8_t> v1 = opt.getMacPrefix();
+ ASSERT_EQ(6, v1.size());
+ EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+ // "3x" is invalid value in MAC address
+ EXPECT_THROW(process("perfdhcp -b mac=10::2::3x::4::5::6 -l ethx"), isc::InvalidParameter);
+
+ // Test DUID
+ std::vector<uint8_t> v2 = opt.getDuidPrefix();
+ ASSERT_EQ(sizeof(duid) / sizeof(uint8_t), v2.size());
+ EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
+ // "t" is invalid digit in DUID
+ EXPECT_THROW(process("perfdhcp -6 -l ethx -b duiD=1AB7Ft670901FF"), isc::InvalidParameter);
+
+ // Some more negative test cases
+ // Base is not specified
+ EXPECT_THROW(process("perfdhcp -b -l ethx"), isc::InvalidParameter);
+ // Typo: should be mac= instead of mc=
+ EXPECT_THROW(process("perfdhcp -l ethx -b mc=00:01:02:03::04:05"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, DropTime) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -d 12");
+ ASSERT_EQ(2, opt.getDropTime().size());
+ EXPECT_DOUBLE_EQ(12, opt.getDropTime()[0]);
+ EXPECT_DOUBLE_EQ(1, opt.getDropTime()[1]);
+
+ process("perfdhcp -l ethx -d 2 -d 4.7");
+ ASSERT_EQ(2, opt.getDropTime().size());
+ EXPECT_DOUBLE_EQ(2, opt.getDropTime()[0]);
+ EXPECT_DOUBLE_EQ(4.7, opt.getDropTime()[1]);
+
+ // Negative test cases
+ // Drop time must not be negative
+ EXPECT_THROW(process("perfdhcp -l ethx -d -2 -d 4.7"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -d -9.1 -d 0"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, TimeOffset) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -T file1.x -T file2.x -E 4");
+ EXPECT_EQ(4, opt.getElapsedTimeOffset());
+
+ // Negative test cases
+ // Argument -E must be used with -T
+ EXPECT_THROW(process("perfdhcp -l ethx -E 3 -i"), isc::InvalidParameter);
+ // Value in -E not specified
+ EXPECT_THROW(process("perfdhcp -l ethx -T file.x -E -i"), isc::InvalidParameter);
+ // Value for -E must not be negative
+ EXPECT_THROW(process("perfdhcp -l ethx -E -3 -T file.x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ExchangeMode) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -i");
+ EXPECT_EQ(CommandOptions::DO_SA, opt.getExchangeMode());
+
+ // Negative test cases
+ // No template file specified
+ EXPECT_THROW(process("perfdhcp -i -l ethx -X 3"), isc::InvalidParameter);
+ // Offsets can't be used in simple exchanges (-i)
+ EXPECT_THROW(process("perfdhcp -i -l ethx -O 2 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -E 3 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -S 1 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -I 2 -T file.x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Offsets) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -E5 -4 -I 2 -S3 -O 30 -X7 -l ethx -X3 -T file1.x -T file2.x");
+ EXPECT_EQ(2, opt.getRequestedIpOffset());
+ EXPECT_EQ(5, opt.getElapsedTimeOffset());
+ EXPECT_EQ(3, opt.getServerIdOffset());
+ ASSERT_EQ(2, opt.getRandomOffset().size());
+ EXPECT_EQ(30, opt.getRandomOffset()[0]);
+ EXPECT_EQ(30, opt.getRandomOffset()[1]);
+ ASSERT_EQ(2, opt.getTransactionIdOffset().size());
+ EXPECT_EQ(7, opt.getTransactionIdOffset()[0]);
+ EXPECT_EQ(3, opt.getTransactionIdOffset()[1]);
+
+ // Negative test cases
+ // IP offset/IA_NA offset must be positive
+ EXPECT_THROW(process("perfdhcp -6 -I 0 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -I -4 -l ethx"), isc::InvalidParameter);
+
+ // TODO - other negative cases
+}
+
+TEST_F(CommandOptionsTest, LocalPort) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -L 2000");
+ EXPECT_EQ(2000, opt.getLocalPort());
+
+ // Negative test cases
+ // Local port must be between 0..65535
+ EXPECT_THROW(process("perfdhcp -l ethx -L -2"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -L"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -L 65540"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Preload) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -1 -P 3 -l ethx");
+ EXPECT_EQ(3, opt.getPreload());
+
+ // Negative test cases
+ // Number of preload packages must not be negative integer
+ EXPECT_THROW(process("perfdhcp -P -1 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -P -3 -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Seed) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -6 -P 2 -s 23 -l ethx");
+ EXPECT_EQ(23, opt.getSeed());
+ EXPECT_TRUE(opt.isSeeded());
+
+ process("perfdhcp -6 -P 2 -s 0 -l ethx");
+ EXPECT_EQ(0, opt.getSeed());
+ EXPECT_FALSE(opt.isSeeded());
+
+ // Negtaive test cases
+ // Seed must be non-negative integer
+ EXPECT_THROW(process("perfdhcp -6 -P 2 -s -5 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -P 2 -s -l ethx"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, TemplateFiles) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -T file1.x -l ethx");
+ ASSERT_EQ(1, opt.getTemplateFiles().size());
+ EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
+
+ process("perfdhcp -T file1.x -s 12 -w start -T file2.x -4 -l ethx");
+ ASSERT_EQ(2, opt.getTemplateFiles().size());
+ EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
+ EXPECT_EQ("file2.x", opt.getTemplateFiles()[1]);
+
+ // Negative test cases
+ // No template file specified
+ EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T"), isc::InvalidParameter);
+ // Too many template files specified
+ EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T file.x -T file.x -T file.x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Wrapped) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -B -w start -i -l ethx");
+ EXPECT_EQ("start", opt.getWrapped());
+
+ // Negative test cases
+ // Missing command after -w, expected start/stop
+ EXPECT_THROW(process("perfdhcp -B -i -l ethx -w"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Diagnostics) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -l ethx -i -x asTe");
+ EXPECT_EQ("asTe", opt.getDiags());
+
+ // Negative test cases
+ // No diagnostics string specified
+ EXPECT_THROW(process("perfdhcp -l ethx -i -x"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Aggressivity) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -a 10 -l 192.168.0.1");
+ EXPECT_EQ(10, opt.getAggressivity());
+
+ // Negative test cases
+ // Aggressivity must be non negative integer
+ EXPECT_THROW(process("perfdhcp -l ethx -a 0"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -a"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -a -2 -l ethx -a 3"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, MaxDrop) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -D 25 -l ethx -r 10");
+ EXPECT_EQ(25, opt.getMaxDrop()[0]);
+ process("perfdhcp -D 25 -l ethx -D 15 -r 10");
+ EXPECT_EQ(25, opt.getMaxDrop()[0]);
+ EXPECT_EQ(15, opt.getMaxDrop()[1]);
+
+ process("perfdhcp -D 15% -l ethx -r 10");
+ EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
+ process("perfdhcp -D 15% -D25% -l ethx -r 10");
+ EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
+ EXPECT_EQ(25, opt.getMaxDropPercentage()[1]);
+ process("perfdhcp -D 1% -D 99% -l ethx -r 10");
+ EXPECT_EQ(1, opt.getMaxDropPercentage()[0]);
+ EXPECT_EQ(99, opt.getMaxDropPercentage()[1]);
+
+ // Negative test cases
+ // Too many -D<value> options
+ EXPECT_THROW(process("perfdhcp -D 0% -D 1 -l ethx -r20 -D 3"), isc::InvalidParameter);
+ // Too many -D<value%> options
+ EXPECT_THROW(process("perfdhcp -D 99% -D 13% -l ethx -r20 -D 10%"), isc::InvalidParameter);
+ // Percentage is out of bounds
+ EXPECT_THROW(process("perfdhcp -D101% -D 13% -l ethx -r20"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -D0% -D 13% -l ethx -r20"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, NumRequest) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -n 1000 -r 10 -l ethx");
+ EXPECT_EQ(1000, opt.getNumRequests()[0]);
+ process("perfdhcp -n 5 -r 10 -n 500 -l ethx");
+ EXPECT_EQ(5, opt.getNumRequests()[0]);
+ EXPECT_EQ(500, opt.getNumRequests()[1]);
+
+ // Negative test cases
+ // Too many -n<value> parameters, expected maximum 2
+ EXPECT_THROW(process("perfdhcp -n 1 -n 2 -l ethx -n3 -r 20"), isc::InvalidParameter);
+ // Num request must be positive integer
+ EXPECT_THROW(process("perfdhcp -n 1 -n -22 -l ethx -r 10"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -n 0 -l ethx -r 10"), isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Period) {
+ CommandOptions& opt = CommandOptions::instance();
+ process("perfdhcp -p 120 -l ethx -r 100");
+ EXPECT_EQ(120, opt.getPeriod());
+
+ // Negative test cases
+ // Test period must be positive integer
+ EXPECT_THROW(process("perfdhcp -p 0 -l ethx -r 50"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -p -3 -l ethx -r 50"), isc::InvalidParameter);
+}
diff --git a/tests/tools/perfdhcp/tests/run_unittests.cc b/tests/tools/perfdhcp/tests/run_unittests.cc
new file mode 100644
index 0000000..6eeca75
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/run_unittests.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (isc::util::unittests::run_all());
+}
More information about the bind10-changes
mailing list