Browse Source

Merge branch 'trac1954'

Marcin Siodelski 13 years ago
parent
commit
8d56105742

+ 1 - 0
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

+ 20 - 2
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

+ 681 - 0
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

+ 412 - 0
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

File diff suppressed because it is too large
+ 3566 - 0
tests/tools/perfdhcp/perfdhcp.cc


+ 29 - 0
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)

+ 454 - 0
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);
+}

+ 25 - 0
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());
+}