David van Moolenbroek 00b67f09dd Import NetBSD named(8)
Also known as ISC bind.  This import adds utilities such as host(1),
dig(1), and nslookup(1), as well as many other tools and libraries.

Change-Id: I035ca46e64f1965d57019e773f4ff0ef035e4aa3
2017-03-21 22:00:06 +00:00

2240 lines
53 KiB
C

/* $NetBSD: queryperf.c,v 1.6 2014/12/10 04:37:56 christos Exp $ */
/*
* 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 DNS_HEADERLEN 12
#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", "RP", \
"AFSDB", "X25", "ISDN", "RT", "NSAP", "NSAP-PTR", "SIG", \
"KEY", "PX", "GPOS", "AAAA", "LOC", "NXT", "EID", "NIMLOC", \
"SRV", "ATMA", "NAPTR", "KX", "CERT", "A6", "DNAME", "SINK", \
"OPT", "APL", "DS", "SSHFP", "IPSECKEY", "RRSIG", "NSEC", \
"DNSKEY", "DHCID", "NSEC3", "NSEC3PARAM", "TLSA", "HIP", \
"NINFO", "RKEY", "TALINK", "CDS", "SPF", "UINFO", "UID", \
"GID", "UNSPEC", "NID", "L32", "L64", "LP", "TKEY", "TSIG", \
"IXFR", "AXFR", "MAILB", "MAILA", "URI", "CAA", "*", "ANY", \
"TA", "DLV" \
}
#define QTYPE_CODES { \
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, \
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, \
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, \
49, 50, 51, 52, 55, 56, 57, 58, 59, 99, 100, 101, 102, 103, \
104, 105, 106, 107, 249, 250, 251, 252, 253, 254, 255, 255, \
256, 257, 32768, 32769 \
}
#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;
int qtype;
char qname[MAX_DOMAIN_LEN + 1];
};
struct query_mininfo { /* minimum info for timeout queries */
int qtype; /* use -1 if N/A */
struct timeval sent_timestamp;
char qname[MAX_DOMAIN_LEN + 1];
};
/*
* 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;
unsigned int rtt_counted;
unsigned int rtt_counted_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;
static struct query_mininfo *timeout_queries;
/*
* 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, unsigned int qnlen, int *qtype) {
static char *qtype_strings[] = QTYPE_STRINGS;
static int qtype_codes[] = QTYPE_CODES;
unsigned 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, u_char **pktp,
int *pktlenp)
{
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);
*pktp = packet_buffer;
*pktlenp = buffer_len;
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];
u_char *qpkt;
char serveraddr[NI_MAXHOST];
int query_type, qpkt_len;
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,
&qpkt, &qpkt_len) == -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].id = use_query_id;
if (verbose)
status[count].desc = strdup(query_desc);
set_timenow(&status[count].sent_timestamp);
status[count].qtype = query_type;
if (dn_expand(qpkt, qpkt + qpkt_len, qpkt + DNS_HEADERLEN,
status[count].qname, MAX_DOMAIN_LEN) == -1) {
fprintf(stderr, "Unexpected error: "
"query message doesn't have qname?\n");
return;
}
status[count].in_use = TRUE;
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, char *qname, int qtype,
unsigned int rcode)
{
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;
rtt_counted++;
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;
rtt_counted_interval++;
}
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 "
"[query=%s/%d, rcode=%u]\n", rtt, qname, qtype, rcode);
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, char *qname,
int qtype)
{
unsigned int ct = 0;
int found = FALSE;
struct timeval now;
double rtt;
if (timeout_queries != NULL) {
struct query_mininfo *qi = &timeout_queries[id];
if (qi->qtype == qtype && strcasecmp(qi->qname, qname) == 0) {
register_rtt(&qi->sent_timestamp, qname, qtype, rcode);
qi->qtype = -1;
found = TRUE;
}
}
for (; (ct < query_status_allocated) && (found == FALSE); ct++) {
if (status[ct].in_use == TRUE && status[ct].id == id &&
status[ct].qtype == qtype &&
strcasecmp(status[ct].qname, qname) == 0) {
status[ct].in_use = FALSE;
num_queries_outstanding--;
found = TRUE;
register_rtt(&status[ct].sent_timestamp, qname, qtype,
rcode);
if (status[ct].desc) {
printf("> %s %s\n", rcode_strings[rcode],
status[ct].desc);
free(status[ct].desc);
}
}
}
if (countrcodes && (found == TRUE || target_qps > 0))
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];
char qname[MAX_DOMAIN_LEN + 1];
int numbytes, addr_len, resp_id, qnamelen;
int qtype, 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;
}
if (numbytes < DNS_HEADERLEN) {
if (verbose)
fprintf(stderr, "Malformed response\n");
return;
}
resp_id = get_uint16(in_buf);
flags = get_uint16(in_buf + 2);
qnamelen = dn_expand(in_buf, in_buf + numbytes, in_buf + DNS_HEADERLEN,
qname, MAX_DOMAIN_LEN);
if (qnamelen == -1) {
if (verbose)
fprintf(stderr,
"Failed to retrieve qname from response\n");
return;
}
if (numbytes < DNS_HEADERLEN + qnamelen + 2) {
if (verbose)
fprintf(stderr, "Malformed response\n");
return;
}
qtype = get_uint16(in_buf + DNS_HEADERLEN + qnamelen);
register_response(resp_id, flags & 0xF, qname, qtype);
}
/*
* 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--;
if (timeout_queries != NULL) {
struct query_mininfo *qi;
qi = &timeout_queries[status[count].id];
if (qi->qtype != -1) {
/* now really retire this query */
num_queries_timed_out++;
num_queries_timed_out_interval++;
}
qi->qtype = status[count].qtype;
qi->sent_timestamp =
status[count].sent_timestamp;
strcpy(qi->qname, status[count].qname);
} else {
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,
unsigned int rcounted, 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 (rcounted > 0) {
int i;
double sum = 0;
rtt_average = rtotal / (double)rcounted;
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)rcounted);
} 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_counted_interval, 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_counted_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);
/* XXX: move this to setup: */
timeout_queries = malloc(sizeof(struct query_mininfo) * 65536);
if (timeout_queries == NULL) {
fprintf(stderr,
"failed to allocate memory for timeout queries\n");
return (-1);
} else {
int i;
for (i = 0; i < 65536; i++)
timeout_queries[i].qtype = -1;
}
printf("[Status] Processing input data\n");
while ((sending = keep_sending(&got_eof)) == TRUE ||
queries_outstanding() > 0)
{
if (num_queries_sent_interval > 0){
/*
* After statistics are printed, send_query()
* needs to be called at least once so that
* time_of_first_query_interval is reset
*/
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_counted, rtt_max, rtt_min, rtt_total,
rtt_overflows, rttarray);
return (0);
}