queryperf.c revision 4848fe4ad2c0ba6e2e69e4a2617727f8556d79a0
/***
*** DNS Query Performance Testing Tool (queryperf.c)
***
*** Version $Id: queryperf.c,v 1.1 2001/07/12 02:02:09 gson Exp $
***
*** Copyright (C) 2000, 2001 Nominum, Inc. All Rights Reserved.
***
*** Stephen Jacob <sj@nominum.com>
***/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <unistd.h>
#include <netdb.h>
#include <resolv.h>
#include <math.h>
#include <errno.h>
/*
* Configuration defaults
*/
#define DEF_MAX_QUERIES_OUTSTANDING 20
#define DEF_SERVER_TO_QUERY "localhost"
#define DEF_SERVER_PORT 53
/*
* Other constants / definitions
*/
#define COMMENT_CHAR ';'
#define CONFIG_CHAR '#'
#define MAX_PORT 65535
#define MAX_INPUT_LEN 512
#define MAX_DOMAIN_LEN 255
#define FALSE 0
#define TRUE 1
#define WHITESPACE " \t\n"
#define QTYPE_STRINGS { \
"A", "NS", "MD", "MF", "CNAME", "SOA", "MB", "MG", \
"MR", "NULL", "WKS", "PTR", "HINFO", "MINFO", "MX", "TXT", \
"AAAA", "AXFR", "MAILB", "MAILA", "*", "ANY" \
}
#define QTYPE_CODES { \
1, 2, 3, 4, 5, 6, 7, 8, \
9, 10, 11, 12, 13, 14, 15, 16, \
28, 252, 253, 254, 255, 255 \
}
/*
* Data type definitions
*/
#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;
};
/*
* 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;
char *datafile_name; /* init NULL */
char *server_to_query; /* init NULL */
unsigned int server_port = DEF_SERVER_PORT;
int run_only_once = FALSE;
int use_timelimit = FALSE;
unsigned int run_timelimit; /* init 0 */
/*
* Other global stuff
*/
int setup_phase = TRUE;
unsigned int runs_through_file; /* init 0 */
unsigned int num_queries_sent; /* init 0 */
unsigned int num_queries_outstanding; /* init 0 */
unsigned int num_queries_timed_out; /* init 0 */
struct timeval time_of_program_start;
struct timeval time_of_first_query;
struct timeval time_of_end_of_run;
unsigned int query_status_allocated; /* init 0 */
int query_socket; /* init 0 */
struct sockaddr_in qaddr;
/*
* get_uint16:
* Get an unsigned short integer from a buffer (in network order)
*/
static unsigned short
get_uint16(unsigned char *buf) {
unsigned short ret;
return (ret);
}
/*
* show_startup_info:
*/
void
show_startup_info(void) {
printf("\n"
"DNS Query Performance Testing Tool\n"
"Version: $Id: queryperf.c,v 1.1 2001/07/12 02:02:09 gson Exp $\n"
"\n");
}
/*
* show_usage:
*/
void
show_usage(void) {
"\n"
"Usage: queryperf [-d datafile] [-s server_addr] [-p port] [-q num_queries]\n"
" [-b bufsize] [-t timeout] [-n] [-l limit] [-1]\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: %u)\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"
"\n",
}
/*
* 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;
return (-1);
}
"%s\n", new_file);
return (-1);
}
return (0);
}
/*
* set_input_stdin:
* Set the input to be stdin (instead of a datafile)
*/
void
set_input_stdin(void) {
}
/*
* set_server:
* Set the server to be queried
*
* Return -1 on failure
* Return a non-negative integer otherwise
*/
int
set_server(char *new_name) {
/* If no change in server name, don't do anything... */
return (0);
return (-1);
}
"%s\n", new_name);
return (-1);
}
new_name);
return (-1);
}
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(unsigned int new_port) {
return (-1);
else {
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
unsigned long int value;
char *end;
return (FALSE);
return (FALSE);
return (FALSE);
return (TRUE);
}
/*
* set_max_queries:
* Set the maximum number of outstanding queries
*
* Returns -1 on failure
* Returns a non-negative integer otherwise
*/
int
set_max_queries(unsigned int new_max) {
static unsigned int size_qs = sizeof(struct query_status);
struct query_status *temp_stat;
unsigned int count;
if (new_max < 0) {
"must be positive and non-zero: %u\n", new_max);
return (-1);
}
if (new_max > query_status_allocated) {
return (-1);
} else {
/*
* 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.
*/
}
}
}
return (0);
}
/*
* parse_args:
* Parse program arguments and set configuration options
*
* Return -1 on failure
* Return a non-negative integer otherwise
*/
int
int c;
unsigned int uint_arg_val;
switch (c) {
case 'q':
queriesset = TRUE;
} else {
"integer value: -%c %s\n",
c, optarg);
return (-1);
}
break;
case 't':
timeoutset = TRUE;
} else {
"integer value: -%c %s\n",
c, optarg);
return (-1);
}
break;
case 'n':
break;
case 'd':
"name: %s\n", optarg);
return (-1);
}
break;
case 's':
"name: %s\n", optarg);
return (-1);
}
break;
case 'p':
{
} else {
"integer between 0 and %d: -%c %s\n",
return (-1);
}
break;
case '1':
break;
case 'l':
} else {
"integer: -%c %s\n",
c, optarg);
return (-1);
}
break;
case 'b':
} else {
"integer: -%c %s\n",
c, optarg);
return (-1);
}
break;
default:
return (-1);
}
}
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) {
return (0);
} else {
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 (fclose(datafile_ptr) != 0) {
return (-1);
}
}
return (0);
}
/*
* open_socket:
* Open a socket for the queries
*
* Return -1 on failure
* Return a non-negative integer otherwise
*/
int
open_socket(void) {
int sock;
struct sockaddr_in bind_addr;
int ret;
int bufsize;
return (-1);
}
return (-1);
}
== -1) {
return (-1);
}
if (ret < 0)
if (ret < 0)
query_socket = sock;
return (0);
}
/*
* close_socket:
* Close the query socket
*
* Return -1 on failure
* Return a non-negative integer otherwise
*/
int
close_socket(void) {
if (query_socket != 0) {
if (close(query_socket) != 0) {
return (-1);
}
}
query_socket = 0;
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
"queries\n", argv[0]);
return (-1);
}
argv[0]);
return (-1);
}
argv[0]);
return (-1);
}
show_usage();
return (-1);
}
if (open_datafile() == -1)
return (-1);
if (open_socket() == -1)
return (-1);
return (0);
}
/*
* set_timenow:
* Set a timeval struct to indicate the current time
*/
void
"time() instead\n");
}
}
/*
* difftv:
* Find the difference in seconds between two timeval structs.
*
* Return the difference between tv1 and tv2 in seconds in a double.
*/
double
double diff;
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) {
if (use_timelimit == FALSE)
return (FALSE);
if (setup_phase == TRUE) {
< (double)(run_timelimit + HARD_TIMEOUT_EXTRA))
return (FALSE);
else
return (TRUE);
} else {
< (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) {
return (FALSE);
return (TRUE);
&& (timelimit_reached() == FALSE)) {
return (TRUE);
} else {
if (*reached_end_input == 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 {
return (0);
else
}
/*
* 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;
if (num_directives > (sizeof(dir_values) / sizeof(int)))
num_directives = sizeof(dir_values) / sizeof(int);
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) {
unsigned int uint_val;
int directive_number;
int check;
if (ignore_config_changes == TRUE) {
return;
}
return;
}
"no directive present: %s", conf_copy);
return;
}
return;
}
if (config_value == NULL) {
return;
}
if (trailing_garbage != NULL) {
"trailing garbage: %s", conf_copy);
}
switch(directive_number) {
case V_SERVER:
"line: %s\n", directive);
return;
}
"the server name to '%s'\n", config_value);
break;
case V_PORT:
"line: %s\n", directive);
return;
}
}
} else
break;
case V_MAXQUERIES:
"line: %s\n", directive);
return;
}
} else
break;
case V_MAXWAIT:
"line: %s\n", directive);
return;
}
} else
break;
default:
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
static char *qtype_strings[] = QTYPE_STRINGS;
static int qtype_codes[] = QTYPE_CODES;
char *domain_str, *type_str;
if (num_types > (sizeof(qtype_codes) / sizeof(int)))
num_types = sizeof(qtype_codes) / sizeof(int);
return (-1);
}
return (-1);
}
}
}
return (-1);
}
return (0);
}
/*
* dispatch_query:
* Send the query packet for the entry
*
* Return -1 on failure
* Return a non-negative integer otherwise
*/
int
int buffer_len = PACKETSZ;
int bytes_sent;
if (buffer_len == -1) {
return (-1);
}
packet_buffer[0] = id_ptr[0];
if (bytes_sent == -1) {
return (-1);
}
if (bytes_sent != 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;
int query_type;
unsigned int count;
use_query_id++;
return;
}
return;
}
if (setup_phase == TRUE) {
setup_phase = FALSE;
printf("[Status] Sending queries\n");
}
/* Find the first slot in status[] that is not in use */
"status[] space!\n");
return;
}
/* Register the query in status[] */
}
/*
* data_available:
* Is there data available on the given file descriptor?
*
* Return TRUE if there is
* Return FALSE otherwise
*/
int
int retval;
/* Set list of file descriptors */
} else {
}
return (TRUE);
else
return (FALSE);
}
/*
* 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 ct = 0;
}
}
"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) {
static struct sockaddr_in from_addr;
static unsigned char in_buf[MAX_BUFFER_LEN];
return;
}
}
/*
* process_responses:
* open queries (set in_use = FALSE for their entry in status[]), also
* decrementing the number of outstanding queries.
*/
void
process_responses(void) {
double first_packet_wait = RESPONSE_BLOCKING_WAIT_TIME;
unsigned int outstanding = queries_outstanding();
/*
* 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.
*/
first_packet_wait = 0.0;
}
}
}
/*
* 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(void) {
unsigned int count = 0;
>= (double)query_timeout)) {
printf("[Timeout] Query timed out: msg id %u\n",
}
}
}
/*
* print_statistics:
* Print out statistics based on the results of the test
*/
void
print_statistics(void) {
unsigned int num_queries_completed;
double per_lost, per_completed;
double run_time, queries_per_sec;
struct timeval start_time;
if (num_queries_completed == 0) {
per_lost = 0.0;
per_completed = 0.0;
} else {
/ (double)num_queries_sent;
}
if (num_queries_sent == 0) {
run_time = 0.0;
queries_per_sec = 0.0;
} else {
}
printf("\n");
printf("Statistics:\n");
printf("\n");
printf(" Parse input file: %s\n",
if (use_timelimit)
if (run_only_once == FALSE)
printf(" Ran through file: %u times\n",
else
printf(" Ended due to: reaching %s\n",
((runs_through_file == 0) ? "time limit"
: "end of file"));
printf("\n");
printf("\n");
printf("\n");
printf(" Finished at: %s",
printf("\n");
printf("\n");
}
/*
* dnsqtest Program Mainline
*/
int
int input_length = MAX_INPUT_LEN;
time_of_end_of_run.tv_sec = 0;
input_line[0] = '\0';
return (-1);
printf("[Status] Processing input data\n");
&& queries_outstanding() < max_queries_outstanding) {
} else {
/*
* 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)
else {
}
}
}
}
printf("[Status] Testing complete\n");
close_socket();
return (0);
}