Browse Source

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

John DuBois 13 years ago
parent
commit
7c229ebaca

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

+ 1 - 1
tests/tools/Makefile.am

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

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

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

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

+ 202 - 0
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@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 */
+

+ 213 - 0
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@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
+

+ 41 - 0
tests/tools/perfdhcp/main.c

@@ -13,3 +13,44 @@
  * 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);
+}

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

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

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

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

+ 496 - 0
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@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;
+}

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

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

@@ -0,0 +1,29 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES  = run_unittests.cc
+run_unittests_SOURCES += 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)

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

+ 0 - 0
tests/tools/perfdhcp/tests/packetdisp_unittest.cc


+ 25 - 0
tests/tools/perfdhcp/tests/run_unittests.cc

@@ -0,0 +1,25 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    return (isc::util::unittests::run_all());
+}