queryperf.c   [plain text]


/*
 * Copyright (C) 2000, 2001  Nominum, Inc.
 *
 * 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 INTERNET SOFTWARE CONSORTIUM
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * INTERNET SOFTWARE CONSORTIUM 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.
 */

/***
 ***	DNS Query Performance Testing Tool  (queryperf.c)
 ***
 ***	Version $Id: queryperf.c,v 1.12 2007/09/05 07:36:04 marka Exp $
 ***
 ***	Stephen Jacob <sj@nominum.com>
 ***/

#define BIND_8_COMPAT	/* Pull in <arpa/nameser_compat.h> */

#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <math.h>
#include <errno.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#ifndef HAVE_GETADDRINFO
#include "missing/addrinfo.h"
#endif
#endif

/*
 * Configuration defaults
 */

#define DEF_MAX_QUERIES_OUTSTANDING	20
#define DEF_QUERY_TIMEOUT		5		/* in seconds */
#define DEF_SERVER_TO_QUERY		"127.0.0.1"
#define DEF_SERVER_PORT			"53"
#define DEF_BUFFER_SIZE			32		/* in k */

#define DEF_RTTARRAY_SIZE		50000
#define DEF_RTTARRAY_UNIT		100		/* in usec */

/*
 * Other constants / definitions
 */

#define COMMENT_CHAR			';'
#define CONFIG_CHAR			'#'
#define MAX_PORT			65535
#define MAX_INPUT_LEN			512
#define MAX_DOMAIN_LEN			255
#define MAX_BUFFER_LEN			8192		/* in bytes */
#define HARD_TIMEOUT_EXTRA		5		/* in seconds */
#define RESPONSE_BLOCKING_WAIT_TIME	0.1		/* in seconds */
#define EDNSLEN				11

#define FALSE				0
#define TRUE				1

#define WHITESPACE			" \t\n"

enum directives_enum	{ V_SERVER, V_PORT, V_MAXQUERIES, V_MAXWAIT };
#define DIRECTIVES	{ "server", "port", "maxqueries", "maxwait" }
#define DIR_VALUES	{ V_SERVER, V_PORT, V_MAXQUERIES, V_MAXWAIT }

#define QTYPE_STRINGS { \
	"A", "NS", "MD", "MF", "CNAME", "SOA", "MB", "MG", \
	"MR", "NULL", "WKS", "PTR", "HINFO", "MINFO", "MX", "TXT", \
	"AAAA", "SRV", "NAPTR", "A6", "AXFR", "MAILB", "MAILA", "*", "ANY" \
}

#define QTYPE_CODES { \
	1, 2, 3, 4, 5, 6, 7, 8, \
	9, 10, 11, 12, 13, 14, 15, 16, \
	28, 33, 35, 38, 252, 253, 254, 255, 255 \
}

#define RCODE_STRINGS { \
	"NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN", \
	"NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET", \
	"NXRRSET", "NOTAUTH", "NOTZONE", "rcode11", \
	"rcode12", "rcode13", "rcode14", "rcode15" \
}

/*
 * Data type definitions
 */

#define QUERY_STATUS_MAGIC	0x51535441U	/* QSTA */
#define VALID_QUERY_STATUS(q)	((q) != NULL && \
				 (q)->magic == QUERY_STATUS_MAGIC)

struct query_status {
	unsigned int magic;
	int in_use;
	unsigned short int id;
	struct timeval sent_timestamp;
	char *desc;
};

/*
 * Forward declarations.
 */
int is_uint(char *test_int, unsigned int *result);

/*
 * Configuration options (global)
 */

unsigned int max_queries_outstanding;			/* init 0 */
unsigned int query_timeout = DEF_QUERY_TIMEOUT;
int ignore_config_changes = FALSE;
unsigned int socket_bufsize = DEF_BUFFER_SIZE;

int family = AF_UNSPEC;
int use_stdin = TRUE;
char *datafile_name;					/* init NULL */

char *server_to_query;					/* init NULL */
char *server_port;					/* init NULL */
struct addrinfo *server_ai;				/* init NULL */

int run_only_once = FALSE;
int use_timelimit = FALSE;
unsigned int run_timelimit;				/* init 0 */
unsigned int print_interval;				/* init 0 */

unsigned int target_qps;				/* init 0 */

int serverset = FALSE, portset = FALSE;
int queriesset = FALSE, timeoutset = FALSE;
int edns = FALSE, dnssec = FALSE;
int countrcodes = FALSE;
int rcodecounts[16] = {0};

int verbose = FALSE;
int recurse = 1;

/*
 * Other global stuff
 */

int setup_phase = TRUE;

FILE *datafile_ptr;					/* init NULL */
unsigned int runs_through_file;				/* init 0 */

unsigned int num_queries_sent;				/* init 0 */
unsigned int num_queries_sent_interval;
unsigned int num_queries_outstanding;			/* init 0 */
unsigned int num_queries_timed_out;			/* init 0 */
unsigned int num_queries_possiblydelayed;		/* init 0 */
unsigned int num_queries_timed_out_interval;
unsigned int num_queries_possiblydelayed_interval;

struct timeval time_of_program_start;
struct timeval time_of_first_query;
double time_of_first_query_sec;
struct timeval time_of_first_query_interval;
struct timeval time_of_end_of_run;
struct timeval time_of_stop_sending;

struct timeval time_of_queryset_start;
double query_interval;
struct timeval time_of_next_queryset;

double rtt_max = -1;
double rtt_max_interval = -1;
double rtt_min = -1;
double rtt_min_interval = -1;
double rtt_total;
double rtt_total_interval;
int rttarray_size = DEF_RTTARRAY_SIZE;
int rttarray_unit = DEF_RTTARRAY_UNIT;
unsigned int *rttarray = NULL;
unsigned int *rttarray_interval = NULL;
unsigned int rtt_overflows;
unsigned int rtt_overflows_interval;
char *rtt_histogram_file = NULL;

struct query_status *status;				/* init NULL */
unsigned int query_status_allocated;			/* init 0 */

int query_socket = -1;
int socket4 = -1, socket6 = -1;

static char *rcode_strings[] = RCODE_STRINGS;

/*
 * get_uint16:
 *   Get an unsigned short integer from a buffer (in network order)
 */
static unsigned short
get_uint16(unsigned char *buf) {
	unsigned short ret;

	ret = buf[0] * 256 + buf[1];

	return (ret);
}

/*
 * show_startup_info:
 *   Show name/version
 */
void
show_startup_info(void) {
	printf("\n"
"DNS Query Performance Testing Tool\n"
"Version: $Id: queryperf.c,v 1.12 2007/09/05 07:36:04 marka Exp $\n"
"\n");
}

/*
 * show_usage:
 *   Print out usage/syntax information
 */
void
show_usage(void) {
	fprintf(stderr,
"\n"
"Usage: queryperf [-d datafile] [-s server_addr] [-p port] [-q num_queries]\n"
"                 [-b bufsize] [-t timeout] [-n] [-l limit] [-f family] [-1]\n"
"                 [-i interval] [-r arraysize] [-u unit] [-H histfile]\n"
"                 [-T qps] [-e] [-D] [-R] [-c] [-v] [-h]\n"
"  -d specifies the input data file (default: stdin)\n"
"  -s sets the server to query (default: %s)\n"
"  -p sets the port on which to query the server (default: %s)\n"
"  -q specifies the maximum number of queries outstanding (default: %d)\n"
"  -t specifies the timeout for query completion in seconds (default: %d)\n"
"  -n causes configuration changes to be ignored\n"
"  -l specifies how a limit for how long to run tests in seconds (no default)\n"
"  -1 run through input only once (default: multiple iff limit given)\n"
"  -b set input/output buffer size in kilobytes (default: %d k)\n"
"  -i specifies interval of intermediate outputs in seconds (default: 0=none)\n"
"  -f specify address family of DNS transport, inet or inet6 (default: any)\n"
"  -r set RTT statistics array size (default: %d)\n"
"  -u set RTT statistics time unit in usec (default: %d)\n"
"  -H specifies RTT histogram data file (default: none)\n"
"  -T specify the target qps (default: 0=unspecified)\n"
"  -e enable EDNS 0\n"
"  -D set the DNSSEC OK bit (implies EDNS)\n"
"  -R disable recursion\n"
"  -c print the number of packets with each rcode\n"
"  -v verbose: report the RCODE of each response on stdout\n"
"  -h print this usage\n"
"\n",
	        DEF_SERVER_TO_QUERY, DEF_SERVER_PORT,
	        DEF_MAX_QUERIES_OUTSTANDING, DEF_QUERY_TIMEOUT,
		DEF_BUFFER_SIZE, DEF_RTTARRAY_SIZE, DEF_RTTARRAY_UNIT);
}

/*
 * set_datafile:
 *   Set the datafile to read
 *
 *   Return -1 on failure
 *   Return a non-negative integer otherwise
 */
int
set_datafile(char *new_file) {
	char *dfname_tmp;

	if ((new_file == NULL) || (new_file[0] == '\0')) {
		fprintf(stderr, "Error: null datafile name\n");
		return (-1);
	}

	if ((dfname_tmp = malloc(strlen(new_file) + 1)) == NULL) {
		fprintf(stderr, "Error allocating memory for datafile name: "
		        "%s\n", new_file);
		return (-1);
	}

	free(datafile_name);
	datafile_name = dfname_tmp;

	strcpy(datafile_name, new_file);
	use_stdin = FALSE;

	return (0);
}

/*
 * set_input_stdin:
 *   Set the input to be stdin (instead of a datafile)
 */
void
set_input_stdin(void) {
	use_stdin = TRUE;
	free(datafile_name);
	datafile_name = NULL;
}

/*
 * set_server:
 *   Set the server to be queried
 *
 *   Return -1 on failure
 *   Return a non-negative integer otherwise
 */
int
set_server(char *new_name) {
	static struct hostent *server_he;

	/* If no change in server name, don't do anything... */
	if ((server_to_query != NULL) && (new_name != NULL))
		if (strcmp(new_name, server_to_query) == 0)
			return (0);

	if ((new_name == NULL) || (new_name[0] == '\0')) {
		fprintf(stderr, "Error: null server name\n");
		return (-1);
	}

	free(server_to_query);
	server_to_query = NULL;

	if ((server_to_query = malloc(strlen(new_name) + 1)) == NULL) {
		fprintf(stderr, "Error allocating memory for server name: "
		        "%s\n", new_name);
		return (-1);
	}

	strcpy(server_to_query, new_name);

	return (0);
}

/*
 * set_server_port:
 *   Set the port on which to contact the server
 *
 *   Return -1 if port is invalid
 *   Return a non-negative integer otherwise
 */
int
set_server_port(char *new_port) {
	unsigned int uint_val;

	if ((is_uint(new_port, &uint_val)) != TRUE)
		return (-1);

	if (uint_val && uint_val > MAX_PORT)
		return (-1);
	else {
		if (server_port != NULL && new_port != NULL &&
		    strcmp(server_port, new_port) == 0)
			return (0);

		free(server_port);
		server_port = NULL;

		if ((server_port = malloc(strlen(new_port) + 1)) == NULL) {
			fprintf(stderr,
				"Error allocating memory for server port: "
				"%s\n", new_port);
			return (-1);
		}

		strcpy(server_port, new_port);

		return (0);
	}
}

int
set_server_sa(void) {
	struct addrinfo hints, *res;
	static struct protoent *proto;
	int error;

	if (proto == NULL && (proto = getprotobyname("udp")) == NULL) {
		fprintf(stderr, "Error: getprotobyname call failed");
		return (-1);
	}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = family;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_protocol = proto->p_proto;
	if ((error = getaddrinfo(server_to_query, server_port,
				 &hints, &res)) != 0) {
		fprintf(stderr, "Error: getaddrinfo(%s, %s) failed\n",
			server_to_query, server_port);
		return (-1);
	}

	/* replace the server's addrinfo */
	if (server_ai != NULL)
		freeaddrinfo(server_ai);
	server_ai = res;
	return (0);
}

/*
 * is_digit:
 *   Tests if a character is a digit
 *
 *   Return TRUE if it is
 *   Return FALSE if it is not
 */
int
is_digit(char d) {
	if (d < '0' || d > '9')
		return (FALSE);
	else
		return (TRUE);
}

/*
 * is_uint:
 *   Tests if a string, test_int, is a valid unsigned integer
 *
 *   Sets *result to be the unsigned integer if it is valid
 *
 *   Return TRUE if it is
 *   Return FALSE if it is not
 */
int
is_uint(char *test_int, unsigned int *result) {
	unsigned long int value;
	char *end;

	if (test_int == NULL)
		return (FALSE);

	if (is_digit(test_int[0]) == FALSE)
		return (FALSE);

	value = strtoul(test_int, &end, 10);

	if ((errno == ERANGE) || (*end != '\0') || (value > UINT_MAX))
		return (FALSE);

	*result = (unsigned int)value;
	return (TRUE);
}

/*
 * set_max_queries:
 *   Set the maximum number of outstanding queries
 *
 *   Returns -1 on failure
 *   Returns a non-negative integer otherwise
 */
int
set_max_queries(unsigned int new_max) {
	static unsigned int size_qs = sizeof(struct query_status);
	struct query_status *temp_stat;
	unsigned int count;

	if (new_max > query_status_allocated) {
		temp_stat = realloc(status, new_max * size_qs);

		if (temp_stat == NULL) {
			fprintf(stderr, "Error resizing query_status\n");
			return (-1);
		} else {
			status = temp_stat;

			/*
			 * Be careful to only initialise between above
			 * the previously allocated space. Note that the
			 * allocation may be larger than the current
			 * max_queries_outstanding. We don't want to
			 * "forget" any outstanding queries! We might
			 * still have some above the bounds of the max.
			 */
			count = query_status_allocated;
			for (; count < new_max; count++) {
				status[count].in_use = FALSE;
				status[count].magic = QUERY_STATUS_MAGIC;
				status[count].desc = NULL;
			}

			query_status_allocated = new_max;
		}
	}

	max_queries_outstanding = new_max;

	return (0);
}

/*
 * parse_args:
 *   Parse program arguments and set configuration options
 *
 *   Return -1 on failure
 *   Return a non-negative integer otherwise
 */
int
parse_args(int argc, char **argv) {
	int c;
	unsigned int uint_arg_val;

	while ((c = getopt(argc, argv,
			   "f:q:t:i:nd:s:p:1l:b:eDcvr:RT::u:H:h")) != -1) {
		switch (c) {
		case 'f':
			if (strcmp(optarg, "inet") == 0)
				family = AF_INET;
#ifdef AF_INET6
			else if (strcmp(optarg, "inet6") == 0)
				family = AF_INET6;
#endif
			else if (strcmp(optarg, "any") == 0)
				family = AF_UNSPEC;
			else {
				fprintf(stderr, "Invalid address family: %s\n",
					optarg);
				return (-1);
			}
			break;
		case 'q':
			if (is_uint(optarg, &uint_arg_val) == TRUE) {
				set_max_queries(uint_arg_val);
				queriesset = TRUE;
			} else {
				fprintf(stderr, "Option requires a positive "
				        "integer value: -%c %s\n",
					c, optarg);
				return (-1);
			}
			break;

		case 't':
			if (is_uint(optarg, &uint_arg_val) == TRUE) {
				query_timeout = uint_arg_val;
				timeoutset = TRUE;
			} else {
				fprintf(stderr, "Option requires a positive "
				        "integer value: -%c %s\n",
				        c, optarg);
				return (-1);
			}
			break;

		case 'n':
			ignore_config_changes = TRUE;
			break;

		case 'd':
			if (set_datafile(optarg) == -1) {
				fprintf(stderr, "Error setting datafile "
					"name: %s\n", optarg);
				return (-1);
			}
			break;

		case 's':
			if (set_server(optarg) == -1) {
				fprintf(stderr, "Error setting server "
					"name: %s\n", optarg);
				return (-1);
			}
			serverset = TRUE;
			break;

		case 'p':
			if (is_uint(optarg, &uint_arg_val) == TRUE &&
			    uint_arg_val < MAX_PORT)
			{
				set_server_port(optarg);
				portset = TRUE;
			} else {
				fprintf(stderr, "Option requires a positive "
				        "integer between 0 and %d: -%c %s\n",
					MAX_PORT - 1, c, optarg);
				return (-1);
			}
			break;

		case '1':
			run_only_once = TRUE;
			break;

		case 'l':
			if (is_uint(optarg, &uint_arg_val) == TRUE) {
				use_timelimit = TRUE;
				run_timelimit = uint_arg_val;
			} else {
				fprintf(stderr, "Option requires a positive "
				        "integer: -%c %s\n",
					c, optarg);
				return (-1);
			}
			break;

		case 'b':
			if (is_uint(optarg, &uint_arg_val) == TRUE) {
				socket_bufsize = uint_arg_val;
			} else {
				fprintf(stderr, "Option requires a positive "
					"integer: -%c %s\n",
					c, optarg);
				return (-1);
			}
			break;
		case 'e':
			edns = TRUE;
			break;
		case 'D':
			dnssec = TRUE;
			edns = TRUE;
			break;
		case 'c':
			countrcodes = TRUE;
			break;
		case 'v':
			verbose = 1;
			break;
		case 'i':
			if (is_uint(optarg, &uint_arg_val) == TRUE)
				print_interval = uint_arg_val;
			else {
				fprintf(stderr, "Invalid interval: %s\n",
					optarg);
				return (-1);
			}
			break;
		case 'R':
			recurse = 0;
			break;
		case 'r':
			if (is_uint(optarg, &uint_arg_val) == TRUE)
				rttarray_size = uint_arg_val;
			else {
				fprintf(stderr, "Invalid RTT array size: %s\n",
					optarg);
				return (-1);
			}
			break;
		case 'u':
			if (is_uint(optarg, &uint_arg_val) == TRUE)
				rttarray_unit = uint_arg_val;
			else {
				fprintf(stderr, "Invalid RTT unit: %s\n",
					optarg);
				return (-1);
			}
			break;
		case 'H':
			rtt_histogram_file = optarg;
			break;
		case 'T':
			if (is_uint(optarg, &uint_arg_val) == TRUE)
				target_qps = uint_arg_val;
			else {
				fprintf(stderr, "Invalid target qps: %s\n",
					optarg);
				return (-1);
			}
			break;
		case 'h':
			return (-1);
		default:
			fprintf(stderr, "Invalid option: -%c\n", optopt);
			return (-1);
		}
	}

	if (run_only_once == FALSE && use_timelimit == FALSE)
		run_only_once = TRUE;

	return (0);
}

/*
 * open_datafile:
 *   Open the data file ready for reading
 *
 *   Return -1 on failure
 *   Return non-negative integer on success
 */
int
open_datafile(void) {
	if (use_stdin == TRUE) {
		datafile_ptr = stdin;
		return (0);
	} else {
		if ((datafile_ptr = fopen(datafile_name, "r")) == NULL) {
			fprintf(stderr, "Error: unable to open datafile: %s\n",
			        datafile_name);
			return (-1);
		} else
			return (0);
	}
}

/*
 * close_datafile:
 *   Close the data file if any is open
 *
 *   Return -1 on failure
 *   Return non-negative integer on success, including if not needed
 */
int
close_datafile(void) {
	if ((use_stdin == FALSE) && (datafile_ptr != NULL)) {
		if (fclose(datafile_ptr) != 0) {
			fprintf(stderr, "Error: unable to close datafile\n");
			return (-1);
		}
	}

	return (0);
}

/*
 * open_socket:
 *   Open a socket for the queries.  When we have an active socket already,
 *   close it and open a new one.
 *
 *   Return -1 on failure
 *   Return the socket identifier
 */
int
open_socket(void) {
	int sock;
	int ret;
	int bufsize;
	struct addrinfo hints, *res;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = server_ai->ai_family;
	hints.ai_socktype = server_ai->ai_socktype;
	hints.ai_protocol = server_ai->ai_protocol;
	hints.ai_flags = AI_PASSIVE;

	if ((ret = getaddrinfo(NULL, "0", &hints, &res)) != 0) {
		fprintf(stderr,
			"Error: getaddrinfo for bind socket failed: %s\n",
			gai_strerror(ret));
		return (-1);
	}

	if ((sock = socket(res->ai_family, SOCK_DGRAM,
			   res->ai_protocol)) == -1) {
		fprintf(stderr, "Error: socket call failed");
		goto fail;
	}

#if defined(AF_INET6) && defined(IPV6_V6ONLY)
	if (res->ai_family == AF_INET6) {
		int on = 1;

		if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
			       &on, sizeof(on)) == -1) {
			fprintf(stderr,
				"Warning: setsockopt(IPV6_V6ONLY) failed\n");
		}
	}
#endif

	if (bind(sock, res->ai_addr, res->ai_addrlen) == -1)
		fprintf(stderr, "Error: bind call failed");

	freeaddrinfo(res);

	bufsize = 1024 * socket_bufsize;

	ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
			 (char *) &bufsize, sizeof(bufsize));
	if (ret < 0)
		fprintf(stderr, "Warning:  setsockbuf(SO_RCVBUF) failed\n");

	ret = setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
			 (char *) &bufsize, sizeof(bufsize));
	if (ret < 0)
		fprintf(stderr, "Warning:  setsockbuf(SO_SNDBUF) failed\n");

	return (sock);

 fail:
	if (res)
		freeaddrinfo(res);
	return (-1);
}

/*
 * close_socket:
 *   Close the query socket(s)
 *
 *   Return -1 on failure
 *   Return a non-negative integer otherwise
 */
int
close_socket(void) {
	if (socket4 != -1) {
		if (close(socket4) != 0) {
			fprintf(stderr,
				"Error: unable to close IPv4 socket\n");
			return (-1);
		}
	}

	if (socket6 != -1) {
		if (close(socket6) != 0) {
			fprintf(stderr,
				"Error: unable to close IPv6 socket\n");
			return (-1);
		}
	}

	query_socket = -1;

	return (0);
}

/*
 * change_socket:
 *   Choose an appropriate socket according to the address family of the
 *   current server.  Open a new socket if necessary.
 *
 *   Return -1 on failure
 *   Return the socket identifier
 */
int
change_socket(void) {
	int s, *sockp;

	switch (server_ai->ai_family) {
	case AF_INET:
		sockp = &socket4;
		break;
#ifdef AF_INET6
	case AF_INET6:
		sockp = &socket6;
		break;
#endif
	default:
		fprintf(stderr, "unexpected address family: %d\n",
			server_ai->ai_family);
		exit(1);
	}

	if (*sockp == -1) {
		if ((s = open_socket()) == -1)
			return (-1);
		*sockp = s;
	}

	return (*sockp);
}

/*
 * reset_rttarray:
 *   (re)allocate RTT array and zero-clear the whole buffer.
 *   if array is being used, it is freed.
 *   Returns -1 on failure
 *   Returns a non-negative integer otherwise
 */
int
reset_rttarray(int size) {
	if (rttarray != NULL)
		free(rttarray);
	if (rttarray_interval != NULL)
		free(rttarray_interval);

	rttarray = NULL;
	rttarray_interval = NULL;
	rtt_max = -1;
	rtt_min = -1;

	if (size > 0) {
		rttarray = malloc(size * sizeof(rttarray[0]));
		if (rttarray == NULL) {
			fprintf(stderr,
				"Error: allocating memory for RTT array\n");
			return (-1);
		}
		memset(rttarray, 0, size * sizeof(rttarray[0]));

		rttarray_interval = malloc(size *
					   sizeof(rttarray_interval[0]));
		if (rttarray_interval == NULL) {
			fprintf(stderr,
				"Error: allocating memory for RTT array\n");
			return (-1);
		}

		memset(rttarray_interval, 0,
		       size * sizeof(rttarray_interval[0]));
	}

	return (0);
}

/*
 * set_query_interval:
 *   set the interval of consecutive queries if the target qps are specified.
 *   Returns -1 on failure
 *   Returns a non-negative integer otherwise
 */
int
set_query_interval(unsigned int qps) {
	if (qps == 0)
		return (0);

	query_interval = (1.0 / (double)qps);

	return (0);
}

/*
 * setup:
 *   Set configuration options from command line arguments
 *   Open datafile ready for reading
 *
 *   Return -1 on failure
 *   Return non-negative integer on success
 */
int
setup(int argc, char **argv) {
	set_input_stdin();

	if (set_max_queries(DEF_MAX_QUERIES_OUTSTANDING) == -1) {
		fprintf(stderr, "%s: Unable to set default max outstanding "
		        "queries\n", argv[0]);
		return (-1);
	}

	if (set_server(DEF_SERVER_TO_QUERY) == -1) {
		fprintf(stderr, "%s: Error setting default server name\n",
		        argv[0]);
		return (-1);
	}

	if (set_server_port(DEF_SERVER_PORT) == -1) {
		fprintf(stderr, "%s: Error setting default server port\n",
		        argv[0]);
		return (-1);
	}

	if (parse_args(argc, argv) == -1) {
		show_usage();
		return (-1);
	}

	if (open_datafile() == -1)
		return (-1);

	if (set_server_sa() == -1)
		return (-1);

	if ((query_socket = change_socket()) == -1)
		return (-1);

	if (reset_rttarray(rttarray_size) == -1)
		return (-1);

	if (set_query_interval(target_qps) == -1)
		return (-1);

	return (0);
}

/*
 * set_timenow:
 *   Set a timeval struct to indicate the current time
 */
void
set_timenow(struct timeval *tv) {
	if (gettimeofday(tv, NULL) == -1) {
		fprintf(stderr, "Error in gettimeofday(). Using inaccurate "
		        "time() instead\n");
		tv->tv_sec = time(NULL);
		tv->tv_usec = 0;
	}
}

/*
 * addtv:
 *   add tv1 and tv2, store the result in tv_result.
 */
void
addtv(struct timeval *tv1, struct timeval *tv2, struct timeval *tv_result) {
	tv_result->tv_sec = tv1->tv_sec + tv2->tv_sec;
	tv_result->tv_usec = tv1->tv_usec + tv2->tv_usec;
	if (tv_result->tv_usec > 1000000) {
		tv_result->tv_sec++;
		tv_result->tv_usec -= 1000000;
	}
}

/*
 * difftv:
 *   Find the difference in seconds between two timeval structs.
 *
 *   Return the difference between tv1 and tv2 in seconds in a double.
 */
double
difftv(struct timeval tv1, struct timeval tv2) {
	long diff_sec, diff_usec;
	double diff;

	diff_sec = tv1.tv_sec - tv2.tv_sec;
	diff_usec = tv1.tv_usec - tv2.tv_usec;

	diff = (double)diff_sec + ((double)diff_usec / 1000000.0);

	return (diff);
}

/*
 * timelimit_reached:
 *   Have we reached the time limit (if any)?
 *
 *   Returns FALSE if there is no time limit or if we have not reached it
 *   Returns TRUE otherwise
 */
int
timelimit_reached(void) {
	struct timeval time_now;

	set_timenow(&time_now);

	if (use_timelimit == FALSE)
		return (FALSE);

	if (setup_phase == TRUE) {
		if (difftv(time_now, time_of_program_start)
		    < (double)(run_timelimit + HARD_TIMEOUT_EXTRA))
			return (FALSE);
		else
			return (TRUE);
	} else {
		if (difftv(time_now, time_of_first_query)
		    < (double)run_timelimit)
			return (FALSE);
		else
			return (TRUE);
	}
}

/*
 * keep_sending:
 *   Should we keep sending queries or stop here?
 *
 *   Return TRUE if we should keep on sending queries
 *   Return FALSE if we should stop
 *
 *   Side effects:
 *   Rewinds the input and clears reached_end_input if we have reached the
 *   end of the input, but we are meant to run through it multiple times
 *   and have not hit the time limit yet (if any is set).
 */
int
keep_sending(int *reached_end_input) {
	static int stop = FALSE;

	if (stop == TRUE)
		return (FALSE);

	if ((*reached_end_input == FALSE) && (timelimit_reached() == FALSE))
		return (TRUE);
	else if ((*reached_end_input == TRUE) && (run_only_once == FALSE)
	         && (timelimit_reached() == FALSE)) {
		rewind(datafile_ptr);
		*reached_end_input = FALSE;
		runs_through_file++;
		return (TRUE);
	} else {
		if (*reached_end_input == TRUE)
			runs_through_file++;
		set_timenow(&time_of_stop_sending);
		stop = TRUE;
		return (FALSE);
	}
}

/*
 * queries_outstanding:
 *   How many queries are outstanding?
 *
 *   Returns the number of outstanding queries
 */
unsigned int
queries_outstanding(void) {
	return (num_queries_outstanding);
}

/*
 * next_input_line:
 *   Get the next non-comment line from the input file
 *
 *   Put text in line, up to max of n chars. Skip comment lines.
 *   Skip empty lines.
 *
 *   Return line length on success
 *   Return 0 if cannot read a non-comment line (EOF or error)
 */
int
next_input_line(char *line, int n) {
	char *result;

	do {
		result = fgets(line, n, datafile_ptr);
	} while ((result != NULL) &&
	         ((line[0] == COMMENT_CHAR) || (line[0] == '\n')));

	if (result == NULL)
		return (0);
	else
		return (strlen(line));
}

/*
 * identify_directive:
 *   Gives us a numerical value equivelant for a directive string
 *
 *   Returns the value for the directive
 *   Returns -1 if not a valid directive
 */
int
identify_directive(char *dir) {
	static char *directives[] = DIRECTIVES;
	static int dir_values[] = DIR_VALUES;
	unsigned int index, num_directives;

	num_directives = sizeof(directives) / sizeof(directives[0]);

	if (num_directives > (sizeof(dir_values) / sizeof(int)))
		num_directives = sizeof(dir_values) / sizeof(int);

	for (index = 0; index < num_directives; index++) {
		if (strcmp(dir, directives[index]) == 0)
			return (dir_values[index]);
	}

	return (-1);
}

/*
 * update_config:
 *   Update configuration options from a line from the input file
 */
void
update_config(char *config_change_desc) {
	char *directive, *config_value, *trailing_garbage;
	char conf_copy[MAX_INPUT_LEN + 1];
	unsigned int uint_val;
	int directive_number;
	int check;
	int old_af;

	if (ignore_config_changes == TRUE) {
		fprintf(stderr, "Ignoring configuration change: %s",
		        config_change_desc);
		return;
	}

	strcpy(conf_copy, config_change_desc);

	++config_change_desc;

	if (*config_change_desc == '\0') {
		fprintf(stderr, "Invalid config: No directive present: %s\n",
		        conf_copy);
		return;
	}

	if (index(WHITESPACE, *config_change_desc) != NULL) {
		fprintf(stderr, "Invalid config: Space before directive or "
		        "no directive present: %s\n", conf_copy);
		return;
	}

	directive = strtok(config_change_desc, WHITESPACE);
	config_value = strtok(NULL, WHITESPACE);
	trailing_garbage = strtok(NULL, WHITESPACE);

	if ((directive_number = identify_directive(directive)) == -1) {
		fprintf(stderr, "Invalid config: Bad directive: %s\n",
		        conf_copy);
		return;
	}

	if (config_value == NULL) {
		fprintf(stderr, "Invalid config: No value present: %s\n",
		        conf_copy);
		return;
	}

	if (trailing_garbage != NULL) {
		fprintf(stderr, "Config warning: "
		        "trailing garbage: %s\n", conf_copy);
	}

	switch(directive_number) {

	case V_SERVER:
		if (serverset && (setup_phase == TRUE)) {
			fprintf(stderr, "Config change overridden by command "
			        "line: %s\n", directive);
			return;
		}

		if (set_server(config_value) == -1) {
			fprintf(stderr, "Set server error: unable to change "
			        "the server name to '%s'\n", config_value);
			return;
		}

		old_af = server_ai->ai_family;
		if (set_server_sa() == -1) {
			fprintf(stderr, "Set server error: unable to resolve "
				"a new server '%s'\n",
				config_value);
			return;
		}
		if (old_af != server_ai->ai_family) {
			if ((query_socket = change_socket()) == -1) {
				/* XXX: this is fatal */
				fprintf(stderr, "Set server error: "
					"unable to open a new socket "
					"for '%s'\n", config_value);
				exit(1);
			}
		}

		break;

	case V_PORT:
		if (portset && (setup_phase == TRUE)) {
			fprintf(stderr, "Config change overridden by command "
			        "line: %s\n", directive);
			return;
		}

		check = is_uint(config_value, &uint_val);

		if ((check == TRUE) && (uint_val > 0)) {
			if (set_server_port(config_value) == -1) {
				fprintf(stderr, "Invalid config: Bad value for"
				        " %s: %s\n", directive, config_value);
			} else {
				if (set_server_sa() == -1) {
					fprintf(stderr,
						"Failed to set a new port\n");
					return;
				}
			}
		} else
			fprintf(stderr, "Invalid config: Bad value for "
			        "%s: %s\n", directive, config_value);
		break;

	case V_MAXQUERIES:
		if (queriesset && (setup_phase == TRUE)) {
			fprintf(stderr, "Config change overridden by command "
			        "line: %s\n", directive);
			return;
		}

		check = is_uint(config_value, &uint_val);

		if ((check == TRUE) && (uint_val > 0)) {
			set_max_queries(uint_val);
		} else
			fprintf(stderr, "Invalid config: Bad value for "
			        "%s: %s\n", directive, config_value);
		break;

	case V_MAXWAIT:
		if (timeoutset && (setup_phase == TRUE)) {
			fprintf(stderr, "Config change overridden by command "
			        "line: %s\n", directive);
			return;
		}

		check = is_uint(config_value, &uint_val);

		if ((check == TRUE) && (uint_val > 0)) {
			query_timeout = uint_val;
		} else
			fprintf(stderr, "Invalid config: Bad value for "
			        "%s: %s\n", directive, config_value);
		break;

	default:
		fprintf(stderr, "Invalid config: Bad directive: %s\n",
		        directive);
		break;
	}
}

/*
 * parse_query:
 *   Parse a query line from the input file
 *
 *   Set qname to be the domain to query (up to a max of qnlen chars)
 *   Set qtype to be the type of the query
 *
 *   Return -1 on failure
 *   Return a non-negative integer otherwise
 */
int
parse_query(char *input, char *qname, int qnlen, int *qtype) {
	static char *qtype_strings[] = QTYPE_STRINGS;
	static int qtype_codes[] = QTYPE_CODES;
	int num_types, index;
	int found = FALSE;
	char incopy[MAX_INPUT_LEN + 1];
	char *domain_str, *type_str;

	num_types = sizeof(qtype_strings) / sizeof(qtype_strings[0]);
	if (num_types > (sizeof(qtype_codes) / sizeof(int)))
		num_types = sizeof(qtype_codes) / sizeof(int);

	strcpy(incopy, input);

	domain_str = strtok(incopy, WHITESPACE);
	type_str = strtok(NULL, WHITESPACE);

	if ((domain_str == NULL) || (type_str == NULL)) {
		fprintf(stderr, "Invalid query input format: %s\n", input);
		return (-1);
	}

	if (strlen(domain_str) > qnlen) {
		fprintf(stderr, "Query domain too long: %s\n", domain_str);
		return (-1);
	}

	for (index = 0; (index < num_types) && (found == FALSE); index++) {
		if (strcasecmp(type_str, qtype_strings[index]) == 0) {
			*qtype = qtype_codes[index];
			found = TRUE;
		}
	}

	if (found == FALSE) {
		fprintf(stderr, "Query type not understood: %s\n", type_str);
		return (-1);
	}

	strcpy(qname, domain_str);

	return (0);
}

/*
 * dispatch_query:
 *   Send the query packet for the entry 
 *
 *   Return -1 on failure
 *   Return a non-negative integer otherwise
 */
int
dispatch_query(unsigned short int id, char *dom, int qt) {
	static u_char packet_buffer[PACKETSZ + 1];
	int buffer_len = PACKETSZ;
	int bytes_sent;
	unsigned short int net_id = htons(id);
	char *id_ptr = (char *)&net_id;
	HEADER *hp = (HEADER *)packet_buffer;

	buffer_len = res_mkquery(QUERY, dom, C_IN, qt, NULL, 0,
				 NULL, packet_buffer, PACKETSZ);
	if (buffer_len == -1) {
		fprintf(stderr, "Failed to create query packet: %s %d\n",
		        dom, qt);
		return (-1);
	}
	hp->rd = recurse;
	if (edns) {
		unsigned char *p;
		if (buffer_len + EDNSLEN >= PACKETSZ) {
			fprintf(stderr, "Failed to add OPT to query packet\n");
			return (-1);
		}
		packet_buffer[11] = 1;
		p = &packet_buffer[buffer_len];
		*p++ = 0;	/* root name */
		*p++ = 0;
		*p++ = 41;	/* OPT */
		*p++ = 16;	
		*p++ = 0;	/* UDP payload size (4K) */
		*p++ = 0;	/* extended rcode */
		*p++ = 0;	/* version */
		if (dnssec)
			*p++ = 0x80;	/* upper flag bits - DO set */
		else
			*p++ = 0;	/* upper flag bits */
		*p++ = 0;	/* lower flag bit */
		*p++ = 0;
		*p++ = 0;	/* rdlen == 0 */
		buffer_len += EDNSLEN;
	}

	packet_buffer[0] = id_ptr[0];
	packet_buffer[1] = id_ptr[1];

	bytes_sent = sendto(query_socket, packet_buffer, buffer_len, 0,
			    server_ai->ai_addr, server_ai->ai_addrlen);
	if (bytes_sent == -1) {
		fprintf(stderr, "Failed to send query packet: %s %d\n",
		        dom, qt);
		return (-1);
	}

	if (bytes_sent != buffer_len)
		fprintf(stderr, "Warning: incomplete packet sent: %s %d\n",
		        dom, qt);

	return (0);
}

/*
 * send_query:
 *   Send a query based on a line of input
 */
void
send_query(char *query_desc) {
	static unsigned short int use_query_id = 0;
	static int qname_len = MAX_DOMAIN_LEN;
	static char domain[MAX_DOMAIN_LEN + 1];
	char serveraddr[NI_MAXHOST];
	int query_type;
	unsigned int count;

	use_query_id++;

	if (parse_query(query_desc, domain, qname_len, &query_type) == -1) {
		fprintf(stderr, "Error parsing query: %s\n", query_desc);
		return;
	}

	if (dispatch_query(use_query_id, domain, query_type) == -1) {
		char *addrstr;

		if (getnameinfo(server_ai->ai_addr, server_ai->ai_addrlen,
				serveraddr, sizeof(serveraddr), NULL, 0,
				NI_NUMERICHOST) == 0) {
			addrstr = serveraddr;
		} else
			addrstr = "???"; /* XXX: this should not happen */
		fprintf(stderr, "Error sending query to %s: %s\n",
			addrstr, query_desc);
		return;
	}

	if (setup_phase == TRUE) {
		set_timenow(&time_of_first_query);
		time_of_first_query_sec = (double)time_of_first_query.tv_sec +
			((double)time_of_first_query.tv_usec / 1000000.0);
		setup_phase = FALSE;
		if (getnameinfo(server_ai->ai_addr, server_ai->ai_addrlen,
				serveraddr, sizeof(serveraddr), NULL, 0,
				NI_NUMERICHOST) != 0) {
			fprintf(stderr, "Error printing server address\n");
			return;
		}
		printf("[Status] Sending queries (beginning with %s)\n",
		       serveraddr);
	}

	/* Find the first slot in status[] that is not in use */
	for (count = 0; (status[count].in_use == TRUE)
	     && (count < max_queries_outstanding); count++);

	if (status[count].in_use == TRUE) {
		fprintf(stderr, "Unexpected error: We have run out of "
			"status[] space!\n");
		return;
	}

	/* Register the query in status[] */
	status[count].in_use = TRUE;
	status[count].id = use_query_id;
	if (verbose)
		status[count].desc = strdup(query_desc);
	set_timenow(&status[count].sent_timestamp);

	if (num_queries_sent_interval == 0)
		set_timenow(&time_of_first_query_interval);

	num_queries_sent++;
	num_queries_sent_interval++;
	num_queries_outstanding++;
}

void
register_rtt(struct timeval *timestamp) {
	int i;
	int oldquery = FALSE;
	struct timeval now;
	double rtt;

	set_timenow(&now);
	rtt = difftv(now, *timestamp);

	if (difftv(*timestamp, time_of_first_query_interval) < 0)
		oldquery = TRUE;

	if (rtt_max < 0 || rtt_max < rtt)
		rtt_max = rtt;

	if (rtt_min < 0 || rtt_min > rtt)
		rtt_min = rtt;

	rtt_total += rtt;

	if (!oldquery) {
		if (rtt_max_interval < 0 || rtt_max_interval < rtt)
			rtt_max_interval = rtt;

		if (rtt_min_interval < 0 || rtt_min_interval > rtt)
			rtt_min_interval = rtt;

		rtt_total_interval += rtt;
	}

	if (rttarray == NULL)
		return;

	i = (int)(rtt * (1000000.0 / rttarray_unit));
	if (i < rttarray_size) {
		rttarray[i]++;
		if (!oldquery)
			rttarray_interval[i]++;
	} else {
		fprintf(stderr, "Warning: RTT is out of range: %.6lf\n",
			rtt);
		rtt_overflows++;
		if (!oldquery)
			rtt_overflows_interval++;
	}
}

/*
 * register_response:
 *   Register receipt of a query
 *
 *   Removes (sets in_use = FALSE) the record for the given query id in
 *   status[] if any exists.
 */
void
register_response(unsigned short int id, unsigned int rcode) {
	unsigned int ct = 0;
	int found = FALSE;
	struct timeval now;
	double rtt;

	for (; (ct < query_status_allocated) && (found == FALSE); ct++) {
		if ((status[ct].in_use == TRUE) && (status[ct].id == id)) {
			status[ct].in_use = FALSE;
			num_queries_outstanding--;
			found = TRUE;

			register_rtt(&status[ct].sent_timestamp);

			if (status[ct].desc) {
				printf("> %s %s\n", rcode_strings[rcode],
				       status[ct].desc);
				free(status[ct].desc);
			}
			if (countrcodes)
				rcodecounts[rcode]++;
		}
	}

	if (found == FALSE) {
		if (target_qps > 0) {
			num_queries_possiblydelayed++;
			num_queries_possiblydelayed_interval++;
		} else {
			fprintf(stderr, "Warning: Received a response with an "
				"unexpected (maybe timed out) id: %u\n", id);
		}
	}
}

/*
 * process_single_response:
 *   Receive from the given socket & process an invididual response packet.
 *   Remove it from the list of open queries (status[]) and decrement the
 *   number of outstanding queries if it matches an open query.
 */
void
process_single_response(int sockfd) {
	struct sockaddr_storage from_addr_ss;
	struct sockaddr *from_addr;
	static unsigned char in_buf[MAX_BUFFER_LEN];
	int numbytes, resp_id;
	socklen_t addr_len;
	int flags;

	memset(&from_addr_ss, 0, sizeof(from_addr_ss));
	from_addr = (struct sockaddr *)&from_addr_ss;
	addr_len = sizeof(from_addr_ss);

	if ((numbytes = recvfrom(sockfd, in_buf, MAX_BUFFER_LEN,
	     0, from_addr, &addr_len)) == -1) {
		fprintf(stderr, "Error receiving datagram\n");
		return;
	}

	resp_id = get_uint16(in_buf);
	flags = get_uint16(in_buf + 2);

	register_response(resp_id, flags & 0xF);
}

/*
 * data_available:
 *   Is there data available on the given file descriptor?
 *
 *   Return TRUE if there is
 *   Return FALSE otherwise
 */
int
data_available(double wait) {
	fd_set read_fds;
	struct timeval tv;
	int retval;
	int available = FALSE;
	int maxfd = -1;

	/* Set list of file descriptors */
	FD_ZERO(&read_fds);
	if (socket4 != -1) {
		FD_SET(socket4, &read_fds);
		maxfd = socket4;
	}
	if (socket6 != -1) {
		FD_SET(socket6, &read_fds);
		if (maxfd == -1 || maxfd < socket6)
			maxfd = socket6;
	}

	if ((wait > 0.0) && (wait < (double)LONG_MAX)) {
		tv.tv_sec = (long)floor(wait);
		tv.tv_usec = (long)(1000000.0 * (wait - floor(wait)));
	} else {
		tv.tv_sec = 0;
		tv.tv_usec = 0;
	}

	retval = select(maxfd + 1, &read_fds, NULL, NULL, &tv);

	if (socket4 != -1 && FD_ISSET(socket4, &read_fds)) {
		available = TRUE;
		process_single_response(socket4);
	}
	if (socket6 != -1 && FD_ISSET(socket6, &read_fds)) {
		available = TRUE;
		process_single_response(socket6);
	}

	return (available);
}

/*
 * process_responses:
 *   Go through any/all received responses and remove them from the list of
 *   open queries (set in_use = FALSE for their entry in status[]), also
 *   decrementing the number of outstanding queries.
 */
void
process_responses(int adjust_rate) {
	double wait;
	struct timeval now, waituntil;
	double first_packet_wait = RESPONSE_BLOCKING_WAIT_TIME;
	unsigned int outstanding = queries_outstanding();

	if (adjust_rate == TRUE) {
		double u;

		u = time_of_first_query_sec +
			query_interval * num_queries_sent;
		waituntil.tv_sec = (long)floor(u);
		waituntil.tv_usec = (long)(1000000.0 * (u - waituntil.tv_sec));

		/*
		 * Wait until a response arrives or the specified limit is
		 * reached.
		 */
		while (1) {
			set_timenow(&now);
			wait = difftv(waituntil, now);
			if (wait <= 0)
				wait = 0.0;
			if (data_available(wait) != TRUE)
				break;

			/*
			 * We have reached the limit.  Read as many responses
			 * as possible without waiting, and exit.
			 */
			if (wait == 0) {
				while (data_available(0.0) == TRUE)
					;
				break;
			}
		}
	} else {
		/*
		 * Don't block waiting for packets at all if we aren't
		 * looking for any responses or if we are now able to send new
		 * queries.
		 */
		if ((outstanding == 0) ||
		    (outstanding < max_queries_outstanding)) {
			first_packet_wait = 0.0;
		}

		if (data_available(first_packet_wait) == TRUE) {
			while (data_available(0.0) == TRUE)
				;
		}
	}
}

/*
 * retire_old_queries:
 *   Go through the list of open queries (status[]) and remove any queries
 *   (i.e. set in_use = FALSE) which are older than the timeout, decrementing
 *   the number of queries outstanding for each one removed.
 */
void
retire_old_queries(int sending) {
	unsigned int count = 0;
	struct timeval curr_time;
	double timeout = query_timeout;
	int timeout_reduced = FALSE;

	/*
	 * If we have target qps and would not be able to send any packets
	 * due to buffer full, check whether we are behind the schedule.
	 * If we are, purge some queries more aggressively.
	 */
	if (target_qps > 0 && sending == TRUE && count == 0 &&
	    queries_outstanding() == max_queries_outstanding) {
		struct timeval next, now;
		double n;

		n = time_of_first_query_sec +
			query_interval * num_queries_sent;
		next.tv_sec = (long)floor(n);
		next.tv_usec = (long)(1000000.0 * (n - next.tv_sec));

		set_timenow(&now);
		if (difftv(next, now) <= 0) {
			timeout_reduced = TRUE;
			timeout = 0.001; /* XXX: ad-hoc value */
		}
	}

	set_timenow(&curr_time);

	for (; count < query_status_allocated; count++) {

		if ((status[count].in_use == TRUE)
		    && (difftv(curr_time, status[count].sent_timestamp)
		    >= (double)timeout)) {

			status[count].in_use = FALSE;
			num_queries_outstanding--;
			num_queries_timed_out++;
			num_queries_timed_out_interval++;

			if (timeout_reduced == FALSE) {
				if (status[count].desc) {
					printf("> T %s\n", status[count].desc);
					free(status[count].desc);
				} else {
					printf("[Timeout] Query timed out: "
					       "msg id %u\n",
					       status[count].id);
				}
			}
		}
	}
}

/*
 * print_histogram
 *   Print RTT histogram to the specified file in the gnuplot format
 */
void
print_histogram(unsigned int total) {
	int i;
	double ratio;
	FILE *fp;

	if (rtt_histogram_file == NULL || rttarray == NULL)
		return;

	fp = fopen((const char *)rtt_histogram_file, "w+");
	if (fp == NULL) {
		fprintf(stderr, "Error opening RTT histogram file: %s\n",
			rtt_histogram_file);
		return;
	}

	for (i = 0; i < rttarray_size; i++) {
		ratio = ((double)rttarray[i] / (double)total) * 100;
		fprintf(fp, "%.6lf %.3lf\n",
			(double)(i * rttarray_unit) +
			(double)rttarray_unit / 2,
			ratio);
	}

	(void)fclose(fp);
}

/*
 * print_statistics:
 *   Print out statistics based on the results of the test
 */
void
print_statistics(int intermediate, unsigned int sent, unsigned int timed_out,
		 unsigned int possibly_delayed,
		 struct timeval *first_query,
		 struct timeval *program_start,
		 struct timeval *end_perf, struct timeval *end_query,
		 double rmax, double rmin, double rtotal,
		 unsigned int roverflows, unsigned int *rarray)
{
	unsigned int num_queries_completed;
	double per_lost, per_completed, per_lost2, per_completed2; 
	double run_time, queries_per_sec, queries_per_sec2;
	double queries_per_sec_total;
	double rtt_average, rtt_stddev;
	struct timeval start_time;

	num_queries_completed = sent - timed_out;

	if (num_queries_completed == 0) {
		per_lost = 0.0;
		per_completed = 0.0;

		per_lost2 = 0.0;
		per_completed2 = 0.0;
	} else {
		per_lost = (100.0 * (double)timed_out) / (double)sent;
		per_completed = 100.0 - per_lost;

		per_lost2 = (100.0 * (double)(timed_out - possibly_delayed))
			/ (double)sent;
		per_completed2 = 100 - per_lost2;
	}

	if (sent == 0) {
		start_time.tv_sec = program_start->tv_sec;
		start_time.tv_usec = program_start->tv_usec;
		run_time = 0.0;
		queries_per_sec = 0.0;
		queries_per_sec2 = 0.0;
		queries_per_sec_total = 0.0;
	} else {
		start_time.tv_sec = first_query->tv_sec;
		start_time.tv_usec = first_query->tv_usec;
		run_time = difftv(*end_perf, *first_query);
		queries_per_sec = (double)num_queries_completed / run_time;
		queries_per_sec2 = (double)(num_queries_completed +
					    possibly_delayed) / run_time;

		queries_per_sec_total = (double)sent /
			difftv(*end_query, *first_query);
	}

	if (num_queries_completed > 0) {
		int i;
		double sum = 0;

		rtt_average = rtt_total / (double)num_queries_completed;
		for (i = 0; i < rttarray_size; i++) {
			if (rarray[i] != 0) {
				double mean, diff;

				mean = (double)(i * rttarray_unit) +
				(double)rttarray_unit / 2;
				diff = rtt_average - (mean / 1000000.0);
				sum += (diff * diff) * rarray[i];
			}
		}
		rtt_stddev = sqrt(sum / (double)num_queries_completed);
	} else {
		rtt_average = 0.0;
		rtt_stddev = 0.0;
	}

	printf("\n");

	printf("%sStatistics:\n", intermediate ? "Intermediate " : "");

	printf("\n");

	if (!intermediate) {
		printf("  Parse input file:     %s\n",
		       ((run_only_once == TRUE) ? "once" : "multiple times"));
		if (use_timelimit)
			printf("  Run time limit:       %u seconds\n",
			       run_timelimit);
		if (run_only_once == FALSE)
			printf("  Ran through file:     %u times\n",
			       runs_through_file);
		else
			printf("  Ended due to:         reaching %s\n",
			       ((runs_through_file == 0) ? "time limit"
				: "end of file"));

		printf("\n");
	}

	printf("  Queries sent:         %u queries\n", sent);
	printf("  Queries completed:    %u queries\n", num_queries_completed);
	printf("  Queries lost:         %u queries\n", timed_out);
	printf("  Queries delayed(?):   %u queries\n", possibly_delayed);

	printf("\n");

	printf("  RTT max:         	%3.6lf sec\n", rmax);
	printf("  RTT min:              %3.6lf sec\n", rmin);
	printf("  RTT average:          %3.6lf sec\n", rtt_average);
	printf("  RTT std deviation:    %3.6lf sec\n", rtt_stddev);
	printf("  RTT out of range:     %u queries\n", roverflows);

	if (!intermediate)	/* XXX should we print this case also? */
		print_histogram(num_queries_completed);

	printf("\n");

	if (countrcodes) {
		unsigned int i;

		for (i = 0; i < 16; i++) {
			if (rcodecounts[i] == 0)
				continue;
			printf("  Returned %8s:    %u queries\n",
			       rcode_strings[i], rcodecounts[i]);
		}
		printf("\n");
	}

	printf("  Percentage completed: %6.2lf%%\n", per_completed);
	if (possibly_delayed > 0)
		printf("     (w/ delayed qrys): %6.2lf%%\n", per_completed2);
	printf("  Percentage lost:      %6.2lf%%\n", per_lost);
	if (possibly_delayed > 0)
		printf("    (w/o delayed qrys): %6.2lf%%\n", per_lost2);

	printf("\n");

	printf("  Started at:           %s",
	       ctime((const time_t *)&start_time.tv_sec));
	printf("  Finished at:          %s",
	       ctime((const time_t *)&end_perf->tv_sec));
	printf("  Ran for:              %.6lf seconds\n", run_time);

	printf("\n");

	printf("  Queries per second:   %.6lf qps\n", queries_per_sec);
	if (possibly_delayed > 0) {
		printf("   (w/ delayed qrys):   %.6lf qps\n",
		       queries_per_sec2);
	}
	if (target_qps > 0) {
		printf("  Total QPS/target:     %.6lf/%d qps\n",
		       queries_per_sec_total, target_qps);		
	}

	printf("\n");
}

void
print_interval_statistics() {
	struct timeval time_now;

	if (use_timelimit == FALSE)
		return;

	if (setup_phase == TRUE)
		return;

	if (print_interval == 0)
		return;

	if (timelimit_reached() == TRUE)
		return;

	set_timenow(&time_now);
	if (difftv(time_now, time_of_first_query_interval)
	    <= (double)print_interval)
		return;

	/* Don't count currently outstanding queries */
	num_queries_sent_interval -= queries_outstanding();
	print_statistics(TRUE, num_queries_sent_interval,
			 num_queries_timed_out_interval,
			 num_queries_possiblydelayed_interval,
			 &time_of_first_query_interval,
			 &time_of_first_query_interval, &time_now, &time_now,
			 rtt_max_interval, rtt_min_interval,
			 rtt_total_interval, rtt_overflows_interval,
			 rttarray_interval);

	/* Reset intermediate counters */
	num_queries_sent_interval = 0;
	num_queries_timed_out_interval = 0;
	num_queries_possiblydelayed_interval = 0;
	rtt_max_interval = -1;
	rtt_min_interval = -1;
	rtt_total_interval = 0.0;
	rtt_overflows_interval = 0;
	if (rttarray_interval != NULL) {
		memset(rttarray_interval, 0,
		       sizeof(rttarray_interval[0]) * rttarray_size);
	}
}

/*
 * queryperf Program Mainline
 */
int
main(int argc, char **argv) {
	int adjust_rate;
	int sending = FALSE;
	int got_eof = FALSE;
	int input_length = MAX_INPUT_LEN;
	char input_line[MAX_INPUT_LEN + 1];

	set_timenow(&time_of_program_start);
	time_of_first_query.tv_sec = 0;
	time_of_first_query.tv_usec = 0;
	time_of_first_query_interval.tv_sec = 0;
	time_of_first_query_interval.tv_usec = 0;
	time_of_end_of_run.tv_sec = 0;
	time_of_end_of_run.tv_usec = 0;

	input_line[0] = '\0';

	show_startup_info();

	if (setup(argc, argv) == -1)
		return (-1);

	printf("[Status] Processing input data\n");

	while ((sending = keep_sending(&got_eof)) == TRUE ||
	       queries_outstanding() > 0) {
		print_interval_statistics();
		adjust_rate = FALSE;

		while ((sending = keep_sending(&got_eof)) == TRUE &&
		       queries_outstanding() < max_queries_outstanding) {
			int len = next_input_line(input_line, input_length);
			if (len == 0) {
				got_eof = TRUE;
			} else {
				/* Zap the trailing newline */
				if (input_line[len - 1] == '\n')
					input_line[len - 1] = '\0';

				/*
				 * TODO: Should test if we got a whole line
				 * and flush to the next \n in input if not
				 * here... Add this later. Only do the next
				 * few lines if we got a whole line, else
				 * print a warning. Alternative: Make the
				 * max line size really big. BAD! :)
				 */

				if (input_line[0] == CONFIG_CHAR)
					update_config(input_line);
				else {
					send_query(input_line);
					if (target_qps > 0 &&
					    (num_queries_sent %
					     max_queries_outstanding) == 0) {
						adjust_rate = TRUE;
					}
				}
			}
		}

		process_responses(adjust_rate);
		retire_old_queries(sending);
	}

	set_timenow(&time_of_end_of_run);

	printf("[Status] Testing complete\n");

	close_socket();
	close_datafile();

	print_statistics(FALSE, num_queries_sent, num_queries_timed_out,
			 num_queries_possiblydelayed,
			 &time_of_first_query, &time_of_program_start,
			 &time_of_end_of_run, &time_of_stop_sending,
			 rtt_max, rtt_min, rtt_total, rtt_overflows, rttarray);

	return (0);
}