BIND 10 trac1324, updated. 7c229ebaca82e06899126f9b364fe524ec6d4b56 [1324] Initial work on perfdhcp - argument processing, debugging, packet analysis, etc., currently applied to experimental DHCP probing, NOT functional.

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Nov 2 19:36:27 UTC 2011


The branch, trac1324 has been updated
       via  7c229ebaca82e06899126f9b364fe524ec6d4b56 (commit)
      from  599ec7f889bba386c838ec85735b203514905d9d (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 7c229ebaca82e06899126f9b364fe524ec6d4b56
Author: John DuBois <johnd at isc.org>
Date:   Wed Nov 2 12:32:52 2011 -0700

    [1324] Initial work on perfdhcp - argument processing, debugging, packet
    analysis, etc., currently applied to experimental DHCP probing, NOT functional.

-----------------------------------------------------------------------

Summary of changes:
 configure.ac                                       |    2 +
 tests/tools/Makefile.am                            |    2 +-
 tests/tools/perfdhcp/Makefile.am                   |   27 +
 tests/tools/perfdhcp/cloptions.cc                  |  175 +++++
 tests/tools/perfdhcp/cloptions.h                   |   17 +
 tests/tools/perfdhcp/dhcp.h                        |  202 ++++++
 tests/tools/perfdhcp/dhcp6.h                       |  213 ++++++
 .../tools/perfdhcp/dkdebug.cc                      |   56 +-
 tests/tools/perfdhcp/dkdebug.h                     |   23 +
 tests/tools/perfdhcp/main.c                        |   15 -
 tests/tools/perfdhcp/packetdisp.c                  |  397 +++++++++++
 tests/tools/perfdhcp/perfdhcp.cc                   |  696 ++++++++++++++++++++
 tests/tools/perfdhcp/perfdhcp.h                    |   33 +
 tests/tools/perfdhcp/procconf.cc                   |  496 ++++++++++++++
 tests/tools/perfdhcp/procconf.h                    |   80 +++
 .../{badpacket => perfdhcp}/tests/Makefile.am      |    7 +-
 .../tests/cloptions_unittest.cc}                   |    2 +-
 .../tools/perfdhcp/tests/packetdisp_unittest.cc    |    0 
 .../{badpacket => perfdhcp}/tests/run_unittests.cc |    0 
 19 files changed, 2398 insertions(+), 45 deletions(-)
 create mode 100644 tests/tools/perfdhcp/Makefile.am
 create mode 100644 tests/tools/perfdhcp/cloptions.cc
 create mode 100644 tests/tools/perfdhcp/cloptions.h
 create mode 100644 tests/tools/perfdhcp/dhcp.h
 create mode 100644 tests/tools/perfdhcp/dhcp6.h
 copy src/lib/datasrc/tests/testdata/mkbrokendb.c => tests/tools/perfdhcp/dkdebug.cc (50%)
 create mode 100644 tests/tools/perfdhcp/dkdebug.h
 delete mode 100644 tests/tools/perfdhcp/main.c
 create mode 100644 tests/tools/perfdhcp/packetdisp.c
 create mode 100644 tests/tools/perfdhcp/perfdhcp.cc
 create mode 100644 tests/tools/perfdhcp/perfdhcp.h
 create mode 100644 tests/tools/perfdhcp/procconf.cc
 create mode 100644 tests/tools/perfdhcp/procconf.h
 copy tests/tools/{badpacket => perfdhcp}/tests/Makefile.am (69%)
 copy tests/tools/{badpacket/tests/command_options_unittest.cc => perfdhcp/tests/cloptions_unittest.cc} (99%)
 copy AUTHORS => tests/tools/perfdhcp/tests/packetdisp_unittest.cc (100%)
 copy tests/tools/{badpacket => perfdhcp}/tests/run_unittests.cc (100%)

-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 1fdf5bf..26454ff 100644
--- a/configure.ac
+++ b/configure.ac
@@ -906,6 +906,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
diff --git a/tests/tools/Makefile.am b/tests/tools/Makefile.am
index 2f07494..ac10c40 100644
--- a/tests/tools/Makefile.am
+++ b/tests/tools/Makefile.am
@@ -1 +1 @@
-SUBDIRS = badpacket
+SUBDIRS = badpacket perfdhcp
diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am
new file mode 100644
index 0000000..2adbd25
--- /dev/null
+++ b/tests/tools/perfdhcp/Makefile.am
@@ -0,0 +1,27 @@
+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)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+noinst_PROGRAMS  = perfdhcp
+perfdhcp_SOURCES  = perfdhcp.cc
+perfdhcp_SOURCES += packetdisp.c
+perfdhcp_SOURCES += perfdhcp.h
+perfdhcp_SOURCES += dhcp.h dhcp6.h
+perfdhcp_SOURCES += cloptions.cc
+perfdhcp_SOURCES += dkdebug.cc dkdebug.h
+perfdhcp_SOURCES += procconf.h procconf.cc
+
+perfdhcp_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_CLANGPP
+perfdhcp_CXXFLAGS += -Wno-error
+endif
diff --git a/tests/tools/perfdhcp/cloptions.cc b/tests/tools/perfdhcp/cloptions.cc
new file mode 100644
index 0000000..339a0d6
--- /dev/null
+++ b/tests/tools/perfdhcp/cloptions.cc
@@ -0,0 +1,175 @@
+/*
+ * 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"
+
+static void printHelp(const char *progName, const char *usage);
+
+/*
+ * 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, 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;
+    char *maxDropOpt;
+    double dropTime;
+    double testPeriod;
+
+    /* Names of configuration variables, for defaults file processor */
+    confvar_t optConf[] = {
+	{ 'h', NULL,		CF_SWITCH,	NULL,		0 },
+	{ 'v', NULL,		CF_SWITCH,	NULL,		0 },
+	{ '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 },
+	{ 'x', NULL,		CF_STRING,	&diagSelector,	0 },
+	{ 'd', NULL,		CF_POS_FLOAT,	&dropTime,	0 },
+	{ 'D', NULL,		CF_STRING,	&maxDropOpt,	0 },
+	{ 'n', NULL,		CF_POS_INT,	&numRequest,	0 },
+	{ 'p', NULL,		CF_POS_FLOAT,	&testPeriod,	0 },
+        { '\0', NULL,           CF_ENDLIST,	NULL,		0 }
+    };
+
+    confdata_t confdata;
+
+    /* Process command line options and config file */
+    procOpts(&argc, &argv, optConf, &confdata, NULL, progName, usage);
+
+    if (confdata.map['h']->num > 0) {
+        printHelp(progName, usage);
+        return 0;
+    }
+    if (confdata.map['v']->num > 0) {
+        printf("dhcpperf v1.0 2011-10-30\n");
+        return 0;
+    }
+
+    if (v4 && v6) {
+	fprintf(stderr, "Must not give -4 and -6 together.\n");
+	return 2;
+    }
+    switch (argc) {
+    case 0:
+	if (v6 && localName != NULL)
+	    server = "all";
+	else {
+	    if (v6)
+		fprintf(stderr, "Use -l to specify an interface name.\n\%s\n", usage);
+	    else
+		fprintf(stderr, "Must specify a DHCP server.\n\%s\n", usage);
+	    return 2;
+	}
+	break;
+    case 1:
+	server = argv[0];
+	break;
+    default:
+	fprintf(stderr, "Too many arguments.\n\%s\n", usage);
+	return 2;
+    }
+    return 1;
+}
+
+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.  For\n\
+DHCPv4 operation, exchanges are initiated by transmitting a DHCP DISCOVER to\n\
+this address.  For DHCPv6 operation, exchanges are initiated by transmitting a\n\
+DHCP SOLICIT to this address.  In the DHCPv6 case, the special name \"all\" can\n\
+be used to refer to All_DHCP_Relay_Agents_and_Servers (the multicast address\n\
+FF02::1:2), or the special name \"servers\" to refer to All_DHCP_Servers (the\n\
+multicast address FF05::1:3).  The [server] argument is optional only in the\n\
+case 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 this\n\
+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 whether -6\n\
+    is given.\n\
+-l<local-addr|interface>: For DHCPv4 operation, specify the local\n\
+    hostname/address to use when communicating with the server.  By default,\n\
+    the interface address through which traffic would normally be routed to the\n\
+    server is used.\n\
+    For DHCPv6 operation, specify the name of the network interface via which\n\
+    exchanges are initiated.  This must be specified unless a server name is\n\
+    given, in which case the interface through which traffic would normally be\n\
+    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 single-keywords specifying the\n\
+    operations for which verbose output is desired.  The selector keyletters\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 been\n\
+    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.  If\n\
+    <max-drop> includes the suffix \"%%\", it specifies a maximum percentage of\n\
+    requests that may be dropped before abort.  In this case, testing of the\n\
+    threshold begins after 10 requests have been expected to be received.\n\
+-n<num-request>: Initiate <num-request> transactions.  No report is generated\n\
+    until all transactions have been initiated/waited-for, after which a report\n\
+    is generated and the program terminates.\n\
+-p<test-period>: Send requests for the given test period, which is specified in\n\
+    the same manner as -d.  This can be used as an alternative to -n, or both\n\
+    options can be given, in which case the testing is completed when either\n\
+    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 are\n\
+  not successfully completed.\n",
+		    progName, usage);
+}
diff --git a/tests/tools/perfdhcp/cloptions.h b/tests/tools/perfdhcp/cloptions.h
new file mode 100644
index 0000000..afe89ba
--- /dev/null
+++ b/tests/tools/perfdhcp/cloptions.h
@@ -0,0 +1,17 @@
+#ifdef __cplusplus
+extern "C" {
+#endif 
+
+extern int v6;
+extern int initialOnly;
+extern const char *localName;
+extern unsigned rate;
+extern unsigned numRequest;
+extern const char *server;
+extern const char *diagSelector;
+
+int procArgs(int argc, char **argv);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/tests/tools/perfdhcp/dhcp.h b/tests/tools/perfdhcp/dhcp.h
new file mode 100644
index 0000000..1af2adf
--- /dev/null
+++ b/tests/tools/perfdhcp/dhcp.h
@@ -0,0 +1,202 @@
+/* dhcp.h
+
+   Protocol structures... */
+
+/*
+ * Copyright (c) 2004-2009 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and 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.
+ *
+ *   Internet Systems Consortium, Inc.
+ *   950 Charter Street
+ *   Redwood City, CA 94063
+ *   <info at isc.org>
+ *   https://www.isc.org/
+ *
+ * This software has been written for Internet Systems Consortium
+ * by Ted Lemon in cooperation with Vixie Enterprises.  To learn more
+ * about Internet Systems Consortium, see ``https://www.isc.org''.
+ * To learn more about Vixie Enterprises, see ``http://www.vix.com''.
+ */
+
+#ifndef DHCP_H
+#define DHCP_H
+
+#define DHCP_UDP_OVERHEAD	(20 + /* IP header */			\
+			        8)   /* UDP header */
+#define DHCP_SNAME_LEN		64
+#define DHCP_FILE_LEN		128
+#define DHCP_FIXED_NON_UDP	236
+#define DHCP_FIXED_LEN		(DHCP_FIXED_NON_UDP + DHCP_UDP_OVERHEAD)
+						/* Everything but options. */
+#define BOOTP_MIN_LEN		300
+
+#define DHCP_MTU_MAX		1500
+#define DHCP_MTU_MIN            576
+
+#define DHCP_MAX_OPTION_LEN	(DHCP_MTU_MAX - DHCP_FIXED_LEN)
+#define DHCP_MIN_OPTION_LEN     (DHCP_MTU_MIN - DHCP_FIXED_LEN)
+
+struct dhcp_packet {
+ u_int8_t  op;		/* 0: Message opcode/type */
+	u_int8_t  htype;	/* 1: Hardware addr type (net/if_types.h) */
+	u_int8_t  hlen;		/* 2: Hardware addr length */
+	u_int8_t  hops;		/* 3: Number of relay agent hops from client */
+	u_int32_t xid;		/* 4: Transaction ID */
+	u_int16_t secs;		/* 8: Seconds since client started looking */
+	u_int16_t flags;	/* 10: Flag bits */
+	struct in_addr ciaddr;	/* 12: Client IP address (if already in use) */
+	struct in_addr yiaddr;	/* 16: Client IP address */
+	struct in_addr siaddr;	/* 18: IP address of next server to talk to */
+	struct in_addr giaddr;	/* 20: DHCP relay agent IP address */
+	unsigned char chaddr [16];	/* 24: Client hardware address */
+	char sname [DHCP_SNAME_LEN];	/* 40: Server name */
+	char file [DHCP_FILE_LEN];	/* 104: Boot filename */
+	unsigned char options [DHCP_MAX_OPTION_LEN];
+				/* 212: Optional parameters
+			  (actual length dependent on MTU). */
+};
+
+/* BOOTP (rfc951) message types */
+#define	BOOTREQUEST	1
+#define BOOTREPLY	2
+
+/* Possible values for flags field... */
+#define BOOTP_BROADCAST 32768L
+
+/* Possible values for hardware type (htype) field... */
+#define HTYPE_ETHER	1               /* Ethernet 10Mbps              */
+#define HTYPE_IEEE802	6               /* IEEE 802.2 Token Ring...	*/
+#define HTYPE_FDDI	8		/* FDDI...			*/
+
+/* Magic cookie validating dhcp options field (and bootp vendor
+   extensions field). */
+#define DHCP_OPTIONS_COOKIE	"\143\202\123\143"
+
+/* DHCP Option codes: */
+
+#define DHO_PAD					0
+#define DHO_SUBNET_MASK				1
+#define DHO_TIME_OFFSET				2
+#define DHO_ROUTERS				3
+#define DHO_TIME_SERVERS			4
+#define DHO_NAME_SERVERS			5
+#define DHO_DOMAIN_NAME_SERVERS			6
+#define DHO_LOG_SERVERS				7
+#define DHO_COOKIE_SERVERS			8
+#define DHO_LPR_SERVERS				9
+#define DHO_IMPRESS_SERVERS			10
+#define DHO_RESOURCE_LOCATION_SERVERS		11
+#define DHO_HOST_NAME				12
+#define DHO_BOOT_SIZE				13
+#define DHO_MERIT_DUMP				14
+#define DHO_DOMAIN_NAME				15
+#define DHO_SWAP_SERVER				16
+#define DHO_ROOT_PATH				17
+#define DHO_EXTENSIONS_PATH			18
+#define DHO_IP_FORWARDING			19
+#define DHO_NON_LOCAL_SOURCE_ROUTING		20
+#define DHO_POLICY_FILTER			21
+#define DHO_MAX_DGRAM_REASSEMBLY		22
+#define DHO_DEFAULT_IP_TTL			23
+#define DHO_PATH_MTU_AGING_TIMEOUT		24
+#define DHO_PATH_MTU_PLATEAU_TABLE		25
+#define DHO_INTERFACE_MTU			26
+#define DHO_ALL_SUBNETS_LOCAL			27
+#define DHO_BROADCAST_ADDRESS			28
+#define DHO_PERFORM_MASK_DISCOVERY		29
+#define DHO_MASK_SUPPLIER			30
+#define DHO_ROUTER_DISCOVERY			31
+#define DHO_ROUTER_SOLICITATION_ADDRESS		32
+#define DHO_STATIC_ROUTES			33
+#define DHO_TRAILER_ENCAPSULATION		34
+#define DHO_ARP_CACHE_TIMEOUT			35
+#define DHO_IEEE802_3_ENCAPSULATION		36
+#define DHO_DEFAULT_TCP_TTL			37
+#define DHO_TCP_KEEPALIVE_INTERVAL		38
+#define DHO_TCP_KEEPALIVE_GARBAGE		39
+#define DHO_NIS_DOMAIN				40
+#define DHO_NIS_SERVERS				41
+#define DHO_NTP_SERVERS				42
+#define DHO_VENDOR_ENCAPSULATED_OPTIONS		43
+#define DHO_NETBIOS_NAME_SERVERS		44
+#define DHO_NETBIOS_DD_SERVER			45
+#define DHO_NETBIOS_NODE_TYPE			46
+#define DHO_NETBIOS_SCOPE			47
+#define DHO_FONT_SERVERS			48
+#define DHO_X_DISPLAY_MANAGER			49
+#define DHO_DHCP_REQUESTED_ADDRESS		50
+#define DHO_DHCP_LEASE_TIME			51
+#define DHO_DHCP_OPTION_OVERLOAD		52
+#define DHO_DHCP_MESSAGE_TYPE			53
+#define DHO_DHCP_SERVER_IDENTIFIER		54
+#define DHO_DHCP_PARAMETER_REQUEST_LIST		55
+#define DHO_DHCP_MESSAGE			56
+#define DHO_DHCP_MAX_MESSAGE_SIZE		57
+#define DHO_DHCP_RENEWAL_TIME			58
+#define DHO_DHCP_REBINDING_TIME			59
+#define DHO_VENDOR_CLASS_IDENTIFIER		60
+#define DHO_DHCP_CLIENT_IDENTIFIER		61
+#define DHO_NWIP_DOMAIN_NAME			62
+#define DHO_NWIP_SUBOPTIONS			63
+#define DHO_USER_CLASS				77
+#define DHO_FQDN				81
+#define DHO_DHCP_AGENT_OPTIONS			82
+#define DHO_AUTHENTICATE			90  /* RFC3118, was 210 */
+#define DHO_CLIENT_LAST_TRANSACTION_TIME	91
+#define DHO_ASSOCIATED_IP			92
+#define DHO_SUBNET_SELECTION			118 /* RFC3011! */
+#define DHO_DOMAIN_SEARCH			119 /* RFC3397 */
+#define DHO_VIVCO_SUBOPTIONS			124
+#define DHO_VIVSO_SUBOPTIONS			125
+
+#define DHO_END					255
+
+/* DHCP message types. */
+#define DHCPDISCOVER		1
+#define DHCPOFFER		2
+#define DHCPREQUEST		3
+#define DHCPDECLINE		4
+#define DHCPACK			5
+#define DHCPNAK			6
+#define DHCPRELEASE		7
+#define DHCPINFORM		8
+#define DHCPLEASEQUERY		10
+#define DHCPLEASEUNASSIGNED	11
+#define DHCPLEASEUNKNOWN	12
+#define DHCPLEASEACTIVE		13
+
+
+/* Relay Agent Information option subtypes: */
+#define RAI_CIRCUIT_ID	1
+#define RAI_REMOTE_ID	2
+#define RAI_AGENT_ID	3
+#define RAI_LINK_SELECT	5
+
+/* FQDN suboptions: */
+#define FQDN_NO_CLIENT_UPDATE		1
+#define FQDN_SERVER_UPDATE		2
+#define FQDN_ENCODED			3
+#define FQDN_RCODE1			4
+#define FQDN_RCODE2			5
+#define FQDN_HOSTNAME			6
+#define FQDN_DOMAINNAME			7
+#define FQDN_FQDN			8
+#define FQDN_SUBOPTION_COUNT		8
+
+/* Enterprise Suboptions: */
+#define VENDOR_ISC_SUBOPTIONS		2495
+
+#endif /* DHCP_H */
+
diff --git a/tests/tools/perfdhcp/dhcp6.h b/tests/tools/perfdhcp/dhcp6.h
new file mode 100644
index 0000000..03db160
--- /dev/null
+++ b/tests/tools/perfdhcp/dhcp6.h
@@ -0,0 +1,213 @@
+/* dhcp6.h
+
+   DHCPv6 Protocol structures... */
+
+/*
+ * Copyright (c) 2006-2009 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and 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.
+ *
+ *   Internet Systems Consortium, Inc.
+ *   950 Charter Street
+ *   Redwood City, CA 94063
+ *   <info at isc.org>
+ *   https://www.isc.org/
+ */
+
+
+/* DHCPv6 Option codes: */
+
+#define D6O_CLIENTID				1 /* RFC3315 */
+#define D6O_SERVERID				2
+#define D6O_IA_NA				3
+#define D6O_IA_TA				4
+#define D6O_IAADDR				5
+#define D6O_ORO					6
+#define D6O_PREFERENCE				7
+#define D6O_ELAPSED_TIME			8
+#define D6O_RELAY_MSG				9
+/* Option code 10 unassigned. */
+#define D6O_AUTH				11
+#define D6O_UNICAST				12
+#define D6O_STATUS_CODE				13
+#define D6O_RAPID_COMMIT			14
+#define D6O_USER_CLASS				15
+#define D6O_VENDOR_CLASS			16
+#define D6O_VENDOR_OPTS				17
+#define D6O_INTERFACE_ID			18
+#define D6O_RECONF_MSG				19
+#define D6O_RECONF_ACCEPT			20
+#define D6O_SIP_SERVERS_DNS			21 /* RFC3319 */
+#define D6O_SIP_SERVERS_ADDR			22 /* RFC3319 */
+#define D6O_NAME_SERVERS			23 /* RFC3646 */
+#define D6O_DOMAIN_SEARCH			24 /* RFC3646 */
+#define D6O_IA_PD				25 /* RFC3633 */
+#define D6O_IAPREFIX				26 /* RFC3633 */
+#define D6O_NIS_SERVERS				27 /* RFC3898 */
+#define D6O_NISP_SERVERS			28 /* RFC3898 */
+#define D6O_NIS_DOMAIN_NAME			29 /* RFC3898 */
+#define D6O_NISP_DOMAIN_NAME			30 /* RFC3898 */
+#define D6O_SNTP_SERVERS			31 /* RFC4075 */
+#define D6O_INFORMATION_REFRESH_TIME		32 /* RFC4242 */
+#define D6O_BCMCS_SERVER_D			33 /* RFC4280 */
+#define D6O_BCMCS_SERVER_A			34 /* RFC4280 */
+/* 35 is unassigned */
+#define D6O_GEOCONF_CIVIC			36 /* RFC4776 */
+#define D6O_REMOTE_ID				37 /* RFC4649 */
+#define D6O_SUBSCRIBER_ID			38 /* RFC4580 */
+#define D6O_CLIENT_FQDN				39 /* RFC4704 */
+#define D6O_PANA_AGENT				40 /* paa-option */
+#define D6O_NEW_POSIX_TIMEZONE			41 /* RFC4833 */
+#define D6O_NEW_TZDB_TIMEZONE			42 /* RFC4833 */
+#define D6O_ERO					43 /* RFC4994 */
+#define D6O_LQ_QUERY				44 /* RFC5007 */
+#define D6O_CLIENT_DATA				45 /* RFC5007 */
+#define D6O_CLT_TIME				46 /* RFC5007 */
+#define D6O_LQ_RELAY_DATA			47 /* RFC5007 */
+#define D6O_LQ_CLIENT_LINK			48 /* RFC5007 */
+
+/* 
+ * Status Codes, from RFC 3315 section 24.4, and RFC 3633, 5007.
+ */
+#define STATUS_Success		 0
+#define STATUS_UnspecFail	 1
+#define STATUS_NoAddrsAvail	 2
+#define STATUS_NoBinding	 3
+#define STATUS_NotOnLink	 4 
+#define STATUS_UseMulticast	 5 
+#define STATUS_NoPrefixAvail	 6
+#define STATUS_UnknownQueryType	 7
+#define STATUS_MalformedQuery	 8
+#define STATUS_NotConfigured	 9
+#define STATUS_NotAllowed	10
+
+/* 
+ * DHCPv6 message types, defined in section 5.3 of RFC 3315 
+ */
+#define DHCPV6_SOLICIT		    1
+#define DHCPV6_ADVERTISE	    2
+#define DHCPV6_REQUEST		    3
+#define DHCPV6_CONFIRM		    4
+#define DHCPV6_RENEW		    5
+#define DHCPV6_REBIND		    6
+#define DHCPV6_REPLY		    7
+#define DHCPV6_RELEASE		    8
+#define DHCPV6_DECLINE		    9
+#define DHCPV6_RECONFIGURE	   10
+#define DHCPV6_INFORMATION_REQUEST 11
+#define DHCPV6_RELAY_FORW	   12
+#define DHCPV6_RELAY_REPL	   13
+#define DHCPV6_LEASEQUERY	   14
+#define DHCPV6_LEASEQUERY_REPLY    15
+
+extern const char *dhcpv6_type_names[];
+extern const int dhcpv6_type_name_max;
+
+/* DUID type definitions (RFC3315 section 9).
+ */
+#define DUID_LLT	1
+#define DUID_EN		2
+#define DUID_LL		3
+
+/* Offsets into IA_*'s where Option spaces commence.  */
+#define IA_NA_OFFSET 12 /* IAID, T1, T2, all 4 octets each */
+#define IA_TA_OFFSET  4 /* IAID only, 4 octets */
+#define IA_PD_OFFSET 12 /* IAID, T1, T2, all 4 octets each */
+
+/* Offset into IAADDR's where Option spaces commence. */
+#define IAADDR_OFFSET 24
+
+/* Offset into IAPREFIX's where Option spaces commence. */
+#define IAPREFIX_OFFSET 25
+
+/* Offset into LQ_QUERY's where Option spaces commence. */
+#define LQ_QUERY_OFFSET 17
+
+/* 
+ * DHCPv6 well-known multicast addressess, from section 5.1 of RFC 3315 
+ */
+#define All_DHCP_Relay_Agents_and_Servers "FF02::1:2"
+#define All_DHCP_Servers "FF05::1:3"
+
+/*
+ * DHCPv6 Retransmission Constants (RFC3315 section 5.5, RFC 5007)
+ */
+
+#define SOL_MAX_DELAY     1
+#define SOL_TIMEOUT       1
+#define SOL_MAX_RT      120
+#define REQ_TIMEOUT       1
+#define REQ_MAX_RT       30
+#define REQ_MAX_RC       10
+#define CNF_MAX_DELAY     1
+#define CNF_TIMEOUT       1
+#define CNF_MAX_RT        4
+#define CNF_MAX_RD       10
+#define REN_TIMEOUT      10
+#define REN_MAX_RT      600
+#define REB_TIMEOUT      10
+#define REB_MAX_RT      600
+#define INF_MAX_DELAY     1
+#define INF_TIMEOUT       1
+#define INF_MAX_RT      120
+#define REL_TIMEOUT       1
+#define REL_MAX_RC        5
+#define DEC_TIMEOUT       1
+#define DEC_MAX_RC        5
+#define REC_TIMEOUT       2
+#define REC_MAX_RC        8
+#define HOP_COUNT_LIMIT  32
+#define LQ6_TIMEOUT       1
+#define LQ6_MAX_RT       10
+#define LQ6_MAX_RC        5
+
+/* 
+ * Normal packet format, defined in section 6 of RFC 3315 
+ */
+struct dhcpv6_packet {
+	unsigned char msg_type;
+	unsigned char transaction_id[3];
+	unsigned char options[FLEXIBLE_ARRAY_MEMBER];
+};
+
+/* Offset into DHCPV6 Reply packets where Options spaces commence. */
+#define REPLY_OPTIONS_INDEX 4
+
+/* 
+ * Relay packet format, defined in section 7 of RFC 3315 
+ */
+struct dhcpv6_relay_packet {
+	unsigned char msg_type;
+	unsigned char hop_count;
+	unsigned char link_address[16];
+	unsigned char peer_address[16];
+	unsigned char options[FLEXIBLE_ARRAY_MEMBER];
+};
+
+/* Leasequery query-types (RFC 5007) */
+
+#define LQ6QT_BY_ADDRESS	1
+#define LQ6QT_BY_CLIENTID	2
+
+/*
+ * DUID time starts 2000-01-01.
+ * This constant is the number of seconds since 1970-01-01,
+ * when the Unix epoch began.
+ */
+#define DUID_TIME_EPOCH 946684800
+
+/* Information-Request Time option (RFC 4242) */
+
+#define IRT_DEFAULT	86400
+#define IRT_MINIMUM	600
+
diff --git a/tests/tools/perfdhcp/dkdebug.cc b/tests/tools/perfdhcp/dkdebug.cc
new file mode 100644
index 0000000..0b16b34
--- /dev/null
+++ b/tests/tools/perfdhcp/dkdebug.cc
@@ -0,0 +1,56 @@
+/*
+ * 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"
+
+unsigned dk_diag_mask;
+
+int
+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 0;
+	}
+    return 1;
+}
+
+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);
+}
diff --git a/tests/tools/perfdhcp/dkdebug.h b/tests/tools/perfdhcp/dkdebug.h
new file mode 100644
index 0000000..bf6b299
--- /dev/null
+++ b/tests/tools/perfdhcp/dkdebug.h
@@ -0,0 +1,23 @@
+#ifdef __cplusplus
+extern "C" {
+#endif 
+
+#include <stdarg.h>
+
+extern unsigned dk_diag_mask;
+
+#define dk_set(diag_req) ((diag_req) & dk_diag_mask)
+#define DK_ALL (~0)
+
+struct dkdesc {
+    char keyletter;
+    unsigned mask;
+};
+
+void dkprintf(unsigned diag_req, const char format[], ...);
+void vdkprintf(unsigned diag_req, const char format[], va_list ap);
+int dk_setup(const char *diag_str, const struct dkdesc *diags);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/tests/tools/perfdhcp/main.c b/tests/tools/perfdhcp/main.c
deleted file mode 100644
index a1eb086..0000000
--- a/tests/tools/perfdhcp/main.c
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * 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.
- */
diff --git a/tests/tools/perfdhcp/packetdisp.c b/tests/tools/perfdhcp/packetdisp.c
new file mode 100644
index 0000000..9df671c
--- /dev/null
+++ b/tests/tools/perfdhcp/packetdisp.c
@@ -0,0 +1,397 @@
+/*
+ * 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 <unistd.h>
+#include <strings.h>
+#include <string.h>
+#include "perfdhcp.h"
+#include "dkdebug.h"
+
+static const char * enum_lookup(const char * const table[], size_t size, unsigned index);
+static const char *optionName(int v6, unsigned optnum);
+static void printOption(int v6, unsigned optnum, size_t len, const unsigned char *value);
+static void print_dhcpv4_packet(const struct dhcp_packet *pkt, size_t len);
+static void print_dhcpv6_packet(const struct dhcpv6_packet *pkt, size_t len);
+
+typedef enum { T_UNK, T_STRING, T_PERIOD, T_IPADDR, T_MTYPE, T_STATUS } opt_type;
+
+struct dhcp_option_desc {
+    char *name;
+    opt_type type;
+};
+
+static struct dhcp_option_desc option_desc[] = {
+[DHO_PAD] = { "PAD", T_UNK },
+[DHO_SUBNET_MASK] = { "SUBNET_MASK", T_UNK },
+[DHO_TIME_OFFSET] = { "TIME_OFFSET", T_PERIOD },
+[DHO_ROUTERS] = { "ROUTERS", T_IPADDR },
+[DHO_TIME_SERVERS] = { "TIME_SERVERS", T_UNK },
+[DHO_NAME_SERVERS] = { "NAME_SERVERS", T_UNK },
+[DHO_DOMAIN_NAME_SERVERS] = { "DOMAIN_NAME_SERVERS", T_IPADDR },
+[DHO_LOG_SERVERS] = { "LOG_SERVERS", T_UNK },
+[DHO_COOKIE_SERVERS] = { "COOKIE_SERVERS", T_UNK },
+[DHO_LPR_SERVERS] = { "LPR_SERVERS", T_IPADDR },
+[DHO_IMPRESS_SERVERS] = { "IMPRESS_SERVERS", T_UNK },
+[DHO_RESOURCE_LOCATION_SERVERS] = { "RESOURCE_LOCATION_SERVERS", T_UNK },
+[DHO_HOST_NAME] = { "HOST_NAME", T_UNK },
+[DHO_BOOT_SIZE] = { "BOOT_SIZE", T_UNK },
+[DHO_MERIT_DUMP] = { "MERIT_DUMP", T_UNK },
+[DHO_DOMAIN_NAME] = { "DOMAIN_NAME", T_STRING },
+[DHO_SWAP_SERVER] = { "SWAP_SERVER", T_UNK },
+[DHO_ROOT_PATH] = { "ROOT_PATH", T_UNK },
+[DHO_EXTENSIONS_PATH] = { "EXTENSIONS_PATH", T_UNK },
+[DHO_IP_FORWARDING] = { "IP_FORWARDING", T_UNK },
+[DHO_NON_LOCAL_SOURCE_ROUTING] = { "NON_LOCAL_SOURCE_ROUTING", T_UNK },
+[DHO_POLICY_FILTER] = { "POLICY_FILTER", T_UNK },
+[DHO_MAX_DGRAM_REASSEMBLY] = { "MAX_DGRAM_REASSEMBLY", T_UNK },
+[DHO_DEFAULT_IP_TTL] = { "DEFAULT_IP_TTL", T_UNK },
+[DHO_PATH_MTU_AGING_TIMEOUT] = { "PATH_MTU_AGING_TIMEOUT", T_UNK },
+[DHO_PATH_MTU_PLATEAU_TABLE] = { "PATH_MTU_PLATEAU_TABLE", T_UNK },
+[DHO_INTERFACE_MTU] = { "INTERFACE_MTU", T_UNK },
+[DHO_ALL_SUBNETS_LOCAL] = { "ALL_SUBNETS_LOCAL", T_UNK },
+[DHO_BROADCAST_ADDRESS] = { "BROADCAST_ADDRESS", T_UNK },
+[DHO_PERFORM_MASK_DISCOVERY] = { "PERFORM_MASK_DISCOVERY", T_UNK },
+[DHO_MASK_SUPPLIER] = { "MASK_SUPPLIER", T_UNK },
+[DHO_ROUTER_DISCOVERY] = { "ROUTER_DISCOVERY", T_UNK },
+[DHO_ROUTER_SOLICITATION_ADDRESS] = { "ROUTER_SOLICITATION_ADDRESS", T_UNK },
+[DHO_STATIC_ROUTES] = { "STATIC_ROUTES", T_UNK },
+[DHO_TRAILER_ENCAPSULATION] = { "TRAILER_ENCAPSULATION", T_UNK },
+[DHO_ARP_CACHE_TIMEOUT] = { "ARP_CACHE_TIMEOUT", T_UNK },
+[DHO_IEEE802_3_ENCAPSULATION] = { "IEEE802_3_ENCAPSULATION", T_UNK },
+[DHO_DEFAULT_TCP_TTL] = { "DEFAULT_TCP_TTL", T_UNK },
+[DHO_TCP_KEEPALIVE_INTERVAL] = { "TCP_KEEPALIVE_INTERVAL", T_UNK },
+[DHO_TCP_KEEPALIVE_GARBAGE] = { "TCP_KEEPALIVE_GARBAGE", T_UNK },
+[DHO_NIS_DOMAIN] = { "NIS_DOMAIN", T_UNK },
+[DHO_NIS_SERVERS] = { "NIS_SERVERS", T_UNK },
+[DHO_NTP_SERVERS] = { "NTP_SERVERS", T_IPADDR },
+[DHO_VENDOR_ENCAPSULATED_OPTIONS] = { "VENDOR_ENCAPSULATED_OPTIONS", T_UNK },
+[DHO_NETBIOS_NAME_SERVERS] = { "NETBIOS_NAME_SERVERS", T_UNK },
+[DHO_NETBIOS_DD_SERVER] = { "NETBIOS_DD_SERVER", T_UNK },
+[DHO_NETBIOS_NODE_TYPE] = { "NETBIOS_NODE_TYPE", T_UNK },
+[DHO_NETBIOS_SCOPE] = { "NETBIOS_SCOPE", T_UNK },
+[DHO_FONT_SERVERS] = { "FONT_SERVERS", T_UNK },
+[DHO_X_DISPLAY_MANAGER] = { "X_DISPLAY_MANAGER", T_UNK },
+[DHO_DHCP_REQUESTED_ADDRESS] = { "DHCP_REQUESTED_ADDRESS", T_IPADDR },
+[DHO_DHCP_LEASE_TIME] = { "DHCP_LEASE_TIME", T_PERIOD },
+[DHO_DHCP_OPTION_OVERLOAD] = { "DHCP_OPTION_OVERLOAD", T_UNK },
+[DHO_DHCP_MESSAGE_TYPE] = { "DHCP_MESSAGE_TYPE", T_MTYPE },
+[DHO_DHCP_SERVER_IDENTIFIER] = { "DHCP_SERVER_IDENTIFIER", T_UNK },
+[DHO_DHCP_PARAMETER_REQUEST_LIST] = { "DHCP_PARAMETER_REQUEST_LIST", T_UNK },
+[DHO_DHCP_MESSAGE] = { "DHCP_MESSAGE", T_UNK },
+[DHO_DHCP_MAX_MESSAGE_SIZE] = { "DHCP_MAX_MESSAGE_SIZE", T_UNK },
+[DHO_DHCP_RENEWAL_TIME] = { "DHCP_RENEWAL_TIME", T_UNK },
+[DHO_DHCP_REBINDING_TIME] = { "DHCP_REBINDING_TIME", T_UNK },
+[DHO_VENDOR_CLASS_IDENTIFIER] = { "VENDOR_CLASS_IDENTIFIER", T_UNK },
+[DHO_DHCP_CLIENT_IDENTIFIER] = { "DHCP_CLIENT_IDENTIFIER", T_UNK },
+[DHO_NWIP_DOMAIN_NAME] = { "NWIP_DOMAIN_NAME", T_UNK },
+[DHO_NWIP_SUBOPTIONS] = { "NWIP_SUBOPTIONS", T_UNK },
+[DHO_USER_CLASS] = { "USER_CLASS", T_UNK },
+[DHO_USER_CLASS] = { "USER_CLASS", T_UNK },
+[DHO_FQDN] = { "FQDN", T_UNK },
+[DHO_DHCP_AGENT_OPTIONS] = { "DHCP_AGENT_OPTIONS", T_UNK },
+[DHO_AUTHENTICATE] = { "AUTHENTICATE", T_UNK },
+[DHO_CLIENT_LAST_TRANSACTION_TIME] = { "CLIENT_LAST_TRANSACTION_TIME", T_UNK },
+[DHO_ASSOCIATED_IP] = { "ASSOCIATED_IP", T_UNK },
+[DHO_SUBNET_SELECTION] = { "SUBNET_SELECTION", T_UNK },
+[DHO_DOMAIN_SEARCH] = { "DOMAIN_SEARCH", T_UNK },
+[DHO_VIVCO_SUBOPTIONS] = { "VIVCO_SUBOPTIONS", T_UNK },
+[DHO_VIVSO_SUBOPTIONS] = { "VIVSO_SUBOPTIONS", T_UNK },
+[DHO_END] = { "END", T_UNK },
+[64] = { "NIS Domain", T_UNK },
+[65] = { "NIS Servers", T_UNK },
+[66] = { "TFTP Server", T_UNK },
+[67] = { "Bootfile name", T_UNK },
+[68] = { "Mobile IP Home Agent", T_UNK },
+[69] = { "SMTP Server", T_IPADDR },
+[70] = { "POP Server", T_IPADDR },
+[71] = { "NNTP Server", T_IPADDR },
+[72] = { "WWW Server", T_IPADDR },
+[73] = { "Finger Server", T_IPADDR },
+[74] = { "IRC Server", T_IPADDR },
+[75] = { "StreetTalk Server", T_IPADDR },
+[76] = { "StreetTalk Directory Assistance Server", T_IPADDR }
+};
+
+static const char * const message_types[] = {
+    [DHCPDISCOVER] = "DHCPDISCOVER",
+    [DHCPOFFER] = "DHCPOFFER",
+    [DHCPREQUEST] = "DHCPREQUEST",
+    [DHCPDECLINE] = "DHCPDECLINE",
+    [DHCPACK] = "DHCPACK",
+    [DHCPNAK] = "DHCPNAK",
+    [DHCPRELEASE] = "DHCPRELEASE",
+    [DHCPINFORM] = "DHCPINFORM",
+    [DHCPLEASEQUERY] = "DHCPLEASEQUERY",
+    [DHCPLEASEUNASSIGNED] = "DHCPLEASEUNASSIGNED",
+    [DHCPLEASEUNKNOWN] = "DHCPLEASEUNKNOWN",
+    [DHCPLEASEACTIVE] = "DHCPLEASEACTIVE"
+};
+
+static struct dhcp_option_desc option6_desc[] = {
+    [D6O_CLIENTID] = { "D6O_CLIENTID", T_UNK },
+    [D6O_SERVERID] = { "D6O_SERVERID", T_UNK },
+    [D6O_IA_NA] = { "D6O_IA_NA", T_UNK },
+    [D6O_IA_TA] = { "D6O_IA_TA", T_UNK },
+    [D6O_IAADDR] = { "D6O_IAADDR", T_UNK },
+    [D6O_ORO] = { "D6O_ORO", T_UNK },
+    [D6O_PREFERENCE] = { "D6O_PREFERENCE", T_UNK },
+    [D6O_ELAPSED_TIME] = { "D6O_ELAPSED_TIME", T_PERIOD },
+    [D6O_RELAY_MSG] = { "D6O_RELAY_MSG", T_UNK },
+    [D6O_AUTH] = { "D6O_AUTH", T_UNK },
+    [D6O_UNICAST] = { "D6O_UNICAST", T_UNK },
+    [D6O_STATUS_CODE] = { "D6O_STATUS_CODE", T_STATUS },
+    [D6O_RAPID_COMMIT] = { "D6O_RAPID_COMMIT", T_UNK },
+    [D6O_USER_CLASS] = { "D6O_USER_CLASS", T_UNK },
+    [D6O_VENDOR_CLASS] = { "D6O_VENDOR_CLASS", T_UNK },
+    [D6O_VENDOR_OPTS] = { "D6O_VENDOR_OPTS", T_UNK },
+    [D6O_INTERFACE_ID] = { "D6O_INTERFACE_ID", T_UNK },
+    [D6O_RECONF_MSG] = { "D6O_RECONF_MSG", T_UNK },
+    [D6O_RECONF_ACCEPT] = { "D6O_RECONF_ACCEPT", T_UNK },
+    [D6O_SIP_SERVERS_DNS] = { "D6O_SIP_SERVERS_DNS", T_UNK },
+    [D6O_SIP_SERVERS_ADDR] = { "D6O_SIP_SERVERS_ADDR", T_UNK },
+    [D6O_NAME_SERVERS] = { "D6O_NAME_SERVERS", T_IPADDR },
+    [D6O_DOMAIN_SEARCH] = { "D6O_DOMAIN_SEARCH", T_UNK },
+    [D6O_IA_PD] = { "D6O_IA_PD", T_UNK },
+    [D6O_IAPREFIX] = { "D6O_IAPREFIX", T_UNK },
+    [D6O_NIS_SERVERS] = { "D6O_NIS_SERVERS", T_UNK },
+    [D6O_NISP_SERVERS] = { "D6O_NISP_SERVERS", T_UNK },
+    [D6O_NIS_DOMAIN_NAME] = { "D6O_NIS_DOMAIN_NAME", T_UNK },
+    [D6O_NISP_DOMAIN_NAME] = { "D6O_NISP_DOMAIN_NAME", T_UNK },
+    [D6O_SNTP_SERVERS] = { "D6O_SNTP_SERVERS", T_UNK },
+    [D6O_INFORMATION_REFRESH_TIME] = { "D6O_INFORMATION_REFRESH_TIME", T_UNK },
+    [D6O_BCMCS_SERVER_D] = { "D6O_BCMCS_SERVER_D", T_UNK },
+    [D6O_BCMCS_SERVER_A] = { "D6O_BCMCS_SERVER_A", T_UNK },
+    [D6O_GEOCONF_CIVIC] = { "D6O_GEOCONF_CIVIC", T_UNK },
+    [D6O_REMOTE_ID] = { "D6O_REMOTE_ID", T_UNK },
+    [D6O_SUBSCRIBER_ID] = { "D6O_SUBSCRIBER_ID", T_UNK },
+    [D6O_CLIENT_FQDN] = { "D6O_CLIENT_FQDN", T_UNK },
+    [D6O_PANA_AGENT] = { "D6O_PANA_AGENT", T_UNK },
+    [D6O_NEW_POSIX_TIMEZONE] = { "D6O_NEW_POSIX_TIMEZONE", T_UNK },
+    [D6O_NEW_TZDB_TIMEZONE] = { "D6O_NEW_TZDB_TIMEZONE", T_UNK },
+    [D6O_ERO] = { "D6O_ERO", T_UNK },
+    [D6O_LQ_QUERY] = { "D6O_LQ_QUERY", T_UNK },
+    [D6O_CLIENT_DATA] = { "D6O_CLIENT_DATA", T_UNK },
+    [D6O_CLT_TIME] = { "D6O_CLT_TIME", T_UNK },
+    [D6O_LQ_RELAY_DATA] = { "D6O_LQ_RELAY_DATA", T_UNK },
+    [D6O_LQ_CLIENT_LINK] = { "D6O_LQ_CLIENT_LINK", T_UNK }
+};
+
+static const char * const message6_types[] = {
+    [DHCPV6_SOLICIT] = "DHCPV6_SOLICIT",
+    [DHCPV6_ADVERTISE] = "DHCPV6_ADVERTISE",
+    [DHCPV6_REQUEST] = "DHCPV6_REQUEST",
+    [DHCPV6_CONFIRM] = "DHCPV6_CONFIRM",
+    [DHCPV6_RENEW] = "DHCPV6_RENEW",
+    [DHCPV6_REBIND] = "DHCPV6_REBIND",
+    [DHCPV6_REPLY] = "DHCPV6_REPLY",
+    [DHCPV6_RELEASE] = "DHCPV6_RELEASE",
+    [DHCPV6_DECLINE] = "DHCPV6_DECLINE",
+    [DHCPV6_RECONFIGURE] = "DHCPV6_RECONFIGURE",
+    [DHCPV6_INFORMATION_REQUEST] = "DHCPV6_INFORMATION_REQUEST",
+    [DHCPV6_RELAY_FORW] = "DHCPV6_RELAY_FORW",
+    [DHCPV6_RELAY_REPL] = "DHCPV6_RELAY_REPL",
+    [DHCPV6_LEASEQUERY] = "DHCPV6_LEASEQUERY",
+    [DHCPV6_LEASEQUERY_REPLY] = "DHCPV6_LEASEQUERY_REPLY"
+};
+
+static const char * const status_codes[] = {
+    [STATUS_Success] = "Success",
+    [STATUS_UnspecFail] = "UnspecFail",
+    [STATUS_NoAddrsAvail] = "NoAddrsAvail",
+    [STATUS_NoBinding] = "NoBinding",
+    [STATUS_NotOnLink] = "NotOnLink",
+    [STATUS_UseMulticast] = "UseMulticast",
+    [STATUS_NoPrefixAvail] = "NoPrefixAvail",
+    [STATUS_UnknownQueryType] = "UnknownQueryType",
+    [STATUS_MalformedQuery] = "MalformedQuery",
+    [STATUS_NotConfigured] = "NotConfigured",
+    [STATUS_NotAllowed] = "NotAllowed",
+};
+
+void
+print_dhcp_packet(int v6, const void *pkt, size_t len)
+{
+    if (v6)
+	print_dhcpv6_packet((struct dhcpv6_packet *)pkt, len);
+    else
+	print_dhcpv4_packet((struct dhcp_packet *)pkt, len);
+
+    if (dk_set(DK_PACKET)) {
+	unsigned i;
+
+	fprintf(stderr, "Raw packet contents (%zu bytes):\n", len);
+	for (i = 0; i < len; i++)
+	    fprintf(stderr, "%02x ", ((unsigned char *)pkt)[i]);
+	fputc('\n', stderr);
+    }
+}
+
+/*
+ * Note: Not reentrant
+ */
+static const char *
+enum_lookup(const char * const table[], size_t size, unsigned index)
+{
+    static char numbuf[10];
+
+    if (index < (size / sizeof(char *)) && table[index] != NULL)
+	return table[index];
+    else {
+	snprintf(numbuf, sizeof(numbuf), "%u", index);
+	return numbuf;
+    }
+}
+
+/*
+ * Note: Not reentrant
+ */
+static const char *
+optionName(int v6, unsigned optnum)
+{
+    static char numbuf[16];
+    unsigned maxOpt = ((v6 ? sizeof(option6_desc) : sizeof(option_desc)) - 1) /
+	    sizeof(struct dhcp_option_desc);
+    struct dhcp_option_desc *descTable;
+
+    if (optnum > maxOpt) {
+	snprintf(numbuf, sizeof(numbuf), "invalid (%u)", optnum);
+	return numbuf;
+    }
+    descTable = v6 ? option6_desc : option_desc;
+    if (descTable[optnum].name == NULL) {
+	snprintf(numbuf, sizeof(numbuf), "%u", optnum);
+	return numbuf;
+    }
+    else
+	return descTable[optnum].name;
+}
+
+/*
+ *
+ * Output variables: None
+ */
+static void
+printOption(int v6, unsigned optnum, size_t len, const unsigned char *value)
+{
+    struct dhcp_option_desc *descTable = v6 ? option6_desc : option_desc;
+    char buf[ADDR_NAME_BUFSIZE];
+    unsigned i;
+    unsigned maxOpt = ((v6 ? sizeof(option6_desc) : sizeof(option_desc)) - 1) / sizeof(struct dhcp_option_desc);
+    unsigned statusCode;
+
+    fprintf(stderr, "Option %s (%d) length %zu value ", optionName(v6, optnum), optnum, len);
+
+    switch (optnum > maxOpt ? T_UNK : descTable[optnum].type) {
+    case T_STRING:
+	fprintf(stderr, "\"%.*s\"\n", (int) len, value);
+	break;
+    case T_PERIOD:
+	fprintf(stderr, "%d seconds\n", ntohl(*(int *)value));
+	break;
+    case T_MTYPE:
+	fprintf(stderr, "%s\n", enum_lookup(message_types, sizeof(message_types), *value));
+	break;
+    case T_IPADDR:
+	for (i = 0; len - i >= 4; i += 4)
+	    fprintf(stderr, "%s%s", addrtoa(AF_INET, (struct in_addr *)(value+i), buf), len - i > 4 ? ", " : "\n");
+	break;
+    case T_STATUS:
+	statusCode = ntohs(*(uint16_t *)value);
+	fprintf(stderr, "%s: %*.*s\n", 
+		enum_lookup(status_codes, sizeof(status_codes), statusCode),
+		(int)len - 2, (int)len - 2, value + 2);
+	break;
+    default:
+	for (i = 0; i < len; i++)
+	    fprintf(stderr, "%02x", value[i]);
+	fputc('\n', stderr);
+	break;
+    }
+}
+
+static void
+print_dhcpv4_packet(const struct dhcp_packet *pkt, size_t msglen)
+{
+    int i;
+    const unsigned char *p;
+    aaddr_buf buf1, buf2, buf3, buf4;
+
+    fprintf(stderr, 
+"opcode	%u\n\
+htype	%u\n\
+hlen	%u\n\
+hops	%u\n\
+xid	0x%0x\n\
+secs	%u\n\
+flags	0x%x\n\
+ciaddr	%s\n\
+yiaddr	%s\n\
+siaddr	%s\n\
+giaddr	%s\n\
+chaddr	",
+	pkt->op,
+	pkt->htype,
+	pkt->hlen,
+	pkt->hops,
+	pkt->xid,
+	pkt->secs,
+	pkt->flags,
+	addrtoa(AF_INET, &pkt->ciaddr, buf1),
+	addrtoa(AF_INET, &pkt->yiaddr, buf2),
+	addrtoa(AF_INET, &pkt->siaddr, buf3),
+	addrtoa(AF_INET, &pkt->giaddr, buf4));
+    for (i = 0; i < pkt->hlen; i++)
+	fprintf(stderr, "%02x", pkt->chaddr[i]);
+    fputc('\n', stderr);
+    fprintf(stderr, "magic	0x%x\n", *(unsigned int *)&pkt->options);
+    p = &pkt->options[4];
+    while ((unsigned) (p - (unsigned char *)pkt) < msglen && *p != DHO_END) {
+	if (*p == DHO_PAD) {
+	    fprintf(stderr, "Option PAD\n");
+	    p++;
+	}
+	else {
+	    unsigned optnum = *p++;
+	    size_t len = *p++;
+
+	    printOption(0, optnum, len, p);
+	    p += len;
+	}
+    }
+}
+
+static void
+print_dhcpv6_packet(const struct dhcpv6_packet *pkt, size_t msglen)
+{
+    int i;
+    const unsigned char *p;
+    struct v6_option *opt;
+
+    fprintf(stderr, "msgtype\t%s\ntid\t", enum_lookup(message6_types, sizeof(message6_types), pkt->msg_type));
+    for (i = 0; i <= 2; i++)
+	fprintf(stderr, "%02x", pkt->transaction_id[i]);
+    fputc('\n', stderr);
+
+    p = pkt->options;
+    opt = (struct v6_option *)p;
+    while ((unsigned) (p - (unsigned char *)pkt) < msglen && opt->code != 0) {
+	unsigned optlen = ntohs(opt->len);
+	printOption(1, ntohs(opt->code), optlen, &opt->value);
+	p += 4 + optlen;
+	opt = (struct v6_option *)p;
+    }
+}
diff --git a/tests/tools/perfdhcp/perfdhcp.cc b/tests/tools/perfdhcp/perfdhcp.cc
new file mode 100644
index 0000000..1cd8c66
--- /dev/null
+++ b/tests/tools/perfdhcp/perfdhcp.cc
@@ -0,0 +1,696 @@
+/*
+ * 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 <unistd.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <net/if_arp.h>
+#include <ifaddrs.h>
+#include <strings.h>
+#include <string.h>
+#include <stdarg.h>
+#include <netdb.h>
+#include <errno.h>
+#include <time.h>
+#include "perfdhcp.h"
+#include "cloptions.h"
+#include "dkdebug.h"
+
+struct duid {
+    uint16_t duid_type;
+    uint16_t htype;
+    unsigned char hwaddr[6];
+};
+
+struct addrinfo *getaddr(int addr_fam, const char *hostname, const char *port);
+char *addrName(const struct sockaddr_storage *addr, char *buf, size_t bufsize);
+void add_option(int v6, unsigned optnum, unsigned count,
+	size_t size, int direct, unsigned char options[], size_t *buffer_used,
+	...);
+int get_linklocal_addr(const char if_name[], struct sockaddr_storage *addr);
+const char *optionName(int v6, unsigned optnum);
+const unsigned char *find_option(const struct dhcp_packet *pkt, int search_opt);
+int socket_setup(int addr_fam, const char *localAddr, const char *port,
+	const char *type, struct sockaddr_storage *l_addr);
+void gen_discover(struct dhcp_packet *discover_pkt, const struct in_addr *giaddr);
+void gen_request(struct dhcp_packet *dhcp_pkt, const struct in_addr *giaddr,
+	const struct in_addr *yiaddr, const unsigned char *server_id);
+void dhcp_recv(int v6, void *msg, int recv_fd, const struct sockaddr_storage *recv_laddr);
+void dora(const char *server, const char *localAddr);
+void sarr(const char *server, const char *if_name);
+void print_addrinfo(FILE *f, const struct addrinfo *addr);
+void print_sa6_info(FILE *f, const struct sockaddr_in6 *sa);
+void gen_solicit(struct dhcpv6_packet *dhcp_pkt, const struct duid *client_id);
+void dhcp_send(int v6, const unsigned char *msg, int send_fd, const struct
+	sockaddr *r_addr, const struct sockaddr_storage *send_laddr);
+
+int v6 = 0;
+int initialOnly = 0;
+const char *localName = NULL;
+unsigned rate = 0;
+unsigned numRequest = 0;
+const char *server = NULL;
+const char *diagSelector = "";
+
+static const struct dkdesc diagLetters[] = {
+    { 's', DK_SOCK },
+    { 'm', DK_MSG },
+    { 'p', DK_PACKET },
+    { 'a', DK_ALL },
+    { '\0', 0 }
+};
+
+int
+main(int argc, char *argv[])
+{
+    int ret;
+
+    if ((ret = procArgs(argc, argv)) != 1)
+	exit(ret);
+
+    srand(time(NULL));
+    if (v6)
+	sarr(server, localName);
+    else
+	dora(server, localName);
+    dk_setup(diagSelector, diagLetters);
+
+    exit(0);
+}
+
+/*
+ * Create a socket for communication with dhcp server:
+ * - Create socket
+ * - Bind it to given local address and port, UDP.
+ *
+ * Input variables:
+ * addr_fam is the address family to use, e.g. AF_INET
+ * localAddr is the local address to bind to.
+ * port is the port to bind to.
+ * type is a string giving the purpose of the socket, for verbose output.
+ * If localAddr is null, the local address etc. is taken from l_addr.
+ *
+ * Output variables:
+ * Socket details are stored in l_addr.
+ *
+ * Return value: The network fd.
+ */
+int
+socket_setup(int addr_fam, const char *localAddr, const char *port,
+	const char *type, struct sockaddr_storage *l_addr)
+{
+    char addrbuf[ADDR_NAME_BUFSIZE];
+    int net_fd;
+    struct addrinfo *addrs;
+
+    if ((addrs = getaddr(addr_fam, localAddr, port)) == NULL) {
+	fprintf(stderr, "No addresses for %s\n", localAddr);
+	exit(1);
+    }
+    if (localAddr == NULL) {
+	if (dk_set(DK_SOCK)) {
+	    fprintf(stderr, "local address:\n");
+	    print_sa6_info(stderr, (struct sockaddr_in6 *)l_addr);
+	}
+	memcpy(&((struct sockaddr_in6 *)addrs->ai_addr)->sin6_addr,
+		&((struct sockaddr_in6 *)l_addr)->sin6_addr,
+		sizeof(struct in6_addr));
+	((struct sockaddr_in6 *)addrs->ai_addr)->sin6_flowinfo =
+		((struct sockaddr_in6 *)l_addr)->sin6_flowinfo;
+	((struct sockaddr_in6 *)addrs->ai_addr)->sin6_scope_id =
+		((struct sockaddr_in6 *)l_addr)->sin6_scope_id;
+    }
+    if (dk_set(DK_SOCK)) {
+	print_addrinfo(stderr, addrs);
+	fprintf(stderr, "Creating socket from addrinfo:\n");
+	print_addrinfo(stderr, addrs);
+    }
+    net_fd = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
+    if (net_fd < 0) {
+        perror("socket");
+        exit(1);
+    }
+    if (bind(net_fd, addrs->ai_addr, addrs->ai_addrlen) == -1) {
+	int s_errno = errno;
+	fprintf(stderr, "Could not bind to %s: %s\n",
+		addrName((struct sockaddr_storage *)addrs->ai_addr, addrbuf,
+			sizeof(addrbuf)), strerror(s_errno));
+	exit(1);
+    }
+    dkprintf(DK_SOCK, "%s fd %d bound to %s\n", type, net_fd, addrName((struct sockaddr_storage *)addrs->ai_addr, addrbuf, sizeof(addrbuf)));
+    memcpy(l_addr, addrs->ai_addr, sizeof(struct sockaddr_storage));
+    freeaddrinfo(addrs);
+    return net_fd;
+}
+
+/*
+ * gen_discover: Generate a DHCP discover packet.
+ *
+ * Input variables:
+ * giaddr is the address to be copied into the giaddr (gateway addr) element.
+ *
+ * Output variables:
+ * discover_packet is a pointer to storage for the packet to be generated.
+ */
+void
+gen_discover(struct dhcp_packet *discover_pkt, const struct in_addr *giaddr)
+{
+    size_t options_len;
+
+    bzero((char *) discover_pkt, sizeof(struct dhcp_packet));
+    discover_pkt->op = BOOTREQUEST;
+    discover_pkt->htype = HTYPE_ETHER;
+    discover_pkt->hlen = 6;
+    discover_pkt->hops = 1;
+    discover_pkt->xid = 0x12345678;	/* transaction id - fix */
+    discover_pkt->secs = 0;
+    discover_pkt->flags = 0;
+    memcpy(&discover_pkt->giaddr, giaddr, sizeof((*discover_pkt).giaddr));
+    strncpy((char *)discover_pkt->chaddr, "\x12\x34\x56\x78\x9a\xbc", 6);	/* client hardware addr - fix */
+    memset(discover_pkt->options, DHO_PAD, DHCP_MAX_OPTION_LEN);
+    strncpy((char *)discover_pkt->options, "\x63\x82\x53\x63", 4);		/* magic cookie */
+    options_len = 4;
+    add_option(0, DHO_DHCP_MESSAGE_TYPE, 1, 1, 1, discover_pkt->options, &options_len,
+	    DHCPDISCOVER);
+    add_option(0, DHO_DHCP_PARAMETER_REQUEST_LIST, 4, 1, 1, discover_pkt->options,
+	    &options_len, DHO_SUBNET_MASK, DHO_ROUTERS, DHO_DOMAIN_NAME,
+	    DHO_DOMAIN_NAME_SERVERS);
+    add_option(0, DHO_DHCP_LEASE_TIME, 1, 4, 1, discover_pkt->options, &options_len,
+	    htonl(60));
+
+    if (options_len < DHCP_MAX_OPTION_LEN)
+	discover_pkt->options[options_len++] = DHO_END;
+}
+
+/*
+ * gen_request(): Generate a DHCPv4 request packet.
+ * 
+ * Input variables
+ * giaddr is the address to copy into the giaddr element.
+ * yiaddr is the address to store in the DHO_DHCP_REQUESTED_ADDRESS option.
+ * server_id is the ID to store in the DHO_DHCP_SERVER_IDENTIFIER option.
+ *
+ * Output variables:
+ * dhcp_pkt points to storage for the packet to be generated.
+ */
+void
+gen_request(struct dhcp_packet *dhcp_pkt, const struct in_addr *giaddr,
+	const struct in_addr *yiaddr, const unsigned char *server_id)
+{
+    size_t options_len;
+
+    bzero((char *) dhcp_pkt, sizeof(struct dhcp_packet));
+    dhcp_pkt->op = BOOTREQUEST;
+    dhcp_pkt->htype = HTYPE_ETHER;
+    dhcp_pkt->hlen = 6;
+    dhcp_pkt->hops = 1;
+    dhcp_pkt->xid = 0x12345678;	/* transaction id - fix */
+    dhcp_pkt->secs = 0;
+    dhcp_pkt->flags = 0;
+    memcpy(&dhcp_pkt->giaddr, giaddr, sizeof((*dhcp_pkt).giaddr));
+    /*memcpy(&dhcp_pkt->yiaddr, yiaddr, sizeof((*dhcp_pkt).yiaddr));*/
+    strncpy((char *)dhcp_pkt->chaddr, "\x12\x34\x56\x78\x9a\xbc", 6);	/* client hardware addr - fix */
+    memset(dhcp_pkt->options, DHO_PAD, DHCP_MAX_OPTION_LEN);
+    strncpy((char *)dhcp_pkt->options, "\x63\x82\x53\x63", 4);		/* magic cookie */
+    options_len = 4;
+    add_option(0, DHO_DHCP_MESSAGE_TYPE, 1, 1, 1, dhcp_pkt->options, &options_len,
+	    DHCPREQUEST);
+    add_option(0, DHO_DHCP_SERVER_IDENTIFIER, 1, 4, 0, dhcp_pkt->options,
+	    &options_len, server_id);
+    add_option(0, DHO_DHCP_REQUESTED_ADDRESS, 1, 4, 1, dhcp_pkt->options,
+	    &options_len, *yiaddr);
+    add_option(0, DHO_DHCP_LEASE_TIME, 1, 4, 1, dhcp_pkt->options, &options_len,
+	    htonl(60));
+
+    if (options_len < DHCP_MAX_OPTION_LEN)
+	dhcp_pkt->options[options_len++] = DHO_END;
+}
+
+/*
+ * dhcp_send: Send a DHCP packet.
+ * If the send fails, the program exits with an error message.
+ *
+ * Input variables:
+ * dhcp_packet: DHCP message to send.
+ * send_fd: Socket to send message on.
+ * r_addr: Remote address to send message to.
+ * send_laddr: Local address of socket, for informational messages only.
+ */
+void
+dhcp_send(int v6, const unsigned char *msg, int send_fd, const struct sockaddr *r_addr,
+	const struct sockaddr_storage *send_laddr) {
+    
+    size_t num_octets;
+    ssize_t num_written;
+    char addrbuf[ADDR_NAME_BUFSIZE];
+    char addrbuf2[ADDR_NAME_BUFSIZE];
+
+    num_octets = v6 ? sizeof(struct dhcpv6_packet) : sizeof(struct dhcp_packet);
+    if (dk_set(DK_MSG)) {
+	fprintf(stderr, "Sending %zu octets to socket fd %u, local %s remote %s",
+		num_octets, send_fd,
+		addrName((struct sockaddr_storage *)send_laddr, addrbuf, sizeof(addrbuf)),
+		addrName((struct sockaddr_storage *)r_addr, addrbuf2, sizeof(addrbuf2)));
+	fprintf(stderr, "Packet contents:\n");
+	print_dhcp_packet(v6, msg, num_octets);
+    }
+    num_written = sendto(send_fd, msg, num_octets, 0,
+	    r_addr, sizeof(struct sockaddr_storage));
+    if (num_written < 0) {
+	int s_errno = errno;
+	fprintf(stderr, "Send failed: %s\n", strerror(s_errno));
+	exit(1);
+    }
+    if ((size_t) num_written != num_octets) {
+	fprintf(stderr, "Only %d of %u octets written\n", (int) num_written, (unsigned) num_octets);
+    }
+}
+
+/*
+ * dhcp_recv: Receive a DHCP packet.
+ *
+ * Input variables:
+ * recv_fd is the socket to receive on.
+ * recv_laddr is the socket's address, solely for informational messages.
+ *
+ * Output variables:
+ * msg points to storage for the received message.
+ */
+void
+dhcp_recv(int v6, void *msg, int recv_fd,
+	const struct sockaddr_storage *recv_laddr) {
+    
+    ssize_t num_octets;
+    struct sockaddr_storage sourceAddr;
+    socklen_t addrSize;
+    char addrbuf[ADDR_NAME_BUFSIZE];
+
+    dkprintf(DK_SOCK, "Waiting for response on socket fd %u, %s",
+	    recv_fd,
+	    addrName(recv_laddr, addrbuf, sizeof(addrbuf)));
+    addrSize = sizeof(sourceAddr);
+    num_octets = recvfrom(recv_fd, msg, v6 ? sizeof(struct dhcpv6_packet) : sizeof(struct dhcp_packet), 0, (struct sockaddr *)&sourceAddr, &addrSize);
+    /* TODO: check for recvfrom failure status here */
+    if (dk_set(DK_MSG)) {
+	fprintf(stderr, "Got %zd octets from fd %u, %s", num_octets, recv_fd,
+		addrName(&sourceAddr, addrbuf, sizeof(addrbuf)));
+	fprintf(stderr, "Received packet contents:\n");
+	print_dhcp_packet(v6, msg, num_octets);
+    }
+}
+
+void
+dora(const char *server, const char *localAddr)
+{
+    struct sockaddr_storage send_laddr, recv_laddr;
+    struct dhcp_packet discover_pkt, offer_pkt, request_pkt, ack_pkt;
+    int send_fd, recv_fd;
+    const unsigned char *type, *server_id;
+    aaddr_buf a_yiaddr;
+    struct addrinfo *remote;
+    struct in_addr *local_address;
+
+    send_fd = socket_setup(AF_INET, localAddr, "bootpc", "Send", &send_laddr);
+    recv_fd = socket_setup(AF_INET, localAddr, "bootps", "Recv", &recv_laddr);
+
+    if ((remote = getaddr(AF_INET, server, "bootps")) == NULL) {
+	fprintf(stderr, "No addresses for %s\n", server);
+	exit(1);
+    }
+
+    local_address = &((struct sockaddr_in *)&send_laddr)->sin_addr;
+    gen_discover(&discover_pkt, local_address);
+
+    dhcp_send(0, (unsigned char *)&discover_pkt, send_fd, remote->ai_addr, &send_laddr);
+    dhcp_recv(0, &offer_pkt, recv_fd, &recv_laddr);
+    type = find_option(&offer_pkt, DHO_DHCP_MESSAGE_TYPE);
+    if (type == NULL) {
+	fprintf(stderr, "DHCP reponse did not include message type option\n");
+	exit(1);
+    }
+    if (type[2] != DHCPOFFER) {
+	fprintf(stderr, "DHCP reponse had message type %d; expecting DHCPOFFER\n", type[2]);
+	exit(1);
+    }
+    server_id = find_option(&offer_pkt, DHO_DHCP_SERVER_IDENTIFIER);
+    if (type == NULL) {
+	fprintf(stderr, "DHCP reponse did not include server identifier option\n");
+	exit(1);
+    }
+    server_id += 2;
+    printf("Server identifier: %08x\n", ntohl(*(int *)server_id));
+    printf("Offered address: %s\n", addrtoa(AF_INET, &offer_pkt.yiaddr, a_yiaddr));
+    gen_request(&request_pkt, local_address, &offer_pkt.yiaddr, server_id);
+    dhcp_send(0, (unsigned char *)&request_pkt, send_fd, remote->ai_addr, &send_laddr);
+    dhcp_recv(0, &ack_pkt, recv_fd, &recv_laddr);
+}
+
+/*
+    client 546, server 547
+    All_DHCP_Relay_Agents_and_Servers FF02::1:2
+    DHCPV6_SOLICIT
+    DHCPV6_ADVERTISE
+    DHCPV6_REQUEST
+    DHCPV6_REPLY
+
+DUID option must be present
+A client SHOULD generate a random number that cannot easily be guessed or
+predicted to use as the transaction ID for each new message it sends
+Discard ADVERTISE messages in which the Client Identifier option does not match the
+      client's DUID, or in which the transaction ID does not match the SOLICIT transaction ID.
+Discard REPLY messsages in whhich the "transaction-id" field in the message does not match the value
+      used in the original message.
+*/
+void
+sarr(const char *server, const char *if_name)
+{
+    struct sockaddr_storage send_laddr, recv_laddr;
+    struct dhcpv6_packet solicit_pkt, advertise_pkt;
+    int send_fd, recv_fd;
+    struct in6_addr *local_address;
+    struct addrinfo *remote;
+    struct duid client_id;
+
+    get_linklocal_addr(if_name, &send_laddr);
+    memcpy(&recv_laddr, &send_laddr, sizeof(recv_laddr));
+    send_fd = socket_setup(AF_INET6, NULL, "546", "Send", &send_laddr);
+    /*
+    recv_fd = socket_setup(AF_INET6, NULL, "547", "Recv", &recv_laddr);
+    */
+    recv_fd = send_fd;
+
+    if (server != NULL) {
+	if (strcmp(server, "all"))
+	    server = All_DHCP_Relay_Agents_and_Servers;
+	else if (strcmp(server, "server"))
+	    server = All_DHCP_Servers;
+    }
+    if ((remote = getaddr(AF_INET6, server, "547")) == NULL) {
+	fprintf(stderr, "Conversion failed for %s\n", server);
+	exit(1);
+    }
+
+    local_address = &((struct sockaddr_in6 *)&send_laddr)->sin6_addr;
+
+    client_id.duid_type = htons(DUID_LL);
+    client_id.htype = htons(HTYPE_ETHER);
+    memset(client_id.hwaddr, 0xA, 6);	/* TEMPORARY - FIX */
+
+    gen_solicit(&solicit_pkt, &client_id);
+    dhcp_send(1, (unsigned char *)&solicit_pkt, send_fd, remote->ai_addr, &send_laddr);
+    dhcp_recv(1, &advertise_pkt, recv_fd, &recv_laddr);
+/*
+ *
+ *    type = find_option(&offer_pkt, DHO_DHCP_MESSAGE_TYPE);
+ *    if (type == NULL) {
+ *	fprintf(stderr, "DHCP reponse did not include message type option\n");
+ *	exit(1);
+ *    }
+ *    if (type[2] != DHCPOFFER) {
+ *	fprintf(stderr, "DHCP reponse had message type %d; expecting DHCPOFFER\n", type[2]);
+ *	exit(1);
+ *    }
+ *    server_id = find_option(&offer_pkt, DHO_DHCP_SERVER_IDENTIFIER);
+ *    if (type == NULL) {
+ *	fprintf(stderr, "DHCP reponse did not include server identifier option\n");
+ *	exit(1);
+ *    }
+ *    server_id += 2;
+ *    printf("Server identifier: %08x\n", ntohl(*(int *)server_id));
+ *    printf("Offered address: %s\n", addrtoa(AF_INET, &offer_pkt.yiaddr, a_yiaddr));
+ *    gen_request(&request_pkt, local_address, &offer_pkt.yiaddr, server_id);
+ *    dhcp_send(&request_pkt, send_fd, remote->ai_addr, &send_laddr);
+ *    dhcp_recv(&ack_pkt, recv_fd, &recv_laddr);
+ */
+}
+
+/*
+ * Must include client identifier, server identifier, IA, DUID?
+ * Solitication: Create an IA.  Assign it an IAID.  Transmit a Solicit
+ * message containing an IA option describing the IA.
+ * Use IA_TA to request temporary addresses
+ */
+void
+gen_solicit(struct dhcpv6_packet *dhcp_pkt, const struct duid *client_id)
+{
+    int tid;
+    int i;
+    size_t options_len = 0;
+
+    bzero((char *) dhcp_pkt, sizeof(struct dhcpv6_packet));
+    dhcp_pkt->msg_type = DHCPV6_SOLICIT;
+    tid = rand();
+    for (i = 0; i < 2; i++) {
+	dhcp_pkt->transaction_id[i] = (unsigned char)tid;
+	tid >>= 8;
+    }
+    add_option(1, D6O_CLIENTID, 1, sizeof(struct duid), 0, dhcp_pkt->options,
+	    &options_len, client_id);
+    add_option(1, D6O_IA_TA, 1, 4, 1, dhcp_pkt->options,
+	    &options_len, "0xabcd");	/* Temporary - FIX */
+    /* D60_ORO: Option Request Option */
+    add_option(1, D6O_ORO, 1, 2, 1, dhcp_pkt->options,
+	    &options_len, D6O_NAME_SERVERS);
+}
+
+/*
+ * Add an option to a DHCP packet.
+ *
+ * Input variables:
+ * If buffer_size is nonzero, options are added to a DHCP6 packet, and
+ *     buffer_size specifies the size of the buffer.  If it is zero, options
+ *     are added to a DHCP4 packet.
+ * optnum is the option number.
+ * count is the number of option parameters passed.
+ * size is the size of each parameter, in bytes.
+ * direct is true if the options are passed by value, false if they are passed
+ * by address.
+ *
+ * Output variables:
+ * options[] is the buffer in which to store the option.
+ *
+ * Input/output variables:
+ * buffer_used is the amount of buffer space currently used.
+ */
+void
+add_option(int v6, unsigned optnum, unsigned count, size_t size,
+	int direct, unsigned char options[], size_t *buffer_used, ...)
+{
+    va_list ap;
+    unsigned i;
+    size_t buffer_size = v6 ? sizeof(struct dhcpv6_packet) : DHCP_MAX_OPTION_LEN;
+
+    if ((*buffer_used + (v6 ? 4 : 2) + count * size) > buffer_size) {
+	fprintf(stderr, "%s: Insufficient option space\n", progName);
+	exit(1);
+    }
+    if (v6) {
+	struct v6_option *opt = (struct v6_option *)&options[(*buffer_used)];
+	opt->code = htons(optnum);
+	opt->len = htons(count * size);
+	*buffer_used += 4;
+    }
+    else {
+	options[(*buffer_used)++] = optnum;
+	options[(*buffer_used)++] = count * size;
+    }
+    va_start(ap,buffer_used);
+    for (i = 1; i <= count; i++) {
+	if (direct) {
+	    int value = va_arg(ap, int);
+	    memcpy(&options[*buffer_used], (char *)&value, size);
+	}
+	else {
+	    char *p = va_arg(ap, char *);
+	    memcpy(&options[*buffer_used], p, size);
+	}
+	(*buffer_used) += size;
+    }
+    /* ap */
+    va_end(ap);
+}
+
+/*
+ * Return value:
+ * buf is returned.
+ */
+char *
+addrtoa(int addr_fam, const struct in_addr *addr, aaddr_buf buf)
+{
+    if (inet_ntop(addr_fam, addr, buf, ADDR_NAME_BUFSIZE) == NULL)
+	strcpy(buf, "untranslatable");
+    return buf;
+}
+
+/*
+ * getaddr: generate an addrinfo list for a given hostname and port, UDP.
+ * If getaddrinfo() fails with the provided information, an error message
+ * is printed and the program exits with status 2.
+ *
+ * Input variables:
+ * hostname: The host name to look up.  This can be either a name or an IPv4 
+ *     dotted-quad address, or null to not fill in the address.
+ * port: The port to include in addrinfo.  This can be either a service name or
+ *     an ASCII decimal number, or null to not fill in the port number.
+ *
+ * Globals:
+ * progName, for error messages.
+ *
+ * Return value:
+ * A pointer to the addrinfo list.  This must be freed by the caller with
+ * freeaddrinfo().
+ */
+struct addrinfo *
+getaddr(int addr_fam, const char *hostname, const char *port)
+{
+    struct addrinfo *ai;
+    struct addrinfo hints;
+    int ret;
+
+    memset (&hints, '\0', sizeof(hints));
+    hints.ai_family = addr_fam;
+    hints.ai_socktype = SOCK_DGRAM;
+    hints.ai_protocol = IPPROTO_UDP;
+
+    if ((ret = getaddrinfo(hostname, port, &hints, &ai)) != 0) {
+	fprintf(stderr, "%s: %s: getaddrinfo: %s/%s\n", progName,
+		hostname == NULL ? "" : hostname, port == NULL ? "" : port, gai_strerror(ret));
+	exit(2);
+    }
+    return ai;
+}
+
+/*
+ * addrName(): Convert the address and port associated with a socket into an a
+ * hostname and numeric string and store them in a buffer.
+ *
+ * Input variables:
+ * addr is the socket to operate on.
+ * bufsize is the size of the buffer.
+ *
+ * Output variables:
+ * name is the buffer to store in.
+ *
+ * Return value:
+ * buf is returned.
+ */
+char *
+addrName(const struct sockaddr_storage *addr, char *name, size_t bufsize)
+{
+    char *buf = name;
+    char servbuf[30];
+
+    if (getnameinfo((struct sockaddr *)addr, sizeof(struct sockaddr_storage),
+	  name, bufsize, servbuf, 30, 0) != 0)
+	strncpy(buf, "untranslatable", bufsize-1);
+    else {
+	size_t len = strlen(buf);
+	if (len < bufsize)
+	    snprintf(name + len, bufsize - len, " port %s", servbuf);
+    }
+    return buf;
+}
+
+/*
+ *
+ * Input variables:
+ * if_name is the name of the interface to search for.
+ *
+ * Output variables:
+ * The link-local address for the interface is stored in addr.
+ * 
+ * Return value:
+ * 1 on success, 0 if no link-local address is found.
+ *
+ * If retrieval of the interface address list fails, an error message is
+ * printed and the program is exited with status 2.
+ */
+int
+get_linklocal_addr(const char if_name[], struct sockaddr_storage *addr)
+{
+
+    struct ifaddrs *ifaddr, *ifa;
+ 
+    if (getifaddrs(&ifaddr) == -1) {
+	fprintf(stderr, "%s: Could not get interface addresses: %s\n",
+		progName, strerror(errno));
+        exit(2);
+    }
+ 
+    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+	if (ifa->ifa_addr->sa_family == AF_INET6 && strcmp(ifa->ifa_name, if_name) == 0 &&
+		(ntohs(((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr.__in6_u.__u6_addr16[0]) & 0xffc0) == 0xfe80)
+	    break;
+    }
+    if (ifa != NULL)
+	memcpy(addr, ifa->ifa_addr, sizeof(struct sockaddr_storage));
+    freeifaddrs(ifaddr);
+    return ifa != NULL;
+}
+
+void
+print_addrinfo(FILE *f, const struct addrinfo *addr)
+{
+    fprintf(f, "Addrinfo:\n");
+    fprintf(f, "flags: 0x%x;  family: %d;  socktype: %d;  proto: %d;\n",
+	    addr->ai_flags, addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+    fprintf(f, "addrlen: %u;  addr: %p;  canonname: %s;  next: %p\n",
+	    addr->ai_addrlen, addr->ai_addr, addr->ai_canonname, addr->ai_next);
+    if (addr->ai_family == AF_INET6)
+	print_sa6_info(f, (struct sockaddr_in6 *)addr->ai_addr);
+}
+
+void
+print_sa6_info(FILE *f, const struct sockaddr_in6 *sa)
+{
+    char addrbuf[ADDR_NAME_BUFSIZE];
+
+    fprintf(f, "IPv6 sockaddr info:\n");
+    fprintf(f, "family: %u;  flowinfo: 0x%x;  scope-id: %u  addr: %s\n",
+	    sa->sin6_family, sa->sin6_flowinfo, sa->sin6_scope_id,
+	    addrName((struct sockaddr_storage *)sa, addrbuf, sizeof(addrbuf)));
+}
+
+/*
+ * Search for a specific option in the options stored in a DHCP packet.
+ *
+ * Input variables:
+ * pkt is the packet to search.
+ * search_opt is the option number to search for.
+ *
+ * Return value:
+ * If the packet contains the option, a pointer to its start (the option
+ * number) is returned.  If not, NULL is returned.
+ */
+const unsigned char *
+find_option(const struct dhcp_packet *pkt, int search_opt)
+{
+    const unsigned char *p;
+
+    p = &pkt->options[4];
+    while ((p - pkt->options) < DHCP_MAX_OPTION_LEN && *p != DHO_END) {
+	if (*p == search_opt)
+	    return p;
+	else if (*p == DHO_PAD)
+	    p++;
+	else {
+	    size_t len = p[1];
+	    p += 2 + len;
+	}
+    }
+    return NULL;
+}
diff --git a/tests/tools/perfdhcp/perfdhcp.h b/tests/tools/perfdhcp/perfdhcp.h
new file mode 100644
index 0000000..cd9066e
--- /dev/null
+++ b/tests/tools/perfdhcp/perfdhcp.h
@@ -0,0 +1,33 @@
+#ifdef __cplusplus
+extern "C" {
+#endif 
+
+#define FLEXIBLE_ARRAY_MEMBER 500
+#define ADDR_NAME_BUFSIZE (NI_MAXHOST + 30)
+
+#define DK_SOCK 1
+#define DK_MSG 2
+#define DK_PACKET 4
+
+#include <stdlib.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include "dhcp.h"
+#include "dhcp6.h"
+
+struct v6_option {
+    uint16_t code;
+    uint16_t len;
+    unsigned char value;
+};
+
+typedef char aaddr_buf[ADDR_NAME_BUFSIZE];
+
+char *addrtoa(int addr_fam, const struct in_addr *addr, aaddr_buf buf);
+void print_dhcp_packet(int v6, const void *pkt, size_t len);
+
+const char progName[] = "dhcpperf";
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/tests/tools/perfdhcp/procconf.cc b/tests/tools/perfdhcp/procconf.cc
new file mode 100644
index 0000000..bc141cb
--- /dev/null
+++ b/tests/tools/perfdhcp/procconf.cc
@@ -0,0 +1,496 @@
+/*
+ * procopts: process command line options and config file variables.
+ * This is still more or less a first cut (despite the rewrite!)
+ * 2000-07-22 John H. DuBois III (john at armory.com)
+ * 2007-04-28 2.0 Added command line processing.  Largely rewritten.
+ * 2011-10-30 Cleaned up.  Added double types and out-of-range checking.
+ */
+
+#include <string.h>
+#include <malloc.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;
+static unsigned debugLevel = 0;		/* set by debug option */
+static int readConf = 1;		/* should config file be read? */
+#define VERBOSE_DEBUG 7
+
+#define INTERNAL_ERROR -1
+#define USAGE_ERROR -2
+
+/*
+ * error: printf-style interface to print an error message or write it into
+ * global errmsg.
+ */
+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);
+}
+
+static void *
+pc_malloc(size_t size)
+{
+    void *ret = malloc(size);
+    if (ret == NULL)
+	error(INTERNAL_ERROR, "Out of memory");
+    return ret;
+}
+
+static void
+opterror(const char *expected, const char *value, const confvar_t *varDesc, const char filename[], const char *detail)
+{
+    if (detail == NULL)
+	detail = "";
+    if (filename == NULL)
+	error(USAGE_ERROR, "Invalid value given for option -%c: expected %s, got: %s%s",
+		varDesc->outind, expected, value, detail);
+    else
+	error(USAGE_ERROR, "Invalid value given in configuration file \"%s\" for option %s: expected %s, got: %s%s",
+		filename, varDesc->varname, 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:
+ * source is the source the option came from (command line, config file, etc.)
+ * value is the string value assigned to the option, for options that take
+ *     values.
+ * varDesc is the description structure for this option.
+ * filename is the configuration file name, if this option came from a config
+ *     file, else null.
+ *
+ * Output variables:
+ * The option value is stored in the option list pointed to by first.
+ * last is used to track the last record in each option list, so option values
+ * can be appended easily.
+ *
+ * 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 cf_source source, const char *value, const confvar_t *varDesc,
+	confval **first, confval **last, const char filename[])
+{
+    const void *addr;
+    confval data, *ret_data;
+    int seen = *first != NULL;
+    char *ptr;
+    int err;
+
+     /* 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, filename, 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, filename, 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:
+    case CF_PDEBUG:
+	/* todo: check for out-of-range result */
+	errno = 0;
+	data.value.intval = strtol(value, &ptr, 0);
+	if (errno == ERANGE) {
+	    opterror("an integer", value, varDesc, filename, " (out of range)");
+	    return -1;
+	}
+	err = *value == '\0' || *ptr != '\0';
+	switch (varDesc->type) {
+	case CF_INT:
+	    if (err) {
+		opterror("an integer", value, varDesc, filename, NULL);
+		return -1;
+	    }
+	    break;
+	case CF_NON_NEG_INT:
+	    if (err || data.value.intval < 0) {
+		opterror("a non-negative integer", value, varDesc, filename, NULL);
+		return -1;
+	    }
+	    data.value.nnint = data.value.intval;
+	    break;
+	case CF_POS_INT:
+	case CF_PDEBUG:
+	    if (err || data.value.intval <= 0) {
+		opterror("a positive integer", value, varDesc, filename, NULL);
+		return -1;
+	    }
+	    data.value.nnint = data.value.intval;
+	    break;
+	default:
+	    /* To avoid complaints from -Wall */
+	    ;
+	}
+	if (addr != NULL)
+	    *(int *) addr = data.value.intval;
+	if (!seen && varDesc->type == CF_PDEBUG)
+	    debugLevel = 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, filename, " (out of range)");
+	    return -1;
+	}
+	err = *value == '\0' || *ptr != '\0';
+	switch (varDesc->type) {
+	case CF_FLOAT:
+	    if (err) {
+		opterror("a number", value, varDesc, filename, NULL);
+		return -1;
+	    }
+	    break;
+	case CF_NON_NEG_FLOAT:
+	    if (err || data.value.floatval < 0) {
+		opterror("a non-negative number", value, varDesc, filename, NULL);
+		return -1;
+	    }
+	    break;
+	case CF_POS_FLOAT:
+	    if (err || data.value.floatval <= 0) {
+		opterror("a positive number", value, varDesc, filename, NULL);
+		return -1;
+	    }
+	    break;
+	default:
+	    /* To avoid complaints from -Wall */
+	    ;
+	}
+	if (addr != NULL)
+	    *(double *) addr = data.value.floatval;
+	break;
+    case CF_SWITCH:
+    case CF_NOCONF:
+    case CF_SDEBUG:
+	if (source == CF_FILE && *value != '1')	/* option not turned on; ignore */
+	    return 0;
+	data.value.switchval = varDesc->value;
+	value = "1";	/* for debugging */
+	if (addr != NULL)
+	    *(int *) addr = varDesc->value;
+	if (!seen)
+	    switch (varDesc->type) {
+		case CF_NOCONF:
+		    readConf = 0;
+		    break;
+		case CF_SDEBUG:
+		    debugLevel = 9;
+		    break;
+		default:
+		    /* To avoid complaints from -Wall */
+		    ;
+	    }
+	break;
+    case CF_ENDLIST:
+	/* To avoid complaints from -Wall */
+	;
+    }
+    data.strval = value;
+    data.source = source;
+    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;
+    if (debugLevel >= VERBOSE_DEBUG)
+	fprintf(stderr, "Option %c (%s) gets value \"%s\" from %s%s\n",
+		0 < varDesc->outind && varDesc->outind < 256 ? varDesc->outind : '-',
+		varDesc->varname == NULL ? "-" : varDesc->varname, value,
+		source == CF_ARGS ? "command line" : "config file", seen ? " (already seen)" : "");
+    return 1;
+}
+
+/*
+ * This currently depends on defread().  A CDDL version of the def* library exists:
+ * http://www.opensource.apple.com/source/autofs/autofs-207/automountlib/deflt.c
+ * But, this should really be rewritten to not use defread().
+ */
+#ifdef DEFREAD
+/*
+ * Input variables:
+ * filename: Name of configuration file.  If it begins with ~/, the ~ is
+ *     replaced with the invoking user's home directory.
+ * optConf[]: Option description structures.
+ *
+ * Output variables:
+ * See addOptVal().
+ *
+ * Return value:
+ * If the config file does not exist, 0.
+ * Otherwise, the number of variable assignments read is returned (which may
+ * also result in a 0 return value).
+ * On error, a string describing the error is stored in the global errmsg and
+ * -1 is returned.
+ */
+static int
+procConfFile(const char filename[], const confvar_t optConf[], confval *first[], confval *last[])
+{
+    int count;
+    char *home;
+    int ind;
+
+    if (!strncmp(filename, "~/", 2)) {
+	char *path;
+
+	if (!(home = getenv("HOME"))) {
+	    if (debugLevel > 2)
+		fprintf(stderr, "HOME environment variable not set.\n");
+	    return 0;
+	}
+	if ((path = pc_malloc(strlen(home) + strlen(filename))) == NULL)
+	    return -1;
+	strcpy(path, home);
+	strcat(path, filename+1);
+	if (defopen(path)) {
+	    free(path);
+	    return 0;
+	}
+	free(path);
+    }
+    else if (defopen((char *)filename)) {
+	if (debugLevel > 2)
+	    fprintf(stderr, "Config file '%s' not found.\n", filename);
+	return 0;
+    }
+    count = 0;
+    for (ind = 0; optConf[ind].type != CF_ENDLIST; ind++) {
+	char buf[128];
+	char *s;
+
+	if (optConf[ind].varname == NULL)
+	    continue;
+	strncpy(buf, optConf[ind].varname, 126);
+	strcat(buf, "=");
+	if ((s = defread(buf)) != NULL) {
+	    int ret;
+
+	    if ((s = strdup(s)) == NULL) {
+		error(INTERNAL_ERROR, "Out of memory");
+		return -1;
+	    }
+	    switch ((ret = addOptVal(CF_FILE, s, &optConf[ind], &first[ind], &last[ind], filename))) {
+	    case 1:
+		count++;
+		break;
+	    case 0:
+		break;
+	    default:
+		return ret;
+	    }
+	}
+    }
+    return count;
+}
+#endif
+
+/*
+ * 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, char **argv[], const confvar_t optConf[], confval **first, confval **last)
+{
+    char *p;
+    extern char *optarg;	/* For getopt */
+    extern int optind;		/* For getopt */
+    extern int optopt;		/* For getopt */
+    char optstr[514];
+    unsigned optCharToConf[256];
+    int optchar;
+    unsigned confNum;
+    int count = 0;
+
+    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:
+	    case CF_NOCONF:
+	    case CF_SDEBUG:
+		break;
+	    default:
+		*(p++) = ':';
+		break;
+	    }
+	    optCharToConf[outind] = confNum;
+	}
+    }
+    *p = '\0';
+    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(CF_ARGS, optarg, &optConf[ind], &first[ind], &last[ind], NULL)) {
+	case 1:
+	    count++;
+	    break;
+	case 0:
+	    break;
+	default:
+	    return ret;
+	}
+    }
+    *argc -= optind;
+    *argv += optind;
+    return count;
+}
+
+const char *
+procOpts(int *argc, char **argv[], const confvar_t optConf[], confdata_t *confdata,
+	const char confFile[], const char name[], const char usage[])
+{
+    unsigned numConf;
+    confval **first, **last;	/* first & last records in the linked list maintained for each option */
+    unsigned maxOptIndex = 0;
+    int count;		/* number of option instances found */
+    unsigned optNum;
+    unsigned i;
+    confval **valuePointers;
+
+    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 ((first = (confval **)pc_malloc(sizeof(confval *) * numConf)) == NULL ||
+	    (last = (confval **)pc_malloc(sizeof(confval *) * numConf)) == NULL)
+	return errmsg;
+    memset(first, '\0', sizeof(confval *) * numConf);
+    memset(last, '\0', sizeof(confval *) * numConf);
+
+    if ((count = procCmdLineArgs(argc, argv, optConf, first, last)) < 0)
+	return errmsg;
+    if (readConf && confFile != NULL) {
+#ifdef DEFREAD
+	int ret;
+
+	if ((ret = procConfFile(confFile, optConf, first, last)) < 0)
+	    return errmsg;
+	else
+	    count += ret;
+#else
+	error(INTERNAL_ERROR, "Built without defread!");
+	return errmsg;
+#endif
+    }
+
+    free(last);
+
+    /*
+     * 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 *) * count)) == NULL ||
+	(confdata->optVals = (cf_option *)pc_malloc(sizeof(cf_option) * numConf)) == NULL)
+	return errmsg;
+    if (maxOptIndex != 0) {
+	if ((confdata->map = (cf_option **)pc_malloc(sizeof(cf_option) * (maxOptIndex+1))) == NULL)
+	    return errmsg;
+	memset(confdata->map, '\0', sizeof(confval *) * (maxOptIndex+1));
+    }
+
+    /*
+     * Store the linked lists of option values into arrays.
+     * Pointers to all option instances are stored in valuePointers.
+     */
+    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 = first[optNum]; optval != NULL; optval = optval->next) {
+	    confdata->optVals[optNum].num++;
+	    valuePointers[i++] = optval;
+	}
+	if (debugLevel > 5)
+	    fprintf(stderr, "Option %c (%s) got %d values\n",
+		    outind == 0 ? '-' : outind,
+		    optConf[optNum].varname == NULL ? "-" : optConf[optNum].varname,         
+		    confdata->optVals[optNum].num);
+    }
+    free(first);
+    return NULL;
+}
diff --git a/tests/tools/perfdhcp/procconf.h b/tests/tools/perfdhcp/procconf.h
new file mode 100644
index 0000000..f252dda
--- /dev/null
+++ b/tests/tools/perfdhcp/procconf.h
@@ -0,0 +1,80 @@
+#ifdef __cplusplus
+extern "C" {
+#endif 
+
+#include <limits.h>	/* for UINT_MAX */
+
+typedef enum {
+    CF_CHAR,
+    CF_STRING,
+    CF_NE_STRING,
+    CF_INT,
+    CF_NON_NEG_INT,
+    CF_POS_INT,
+    CF_FLOAT,
+    CF_NON_NEG_FLOAT,
+    CF_POS_FLOAT,
+    CF_SWITCH,
+    CF_NOCONF,	/* option to specify that config file should not be read */
+    CF_PDEBUG,	/* option to turn on debugging, with positive integer value */
+    CF_SDEBUG,	/* option to turn on debugging, without a value */
+    CF_ENDLIST	/* End of option list */
+} cf_type;
+
+typedef enum {
+    CF_NONE, CF_ARGS, CF_FILE
+} cf_source;
+
+#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 eventually 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 */
+    cf_source source;	/* Where value was taken from */
+    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;
+
+const char *
+procOpts(int *argc, char **argv[], const confvar_t optConf[], confdata_t *confdata, const char confFile[], const char name[],
+	const char usage[]);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am
new file mode 100644
index 0000000..d2672f2
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/Makefile.am
@@ -0,0 +1,29 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES  = run_unittests.cc
+run_unittests_SOURCES += cloptions_unittest.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/cloptions.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
+
+run_unittests_LDADD  = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/tests/tools/perfdhcp/tests/cloptions_unittest.cc b/tests/tools/perfdhcp/tests/cloptions_unittest.cc
new file mode 100644
index 0000000..c2b0628
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/cloptions_unittest.cc
@@ -0,0 +1,300 @@
+// 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 <string>
+#include <gtest/gtest.h>
+
+#include "../cloptions.h"
+
+#include "exceptions/exceptions.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::badpacket;
+
+
+/// \brief Test Fixture Class
+
+class CommandOptionsTest : public virtual ::testing::Test,
+                           public virtual CommandOptions
+{
+public:
+
+    /// \brief Default Constructor
+    CommandOptionsTest()
+    {}
+
+    /// \brief Check Non-Limit Options
+    ///
+    /// Checks that the options that are NOT related to the message are set to
+    /// their default values.
+    void checkDefaultOtherValues() {
+        EXPECT_EQ("127.0.0.1", getAddress());
+        EXPECT_EQ(53, getPort());
+        EXPECT_EQ(500, getTimeout());
+        EXPECT_EQ("www.example.com", getQname());
+    }
+
+    /// \brief Checks the minimum and maximum value specified for an option
+    ///
+    /// Checks the values for one of the options whose values are stored in the
+    /// class's options_ array.
+    ///
+    /// \param index Index of the option in the limits_ array
+    /// \param minval Expected minimum value
+    /// \param maxval Expected maximum value
+    void checkValuePair(int index, uint32_t minval = 0, uint32_t maxval = 0) {
+        EXPECT_EQ(minimum(index), minval);
+        EXPECT_EQ(maximum(index), maxval);
+    }
+
+    /// \brief Checks that all options are at default values
+    ///
+    /// Checks that all options have both their maximum and minimum set to the
+    /// default values.
+    ///
+    /// \param except Index not to check. (This allows options not being tested
+    ///        to be checked to see that they are at the default value.)  As all
+    ///        index values are positive, a negative value means check
+    ///        everything.
+    void checkDefaultLimitsValues(int except = -1) {
+        for (int i = 0; i < OptionInfo::SIZE; ++i) {
+            if (i != except) {
+                checkValuePair(i, OptionInfo::defval(i),
+                               OptionInfo::defval(i));
+            }
+        }
+    }
+
+    /// \brief Check valid command option
+    ///
+    /// Checks that the command line specification of one of the options taking
+    /// a value correctly processes the option.
+    ///
+    /// \param index Option index
+    /// \param optflag Option flag (in the form '--option')
+    /// \param optval Value to be passed to the option.
+    /// \param minval Expected minimum value
+    /// \param maxval Expected maximum value
+    void checkCommandValid(int index, const char* optflag, const char* optval,
+                           uint32_t minval, uint32_t maxval) {
+
+        // Set up the command line and parse it.
+        const char* argv[] = {"badpacket", NULL, NULL};
+        argv[1] = optflag;
+        argv[2] = optval;
+        int argc = 3;
+        parse(argc, const_cast<char**>(argv));
+
+        // Check the results.  Everything should be at the defaults except for
+        // the specified option, where the minimum and maximum should be as
+        // specified.
+        checkDefaultOtherValues();
+        checkDefaultLimitsValues(index);
+        checkValuePair(index, minval, maxval);
+    }
+
+    /// \brief Check invalid command option
+    ///
+    /// Passed a command with an invalid value, checks that the parsing throws
+    /// a BadValue exception.
+    ///
+    /// \param optflag Option flag (in the form '--option')
+    /// \param optval Value to be passed to the option.
+    void checkCommandInvalid(const char* optflag, const char* optval) {
+
+        // Set up the command line and parse it.
+        const char* argv[] = {"badpacket", NULL, NULL};
+        argv[1] = optflag;
+        argv[2] = optval;
+        int argc = 3;
+        EXPECT_THROW(parse(argc, const_cast<char**>(argv)), isc::BadValue);
+    }
+
+    /// \brief Check one-bit field
+    ///
+    /// Explicitly for those fields in the flags word that are one bit wide,
+    /// perform a series of tests to check that they accept valid values and
+    /// reject invalid ones.
+    ///
+    /// \param index Option index
+    /// \param optflag Option flag (in the form '--option')
+    void checkOneBitField(int index, const char* optflag) {
+        checkCommandValid(index, optflag, "0", 0, 0);
+        checkCommandValid(index, optflag, "1", 1, 1);
+        checkCommandValid(index, optflag, "0-1", 0, 1);
+        checkCommandValid(index, optflag, "1-0", 0, 1);
+        checkCommandInvalid(optflag, "0-3");
+        checkCommandInvalid(optflag, "4");
+        checkCommandInvalid(optflag, "xyz");
+    }
+
+    /// \brief Check four-bit field
+    ///
+    /// Explicitly for those fields in the flags word that are four bits wide,
+    /// perform a series of tests to check that they accept valid values and
+    /// reject invalid ones.
+    ///
+    /// \param index Option index
+    /// \param optflag Option flag (in the form '--option')
+    void checkFourBitField(int index, const char* optflag) {
+        checkCommandValid(index, optflag, "0", 0, 0);
+        checkCommandValid(index, optflag, "15", 15, 15);
+        checkCommandValid(index, optflag, "0-15", 0, 15);
+        checkCommandValid(index, optflag, "15-0", 0, 15);
+        checkCommandInvalid(optflag, "0-17");
+        checkCommandInvalid(optflag, "24");
+        checkCommandInvalid(optflag, "xyz");
+    }
+
+    /// \brief Check sixteen-bit field
+    ///
+    /// Explicitly test the parsing of the fields that can take a 16-bit
+    /// value ranging from 0 to 65535.
+    ///
+    /// \param index Option index
+    /// \param optflag Option flag (in the form '--option')
+    void checkSixteenBitField(int index, const char* optflag) {
+        checkCommandValid(index, optflag, "0", 0, 0);
+        checkCommandValid(index, optflag, "65535", 65535, 65535);
+        checkCommandValid(index, optflag, "0-65535", 0, 65535);
+        checkCommandValid(index, optflag, "65535-0", 0, 65535);
+        checkCommandInvalid(optflag, "0-65536");
+        checkCommandInvalid(optflag, "65537");
+        checkCommandInvalid(optflag, "xyz");
+    }
+};
+
+// Check that each of the non-message options will be recognised
+
+TEST_F(CommandOptionsTest, address) {
+    const char* argv[] = {"badpacket",  "--address", "192.0.2.1"};
+    int argc = sizeof(argv) / sizeof(const char*);
+
+    // The conversion is ugly but it simplifies the process of entering the
+    // string constant.  The cast throws away the "const"ness of the pointed-to
+    // strings in order to conform to the function signature; however, the
+    // called functions all treat the strings as const.
+    parse(argc, const_cast<char**>(argv));
+    EXPECT_EQ("192.0.2.1", getAddress());
+    EXPECT_EQ(53, getPort());
+    EXPECT_EQ(500, getTimeout());
+    EXPECT_EQ("www.example.com", getQname());
+    checkDefaultLimitsValues();
+}
+
+TEST_F(CommandOptionsTest, port) {
+    const char* argv[] = {"badpacket",  "--port", "153"};
+    int argc = sizeof(argv) / sizeof(const char*);
+
+    parse(argc, const_cast<char**>(argv));
+    EXPECT_EQ("127.0.0.1", getAddress());
+    EXPECT_EQ(153, getPort());
+    EXPECT_EQ(500, getTimeout());
+    EXPECT_EQ("www.example.com", getQname());
+    checkDefaultLimitsValues();
+}
+
+TEST_F(CommandOptionsTest, timeout) {
+    const char* argv[] = {"badpacket",  "--timeout", "250"};
+    int argc = sizeof(argv) / sizeof(const char*);
+
+    parse(argc, const_cast<char**>(argv));
+    EXPECT_EQ("127.0.0.1", getAddress());
+    EXPECT_EQ(53, getPort());
+    EXPECT_EQ(250, getTimeout());
+    EXPECT_EQ("www.example.com", getQname());
+    checkDefaultLimitsValues();
+}
+
+TEST_F(CommandOptionsTest, parameter) {
+    const char* argv[] = {"badpacket",  "ftp.example.net"};
+    int argc = sizeof(argv) / sizeof(const char*);
+
+    parse(argc, const_cast<char**>(argv));
+    EXPECT_EQ("127.0.0.1", getAddress());
+    EXPECT_EQ(53, getPort());
+    EXPECT_EQ(500, getTimeout());
+    EXPECT_EQ("ftp.example.net", getQname());
+    checkDefaultLimitsValues();
+}
+
+// Test options representing the flags fields.
+
+TEST_F(CommandOptionsTest, qr) {
+    checkOneBitField(OptionInfo::QR, "--qr");
+}
+
+TEST_F(CommandOptionsTest, op) {
+    checkFourBitField(OptionInfo::OP, "--op");
+}
+
+TEST_F(CommandOptionsTest, aa) {
+    checkOneBitField(OptionInfo::AA, "--aa");
+}
+
+TEST_F(CommandOptionsTest, tc) {
+    checkOneBitField(OptionInfo::TC, "--tc");
+}
+
+TEST_F(CommandOptionsTest, z) {
+    checkOneBitField(OptionInfo::Z, "--z");
+}
+
+TEST_F(CommandOptionsTest, ad) {
+    checkOneBitField(OptionInfo::AD, "--ad");
+}
+
+TEST_F(CommandOptionsTest, cd) {
+    checkOneBitField(OptionInfo::CD, "--cd");
+}
+
+TEST_F(CommandOptionsTest, rc) {
+    checkFourBitField(OptionInfo::RC, "--rc");
+}
+
+// Section count options
+
+TEST_F(CommandOptionsTest, qc) {
+    checkSixteenBitField(OptionInfo::QC, "--qc");
+}
+
+TEST_F(CommandOptionsTest, ac) {
+    checkSixteenBitField(OptionInfo::AC, "--ac");
+}
+
+TEST_F(CommandOptionsTest, uc) {
+    checkSixteenBitField(OptionInfo::UC, "--uc");
+}
+
+TEST_F(CommandOptionsTest, dc) {
+    checkSixteenBitField(OptionInfo::DC, "--dc");
+}
+
+// ... and the message size option
+
+TEST_F(CommandOptionsTest, ms) {
+    int index = OptionInfo::MS;
+    const char* optflag = "--ms";
+
+    checkCommandValid(index, optflag, "1", 1, 1);
+    checkCommandValid(index, optflag, "65536", 65536, 65536);
+    checkCommandValid(index, optflag, "1-65536", 1, 65536);
+    checkCommandValid(index, optflag, "65536-1", 1, 65536);
+    checkCommandInvalid(optflag, "0");
+    checkCommandInvalid(optflag, "1-65537");
+    checkCommandInvalid(optflag, "65538");
+    checkCommandInvalid(optflag, "xyz");
+}
diff --git a/tests/tools/perfdhcp/tests/packetdisp_unittest.cc b/tests/tools/perfdhcp/tests/packetdisp_unittest.cc
new file mode 100644
index 0000000..e69de29
diff --git a/tests/tools/perfdhcp/tests/run_unittests.cc b/tests/tools/perfdhcp/tests/run_unittests.cc
new file mode 100644
index 0000000..6eeca75
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/run_unittests.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    return (isc::util::unittests::run_all());
+}




More information about the bind10-changes mailing list