Browse Source

Merge branch 'trac1324'

John DuBois 13 years ago
parent
commit
631c5c2d24

+ 2 - 0
configure.ac

@@ -935,6 +935,8 @@ AC_CONFIG_FILES([Makefile
                  tests/tools/Makefile
                  tests/tools/badpacket/Makefile
                  tests/tools/badpacket/tests/Makefile
+                 tests/tools/perfdhcp/Makefile
+                 tests/tools/perfdhcp/tests/Makefile
                ])
 AC_OUTPUT([doc/version.ent
            src/bin/cfgmgr/b10-cfgmgr.py

+ 1 - 1
tests/tools/Makefile.am

@@ -1 +1 @@
-SUBDIRS = badpacket
+SUBDIRS = badpacket perfdhcp

+ 1 - 0
tests/tools/perfdhcp/Makefile.am

@@ -0,0 +1 @@
+SUBDIRS = . tests

+ 280 - 0
tests/tools/perfdhcp/cloptions.c

@@ -0,0 +1,280 @@
+/*
+ * 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 "procconf.h"
+#include "perfdhcp.h"
+#include "cloptions.h"
+#include "dkdebug.h"
+
+static void printHelp(const char* progName, const char* usage);
+static void initialize(void);
+
+// The current version information
+const char* VERSION = "perfdhcp v1.0 2011-10-30";
+static const char* PROGNAME = "perfdhcp";
+
+static int v6 = 0;                      // DHCPv6 operation (-6)
+static int initialOnly = 0;             // Do only initial exchange (-i)
+static unsigned rate = 0;               // Request rate (-r)
+static unsigned numRequest = 0;         // Number of requests (-n)
+static double dropTime = 0.0;           // Response timeout (-d)
+static double testPeriod = 0.0;         // Test period (-p)
+static const char* server = NULL;       // Server to contact
+static const char* maxDropOpt = NULL;   // Max dropped responses (-D)
+static const char* localName = NULL;    // Local host/interface (-l)
+
+/*
+ * Return value:
+ * 0 if the command has been satisfied and the program should exit 0.
+ * 2 for usage error, in which case an error message will have been printed.
+ * 1 if argument processing was successful and the program should continue.
+ */
+int
+procArgs(int argc, const char* argv[]) {
+    char usage[] =
+        "Usage:\n\
+perfdhcp [-hv] [-4|-6] [-r<rate>] [-n<num-request>] [-p<test-period>]\n\
+         [-d<drop-time>] [-D<max-drop>] [-l<local-addr|interface>] [-i]\n\
+         [-x<diagnostic-selector>] [server]\n";
+    int v4 = 0;                 /* DHCPv4 operation explicitly requested */
+    const char* msg;            /* Failure message from procOpts() */
+    int help = 0;               /* Help requested */
+    int versionReq = 0;         /* Version requested */
+    const char* diagStr = NULL; /* Diagnostics requested (-x) */
+
+    /* option descriptions */
+    confvar_t optConf[] = {
+        { 'h', NULL,        CF_SWITCH,    &help,        1 },
+        { 'v', NULL,        CF_SWITCH,    &versionReq,  1 },
+        { '4', NULL,        CF_SWITCH,    &v4,          1 },
+        { '6', NULL,        CF_SWITCH,    &v6,          1 },
+        { 'i', NULL,        CF_SWITCH,    &initialOnly, 1 },
+        { 'l', NULL,        CF_NE_STRING, &localName,   0 },
+        { 'r', NULL,        CF_POS_INT,   &rate,        0 },
+        { 'n', NULL,        CF_POS_INT,   &numRequest,  0 },
+        { 'd', NULL,        CF_POS_FLOAT, &dropTime,    0 },
+        { 'p', NULL,        CF_POS_FLOAT, &testPeriod,  0 },
+        { 'D', NULL,        CF_NE_STRING, &maxDropOpt,  0 },
+        { 'x', NULL,        CF_STRING,    &diagStr,     0 },
+        { '\0', NULL,       CF_ENDLIST,   NULL,         0 }
+    };
+
+    /* diagnostic map */
+    const struct dkdesc diagLetters[] = {
+        { 's', DK_SOCK },
+        { 'm', DK_MSG },
+        { 'p', DK_PACKET },
+        { 'a', DK_ALL },
+        { '\0', 0 }
+    };
+
+    initialize();
+    /* Process command line options */
+    msg = procOpts(&argc, &argv, optConf, NULL, PROGNAME, NULL);
+    if (msg != NULL) {
+        fprintf(stderr, "%s: %s\n", PROGNAME, msg);
+        return(2);
+    }
+
+    if (help) {
+        printHelp(PROGNAME, usage);
+        return(0);
+    }
+    if (versionReq) {
+        printf("%s\n", VERSION);
+        return(0);
+    }
+    if (diagStr != NULL) {
+        char c;
+        if ((c = dk_setup(diagStr, diagLetters)) != '\0') {
+            fprintf(stderr,
+                    "%s: Invalid selector character given with -x: '%c'\n",
+                    PROGNAME, c);
+            return(2);
+        }
+    }
+
+    if (v4 && v6) {
+        fprintf(stderr, "%s: Must not give -4 and -6 together.\n", PROGNAME);
+        return(2);
+    }
+    switch (argc) {
+    case 0:
+        if (v6 && localName != NULL) {
+            server = "all";
+        } else {
+            if (v6) {
+                fprintf(stderr,
+                        "%s: Use -l to specify an interface name.\n\%s\n",
+                        PROGNAME, usage);
+            }
+            else {
+                fprintf(stderr, "%s: Must specify a DHCP server.\n\%s\n",
+                        PROGNAME, usage);
+            }
+            return(2);
+        }
+        break;
+    case 1:
+        server = argv[0];
+        break;
+    default:
+        fprintf(stderr, "%s: Too many arguments.\n\%s\n", PROGNAME, usage);
+        return(2);
+    }
+    return(1);
+}
+
+/*
+ * Initialize values set by procArgs().
+ * Initialized though they are static to allow for repeated calls for testing.
+ */
+static void
+initialize(void) {
+    v6 = 0;
+    initialOnly = 0;
+    rate = 0;
+    numRequest = 0;
+    dropTime = 0.0;
+    testPeriod = 0.0;
+    maxDropOpt = NULL;
+    localName = NULL;
+    server = NULL;
+}
+
+static void
+printHelp(const char* progName, const char* usage) {
+    printf(
+        "%s: Execute a performance test against a DHCP server.\n\
+%s\n\
+The [server] argument is the name/address of the DHCP server to contact. \n\
+For DHCPv4 operation, exchanges are initiated by transmitting a DHCP\n\
+DISCOVER to this address.  For DHCPv6 operation, exchanges are initiated\n\
+by transmitting a DHCP SOLICIT to this address.  In the DHCPv6 case, the\n\
+special name \"all\" can be used to refer to\n\
+All_DHCP_Relay_Agents_and_Servers (the multicast address FF02::1:2), or\n\
+the special name \"servers\" to refer to All_DHCP_Servers (the multicast\n\
+address FF05::1:3).  The [server] argument is optional only in the case\n\
+that -l is used to specify an interface, in which case [server] defaults\n\
+to \"all\".\n\
+\n\
+Exchanges are initiated by transmitting a DHCP SOLICIT to\n\
+All_DHCP_Relay_Agents_and_Servers (the multicast address FF02::1:2) via\n\
+this interface.\n\
+\n\
+The default is to perform a single 4-way exchange, effectively pinging the\n\
+server.  The -r option is used to set up a performance test.\n\
+\n\
+Options:\n\
+-4: DHCPv4 operation (default). This is incompatible with the -6 option.\n\
+-6: DHCPv6 operation. This is incompatible with the -4 option.\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\
+-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 normally be\n\
+    routed to the server is used.\n\
+    For DHCPv6 operation, specify the name of the network interface via\n\
+    which exchanges are initiated.  This must be specified unless a server\n\
+    name is given, in which case the interface through which traffic would\n\
+    normally be routed to the server is used.\n\
+-r<rate>: Initiate <rate> DORA/SARR (or if -i is given, DO/SA) exchanges per\n\
+    second.  A periodic report is generated showing the number of exchanges\n\
+    which were not completed, as well as the average response latency.  The\n\
+    program continues until interrupted, at which point a final report is\n\
+    generated.\n\
+-v: Report the version number of this program.\n\
+-x<diagnostic-selector>: Include extended diagnostics in the output.\n\
+    <diagnostic-selector> is a string of characters, each specifying an\n\
+    operation for which verbose output is desired.  The selector characters\n\
+    are:\n\
+    [TO BE ADDED]\n\
+\n\
+The remaining options are used only in conjunction with -r:\n\
+\n\
+-d<drop-time>: Specify the time after which a request is treated as having\n\
+    been lost.  The value is given in seconds and may contain a fractional\n\
+    component.  The default is 1 second.\n\
+-D<max-drop>: Abort the test if more than <max-drop> requests have been\n\
+    dropped.  Use -D0 to abort if even a single request has been dropped. \n\
+    If <max-drop> includes the suffix \"%%\", it specifies a maximum\n\
+    percentage of requests that may be dropped before abort.  In this\n\
+    case, testing of the threshold begins after 10 requests have been\n\
+    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, after\n\
+    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\
+\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 exchanges\n\
+  are not successfully completed.\n",
+        progName, usage);
+}
+
+int
+isV6(void) {
+    return v6;
+}
+
+int
+getInitialOnly(void) {
+    return initialOnly;
+}
+
+unsigned
+getRate(void) {
+    return rate;
+}
+
+unsigned
+getNumRequest(void) {
+    return numRequest;
+}
+
+double
+getDropTime(void) {
+    return dropTime;
+}
+
+double
+getTestPeriod(void) {
+    return testPeriod;
+}
+
+const char *
+getServer(void) {
+    return server;
+}
+
+const char *
+getLocalName(void) {
+    return localName;
+}
+
+const char *
+getMaxDrop(void) {
+    return maxDropOpt;
+}

+ 57 - 0
tests/tools/perfdhcp/cloptions.h

@@ -0,0 +1,57 @@
+/*
+ * 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 CLOPTIONS_H
+#define CLOPTIONS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include "procconf.h"
+
+/*
+ * Process the options/arguments passed to the program.
+ *
+ * Input varibles:
+ * argc, argv: Command line data.
+ *
+ * Return value:
+ * 0 if the command has been satisfied and the program should exit 0.
+ * 2 for usage error, in which case an error message will have been printed.
+ * 1 if argument processing was successful and the program should continue.
+ */
+int procArgs(int argc, const char* argv[]);
+
+/*
+ * These functions return values set by command line options
+ */
+int isV6(void);                // DHCPv6 operation (-6)
+int getInitialOnly(void);       // Do only initial exchange (-i)
+unsigned getRate(void);         // Request rate (-r)
+unsigned getNumRequest(void);   // Number of requests (-n)
+double getDropTime(void);       // Response timeout (-d)
+double getTestPeriod(void);     // Test period (-p)
+const char* getServer(void);    // Server to contact
+const char* getLocalName(void); // Local host/interface (-l)
+const char* getMaxDrop(void);   // Max dropped responses (-D)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 65 - 0
tests/tools/perfdhcp/dkdebug.c

@@ -0,0 +1,65 @@
+/*
+ * 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 <stdarg.h>
+#include "dkdebug.h"
+
+/*
+ * The set of diagnostic bits set by dk_setup(), and used by the other
+ * functions to test offered diagnostics against.
+ */
+static unsigned dk_diag_mask = 0;
+
+char
+dk_setup(const char* diag_str, const struct dkdesc* diags) {
+    dk_diag_mask = 0;
+    int i;
+
+    for (; *diag_str != '\0'; diag_str++) {
+        for (i = 0; diags[i].keyletter != '\0'; i++) {
+            if (diags[i].keyletter == *diag_str) {
+                dk_diag_mask |= diags[i].mask;
+                break;
+            }
+        }
+        if (diags[i].keyletter == '\0') {
+            return(*diag_str);
+        }
+    }
+    return('\0');
+}
+
+void
+dkprintf(unsigned diag_req, const char format[], ...) {
+    va_list ap;
+
+    va_start(ap,format);
+    vdkprintf(diag_req, format, ap);
+    va_end(ap);
+}
+
+void
+vdkprintf(unsigned diag_req, const char format[], va_list ap) {
+    if (diag_req & dk_diag_mask) {
+        vfprintf(stderr, format, ap);
+    }
+}
+
+int
+dk_set(unsigned diag_req) {
+    return((diag_req & dk_diag_mask) != 0);
+}

+ 88 - 0
tests/tools/perfdhcp/dkdebug.h

@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+/*
+ * This module implements a mask-style diagnostic printing/selection system.
+ * Each diagnostic is enabled by including an associated keyletter in a
+ * selector string given at initialization time (typically as a command-line
+ * option).
+ */
+
+#ifndef DKDEBUG_H
+#define DKDEBUG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+#define DK_ALL (~0),   /* Select all diagnostics */
+
+/*
+ * Elements of this type are used to map the available diagnostic keyletters to
+ * mask bits.
+ */
+struct dkdesc {
+    char keyletter;
+    unsigned mask;
+};
+
+/*
+ * Initialize diagnostic mask.
+ *
+ * Input variables:
+ *
+ * diag_str is a string giving the keyletters for diagnostics to enable.
+ *
+ * diags describes the available diagnostics, mapping each keyletter to any
+ * number of mask bits.  It should be terminated with an element with keyletter
+ * set to the null character.
+ *
+ * Return value:
+ * If an invalid character is given in diag_str, that character; otherwise a
+ * null character.
+ */
+char dk_setup(const char* diag_str, const struct dkdesc* diags);
+
+/*
+ * The remaining functions test the mask bitset diag_req against the currently
+ * enabled diagnostics, as set by dk_setup().  If any bits set in diag_req are
+ * among the enabled diagnostics, the diagnostic operation is enabled.
+ */
+
+/*
+ * If diagnostic operation is enabled, use the remaining arguments to print
+ * like fprintf(stderr, )
+ */
+void dkprintf(unsigned diag_req, const char format[], ...);
+
+/*
+ * If diagnostic operation is enabled, use the remaining arguments to print
+ * like vfprintf(stderr, )
+ */
+void vdkprintf(unsigned diag_req, const char format[], va_list ap);
+
+/*
+ * If diagnostic operation is enabled, return 1; else return false.
+ */
+int dk_set(unsigned diag_req);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 39 - 0
tests/tools/perfdhcp/perfdhcp.h

@@ -0,0 +1,39 @@
+/*
+ * 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 PERFDHCP_H
+#define PERFDHCP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The masks associated with keyletters, used in dkdesc structures for setup
+ * and passed in the diag_req argument to the output/test functions to
+ * determine which diagnostics they are enabled for.
+ */
+enum {
+    DK_SOCK   = 1,
+    DK_MSG    = 2,
+    DK_PACKET = 4
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 485 - 0
tests/tools/perfdhcp/procconf.c

@@ -0,0 +1,485 @@
+/*
+ * 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 <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include "procconf.h"
+
+static char errmsg[256];    /* for returning error descriptions */
+static const char* pc_name;
+static const char* pc_usage;
+
+#define INTERNAL_ERROR -1
+#define USAGE_ERROR -2
+
+/*
+ * error: printf-style interface to print an error message or write it into
+ * global errmsg.  If a usage message has been given (the indicator that errors
+ * should be handled internally), the message is printed to stderr and the
+ * program is exited.  If not, it is written into errmsg.
+ * Input variables:
+ * errtype is the error type.  If USAGE_ERROR, the program's usage is included
+ *     in error messages, and the exit status is 2; otherwise the exit status
+ *     is 1.
+ * format and the remaining arguments are as for printf().
+ */
+static void
+error(const int errtype, const char* format, ...) {
+    va_list ap;
+
+    va_start(ap,format);
+    if (pc_usage != NULL) { /* error messages should be printed directly */
+        fprintf(stderr, "%s: ", pc_name);
+        vfprintf(stderr, format, ap);
+        putc('\n', stderr);
+        if (errtype == USAGE_ERROR) {
+            fputs(pc_usage, stderr);
+        }
+        exit(errtype == USAGE_ERROR ? 2 : 1);
+    } else {
+        vsnprintf(errmsg, sizeof(errmsg), format, ap);
+    }
+    va_end(ap);
+}
+
+/*
+ * Allocate memory, with error checking.
+ * On allocation failure, error() is called; see its description.
+ * The memory is zeroed before being returned.
+ */
+static void*
+pc_malloc(size_t size) {
+    void* ret = calloc(1, size);
+    if (ret == NULL) {
+        error(INTERNAL_ERROR, "Out of memory");
+    }
+    return(ret);
+}
+
+/*
+ * Generate an error message describing an error in the value passed with an
+ * option.  This is a front end for error(); see its usage for details.
+ * Input variables:
+ * expected: A description of what was expected for the value.
+ * varDesc: The descriptor for the option.
+ * value: The value that was given with the option.
+ * detail: If non-null, additional detail to append regardign the problem.
+ */
+static void
+opterror(const char* expected, const char* value, const confvar_t* varDesc,
+         const char* detail) {
+    if (detail == NULL) {
+        detail = "";
+    }
+    error(USAGE_ERROR,
+          "Invalid value given for option -%c: expected %s, got: %s%s",
+          varDesc->outind, expected, value, detail);
+}
+
+/*
+ * Add an option flag or value assignment to the options database.
+ * This does all option-type-specific processing, and generates linked lists
+ * of option structures.
+ *
+ * Input variables:
+ * value is the string value assigned to the option, for options that take
+ *     values.
+ * varDesc is the description structure for this option.
+ *
+ * Output variables:
+ * The option data is stored in a malloced confval structure, which is appended
+ * to the option list.  The first element of the list is pointed to by first.
+ * first should be NULL if the list contains no elements; in this case it is
+ * set to point to the new structure.  Otherwise the structure is appended to
+ * the element pointed to by last (it is updated to point to that structure).
+ * The 'next' element of the new structure is set to NULL; this is the list
+ * termination indicator when traversing it.
+ *
+ * Return value:
+ * 0 if option was ignored.
+ * 1 if option was processed & added to option chain.
+ * On error, a string describing the error is stored in the global errmsg and
+ * -1 is returned.
+ */
+static int
+addOptVal(const char* value, const confvar_t* varDesc,
+          confval** first, confval** last) {
+    const void* addr;           /* address, if any at which to store value */
+    confval data;               /* data for this option/value */
+    confval *ret_data;          /* allocated structure to add to list */
+    int seen = *first != NULL;  /* has this option been seen before? */
+    char* ptr;        /* character that terminated processing in strtox() */
+    int err;                    /* bad numeric value found? */
+
+    /* if first instance of this option, store result to given addr */
+    addr = seen ? NULL : varDesc->addr;
+    switch (varDesc->type) {
+    case CF_CHAR:
+        if (strlen(value) > 1) {    /* length 0 is OK; gives null char */
+            opterror("a single character", value, varDesc, NULL);
+            return(-1);
+        }
+        data.value.charval = *value;
+        if (addr != NULL) {
+            *(char*)addr = *value;
+        }
+        break;
+    case CF_STRING:
+    case CF_NE_STRING:
+        if (varDesc->type == CF_NE_STRING && *value == '\0') {
+            opterror("a non-empty string", value, varDesc, NULL);
+            return(-1);
+        }
+        data.value.string = value;
+        if (addr != NULL) {
+            *(const char**)addr = value;
+        }
+        break;
+    case CF_INT:
+    case CF_NON_NEG_INT:
+    case CF_POS_INT:
+        /* todo: check for out-of-range result */
+        errno = 0;
+        data.value.intval = strtol(value, &ptr, 0);
+        if (errno == ERANGE) {
+            opterror("an integer", value, varDesc,
+                     " (out of range)");
+            return(-1);
+        }
+        err = *value == '\0' || *ptr != '\0';
+        switch (varDesc->type) {
+        case CF_INT:
+            if (err) {
+                opterror("an integer", value, varDesc, NULL);
+                return(-1);
+            }
+            break;
+        case CF_NON_NEG_INT:
+            if (err || data.value.intval < 0) {
+                opterror("a non-negative integer", value, varDesc,
+                         NULL);
+                return(-1);
+            }
+            data.value.nnint = data.value.intval;
+            break;
+        case CF_POS_INT:
+            if (err || data.value.intval <= 0) {
+                opterror("a positive integer", value, varDesc, NULL);
+                return(-1);
+            }
+            data.value.nnint = data.value.intval;
+            break;
+        default:
+            /* To avoid complaints from -Wall */
+            ;
+        }
+        if (addr != NULL) {
+            *(int*)addr = data.value.intval;
+        }
+        break;
+    case CF_FLOAT:
+    case CF_NON_NEG_FLOAT:
+    case CF_POS_FLOAT:
+        /* todo: check for out-of-range result */
+        errno = 0;
+        data.value.floatval = strtod(value, &ptr);
+        if (errno == ERANGE) {
+            opterror("a number", value, varDesc, " (out of range)");
+            return(-1);
+        }
+        err = *value == '\0' || *ptr != '\0';
+        switch (varDesc->type) {
+        case CF_FLOAT:
+            if (err) {
+                opterror("a number", value, varDesc, NULL);
+                return(-1);
+            }
+            break;
+        case CF_NON_NEG_FLOAT:
+            if (err || data.value.floatval < 0) {
+                opterror("a non-negative number", value, varDesc,
+                         NULL);
+                return(-1);
+            }
+            break;
+        case CF_POS_FLOAT:
+            if (err || data.value.floatval <= 0) {
+                opterror("a positive number", value, varDesc, NULL);
+                return(-1);
+            }
+            break;
+        default:
+            /* To avoid complaints from -Wall */
+            ;
+        }
+        if (addr != NULL) {
+            *(double*)addr = data.value.floatval;
+        }
+        break;
+    case CF_SWITCH:
+        data.value.switchval = varDesc->value;
+        value = "1";    /* for debugging */
+        if (addr != NULL) {
+            *(int*)addr = varDesc->value;
+        }
+        break;
+    case CF_ENDLIST:
+        /* To avoid complaints from -Wall */
+        ;
+    }
+    data.strval = value;
+    data.next = NULL;
+    if ((ret_data = (confval*)pc_malloc(sizeof(confval))) == NULL) {
+        return(-1);
+    }
+    *ret_data = data;
+    if (seen) {
+        (*last)->next = ret_data;
+    } else {
+        *first = ret_data;
+    }
+    *last = ret_data;
+    return(1);
+}
+
+/*
+ * Input variables:
+ * argc, argv: Command line data.
+ * optConf[]: Option description structures.
+ *
+ * Output variables:
+ * See addOptVal().
+ * After processing, argc will be the number of non-option arguments and argv
+ * will start with the first non-option argument.
+ *
+ * Return value:
+ * On success, the number of options processed.
+ * On error, a string describing the error is stored in the global errmsg and
+ * -1 is returned.
+ */
+static int
+procCmdLineArgs(int* argc, const char** argv[], const confvar_t optConf[],
+                confval** perOptRecordsFirst, confval** perOptRecordsLast) {
+    char* p;
+    extern char* optarg;    /* For getopt */
+    extern int optind;      /* For getopt */
+    extern int optopt;      /* For getopt */
+    char optstr[514];       /* List of option chars, for getopt */
+    unsigned optCharToConf[256];        /* Map option char/num to confvar */
+    int optchar;            /* value returned by getopt() */
+    int confNum;       /* to iterate over confvars */
+    int count = 0;          /* number of options processed */
+
+    p = optstr;
+    *(p++) = ':';
+    for (confNum = 0; optConf[confNum].type != CF_ENDLIST; confNum++) {
+        unsigned outind = optConf[confNum].outind;
+        if (outind < 256 && isprint(outind)) {
+            *(p++) = (char)outind;
+            switch (optConf[confNum].type) {
+            case CF_SWITCH:
+                break;
+            default:
+                *(p++) = ':';
+                break;
+            }
+            optCharToConf[outind] = confNum;
+        }
+    }
+
+    *p = '\0';
+    optind = 1;
+    while ((optchar = getopt(*argc, *argv, optstr)) != -1)
+    {
+        int ind;
+        int ret;
+
+        if (optchar == '?') {
+            error(USAGE_ERROR, "Unknown option character '%c'", optopt);
+            return(-1);
+        } else if (optchar == ':') {
+            error(USAGE_ERROR, "No value given for option -%c", optopt);
+            return(-1);
+        }
+        ind = optCharToConf[optchar];
+        switch (ret = addOptVal(optarg, &optConf[ind],
+                                &perOptRecordsFirst[ind],
+                                &perOptRecordsLast[ind])) {
+        case 1:
+            count++;
+            break;
+        case 0:
+            break;
+        default:
+            return(ret);
+        }
+    }
+    *argc -= optind;
+    *argv += optind;
+    return(count);
+}
+
+/*
+ * Input variables:
+ * argc, argv: Command line data.
+ * optConf[]: Option description structures.
+ * name: Name of program, for messages.
+ * usage: Usage message.  If non-null, on error a message is printed to stderr
+ *    and the program exits.
+ *
+ * Output variables:
+ * Option values are stored at the value given by any confvar that has a
+ * non-null address. 
+ * If confdatda is not null, the processed option values are stored in
+ * confdata.
+ * A pointer to the start of the values for each option is stored in
+ * confdata->optVals[].values at the same offset as the option appears in
+ * confdata[].
+ * For any option for option characters/indexes that have been used,
+ * confdata->map[index] is set to the same data.
+ * After processing, argc will have been adjusted to be the number of
+ * non-option arguments and argv will have been adjusted to start with the
+ * first non-option argument.
+ * The malloced data structures returned in confdata are:
+ *   optVals
+ *   optVals[0].values
+ *   If any option characters/indexes are used, map.  If not used, this will be
+ *     a null pointer.
+ *
+ * Return value:
+ * On success, NULL.
+ * On error, a message describing the problem.
+ */
+const char*
+procOpts(int* argc, const char** argv[], const confvar_t optConf[],
+         confdata_t* confdata, const char name[],
+         const char usage[]) {
+    /*
+     * First & last records in the linked lists maintained for each option.
+     * These will point to arrays indexed by option number, giving one pointer
+     * (each) per option, used to maintain/return the list of values seen for
+     * that option (see the description of first & last in addOptVal()
+     */
+    confval** perOptRecordsFirst;
+    confval** perOptRecordsLast;
+
+    /* Number of configuration options given in optConf */
+    unsigned numConf;
+    unsigned maxOptIndex = 0;   /* The highest option index number seen */
+    /* number of option instances + assignments given */
+    int numOptsFound;
+    int optNum;    /* to iterate through the possible options */
+    int i;         /* index into the global list of option value structures */
+    confval** valuePointers;    /* global list of value structures */
+
+    pc_name = name;
+    pc_usage = usage;
+    for (numConf = 0; optConf[numConf].type != CF_ENDLIST; numConf++) {
+        unsigned outind = optConf[numConf].outind;
+
+        if ((outind & ~CF_NOTFLAG) > maxOptIndex) {
+            maxOptIndex = outind & ~CF_NOTFLAG;
+        }
+    }
+    if (numConf == 0) {
+        error(INTERNAL_ERROR, "Empty confvar list");
+        return(errmsg);
+    }
+    if ((perOptRecordsFirst = (confval**)pc_malloc(sizeof(confval*) * numConf))
+            == NULL || (perOptRecordsLast =
+            (confval**)pc_malloc(sizeof(confval*) * numConf)) == NULL) {
+        return(errmsg);
+    }
+
+    numOptsFound = procCmdLineArgs(argc, argv, optConf, perOptRecordsFirst,
+                                   perOptRecordsLast);
+    free(perOptRecordsLast);
+    perOptRecordsLast = NULL;
+
+    if (numOptsFound < 0)
+    {
+        free(perOptRecordsFirst);
+        return(errmsg);
+    }
+    if (confdata == NULL) {
+        free(perOptRecordsFirst);
+        return NULL;
+    }
+
+    /*
+     * All options have been read & initial processing done.
+     * An array of pointers is now generated for the options.
+     */
+    if ((valuePointers =
+            (confval**)pc_malloc(sizeof(confval*) * numOptsFound)) == NULL ||
+            (confdata->optVals =
+            (cf_option*)pc_malloc(sizeof(cf_option) * numConf)) == NULL) {
+        return(errmsg);
+    }
+    /* If option characters / indexes are used, allocate a map for them */
+    if (maxOptIndex == 0) {
+	confdata->map = NULL;
+    } else {
+        if ((confdata->map = (cf_option**)pc_malloc(sizeof(cf_option) *
+             (maxOptIndex+1))) == NULL) {
+            return(errmsg);
+        }
+    }
+
+    /*
+     * Store the linked lists of option values into arrays.
+     * Pointers to all option instances are stored in valuePointers,
+     * with the values for each particular option being contiguous.
+     */
+    i = 0;
+    for (optNum = 0; optNum < numConf; optNum++) {
+        unsigned outind = optConf[optNum].outind;
+        confval* optval;
+
+        confdata->optVals[optNum].num = 0;
+        confdata->optVals[optNum].values = &valuePointers[i];
+        if (outind != 0) {
+            confdata->map[outind & ~CF_NOTFLAG] = &confdata->optVals[optNum];
+        }
+        for (optval = perOptRecordsFirst[optNum]; optval != NULL;
+                 optval = optval->next) {
+            confdata->optVals[optNum].num++;
+            valuePointers[i++] = optval;
+        }
+    }
+    free(perOptRecordsFirst);
+    return(NULL);
+}
+
+/*
+ * Free the malloced data stored in confdata elements by ProcOpts()
+ */
+void
+confdataFree(confdata_t *confdata) {
+    if (confdata->map != NULL) {
+        free(confdata->map);
+        confdata->map = NULL;
+    }
+    free(confdata->optVals[0].values);
+    free(confdata->optVals);
+    confdata->optVals = NULL;
+}

+ 136 - 0
tests/tools/perfdhcp/procconf.h

@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+/*
+ * This header gives the interface types and prototypes for the procconf
+ * module, which processes command line options.
+ */
+
+#ifndef PROCCONF_H
+#define PROCCONF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <limits.h> /* for UINT_MAX */
+
+/*
+ * These are used to specify the option type expected
+ */
+typedef enum {
+    CF_CHAR,          /* A single character */
+    CF_STRING,        /* A string */
+    CF_NE_STRING,     /* A non-empty string */
+    CF_INT,           /* An integer */
+    CF_NON_NEG_INT,   /* A non-negative integer */
+    CF_POS_INT,       /* A positive integer */
+    CF_FLOAT,         /* A floating-point value */
+    CF_NON_NEG_FLOAT, /* A non-negative floating point value */
+    CF_POS_FLOAT,     /* A positive floating point value */
+    CF_SWITCH,        /* An option that does not take a value */
+    CF_ENDLIST        /* End of option list */
+} cf_type;
+
+/*
+ * This is to be OR'd into a confvar_t outind to indicate that an index
+ * number that is given is not to be interpreted as a command line option
+ * character.
+ */
+#define CF_NOTFLAG (UINT_MAX & ~(UINT_MAX >> 1))
+
+/*
+ * Structure for passing varname/value pairs.
+ * This gives the variable names to search for, and allows variable names to
+ * mapped to characters so that the characters may be used as indexes into the
+ * results array.
+ */
+typedef struct {
+    unsigned outind;    /* Single-character option, or option output index */
+    char* varname;  /* Long name, for config file and long option */
+    cf_type type;   /* Option type */
+    const void* addr;   /* Address of variable associated with this option */
+    int value;      /* Value to assign to switch options */
+} confvar_t;
+
+/*
+ * Structure for returning assigned values.
+ */
+typedef struct confval_struct {
+    const char* strval; /* String form of value */
+    unsigned index; /* Relative position of this instance */
+    union {
+        int intval;
+        unsigned int nnint;
+        double floatval;
+        const char* string;
+        int switchval;
+        char charval;
+    } value;
+    struct confval_struct* next;
+} confval;
+
+/* Information about the values assigned to a particular option */
+typedef struct {
+    int num;        /* number of instances of this option */
+    confvar_t* confvar; /* which option descriptor this corresponds to */
+    confval** values;   /* Start of pointers to values for this option */
+} cf_option;
+
+typedef struct {
+    cf_option* optVals; /* All option values */
+    cf_option** map;    /* Option values indexed by option-char/option-index */
+} confdata_t;
+
+/*
+ * Input variables:
+ * argc, argv: Command line data.
+ * optConf[]: Option description structures.  The array should end with an
+ *    element with a type of CF_ENDLIST.
+ * name: Name of program, for messages.
+ * usage: Usage message.  If non-null, on error a message is printed to stderr
+ *    and the program exits.
+ *
+ * Output variables:
+ * The processed option values are stored in confdata.
+ * A pointer to the start of the values for each option is stored in
+ * confdata->optVals[].values at the same offset as the option appears in
+ * confdata[].
+ * For any option for option characters/indexes have been used,
+ * confdata->map[index] is set to the same data.
+ * After processing, argc will have been adjusted to be the number of
+ * non-option arguments and argv will have been adjusted to start with the
+ * first non-option argument.
+ *
+ * Return value:
+ * On success, NULL.
+ * On error, a message describing the problem.
+ */
+const char*
+procOpts(int* argc, const char** argv[], const confvar_t optConf[],
+         confdata_t* confdata, const char name[],
+         const char usage[]);
+
+/*
+ * Free the malloced data stored in confdata elements by ProcOpts()
+ */
+void confdataFree(confdata_t *confdata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 31 - 0
tests/tools/perfdhcp/tests/Makefile.am

@@ -0,0 +1,31 @@
+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 += cloptions_unittest.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/cloptions.c
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/procconf.c
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/dkdebug.c
+
+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)

+ 194 - 0
tests/tools/perfdhcp/tests/cloptions_unittest.cc

@@ -0,0 +1,194 @@
+// 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 <stdint.h>
+#include <gtest/gtest.h>
+#include <exceptions/exceptions.h>
+#include <stdarg.h>
+
+#include "../cloptions.h"
+#include "../perfdhcp.h"
+#include "../dkdebug.h"
+
+// Up to 4 arguments may be specified
+// The final argument must be NULL.  This is not counted in the 4 arguments.
+int checkOption(int retExpected, ...) {
+    const char* argv[6] = { "perfdhcp" };
+    int argc = 1;
+    int ret;
+    const char* p;
+    va_list ap;
+
+    va_start(ap, retExpected);
+    p = va_arg(ap, const char *);
+    while (p != NULL && argc < 5) {
+        argv[argc++] = p;
+        p = va_arg(ap, const char *);
+    }
+    va_end(ap);
+    argv[argc++] = "foo";   /* server */
+    ret = procArgs(argc, argv);
+    EXPECT_EQ(ret, retExpected);
+    return(ret == retExpected);
+}
+
+void checkOptionInvalid(const char* optflag, const char* optval) {
+    checkOption(2, optflag, optval, NULL);
+}
+
+int checkOptionValid(const char* optflag, const char* optval) {
+
+    return(checkOption(1, optflag, optval, NULL));
+}
+
+void checkPosFloatOpt(const char* optflag, double (*retfunc)(void)) {
+    if (checkOptionValid(optflag, "100.0")) {
+        EXPECT_EQ(100.0, (*retfunc)());
+    }
+    checkOptionInvalid(optflag, "0");
+    checkOptionInvalid(optflag, "-1");
+    checkOptionInvalid(optflag, "0.0");
+    checkOptionInvalid(optflag, "-1.0");
+    checkOptionInvalid(optflag, "x");
+}
+
+void checkPosIntOpt(const char* optflag, unsigned (*retfunc)(void)) {
+    if (checkOptionValid(optflag, "100")) {
+        EXPECT_EQ(100, (*retfunc)());
+    }
+    checkOptionInvalid(optflag, "0");
+    checkOptionInvalid(optflag, "-1");
+    checkOptionInvalid(optflag, "1.0");
+    checkOptionInvalid(optflag, "x");
+}
+
+void checkFlag(const char* optflag, int (*retfunc)(void)) {
+    if (checkOptionValid(optflag, NULL)) {
+        EXPECT_EQ(1, (*retfunc)());
+    }
+}
+
+void checkStrOpt(const char* optflag, const char *(*retfunc)(void)) {
+    if (checkOptionValid(optflag, "bar")) {
+        EXPECT_STREQ("bar", (*retfunc)());
+    }
+}
+
+void checkNEStrOpt(const char* optflag, const char *(*retfunc)(void)) {
+    if (checkOptionValid(optflag, "bar")) {
+        EXPECT_STREQ("bar", (*retfunc)());
+    }
+    checkOptionInvalid(optflag, "");
+}
+
+void checkFlagHandled(const char* optflag) {
+    checkOption(0, optflag, NULL);
+}
+
+// Command line option tests
+
+TEST(CommandOptionsTest, numreq) {
+    checkPosIntOpt("-n", getNumRequest);
+}
+
+TEST(CommandOptionsTest, rate) {
+    checkPosIntOpt("-r", getRate);
+}
+
+TEST(CommandOptionsTest, droptime) {
+    checkPosFloatOpt("-d", getDropTime);
+}
+
+TEST(CommandOptionsTest, period) {
+    checkPosFloatOpt("-p", getTestPeriod);
+}
+
+TEST(CommandOptionsTest, help) {
+    checkFlagHandled("-h");
+}
+
+TEST(CommandOptionsTest, version) {
+    checkFlagHandled("-v");
+}
+
+TEST(CommandOptionsTest, v4) {
+    if (checkOptionValid("-4", NULL)) {
+        EXPECT_EQ(0, isV6());
+    }
+}
+
+TEST(CommandOptionsTest, v6) {
+    checkFlag("-6", isV6);
+}
+
+TEST(CommandOptionsTest, initial) {
+    checkFlag("-i", getInitialOnly);
+}
+
+TEST(CommandOptionsTest, localname) {
+    checkNEStrOpt("-l", getLocalName);
+}
+
+TEST(CommandOptionsTest, diagnostics) {
+    if (checkOptionValid("-x", "sm")) {
+        EXPECT_EQ(1, dk_set(DK_SOCK));
+        EXPECT_EQ(1, dk_set(DK_MSG));
+        EXPECT_EQ(0, dk_set(DK_PACKET));
+    }
+    checkOptionInvalid("-x", "z");
+}
+
+TEST(CommandOptionsTest, maxdrop) {
+    checkNEStrOpt("-D", getMaxDrop);
+}
+
+/*
+ * NOTE: GNU getopt() as of 2011-11-07 is subject to a bug that causes it to
+ * break if called with an invalid option followed by another call with optind
+ * re-initialiation.  The code in this module currently doesn't exercise the
+ * bug, but it is highly sensitive to run-time conditions (stack layout etc.)
+ */
+TEST(CommandOptionsTest, nosuchopt) {
+    checkOptionInvalid("-W", "");
+}
+
+TEST(CommandOptionsTest, v4v6) {
+    checkOptionInvalid("-4", "-6");
+}
+
+TEST(CommandOptionsTest, ilr6) {
+    if (checkOption(1, "-6i", "-leth0", "-r", "50", NULL)) {
+        EXPECT_STREQ("eth0", getLocalName());
+        EXPECT_EQ(50, getRate());
+        EXPECT_EQ(1, isV6());
+        EXPECT_EQ(1, getInitialOnly());
+    }
+}
+
+TEST(CommandOptionsTest, ind) {
+    if (checkOption(1, "-i", "-n10", "-d", "5", NULL)) {
+        EXPECT_EQ(1, getInitialOnly());
+        EXPECT_EQ(10, getNumRequest());
+        EXPECT_EQ(5, getDropTime());
+    }
+}
+
+TEST(CommandOptionsTest, px) {
+    if (checkOption(1, "-p", "5.5", "-x", "a", NULL)) {
+        EXPECT_EQ(5.5, getTestPeriod());
+        EXPECT_EQ(1, dk_set(DK_SOCK));
+        EXPECT_EQ(1, dk_set(DK_MSG));
+        EXPECT_EQ(1, dk_set(DK_PACKET));
+    }
+}

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