mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-23 18:49:54 +00:00
3706. [contrib] queryperf: Fixed a possible integer overflow when printing results. [RT #35182]
2238 lines
53 KiB
C
2238 lines
53 KiB
C
/*
|
|
* 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);
|
|
}
|