summaryrefslogtreecommitdiffstats
path: root/contrib/queryperf/queryperf.c
diff options
context:
space:
mode:
authorMartin Nagy <mnagy@redhat.com>2009-02-11 20:37:59 +0100
committerMartin Nagy <mnagy@redhat.com>2009-02-11 20:37:59 +0100
commitf50ae72ec3417cae55dd4e085991c01af9fdc5f1 (patch)
tree0e36c9a3320f6d068df93d3ff6d84b821d23db40 /contrib/queryperf/queryperf.c
downloadbind_dynamic-f50ae72ec3417cae55dd4e085991c01af9fdc5f1.tar.gz
bind_dynamic-f50ae72ec3417cae55dd4e085991c01af9fdc5f1.tar.xz
bind_dynamic-f50ae72ec3417cae55dd4e085991c01af9fdc5f1.zip
Initial commitstart
Diffstat (limited to 'contrib/queryperf/queryperf.c')
-rw-r--r--contrib/queryperf/queryperf.c2113
1 files changed, 2113 insertions, 0 deletions
diff --git a/contrib/queryperf/queryperf.c b/contrib/queryperf/queryperf.c
new file mode 100644
index 0000000..d15b7c4
--- /dev/null
+++ b/contrib/queryperf/queryperf.c
@@ -0,0 +1,2113 @@
+/*
+ * 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>
+ ***/
+
+#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;
+};
+
+/*
+ * 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;
+
+/*
+ * 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] [-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"
+" -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 < 0) {
+ fprintf(stderr, "Unable to change max outstanding queries: "
+ "must be positive and non-zero: %u\n", new_max);
+ return (-1);
+ }
+
+ 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:T::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':
+ 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];
+ static socklen_t sockaddrlen = sizeof(struct sockaddr);
+ int buffer_len = PACKETSZ;
+ int bytes_sent;
+ unsigned short int net_id = htons(id);
+ char *id_ptr = (char *)&net_id;
+
+ 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);
+ }
+ 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, addr_len, resp_id;
+ 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);
+}