2473 lines
56 KiB
C
2473 lines
56 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*
|
|
* TCP DNS perf tool
|
|
*
|
|
* main parameters are -r<rate> and <server>
|
|
* standard options are 4|6 (IPv4|IPv6), rate computations, terminaisons,
|
|
* EDNS0, NOERROR|NXDOMAIN, template (for your own query), diags,
|
|
* alternate server port and UDP version.
|
|
*
|
|
* To help to crush kernels (unfortunately both client and server :-)
|
|
* this version of the tool is multi-threaded:
|
|
* - the master thread inits, monitors the activity each millisecond,
|
|
* and report results when finished
|
|
* - the connecting thread computes the date of the next connection,
|
|
* creates a socket, makes it non blocking, binds it if wanted,
|
|
* connects it and pushes it on the output epoll queue
|
|
* - the sending thread gets by epoll connected sockets, timeouts
|
|
* embryonic connections, sends queries and pushes sockets on
|
|
* the input epoll queue
|
|
* - the receiving thread gets by epoll sockets with a pending
|
|
* response, receives responses, timeouts unanswered queries,
|
|
* and recycles (by closing them) all sockets.
|
|
*
|
|
* Rate computation details:
|
|
* - the target rate is in query+response per second.
|
|
* - rating is done by the connecting thread.
|
|
* - of course the tool is always late so the target rate is never
|
|
* reached. BTW there is no attempt to internally adjust the
|
|
* effective rate to the target one: this must be by tuning
|
|
* the rate related parameters, first the -r<rate> itself.
|
|
* - at the beginning of the connecting thread iteration loop
|
|
* (second "loops" counter) the date of the due (aka next) connect()
|
|
* call is computed from the last one with 101% of the rate.
|
|
* - the due date is compared with the current date (aka now).
|
|
* - if the due is before, lateconn counter is incremented, else
|
|
* the thread sleeps for the difference,
|
|
* - the next step is to reget the current date, if it is still
|
|
* before the due date (e.g., because the sleep was interrupted)
|
|
* the first shortwait counter is incremented.
|
|
* - if it is after (common case) the number of connect calls is
|
|
* computed from the difference between now and due divided by rate,
|
|
* rounded to the next number,
|
|
* - this number of connect() calls is bounded by the -a<aggressiveness>
|
|
* parameter to avoid too many back to back new connection attempts.
|
|
* - the compconn counter is incremented, errors (other than EINPROGRESS
|
|
* from not blocking connect()) are printed. When an error is
|
|
* related to a local limit (e.g., EMFILE, EADDRNOTAVAIL or the
|
|
* internal ENOMEM) the locallimit counter is incremented.
|
|
*/
|
|
|
|
#ifdef __linux__
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
#include <netdb.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
/* DNS defines */
|
|
|
|
#define NS_TYPE_A 1
|
|
#define NS_TYPE_NS 2
|
|
#define NS_TYPE_CNAME 5
|
|
#define NS_TYPE_SOA 6
|
|
#define NS_TYPE_NULL 10
|
|
#define NS_TYPE_PTR 12
|
|
#define NS_TYPE_MX 15
|
|
#define NS_TYPE_TXT 16
|
|
#define NS_TYPE_AAAA 28
|
|
#define NS_TYPE_OPT 41
|
|
#define NS_TYPE_DS 43
|
|
#define NS_TYPE_RRSIG 46
|
|
#define NS_TYPE_NSEC 47
|
|
#define NS_TYPE_DNSKEY 48
|
|
#define NS_TYPE_NSEC3 50
|
|
#define NS_TYPE_NSEC3PARAM 51
|
|
#define NS_TYPE_TSIG 250
|
|
#define NS_TYPE_IXFR 251
|
|
#define NS_TYPE_AXFR 252
|
|
#define NS_TYPE_ANY 255
|
|
|
|
#define NS_CLASS_IN 1
|
|
#define NS_CLASS_ANY 255
|
|
|
|
#define NS_OFF_ID 0
|
|
#define NS_OFF_FLAGS 2
|
|
#define NS_OFF_QDCOUNT 4
|
|
#define NS_OFF_ANCOUNT 6
|
|
#define NS_OFF_NSCOUNT 8
|
|
#define NS_OFF_ARCOUNT 10
|
|
#define NS_OFF_QUESTION 12
|
|
|
|
#define NS_FLAG_QR 0x8000U
|
|
#define NS_FLAG_AA 0x0400U
|
|
#define NS_FLAG_TC 0x0200U
|
|
#define NS_FLAG_RD 0x0100U
|
|
#define NS_FLAG_RA 0x0080U
|
|
#define NS_FLAG_AD 0x0020U
|
|
#define NS_FLAG_CD 0x0010U
|
|
|
|
#define NS_XFLAG_DO 0x8000U
|
|
|
|
#define NS_OPCODE_MASK 0x7000U
|
|
#define NS_OPCODE_QUERY 0
|
|
|
|
#define NS_RCODE_MASK 0x000fU
|
|
#define NS_RCODE_NOERROR 0
|
|
#define NS_RCODE_FORMERR 1
|
|
#define NS_RCODE_SERVFAIL 2
|
|
#define NS_RCODE_NXDOMAIN 3
|
|
#define NS_RCODE_NOIMP 4
|
|
#define NS_RCODE_REFUSED 5
|
|
#define NS_RCODE_LAST 6
|
|
|
|
/* chaining macros */
|
|
|
|
#define ISC_INIT(head, headl) do { \
|
|
(head) = -1; \
|
|
(headl) = &(head); \
|
|
} while (0)
|
|
|
|
#define ISC_INSERT(head, headl, elm) do { \
|
|
(elm)->next = -1; \
|
|
(elm)->prev = (headl); \
|
|
*(headl) = (elm) - xlist; \
|
|
(headl) = &((elm)->next); \
|
|
} while (0)
|
|
|
|
#define ISC_REMOVE(headl, elm) do { \
|
|
if ((elm)->next != -1) \
|
|
xlist[(elm)->next].prev = (elm)->prev; \
|
|
else \
|
|
(headl) = (elm)->prev; \
|
|
*(elm)->prev = (elm)->next; \
|
|
} while (0)
|
|
|
|
/*
|
|
* Data structures
|
|
*/
|
|
|
|
/*
|
|
* exchange:
|
|
* - per exchange values:
|
|
* * order (for debugging)
|
|
* * id
|
|
* * random (for debugging)
|
|
* * time-stamps
|
|
*
|
|
* sent/rcvd chain, "next to be received" on entry cache.
|
|
*/
|
|
|
|
struct exchange { /* per exchange structure */
|
|
int sock; /* socket descriptor */
|
|
int next, *prev; /* chaining */
|
|
#define X_FREE 0
|
|
#define X_CONN 1
|
|
#define X_READY 2
|
|
#define X_SENT 3
|
|
int state; /* state */
|
|
uint16_t id; /* ID */
|
|
uint64_t order; /* number of this exchange */
|
|
struct timespec ts0, ts1, ts2, ts3; /* timespecs */
|
|
};
|
|
struct exchange *xlist; /* exchange list */
|
|
int xlast; /* number of exchanges */
|
|
int xconn, *xconnl; /* connecting list */
|
|
int xready, *xreadyl; /* connected list */
|
|
int xsent, *xsentl; /* sent list */
|
|
int xfree, *xfreel; /* free list */
|
|
int xused; /* next to be used list */
|
|
pthread_mutex_t mtxconn, mtxsent, mtxfree; /* mutexes */
|
|
uint64_t xccount; /* connected counters */
|
|
uint64_t xscount; /* sent counters */
|
|
uint64_t xrcount; /* received counters */
|
|
|
|
/*
|
|
* statictics counters and accumulators
|
|
*/
|
|
|
|
uint64_t recverr, tooshort, locallimit; /* error counters */
|
|
uint64_t loops[4], shortwait[3]; /* rate stats */
|
|
uint64_t lateconn, compconn; /* rate stats (cont) */
|
|
uint64_t badconn, collconn, badsent, collsent; /* rate stats (cont) */
|
|
uint64_t badid, notresp; /* bad response counters */
|
|
uint64_t rcodes[NS_RCODE_LAST + 1]; /* rcode counters */
|
|
double dmin = 999999999.; /* minimum delay */
|
|
double dmax = 0.; /* maximum delay */
|
|
double dsum = 0.; /* delay sum */
|
|
double dsumsq = 0.; /* square delay sum */
|
|
|
|
/*
|
|
* command line parameters
|
|
*/
|
|
|
|
int edns0; /* EDNS0 DO flag */
|
|
int ipversion = 0; /* IP version */
|
|
int rate; /* rate in connections per second */
|
|
int noreport; /* disable auto reporting */
|
|
int report; /* delay between two reports */
|
|
uint32_t range; /* randomization range */
|
|
uint32_t maxrandom; /* maximum random value */
|
|
int basecnt; /* base count */
|
|
char *base[2]; /* bases */
|
|
int gotnumreq = -1; /* numreq[0] was set */
|
|
int numreq[2]; /* number of exchanges */
|
|
int period; /* test period */
|
|
int gotlosttime = -1; /* losttime[0] was set */
|
|
double losttime[2] = {.5, 1.}; /* delay for a timeout */
|
|
int gotmaxloss = -1; /* max{p}loss[0] was set */
|
|
int maxloss[2]; /* maximum number of losses */
|
|
double maxploss[2] = {0., 0.}; /* maximum percentage */
|
|
char *localname; /* local address or interface */
|
|
int aggressiveness = 1; /* back to back connections */
|
|
int seeded; /* is a seed provided */
|
|
unsigned int seed; /* randomization seed */
|
|
char *templatefile; /* template file name */
|
|
int rndoffset = -1; /* template offset (random) */
|
|
char *diags; /* diagnostic selectors */
|
|
char *servername; /* server */
|
|
int ixann; /* ixann NXDOMAIN */
|
|
int udp; /* use UDP in place of TCP */
|
|
int minport, maxport, curport; /* port range */
|
|
|
|
/*
|
|
* global variables
|
|
*/
|
|
|
|
struct sockaddr_storage localaddr; /* local socket address */
|
|
struct sockaddr_storage serveraddr; /* server socket address */
|
|
in_port_t port = 53; /* server socket port */
|
|
|
|
int epoll_ifd, epoll_ofd; /* epoll file descriptors */
|
|
#ifndef EVENTS_CNT
|
|
#define EVENTS_CNT 16
|
|
#endif
|
|
struct epoll_event ievents[EVENTS_CNT]; /* polled input events */
|
|
struct epoll_event oevents[EVENTS_CNT]; /* polled output events */
|
|
int interrupted, fatal; /* to finish flags */
|
|
|
|
uint8_t obuf[4098], ibuf[4098]; /* I/O buffers */
|
|
char tbuf[4098]; /* template buffer */
|
|
|
|
struct timespec boot; /* the date of boot */
|
|
struct timespec last; /* the date of last connect */
|
|
struct timespec due; /* the date of next connect */
|
|
struct timespec dreport; /* the date of next reporting */
|
|
struct timespec finished; /* the date of finish */
|
|
|
|
/*
|
|
* template
|
|
*/
|
|
|
|
size_t length_query;
|
|
uint8_t template_query[4096];
|
|
size_t random_query;
|
|
|
|
/*
|
|
* threads
|
|
*/
|
|
|
|
pthread_t master, connector, sender, receiver;
|
|
|
|
/*
|
|
* initialize data structures handling exchanges
|
|
*/
|
|
|
|
void
|
|
inits(void)
|
|
{
|
|
int idx;
|
|
|
|
ISC_INIT(xconn, xconnl);
|
|
ISC_INIT(xready, xreadyl);
|
|
ISC_INIT(xsent, xsentl);
|
|
ISC_INIT(xfree, xfreel);
|
|
|
|
if ((pthread_mutex_init(&mtxconn, NULL) != 0) ||
|
|
(pthread_mutex_init(&mtxsent, NULL) != 0) ||
|
|
(pthread_mutex_init(&mtxfree, NULL) != 0)) {
|
|
fprintf(stderr, "pthread_mutex_init failed\n");
|
|
exit(1);
|
|
}
|
|
|
|
epoll_ifd = epoll_create(EVENTS_CNT);
|
|
if (epoll_ifd < 0) {
|
|
perror("epoll_create(input)");
|
|
exit(1);
|
|
}
|
|
epoll_ofd = epoll_create(EVENTS_CNT);
|
|
if (epoll_ofd < 0) {
|
|
perror("epoll_create(output)");
|
|
exit(1);
|
|
}
|
|
|
|
xlist = (struct exchange *) malloc(xlast * sizeof(struct exchange));
|
|
if (xlist == NULL) {
|
|
perror("malloc(exchanges)");
|
|
exit(1);
|
|
}
|
|
memset(xlist, 0, xlast * sizeof(struct exchange));
|
|
|
|
for (idx = 0; idx < xlast; idx++)
|
|
xlist[idx].sock = xlist[idx].next = -1;
|
|
}
|
|
|
|
/*
|
|
* build a TCP DNS QUERY
|
|
*/
|
|
|
|
void
|
|
build_template_query(void)
|
|
{
|
|
uint8_t *p = template_query;
|
|
uint16_t v;
|
|
|
|
/* flags */
|
|
p += NS_OFF_FLAGS;
|
|
v = NS_FLAG_RD;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* qdcount */
|
|
v = 1;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* ancount */
|
|
v = 0;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* nscount */
|
|
v = 0;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* arcount */
|
|
v = edns0;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* icann.link (or ixann.link) */
|
|
*p++ = 5;
|
|
*p++ = 'i';
|
|
if (ixann == 0)
|
|
*p++ = 'c';
|
|
else
|
|
*p++ = 'x';
|
|
*p++ = 'a';
|
|
*p++ = 'n';
|
|
*p++ = 'n';
|
|
*p++ = 4;
|
|
*p++ = 'l';
|
|
*p++ = 'i';
|
|
*p++ = 'n';
|
|
*p++ = 'k';
|
|
*p++ = 0;
|
|
/* type A/AAAA */
|
|
if (ipversion == 4)
|
|
v = NS_TYPE_A;
|
|
else
|
|
v = NS_TYPE_AAAA;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* class IN */
|
|
v = NS_CLASS_IN;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* EDNS0 OPT with DO */
|
|
if (edns0) {
|
|
/* root name */
|
|
*p++ = 0;
|
|
/* type OPT */
|
|
v = NS_TYPE_OPT;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* class UDP length */
|
|
v = 4096;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* extended rcode 0 */
|
|
*p++ = 0;
|
|
/* version 0 */
|
|
*p++ = 0;
|
|
/* extended flags DO */
|
|
v = NS_XFLAG_DO;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
/* rdlength */
|
|
v = 0;
|
|
*p++ = v >> 8;
|
|
*p++ = v & 0xff;
|
|
}
|
|
/* length */
|
|
length_query = p - template_query;
|
|
}
|
|
|
|
/*
|
|
* get a TCP DNS client QUERY template
|
|
* from the file given in the command line (-T<template-file>)
|
|
* and rnd offset (-O<random-offset>)
|
|
*/
|
|
|
|
void
|
|
get_template_query(void)
|
|
{
|
|
uint8_t *p = template_query;
|
|
int fd, cc, i, j;
|
|
|
|
fd = open(templatefile, O_RDONLY);
|
|
if (fd < 0) {
|
|
fprintf(stderr, "open(%s): %s\n",
|
|
templatefile, strerror(errno));
|
|
exit(2);
|
|
}
|
|
cc = read(fd, tbuf, sizeof(tbuf));
|
|
(void) close(fd);
|
|
if (cc < 0) {
|
|
fprintf(stderr, "read(%s): %s\n",
|
|
templatefile, strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (cc < NS_OFF_QUESTION + 6) {
|
|
fprintf(stderr,"file '%s' too small\n", templatefile);
|
|
exit(2);
|
|
}
|
|
if (cc > 4096) {
|
|
fprintf(stderr,"file '%s' too large\n", templatefile);
|
|
exit(2);
|
|
}
|
|
j = 0;
|
|
for (i = 0; i < cc; i++) {
|
|
if (isspace((int) tbuf[i]))
|
|
continue;
|
|
if (!isxdigit((int) tbuf[i])) {
|
|
fprintf(stderr,
|
|
"illegal char[%d]='%c' in file '%s'\n",
|
|
i, (int) tbuf[i], templatefile);
|
|
exit(2);
|
|
}
|
|
tbuf[j] = tbuf[i];
|
|
j++;
|
|
}
|
|
cc = j;
|
|
if ((cc & 1) != 0) {
|
|
fprintf(stderr,
|
|
"odd number of hexadecimal digits in file '%s'\n",
|
|
templatefile);
|
|
exit(2);
|
|
}
|
|
length_query = cc >> 1;
|
|
for (i = 0; i < cc; i += 2)
|
|
(void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]);
|
|
if (rndoffset >= 0)
|
|
random_query = (size_t) rndoffset;
|
|
if (random_query > length_query) {
|
|
fprintf(stderr,
|
|
"random (at %zu) outside the template (length %zu)?\n",
|
|
random_query, length_query);
|
|
exit(2);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* randomize the value of the given field:
|
|
* - offset of the field
|
|
* - random seed (used as it when suitable)
|
|
* - returns the random value which was used
|
|
*/
|
|
|
|
uint32_t
|
|
randomize(size_t offset, uint32_t r)
|
|
{
|
|
uint32_t v;
|
|
|
|
if (range == 0)
|
|
return 0;
|
|
if (range == UINT32_MAX)
|
|
return r;
|
|
if (maxrandom != 0)
|
|
while (r >= maxrandom)
|
|
r = (uint32_t) random();
|
|
r %= range + 1;
|
|
v = r;
|
|
v += obuf[offset];
|
|
obuf[offset] = v;
|
|
if (v < 256)
|
|
return r;
|
|
v >>= 8;
|
|
v += obuf[offset - 1];
|
|
obuf[offset - 1] = v;
|
|
if (v < 256)
|
|
return r;
|
|
v >>= 8;
|
|
v += obuf[offset - 2];
|
|
obuf[offset - 2] = v;
|
|
if (v < 256)
|
|
return r;
|
|
v >>= 8;
|
|
v += obuf[offset - 3];
|
|
obuf[offset - 3] = v;
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* flush/timeout connect
|
|
*/
|
|
|
|
void
|
|
flushconnect(void)
|
|
{
|
|
struct exchange *x;
|
|
struct timespec now;
|
|
int idx = xconn;
|
|
int cnt = 10;
|
|
double waited;
|
|
|
|
if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
|
|
perror("clock_gettime(flushconnect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
|
|
while (--cnt >= 0) {
|
|
if (idx < 0)
|
|
return;
|
|
x = xlist + idx;
|
|
idx = x->next;
|
|
if (x->state != X_CONN)
|
|
abort();
|
|
/* check for a timed-out connection */
|
|
waited = now.tv_sec - x->ts0.tv_sec;
|
|
waited += (now.tv_nsec - x->ts0.tv_nsec) / 1e9;
|
|
if (waited < losttime[0])
|
|
return;
|
|
/* garbage collect timed-out connections */
|
|
if (pthread_mutex_lock(&mtxconn) != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(flushconnect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
ISC_REMOVE(xconnl, x);
|
|
if (pthread_mutex_unlock(&mtxconn) != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(flushconnect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
(void) close(x->sock);
|
|
x->sock = -1;
|
|
collconn++;
|
|
if (pthread_mutex_lock(&mtxfree) != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(flushconnect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
x->state = X_FREE;
|
|
ISC_INSERT(xfree, xfreel, x);
|
|
if (pthread_mutex_unlock(&mtxfree) != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(flushconnect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* poll connected
|
|
*/
|
|
|
|
void
|
|
pollconnect(int topoll)
|
|
{
|
|
struct exchange *x;
|
|
int evn, idx, err;
|
|
socklen_t len = sizeof(int);
|
|
|
|
for (evn = 0; evn < topoll; evn++) {
|
|
idx = oevents[evn].data.fd;
|
|
x = xlist + idx;
|
|
if (x->state != X_CONN)
|
|
continue;
|
|
if (oevents[evn].events == 0)
|
|
continue;
|
|
if (pthread_mutex_lock(&mtxconn) != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(pollconnect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
ISC_REMOVE(xconnl, x);
|
|
if (pthread_mutex_unlock(&mtxconn) != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(pollconnect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
oevents[evn].events = 0;
|
|
if ((getsockopt(x->sock, SOL_SOCKET, SO_ERROR,
|
|
&err, &len) < 0) ||
|
|
(err != 0)) {
|
|
(void) close(x->sock);
|
|
x->sock = -1;
|
|
badconn++;
|
|
if (pthread_mutex_lock(&mtxfree) != 0) {
|
|
fprintf(stderr,
|
|
"pthread_mutex_lock(pollconnect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
x->state = X_FREE;
|
|
ISC_INSERT(xfree, xfreel, x);
|
|
if (pthread_mutex_unlock(&mtxfree) != 0) {
|
|
fprintf(stderr,
|
|
"pthread_mutex_unlock(pollconnect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
x->state = X_READY;
|
|
ISC_INSERT(xready, xreadyl, x);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* send the TCP DNS QUERY
|
|
*/
|
|
|
|
int
|
|
sendquery(struct exchange *x)
|
|
{
|
|
ssize_t ret;
|
|
size_t off;
|
|
|
|
if (udp)
|
|
off = 0;
|
|
else {
|
|
off = 2;
|
|
/* message length */
|
|
obuf[0] = length_query >> 8;
|
|
obuf[1]= length_query & 0xff;
|
|
}
|
|
/* message from template */
|
|
memcpy(obuf + off, template_query, length_query);
|
|
/* ID */
|
|
memcpy(obuf + off + NS_OFF_ID, &x->id, 2);
|
|
#if 0
|
|
/* random */
|
|
if (random_query > 0)
|
|
x->rnd = randomize(random_query + off, x->rnd);
|
|
#endif
|
|
/* timestamp */
|
|
errno = 0;
|
|
ret = clock_gettime(CLOCK_REALTIME, &x->ts2);
|
|
if (ret < 0) {
|
|
perror("clock_gettime(send)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -errno;
|
|
}
|
|
ret = send(x->sock, obuf, length_query + off, 0);
|
|
if ((size_t) ret == length_query + off)
|
|
return 0;
|
|
return -errno;
|
|
}
|
|
|
|
/*
|
|
* poll ready and send
|
|
*/
|
|
|
|
void
|
|
pollsend(void)
|
|
{
|
|
struct exchange *x;
|
|
int idx = xready;
|
|
struct epoll_event ev;
|
|
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
|
|
for (;;) {
|
|
if (idx < 0)
|
|
return;
|
|
x = xlist + idx;
|
|
ev.data.fd = idx;
|
|
idx = x->next;
|
|
ISC_REMOVE(xreadyl, x);
|
|
if (sendquery(x) < 0) {
|
|
(void) close(x->sock);
|
|
x->sock = -1;
|
|
badsent++;
|
|
if (pthread_mutex_lock(&mtxfree) != 0) {
|
|
fprintf(stderr,
|
|
"pthread_mutex_lock(pollsend)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
x->state = X_FREE;
|
|
ISC_INSERT(xfree, xfreel, x);
|
|
if (pthread_mutex_unlock(&mtxfree) != 0) {
|
|
fprintf(stderr,
|
|
"pthread_mutex_unlock(pollsend)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
continue;
|
|
}
|
|
xscount++;
|
|
if (pthread_mutex_lock(&mtxsent) != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(pollsend)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
x->state = X_SENT;
|
|
ISC_INSERT(xsent, xsentl, x);
|
|
if (pthread_mutex_unlock(&mtxsent) != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(pollsend)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
if (epoll_ctl(epoll_ifd, EPOLL_CTL_ADD, x->sock, &ev) < 0) {
|
|
perror("epoll_ctl(add input)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* receive a TCP DNS RESPONSE
|
|
*/
|
|
|
|
void
|
|
receiveresp(struct exchange *x)
|
|
{
|
|
struct timespec now;
|
|
ssize_t cc;
|
|
size_t off;
|
|
uint16_t v;
|
|
double delta;
|
|
|
|
cc = recv(x->sock, ibuf, sizeof(ibuf), 0);
|
|
if (cc < 0) {
|
|
if ((errno == EAGAIN) ||
|
|
(errno == EWOULDBLOCK) ||
|
|
(errno == EINTR) ||
|
|
(errno == ECONNRESET)) {
|
|
recverr++;
|
|
return;
|
|
}
|
|
perror("recv");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
if (udp)
|
|
off = 0;
|
|
else
|
|
off = 2;
|
|
/* enforce a reasonable length */
|
|
if ((size_t) cc < length_query + off) {
|
|
tooshort++;
|
|
return;
|
|
}
|
|
/* must match the ID */
|
|
if (memcmp(ibuf + off + NS_OFF_ID, &x->id, 2) != 0) {
|
|
badid++;
|
|
return;
|
|
}
|
|
/* must be a response */
|
|
memcpy(&v, ibuf + off + NS_OFF_FLAGS, 2);
|
|
v = ntohs(v);
|
|
if ((v & NS_FLAG_QR) == 0) {
|
|
notresp++;
|
|
return;
|
|
}
|
|
if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
|
|
perror("clock_gettime(receive)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
/* got it: update stats */
|
|
xrcount++;
|
|
x->ts3 = now;
|
|
delta = x->ts3.tv_sec - x->ts2.tv_sec;
|
|
delta += (x->ts3.tv_nsec - x->ts2.tv_nsec) / 1e9;
|
|
if (delta < dmin)
|
|
dmin = delta;
|
|
if (delta > dmax)
|
|
dmax = delta;
|
|
dsum += delta;
|
|
dsumsq += delta * delta;
|
|
v &= NS_RCODE_MASK;
|
|
if (v >= NS_RCODE_LAST)
|
|
v = NS_RCODE_LAST;
|
|
rcodes[v] += 1;
|
|
}
|
|
|
|
/*
|
|
* flush/timeout receive
|
|
*/
|
|
|
|
void
|
|
flushrecv(void)
|
|
{
|
|
struct exchange *x;
|
|
struct timespec now;
|
|
int idx = xsent;
|
|
int cnt = 5;
|
|
double waited;
|
|
|
|
if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
|
|
perror("clock_gettime(receive)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
|
|
while (--cnt >= 0) {
|
|
if (idx < 0)
|
|
return;
|
|
x = xlist + idx;
|
|
idx = x->next;
|
|
if (x->state != X_SENT)
|
|
abort();
|
|
/* check for a timed-out exchange */
|
|
waited = now.tv_sec - x->ts2.tv_sec;
|
|
waited += (now.tv_nsec - x->ts2.tv_nsec) / 1e9;
|
|
if (waited < losttime[1])
|
|
return;
|
|
/* garbage collect timed-out exchange */
|
|
if (pthread_mutex_lock(&mtxsent) != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(flushrecv)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
ISC_REMOVE(xsentl, x);
|
|
if (pthread_mutex_unlock(&mtxsent) != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(flushrecv)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
(void) close(x->sock);
|
|
x->sock = -1;
|
|
collsent++;
|
|
if (pthread_mutex_lock(&mtxfree) != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(flushrecv)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
x->state = X_FREE;
|
|
ISC_INSERT(xfree, xfreel, x);
|
|
if (pthread_mutex_unlock(&mtxfree) != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(flushrecv)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* poll receive
|
|
*/
|
|
|
|
void
|
|
pollrecv(int topoll)
|
|
{
|
|
struct exchange *x;
|
|
int evn, idx;
|
|
|
|
for (evn = 0; evn < topoll; evn++) {
|
|
idx = ievents[evn].data.fd;
|
|
x = xlist + idx;
|
|
if (x->state != X_SENT)
|
|
continue;
|
|
if (ievents[evn].events == 0)
|
|
continue;
|
|
if (pthread_mutex_lock(&mtxsent) != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(pollrecv)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
ISC_REMOVE(xsentl, x);
|
|
if (pthread_mutex_unlock(&mtxsent) != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(pollrecv)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
receiveresp(x);
|
|
ievents[evn].events = 0;
|
|
(void) close(x->sock);
|
|
x->sock = -1;
|
|
if (pthread_mutex_lock(&mtxfree) != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(pollrecv)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
x->state = X_FREE;
|
|
ISC_INSERT(xfree, xfreel, x);
|
|
if (pthread_mutex_unlock(&mtxfree) != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(pollrecv)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* get the TCP DNS socket descriptor (IPv4)
|
|
*/
|
|
|
|
int
|
|
getsock4(void)
|
|
{
|
|
int sock;
|
|
int flags;
|
|
|
|
errno = 0;
|
|
if (udp)
|
|
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
else
|
|
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (sock < 0)
|
|
return -errno;
|
|
|
|
/* make the socket descriptor not blocking */
|
|
flags = fcntl(sock, F_GETFL, 0);
|
|
if (flags == -1) {
|
|
(void) close(sock);
|
|
return -errno;
|
|
}
|
|
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
|
|
(void) close(sock);
|
|
return -errno;
|
|
}
|
|
|
|
/* bind if wanted */
|
|
if (localname != NULL) {
|
|
if (curport) {
|
|
struct sockaddr_in *l4;
|
|
|
|
l4 = (struct sockaddr_in *) &localaddr;
|
|
l4->sin_port = htons((uint16_t) curport);
|
|
curport++;
|
|
if (curport > maxport)
|
|
curport = minport;
|
|
}
|
|
if (bind(sock,
|
|
(struct sockaddr *) &localaddr,
|
|
sizeof(struct sockaddr_in)) < 0) {
|
|
(void) close(sock);
|
|
return -errno;
|
|
}
|
|
}
|
|
|
|
/* connect */
|
|
if (connect(sock,
|
|
(struct sockaddr *) &serveraddr,
|
|
sizeof(struct sockaddr_in)) < 0) {
|
|
if (errno != EINPROGRESS) {
|
|
(void) close(sock);
|
|
return -errno;
|
|
}
|
|
}
|
|
return sock;
|
|
}
|
|
|
|
/*
|
|
* connect the TCP DNS QUERY (IPv4)
|
|
*/
|
|
|
|
int
|
|
connect4(void)
|
|
{
|
|
struct exchange *x;
|
|
int ret;
|
|
int idx;
|
|
struct epoll_event ev;
|
|
|
|
ret = clock_gettime(CLOCK_REALTIME, &last);
|
|
if (ret < 0) {
|
|
perror("clock_gettime(connect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -errno;
|
|
}
|
|
|
|
if (xfree >= 0) {
|
|
idx = xfree;
|
|
x = xlist + idx;
|
|
ret = pthread_mutex_lock(&mtxfree);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(connect4)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
ISC_REMOVE(xfreel, x);
|
|
ret = pthread_mutex_unlock(&mtxfree);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(connect4)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
} else if (xused < xlast) {
|
|
idx = xused;
|
|
x = xlist + idx;
|
|
xused++;
|
|
} else
|
|
return -ENOMEM;
|
|
|
|
if ((x->state != X_FREE) || (x->sock != -1))
|
|
abort();
|
|
|
|
memset(x, 0, sizeof(*x));
|
|
memset(&ev, 0, sizeof(ev));
|
|
x->next = -1;
|
|
x->prev = NULL;
|
|
x->ts0 = last;
|
|
x->sock = getsock4();
|
|
if (x->sock < 0) {
|
|
int result = x->sock;
|
|
|
|
x->sock = -1;
|
|
ret = pthread_mutex_lock(&mtxfree);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(connect4)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
ISC_INSERT(xfree, xfreel, x);
|
|
ret = pthread_mutex_unlock(&mtxfree);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(connect4)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
return result;
|
|
}
|
|
ret = pthread_mutex_lock(&mtxconn);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(connect4)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
x->state = X_CONN;
|
|
ISC_INSERT(xconn, xconnl, x);
|
|
ret = pthread_mutex_unlock(&mtxconn);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(connect4)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
ev.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;
|
|
ev.data.fd = idx;
|
|
if (epoll_ctl(epoll_ofd, EPOLL_CTL_ADD, x->sock, &ev) < 0) {
|
|
perror("epoll_ctl(add output)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -errno;
|
|
}
|
|
x->order = xccount++;
|
|
x->id = (uint16_t) random();
|
|
#if 0
|
|
if (random_query > 0)
|
|
x->rnd = (uint32_t) random();
|
|
#endif
|
|
return idx;
|
|
}
|
|
|
|
/*
|
|
* get the TCP DNS socket descriptor (IPv6)
|
|
*/
|
|
|
|
int
|
|
getsock6(void)
|
|
{
|
|
int sock;
|
|
int flags;
|
|
|
|
errno = 0;
|
|
if (udp)
|
|
sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
else
|
|
sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
|
|
if (sock < 0)
|
|
return -errno;
|
|
|
|
/* make the socket descriptor not blocking */
|
|
flags = fcntl(sock, F_GETFL, 0);
|
|
if (flags == -1) {
|
|
(void) close(sock);
|
|
return -errno;
|
|
}
|
|
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
|
|
(void) close(sock);
|
|
return -errno;
|
|
}
|
|
|
|
/* bind if wanted */
|
|
if (localname != NULL) {
|
|
if (curport) {
|
|
struct sockaddr_in6 *l6;
|
|
|
|
l6 = (struct sockaddr_in6 *) &localaddr;
|
|
l6->sin6_port = htons((uint16_t) curport);
|
|
curport++;
|
|
if (curport > maxport)
|
|
curport = minport;
|
|
}
|
|
if (bind(sock,
|
|
(struct sockaddr *) &localaddr,
|
|
sizeof(struct sockaddr_in6)) < 0) {
|
|
(void) close(sock);
|
|
return -errno;
|
|
}
|
|
}
|
|
|
|
/* connect */
|
|
if (connect(sock,
|
|
(struct sockaddr *) &serveraddr,
|
|
sizeof(struct sockaddr_in6)) < 0) {
|
|
if (errno != EINPROGRESS) {
|
|
(void) close(sock);
|
|
return -errno;
|
|
}
|
|
}
|
|
return sock;
|
|
}
|
|
|
|
/*
|
|
* connect the TCP DNS QUERY (IPv6)
|
|
*/
|
|
|
|
int
|
|
connect6(void)
|
|
{
|
|
struct exchange *x;
|
|
int ret;
|
|
int idx;
|
|
struct epoll_event ev;
|
|
|
|
ret = clock_gettime(CLOCK_REALTIME, &last);
|
|
if (ret < 0) {
|
|
perror("clock_gettime(connect)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -errno;
|
|
}
|
|
|
|
if (xfree >= 0) {
|
|
idx = xfree;
|
|
x = xlist + idx;
|
|
ret = pthread_mutex_lock(&mtxfree);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(connect6)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
ISC_REMOVE(xfreel, x);
|
|
ret = pthread_mutex_unlock(&mtxfree);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(connect6)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
} else if (xused < xlast) {
|
|
idx = xused;
|
|
x = xlist + idx;
|
|
xused++;
|
|
} else
|
|
return -ENOMEM;
|
|
|
|
memset(x, 0, sizeof(*x));
|
|
memset(&ev, 0, sizeof(ev));
|
|
x->next = -1;
|
|
x->prev = NULL;
|
|
x->ts0 = last;
|
|
x->sock = getsock6();
|
|
if (x->sock < 0) {
|
|
int result = x->sock;
|
|
|
|
x->sock = -1;
|
|
ret = pthread_mutex_lock(&mtxfree);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(connect6)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
ISC_INSERT(xfree, xfreel, x);
|
|
ret = pthread_mutex_unlock(&mtxfree);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(connect6)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
return result;
|
|
}
|
|
ret = pthread_mutex_lock(&mtxconn);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_lock(connect6)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
x->state = X_CONN;
|
|
ISC_INSERT(xconn, xconnl, x);
|
|
ret = pthread_mutex_unlock(&mtxconn);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_mutex_unlock(connect6)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -ret;
|
|
}
|
|
ev.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;
|
|
ev.data.fd = idx;
|
|
if (epoll_ctl(epoll_ofd, EPOLL_CTL_ADD, x->sock, &ev) < 0) {
|
|
perror("epoll_ctl(add output)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
return -errno;
|
|
}
|
|
x->order = xccount++;
|
|
x->id = (uint16_t) random();
|
|
#if 0
|
|
if (random_query > 0)
|
|
x->rnd = (uint32_t) random();
|
|
#endif
|
|
return idx;
|
|
}
|
|
|
|
/*
|
|
* connector working routine
|
|
*/
|
|
|
|
void *
|
|
connecting(void *dummy)
|
|
{
|
|
struct timespec now, ts;
|
|
int ret;
|
|
int i;
|
|
char name[16];
|
|
|
|
dummy = dummy;
|
|
|
|
/* set conn-name */
|
|
memset(name, 0, sizeof(name));
|
|
ret = prctl(PR_GET_NAME, name, 0, 0, 0);
|
|
if (ret < 0)
|
|
perror("prctl(PR_GET_NAME)");
|
|
else {
|
|
memmove(name + 5, name, 11);
|
|
memcpy(name, "conn-", 5);
|
|
ret = prctl(PR_SET_NAME, name, 0, 0, 0);
|
|
if (ret < 0)
|
|
perror("prctl(PR_SET_NAME");
|
|
}
|
|
|
|
for (;;) {
|
|
if (fatal)
|
|
break;
|
|
|
|
loops[1]++;
|
|
|
|
/* compute the delay for the next connection */
|
|
if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
|
|
perror("clock_gettime(connecting)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
break;
|
|
}
|
|
|
|
due = last;
|
|
if (rate == 1)
|
|
due.tv_sec += 1;
|
|
else
|
|
due.tv_nsec += 1010000000 / rate;
|
|
while (due.tv_nsec >= 1000000000) {
|
|
due.tv_sec += 1;
|
|
due.tv_nsec -= 1000000000;
|
|
}
|
|
ts = due;
|
|
ts.tv_sec -= now.tv_sec;
|
|
ts.tv_nsec -= now.tv_nsec;
|
|
while (ts.tv_nsec < 0) {
|
|
ts.tv_sec -= 1;
|
|
ts.tv_nsec += 1000000000;
|
|
}
|
|
/* the connection was already due? */
|
|
if (ts.tv_sec < 0) {
|
|
ts.tv_sec = ts.tv_nsec = 0;
|
|
lateconn++;
|
|
} else {
|
|
/* wait until */
|
|
ret = clock_nanosleep(CLOCK_REALTIME, 0, &ts, NULL);
|
|
if (ret != 0) {
|
|
if (ret == EINTR)
|
|
continue;
|
|
fprintf(stderr, "clock_nanosleep: %s\n",
|
|
strerror(ret));
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* compute how many connections to open */
|
|
if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
|
|
perror("clock_gettime(connecting)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
break;
|
|
}
|
|
|
|
if ((now.tv_sec > due.tv_sec) ||
|
|
((now.tv_sec == due.tv_sec) &&
|
|
(now.tv_nsec >= due.tv_nsec))) {
|
|
double toconnect;
|
|
|
|
toconnect = (now.tv_nsec - due.tv_nsec) / 1e9;
|
|
toconnect += now.tv_sec - due.tv_sec;
|
|
toconnect *= rate;
|
|
toconnect++;
|
|
if (toconnect > (double) aggressiveness)
|
|
i = aggressiveness;
|
|
else
|
|
i = (int) toconnect;
|
|
compconn += i;
|
|
/* open connections */
|
|
while (i-- > 0) {
|
|
if (ipversion == 4)
|
|
ret = connect4();
|
|
else
|
|
ret = connect6();
|
|
if (ret < 0) {
|
|
if ((ret == -EAGAIN) ||
|
|
(ret == -EWOULDBLOCK) ||
|
|
(ret == -ENOBUFS) ||
|
|
(ret == -ENFILE) ||
|
|
(ret == -EMFILE) ||
|
|
(ret == -EADDRNOTAVAIL) ||
|
|
(ret == -ENOMEM))
|
|
locallimit++;
|
|
fprintf(stderr,
|
|
"connect: %s\n",
|
|
strerror(-ret));
|
|
break;
|
|
}
|
|
}
|
|
} else
|
|
/* there was no connection to open */
|
|
shortwait[0]++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* sender working routine
|
|
*/
|
|
|
|
void *
|
|
sending(void *dummy)
|
|
{
|
|
int ret;
|
|
int nfds;
|
|
char name[16];
|
|
|
|
dummy = dummy;
|
|
|
|
/* set send-name */
|
|
memset(name, 0, sizeof(name));
|
|
ret = prctl(PR_GET_NAME, name, 0, 0, 0);
|
|
if (ret < 0)
|
|
perror("prctl(PR_GET_NAME)");
|
|
else {
|
|
memmove(name + 5, name, 11);
|
|
memcpy(name, "send-", 5);
|
|
ret = prctl(PR_SET_NAME, name, 0, 0, 0);
|
|
if (ret < 0)
|
|
perror("prctl(PR_SET_NAME");
|
|
}
|
|
|
|
for (;;) {
|
|
if (fatal)
|
|
break;
|
|
|
|
loops[2]++;
|
|
|
|
/* epoll_wait() */
|
|
memset(oevents, 0, sizeof(oevents));
|
|
nfds = epoll_wait(epoll_ofd, oevents, EVENTS_CNT, 1);
|
|
if (nfds < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
perror("epoll_wait(output)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
break;
|
|
}
|
|
|
|
/* connection(s) to finish */
|
|
if (nfds == 0)
|
|
shortwait[1]++;
|
|
else
|
|
pollconnect(nfds);
|
|
if (fatal)
|
|
break;
|
|
flushconnect();
|
|
if (fatal)
|
|
break;
|
|
|
|
/* packet(s) to send */
|
|
pollsend();
|
|
if (fatal)
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* receiver working routine
|
|
*/
|
|
|
|
void *
|
|
receiving(void *dummy)
|
|
{
|
|
int ret;
|
|
int nfds;
|
|
char name[16];
|
|
|
|
dummy = dummy;
|
|
|
|
/* set recv-name */
|
|
memset(name, 0, sizeof(name));
|
|
ret = prctl(PR_GET_NAME, name, 0, 0, 0);
|
|
if (ret < 0)
|
|
perror("prctl(PR_GET_NAME)");
|
|
else {
|
|
memmove(name + 5, name, 11);
|
|
memcpy(name, "recv-", 5);
|
|
ret = prctl(PR_SET_NAME, name, 0, 0, 0);
|
|
if (ret < 0)
|
|
perror("prctl(PR_SET_NAME");
|
|
}
|
|
|
|
for (;;) {
|
|
if (fatal)
|
|
break;
|
|
|
|
loops[3]++;
|
|
|
|
/* epoll_wait() */
|
|
memset(ievents, 0, sizeof(ievents));
|
|
nfds = epoll_wait(epoll_ifd, ievents, EVENTS_CNT, 1);
|
|
if (nfds < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
perror("epoll_wait(input)");
|
|
fatal = 1;
|
|
(void) pthread_kill(master, SIGTERM);
|
|
break;
|
|
}
|
|
|
|
/* packet(s) to receive */
|
|
if (nfds == 0)
|
|
shortwait[2]++;
|
|
else
|
|
pollrecv(nfds);
|
|
if (fatal)
|
|
break;
|
|
flushrecv();
|
|
if (fatal)
|
|
break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* get the server socket address from the command line:
|
|
* - flags: inherited from main, 0 or AI_NUMERICHOST (for literals)
|
|
*/
|
|
|
|
void
|
|
getserveraddr(const int flags)
|
|
{
|
|
struct addrinfo hints, *res;
|
|
int ret;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
if (ipversion == 4)
|
|
hints.ai_family = AF_INET;
|
|
else
|
|
hints.ai_family = AF_INET6;
|
|
if (udp) {
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
hints.ai_protocol = IPPROTO_UDP;
|
|
} else {
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
}
|
|
hints.ai_flags = AI_ADDRCONFIG | flags;
|
|
|
|
ret = getaddrinfo(servername, NULL, &hints, &res);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "bad server=%s: %s\n",
|
|
servername, gai_strerror(ret));
|
|
exit(2);
|
|
}
|
|
if (res->ai_next != NULL) {
|
|
fprintf(stderr, "ambiguous server=%s\n", servername);
|
|
exit(2);
|
|
}
|
|
memcpy(&serveraddr, res->ai_addr, res->ai_addrlen);
|
|
freeaddrinfo(res);
|
|
if (ipversion == 4)
|
|
((struct sockaddr_in *)&serveraddr)->sin_port = htons(port);
|
|
else
|
|
((struct sockaddr_in6 *)&serveraddr)->sin6_port = htons(port);
|
|
}
|
|
|
|
/*
|
|
* get the local socket address from the command line
|
|
*/
|
|
|
|
void
|
|
getlocaladdr(void)
|
|
{
|
|
struct addrinfo hints, *res;
|
|
int ret;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
if (ipversion == 4)
|
|
hints.ai_family = AF_INET;
|
|
else
|
|
hints.ai_family = AF_INET6;
|
|
if (udp) {
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
hints.ai_protocol = IPPROTO_UDP;
|
|
} else {
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
}
|
|
hints.ai_flags = AI_ADDRCONFIG;
|
|
|
|
ret = getaddrinfo(localname, NULL, &hints, &res);
|
|
if (ret != 0) {
|
|
fprintf(stderr,
|
|
"bad -l<local-addr=%s>: %s\n",
|
|
localname,
|
|
gai_strerror(ret));
|
|
exit(2);
|
|
}
|
|
/* refuse multiple addresses */
|
|
if (res->ai_next != NULL) {
|
|
fprintf(stderr,
|
|
"ambiguous -l<local-addr=%s>\n",
|
|
localname);
|
|
exit(2);
|
|
}
|
|
memcpy(&localaddr, res->ai_addr, res->ai_addrlen);
|
|
freeaddrinfo(res);
|
|
}
|
|
|
|
/*
|
|
* intermediate reporting
|
|
* (note: an in-transit packet can be reported as lost)
|
|
*/
|
|
|
|
void
|
|
reporting(void)
|
|
{
|
|
dreport.tv_sec += report;
|
|
|
|
if (xccount != 0) {
|
|
printf("connect: %llu, sent: %llu, received: %llu "
|
|
"(embryonics: %lld, drops: %lld)",
|
|
(unsigned long long) xccount,
|
|
(unsigned long long) xscount,
|
|
(unsigned long long) xrcount,
|
|
(long long) (xccount - xscount),
|
|
(long long) (xscount - xrcount));
|
|
if (xrcount != 0) {
|
|
double avg;
|
|
|
|
avg = dsum / xrcount;
|
|
printf(" average: %.3f ms", avg * 1e3);
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
/*
|
|
* SIGCHLD handler
|
|
*/
|
|
|
|
void
|
|
reapchild(int sig)
|
|
{
|
|
int status;
|
|
|
|
sig = sig;
|
|
while (wait3(&status, WNOHANG, NULL) > 0)
|
|
/* continue */;
|
|
}
|
|
|
|
/*
|
|
* SIGINT handler
|
|
*/
|
|
|
|
void
|
|
interrupt(int sig)
|
|
{
|
|
sig = sig;
|
|
interrupted = 1;
|
|
}
|
|
|
|
/*
|
|
* SIGTERM handler
|
|
*/
|
|
|
|
void
|
|
terminate(int sig)
|
|
{
|
|
sig = sig;
|
|
fatal = 1;
|
|
}
|
|
|
|
/*
|
|
* '-v' handler
|
|
*/
|
|
|
|
void
|
|
version(void)
|
|
{
|
|
fprintf(stderr, "version 0.01\n");
|
|
}
|
|
|
|
/*
|
|
* usage (from the wiki)
|
|
*/
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "%s",
|
|
"perftcpdns [-huvX0] [-4|-6] [-r<rate>] [-t<report>] [-p<test-period>]\n"
|
|
" [-n<num-request>]* [-d<lost-time>]* [-D<max-loss>]* [-T<template-file>]\n"
|
|
" [-l<local-addr>] [-L<local-port>]* [-a<aggressiveness>] [-s<seed>]\n"
|
|
" [-M<memory>] [-x<diagnostic-selector>] [-P<port>] server\n"
|
|
"\f\n"
|
|
"The server argument is the name/address of the DNS server to contact.\n"
|
|
"\n"
|
|
"Options:\n"
|
|
"-0: Add EDNS0 option with DO flag.\n"
|
|
"-4: TCP/IPv4 operation (default). This is incompatible with the -6 option.\n"
|
|
"-6: TCP/IPv6 operation. This is incompatible with the -4 option.\n"
|
|
"-a<aggressiveness>: When the target sending rate is not yet reached,\n"
|
|
" control how many connections are initiated before the next pause.\n"
|
|
"-d<lost-time>: Specify the time after which a connection or a query is\n"
|
|
" treated as having been lost. The value is given in seconds and\n"
|
|
" may contain a fractional component. The default is 1 second.\n"
|
|
"-h: Print this help.\n"
|
|
"-l<local-addr>: Specify the local hostname/address to use when\n"
|
|
" communicating with the server.\n"
|
|
"-L<local-port>: Specify the (minimal and maximal) local port number\n"
|
|
"-M<memory>: Size of the tables (default 60000)\n"
|
|
"-P<port>: Specify an alternate (i.e., not 53) port\n"
|
|
"-r<rate>: Initiate <rate> TCP DNS connections per second. A periodic\n"
|
|
" report is generated showing the number of exchanges which were not\n"
|
|
" completed, as well as the average response latency. The program\n"
|
|
" continues until interrupted, at which point a final report is\n"
|
|
" generated.\n"
|
|
"-s<seed>: Specify the seed for randomization, making it repeatable.\n"
|
|
"-t<report>: Delay in seconds between two periodic reports.\n"
|
|
"-T<template-file>: The name of a file containing the template to use\n"
|
|
" as a stream of hexadecimal digits.\n"
|
|
"-u: Use UDP in place of TCP.\n"
|
|
"-v: Report the version number of this program.\n"
|
|
"-X: change default template to get NXDOMAIN responses.\n"
|
|
"-x<diagnostic-selector>: Include extended diagnostics in the output.\n"
|
|
" <diagnostic-selector> is a string of single-keywords specifying\n"
|
|
" the operations for which verbose output is desired. The selector\n"
|
|
" keyletters are:\n"
|
|
" * 'a': print the decoded command line arguments\n"
|
|
" * 'e': print the exit reason\n"
|
|
" * 'i': print rate processing details\n"
|
|
" * 'T': when finished, print templates\n"
|
|
"\n"
|
|
"Stopping conditions:\n"
|
|
"-D<max-loss>: Abort the test if more than <max-loss> connections or\n"
|
|
" queries have been lost. If <max-loss> includes the suffix '%', it\n"
|
|
" specifies a maximum percentage of losses before stopping.\n"
|
|
" In this case, testing of the threshold begins after 10\n"
|
|
" connections/responses have been expected to be accepted/received.\n"
|
|
"-n<num-request>: Initiate <num-request> transactions. No report is\n"
|
|
" generated until all transactions have been initiated/waited-for,\n"
|
|
" after which a report is generated and the program terminates.\n"
|
|
"-p<test-period>: Send requests for the given test period, which is\n"
|
|
" specified in the same manner as -d. This can be used as an\n"
|
|
" alternative to -n, or both options can be given, in which case the\n"
|
|
" testing is completed when either limit is reached.\n"
|
|
"\n"
|
|
"Errors:\n"
|
|
"- locallimit: reached to local system limits when sending a message.\n"
|
|
"- badconn: connection failed (from getsockopt(SO_ERROR))\n"
|
|
"- collconn: connect() timed out\n"
|
|
"- badsent: send() failed\n"
|
|
"- callsent: timed out waiting from a response\n"
|
|
"- recverr: recv() system call failed\n"
|
|
"- tooshort: received a too short message\n"
|
|
"- badid: the id mismatches between the query and the response\n"
|
|
"- notresp: doesn't receive a response\n"
|
|
"Rate stats:\n"
|
|
"- loops: number of thread loop iterations\n"
|
|
"- shortwait: no direct activity in a thread iteration\n"
|
|
"- compconn: computed number of connect() calls\n"
|
|
"- lateconn: connect() already dued when computing delay to the next one\n"
|
|
"\n"
|
|
"Exit status:\n"
|
|
"The exit status is:\n"
|
|
"0 on complete success.\n"
|
|
"1 for a general error.\n"
|
|
"2 if an error is found in the command line arguments.\n"
|
|
"3 if there are no general failures in operation, but one or more\n"
|
|
" exchanges are not successfully completed.\n");
|
|
}
|
|
|
|
/*
|
|
* main function / entry point
|
|
*/
|
|
|
|
int
|
|
main(const int argc, char * const argv[])
|
|
{
|
|
int opt, flags = 0, ret, i;
|
|
long long r;
|
|
char *pc;
|
|
double d;
|
|
extern char *optarg;
|
|
extern int optind;
|
|
|
|
#define OPTIONS "hv46u0XM:r:t:R:b:n:p:d:D:l:L:a:s:T:O:x:P:"
|
|
|
|
/* decode options */
|
|
while ((opt = getopt(argc, argv, OPTIONS)) != -1)
|
|
switch (opt) {
|
|
case 'h':
|
|
usage();
|
|
exit(0);
|
|
|
|
case 'u':
|
|
udp = 1;
|
|
break;
|
|
|
|
case 'v':
|
|
version();
|
|
exit(0);
|
|
|
|
case '0':
|
|
edns0 = 1;
|
|
break;
|
|
|
|
case '4':
|
|
if (ipversion == 6) {
|
|
fprintf(stderr, "IP version already set to 6\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
ipversion = 4;
|
|
break;
|
|
|
|
case '6':
|
|
if (ipversion == 4) {
|
|
fprintf(stderr, "IP version already set to 4\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
ipversion = 6;
|
|
break;
|
|
|
|
case 'X':
|
|
ixann = 1;
|
|
break;
|
|
|
|
case 'M':
|
|
xlast = atoi(optarg);
|
|
if (xlast <= 1000) {
|
|
fprintf(stderr, "memory must be greater than 1000\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
break;
|
|
|
|
case 'r':
|
|
rate = atoi(optarg);
|
|
if (rate <= 0) {
|
|
fprintf(stderr, "rate must be a positive integer\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
break;
|
|
|
|
case 't':
|
|
report = atoi(optarg);
|
|
if (report <= 0) {
|
|
fprintf(stderr, "report must be a positive integer\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
break;
|
|
|
|
case 'R':
|
|
r = atoll(optarg);
|
|
if (r < 0) {
|
|
fprintf(stderr,
|
|
"range must not be a negative integer\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
range = (uint32_t) r;
|
|
if ((range != 0) && (range != UINT32_MAX)) {
|
|
uint32_t s = range + 1;
|
|
uint64_t b = UINT32_MAX + 1, m;
|
|
|
|
m = (b / s) * s;
|
|
if (m == b)
|
|
maxrandom = 0;
|
|
else
|
|
maxrandom = (uint32_t) m;
|
|
}
|
|
break;
|
|
|
|
case 'b':
|
|
if (basecnt > 1) {
|
|
fprintf(stderr, "too many bases\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
base[basecnt] = optarg;
|
|
/* decodebase(); */
|
|
basecnt++;
|
|
break;
|
|
|
|
case 'n':
|
|
noreport = 1;
|
|
gotnumreq++;
|
|
if (gotnumreq > 1) {
|
|
fprintf(stderr, "too many num-request's\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
numreq[gotnumreq] = atoi(optarg);
|
|
if ((numreq[gotnumreq] < 0) ||
|
|
((numreq[gotnumreq] == 0) && (gotnumreq == 1))) {
|
|
fprintf(stderr,
|
|
"num-request must be a positive integer\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
break;
|
|
|
|
case 'p':
|
|
noreport = 1;
|
|
period = atoi(optarg);
|
|
if (period <= 0) {
|
|
fprintf(stderr,
|
|
"test-period must be a positive integer\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
break;
|
|
|
|
case 'd':
|
|
gotlosttime++;
|
|
if (gotlosttime > 1) {
|
|
fprintf(stderr, "too many lost-time's\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
d = atof(optarg);
|
|
if ((d < 0.) || ((d == 0.) && (gotlosttime == 1))) {
|
|
fprintf(stderr,
|
|
"lost-time must be a positive number\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
if (d > 0.)
|
|
losttime[gotlosttime] = d;
|
|
break;
|
|
|
|
case 'D':
|
|
noreport = 1;
|
|
gotmaxloss++;
|
|
if (gotmaxloss > 1) {
|
|
fprintf(stderr, "too many max-loss's\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
pc = strchr(optarg, '%');
|
|
if (pc != NULL) {
|
|
*pc = '\0';
|
|
maxploss[gotmaxloss] = atof(optarg);
|
|
if ((maxploss[gotmaxloss] < 0) ||
|
|
(maxploss[gotmaxloss] >= 100)) {
|
|
fprintf(stderr,
|
|
"invalid max-loss percentage\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
} else {
|
|
maxloss[gotmaxloss] = atoi(optarg);
|
|
if ((maxloss[gotmaxloss] < 0) ||
|
|
((maxloss[gotmaxloss] == 0) &&
|
|
(gotmaxloss == 1))) {
|
|
fprintf(stderr,
|
|
"max-loss must be a "
|
|
"positive integer\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'l':
|
|
localname = optarg;
|
|
break;
|
|
|
|
case 'L':
|
|
i = atoi(optarg);
|
|
if ((i <= 0) || (i >65535)) {
|
|
fprintf(stderr,
|
|
"local-port must be a small positive integer\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
if (maxport != 0) {
|
|
fprintf(stderr, "too many local-port's\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
if (curport == 0)
|
|
minport = curport = i;
|
|
else
|
|
maxport = i;
|
|
break;
|
|
|
|
case 'a':
|
|
aggressiveness = atoi(optarg);
|
|
if (aggressiveness <= 0) {
|
|
fprintf(stderr,
|
|
"aggressiveness must be a positive integer\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
seeded = 1;
|
|
seed = (unsigned int) atol(optarg);
|
|
break;
|
|
|
|
case 'T':
|
|
if (templatefile != NULL) {
|
|
fprintf(stderr, "template-file is already set\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
templatefile = optarg;
|
|
break;
|
|
|
|
case 'O':
|
|
rndoffset = atoi(optarg);
|
|
if (rndoffset < 14) {
|
|
fprintf(stderr,
|
|
"random-offset must be greater than 14\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
break;
|
|
|
|
case 'x':
|
|
diags = optarg;
|
|
break;
|
|
|
|
case 'P':
|
|
i = atoi(optarg);
|
|
if ((i <= 0) || (i > 65535)) {
|
|
fprintf(stderr,
|
|
"port must be a positive short integer\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
port = (in_port_t) i;
|
|
break;
|
|
|
|
default:
|
|
usage();
|
|
exit(2);
|
|
}
|
|
|
|
/* adjust some global variables */
|
|
if (ipversion == 0)
|
|
ipversion = 4;
|
|
if (rate == 0)
|
|
rate = 100;
|
|
if (xlast == 0)
|
|
xlast = 60000;
|
|
if (noreport == 0)
|
|
report = 1;
|
|
if ((curport != 0) && (maxport == 0))
|
|
maxport = 65535;
|
|
|
|
/* when required, print the internal view of the command line */
|
|
if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
|
|
if (udp)
|
|
printf("UDP ");
|
|
printf("IPv%d", ipversion);
|
|
printf(" rate=%d", rate);
|
|
if (edns0 != 0)
|
|
printf(" EDNS0");
|
|
if (report != 0)
|
|
printf(" report=%d", report);
|
|
if (range != 0) {
|
|
if (strchr(diags, 'r') != NULL)
|
|
printf(" range=0..%d [0x%x]",
|
|
range,
|
|
(unsigned int) maxrandom);
|
|
else
|
|
printf(" range=0..%d", range);
|
|
}
|
|
if (basecnt != 0)
|
|
for (i = 0; i < basecnt; i++)
|
|
printf(" base[%d]='%s'", i, base[i]);
|
|
if (gotnumreq >= 0) {
|
|
if ((numreq[0] == 0) && (numreq[1] != 0))
|
|
printf(" num-request=*,%d", numreq[1]);
|
|
if ((numreq[0] != 0) && (numreq[1] == 0))
|
|
printf(" num-request=%d,*", numreq[0]);
|
|
if ((numreq[0] != 0) && (numreq[1] != 0))
|
|
printf(" num-request=%d,%d",
|
|
numreq[0], numreq[1]);
|
|
}
|
|
if (period != 0)
|
|
printf(" test-period=%d", period);
|
|
printf(" lost-time=%g,%g", losttime[0], losttime[1]);
|
|
if (gotmaxloss == 0) {
|
|
if (maxloss[0] != 0)
|
|
printf(" max-loss=%d,*", maxloss[0]);
|
|
if (maxploss[0] != 0.)
|
|
printf(" max-loss=%2.2f%%,*", maxploss[0]);
|
|
} else if (gotmaxloss == 1) {
|
|
if (maxloss[0] != 0)
|
|
printf(" max-loss=%d,", maxloss[0]);
|
|
else if (maxploss[0] != 0.)
|
|
printf(" max-loss=%2.2f%%,", maxploss[0]);
|
|
else
|
|
printf(" max-loss=*,");
|
|
if (maxloss[1] != 0)
|
|
printf("%d", maxloss[1]);
|
|
else if (maxploss[1] != 0.)
|
|
printf("%2.2f%%", maxploss[1]);
|
|
else
|
|
printf("*");
|
|
}
|
|
printf(" aggressiveness=%d", aggressiveness);
|
|
if (seeded)
|
|
printf(" seed=%u", seed);
|
|
if (templatefile != NULL)
|
|
printf(" template-file='%s'", templatefile);
|
|
else if (ixann != 0)
|
|
printf(" Xflag");
|
|
if (rndoffset >= 0)
|
|
printf(" rnd-offset=%d", rndoffset);
|
|
printf(" diagnotic-selectors='%s'", diags);
|
|
printf("\n");
|
|
}
|
|
|
|
/* check local address options */
|
|
if ((localname == NULL) && (curport != 0)) {
|
|
fprintf(stderr,
|
|
"-l<local-addr> must be set to use -L<local-port>\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
|
|
/* check template file options */
|
|
if ((templatefile == NULL) && (rndoffset >= 0)) {
|
|
fprintf(stderr,
|
|
"-T<template-file> must be set to "
|
|
"use -O<random-offset>\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
|
|
/* check various template file(s) and other condition(s) options */
|
|
if ((templatefile != NULL) && (range > 0) && (rndoffset < 0)) {
|
|
fprintf(stderr,
|
|
"-O<random-offset> must be set when "
|
|
"-T<template-file> and -R<range> are used\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
|
|
/* get the server argument */
|
|
if (optind < argc - 1) {
|
|
fprintf(stderr, "extra arguments?\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
if (optind == argc - 1)
|
|
servername = argv[optind];
|
|
|
|
/* handle the local '-l' address/interface */
|
|
if (localname != NULL) {
|
|
/* given */
|
|
getlocaladdr();
|
|
if ((diags != NULL) && (strchr(diags, 'a') != NULL)) {
|
|
printf("local-addr='%s'", localname);
|
|
if (curport != 0)
|
|
printf(" local-port='%d..%d'",
|
|
minport, maxport);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/* get the server socket address */
|
|
if (servername == NULL) {
|
|
fprintf(stderr, "server is required\n");
|
|
usage();
|
|
exit(2);
|
|
}
|
|
getserveraddr(flags);
|
|
|
|
/* finish local/server socket address stuff and print it */
|
|
if ((diags != NULL) && (strchr(diags, 'a') != NULL))
|
|
printf("server='%s'\n", servername);
|
|
if ((localname != NULL) &&
|
|
(diags != NULL) && (strchr(diags, 'a') != NULL)) {
|
|
char addr[NI_MAXHOST];
|
|
|
|
ret = getnameinfo((struct sockaddr *) &localaddr,
|
|
sizeof(localaddr),
|
|
addr,
|
|
NI_MAXHOST,
|
|
NULL,
|
|
0,
|
|
NI_NUMERICHOST);
|
|
if (ret != 0) {
|
|
fprintf(stderr,
|
|
"can't get the local address: %s\n",
|
|
gai_strerror(ret));
|
|
exit(1);
|
|
}
|
|
printf("local address='%s'\n", addr);
|
|
}
|
|
|
|
/* initialize exchange structures */
|
|
inits();
|
|
|
|
/* get the socket descriptor and template(s) */
|
|
if (templatefile == NULL)
|
|
build_template_query();
|
|
else
|
|
get_template_query();
|
|
|
|
/* boot is done! */
|
|
if (clock_gettime(CLOCK_REALTIME, &boot) < 0) {
|
|
perror("clock_gettime(boot)");
|
|
exit(1);
|
|
}
|
|
|
|
/* compute the next intermediate reporting date */
|
|
if (report != 0) {
|
|
dreport.tv_sec = boot.tv_sec + report;
|
|
dreport.tv_nsec = boot.tv_nsec;
|
|
}
|
|
|
|
/* seed the random generator */
|
|
if (seeded == 0)
|
|
seed = (unsigned int) (boot.tv_sec + boot.tv_nsec);
|
|
srandom(seed);
|
|
|
|
/* required only before the interrupted flag check */
|
|
(void) signal(SIGINT, interrupt);
|
|
(void) signal(SIGTERM, terminate);
|
|
|
|
/* threads */
|
|
master = pthread_self();
|
|
ret = pthread_create(&connector, NULL, connecting, NULL);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_create: %s\n", strerror(ret));
|
|
exit(1);
|
|
}
|
|
ret = pthread_create(&sender, NULL, sending, NULL);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_create: %s\n", strerror(ret));
|
|
exit(1);
|
|
}
|
|
ret = pthread_create(&receiver, NULL, receiving, NULL);
|
|
if (ret != 0) {
|
|
fprintf(stderr, "pthread_create: %s\n", strerror(ret));
|
|
exit(1);
|
|
}
|
|
|
|
/* main loop */
|
|
for (;;) {
|
|
struct timespec now, ts;
|
|
|
|
/* immediate loop exit conditions */
|
|
if (interrupted) {
|
|
if ((diags != NULL) && (strchr(diags, 'e') != NULL))
|
|
printf("interrupted\n");
|
|
break;
|
|
}
|
|
if (fatal) {
|
|
if ((diags != NULL) && (strchr(diags, 'e') != NULL))
|
|
printf("got a fatal error\n");
|
|
break;
|
|
}
|
|
|
|
loops[0]++;
|
|
|
|
/* get the date and use it */
|
|
if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
|
|
perror("clock_gettime(now)");
|
|
fatal = 1;
|
|
continue;
|
|
}
|
|
if ((period != 0) &&
|
|
((boot.tv_sec + period < now.tv_sec) ||
|
|
((boot.tv_sec + period == now.tv_sec) &&
|
|
(boot.tv_nsec < now.tv_nsec)))) {
|
|
if ((diags != NULL) && (strchr(diags, 'e') != NULL))
|
|
printf("reached test-period\n");
|
|
break;
|
|
}
|
|
if ((report != 0) &&
|
|
((dreport.tv_sec < now.tv_sec) ||
|
|
((dreport.tv_sec == now.tv_sec) &&
|
|
(dreport.tv_nsec < now.tv_nsec))))
|
|
reporting();
|
|
|
|
/* check receive loop exit conditions */
|
|
if ((numreq[0] != 0) && ((int) xccount >= numreq[0])) {
|
|
if ((diags != NULL) && (strchr(diags, 'e') != NULL))
|
|
printf("reached num-connection\n");
|
|
break;
|
|
}
|
|
if ((numreq[1] != 0) && ((int) xscount >= numreq[1])) {
|
|
if ((diags != NULL) && (strchr(diags, 'e') != NULL))
|
|
printf("reached num-query\n");
|
|
break;
|
|
}
|
|
if ((maxloss[0] != 0) &&
|
|
((int) (xccount - xscount) > maxloss[0])) {
|
|
if ((diags != NULL) && (strchr(diags, 'e') != NULL))
|
|
printf("reached max-loss "
|
|
"(connection/absolute)\n");
|
|
break;
|
|
}
|
|
if ((maxloss[1] != 0) &&
|
|
((int) (xscount - xrcount) > maxloss[1])) {
|
|
if ((diags != NULL) && (strchr(diags, 'e') != NULL))
|
|
printf("reached max-loss "
|
|
"(query/absolute)\n");
|
|
break;
|
|
}
|
|
if ((maxploss[0] != 0.) &&
|
|
(xccount > 10) &&
|
|
(((100. * (xccount - xscount)) / xccount) > maxploss[1])) {
|
|
if ((diags != NULL) && (strchr(diags, 'e') != NULL))
|
|
printf("reached max-loss "
|
|
"(connection/percent)\n");
|
|
break;
|
|
}
|
|
if ((maxploss[1] != 0.) &&
|
|
(xscount > 10) &&
|
|
(((100. * (xscount - xrcount)) / xscount) > maxploss[1])) {
|
|
if ((diags != NULL) && (strchr(diags, 'e') != NULL))
|
|
printf("reached max-loss "
|
|
"(query/percent)\n");
|
|
break;
|
|
}
|
|
|
|
/* waiting 1ms */
|
|
memset(&ts, 0, sizeof(ts));
|
|
ts.tv_nsec = 1000000;
|
|
(void) clock_nanosleep(CLOCK_REALTIME, 0, &ts, NULL);
|
|
}
|
|
|
|
/* after main loop: finished */
|
|
if (clock_gettime(CLOCK_REALTIME, &finished) < 0)
|
|
perror("clock_gettime(finished)");
|
|
|
|
/* threads */
|
|
(void) pthread_cancel(connector);
|
|
(void) pthread_cancel(sender);
|
|
(void) pthread_cancel(receiver);
|
|
|
|
/* main statictics */
|
|
printf("connect: %llu, sent: %llu, received: %llu\n",
|
|
(unsigned long long) xccount,
|
|
(unsigned long long) xscount,
|
|
(unsigned long long) xrcount);
|
|
printf("embryonics: %lld (%.1f%%)\n",
|
|
(long long) (xccount - xscount),
|
|
(100. * (xccount - xscount)) / xccount);
|
|
printf("drops: %lld (%.1f%%)\n",
|
|
(long long) (xscount - xrcount),
|
|
(100. * (xscount - xrcount)) / xscount);
|
|
printf("total losses: %lld (%.1f%%)\n",
|
|
(long long) (xccount - xrcount),
|
|
(100. * (xccount - xrcount)) / xccount);
|
|
printf("local limits: %llu, bad connects: %llu, "
|
|
"connect timeouts: %llu\n",
|
|
(unsigned long long) locallimit,
|
|
(unsigned long long) badconn,
|
|
(unsigned long long) collconn);
|
|
printf("bad sends: %llu, bad recvs: %llu, recv timeouts: %llu\n",
|
|
(unsigned long long) badsent,
|
|
(unsigned long long) recverr,
|
|
(unsigned long long) collsent);
|
|
printf("too shorts: %llu, bad IDs: %llu, not responses: %llu\n",
|
|
(unsigned long long) tooshort,
|
|
(unsigned long long) badid,
|
|
(unsigned long long) notresp);
|
|
printf("rcode counters:\n noerror: %llu, formerr: %llu, "
|
|
"servfail: %llu\n "
|
|
"nxdomain: %llu, noimp: %llu, refused: %llu, others: %llu\n",
|
|
(unsigned long long) rcodes[NS_RCODE_NOERROR],
|
|
(unsigned long long) rcodes[NS_RCODE_FORMERR],
|
|
(unsigned long long) rcodes[NS_RCODE_SERVFAIL],
|
|
(unsigned long long) rcodes[NS_RCODE_NXDOMAIN],
|
|
(unsigned long long) rcodes[NS_RCODE_NOIMP],
|
|
(unsigned long long) rcodes[NS_RCODE_REFUSED],
|
|
(unsigned long long) rcodes[NS_RCODE_LAST]);
|
|
|
|
/* print the rates */
|
|
if (finished.tv_sec != 0) {
|
|
double dall, erate[3];
|
|
|
|
dall = (finished.tv_nsec - boot.tv_nsec) / 1e9;
|
|
dall += finished.tv_sec - boot.tv_sec;
|
|
erate[0] = xccount / dall;
|
|
erate[1] = xscount / dall;
|
|
erate[2] = xrcount / dall;
|
|
printf("rates: %.0f,%.0f,%.0f (target %d)\n",
|
|
erate[0], erate[1], erate[2], rate);
|
|
}
|
|
|
|
/* rate processing instrumentation */
|
|
if ((diags != NULL) && (strchr(diags, 'i') != NULL)) {
|
|
printf("loops: %llu,%llu,%llu,%llu\n",
|
|
(unsigned long long) loops[0],
|
|
(unsigned long long) loops[1],
|
|
(unsigned long long) loops[2],
|
|
(unsigned long long) loops[3]);
|
|
printf("shortwait: %llu,%llu,%llu\n",
|
|
(unsigned long long) shortwait[0],
|
|
(unsigned long long) shortwait[1],
|
|
(unsigned long long) shortwait[2]);
|
|
printf("compconn: %llu, lateconn: %llu\n",
|
|
(unsigned long long) compconn,
|
|
(unsigned long long) lateconn);
|
|
printf("badconn: %llu, collconn: %llu, "
|
|
"recverr: %llu, collsent: %llu\n",
|
|
(unsigned long long) badconn,
|
|
(unsigned long long) collconn,
|
|
(unsigned long long) recverr,
|
|
(unsigned long long) collsent);
|
|
printf("memory: used(%d) / allocated(%d)\n",
|
|
xused, xlast);
|
|
}
|
|
|
|
/* round-time trip statistics */
|
|
if (xrcount != 0) {
|
|
double avg, stddev;
|
|
|
|
avg = dsum / xrcount;
|
|
stddev = sqrt(dsumsq / xrcount - avg * avg);
|
|
printf("RTT: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n",
|
|
dmin * 1e3, avg * 1e3, dmax * 1e3, stddev * 1e3);
|
|
}
|
|
printf("\n");
|
|
|
|
/* template(s) */
|
|
if ((diags != NULL) && (strchr(diags, 'T') != NULL)) {
|
|
size_t n;
|
|
|
|
printf("length = 0x%zx\n", length_query);
|
|
if (random_query > 0)
|
|
printf("random offset = %zu\n", random_query);
|
|
printf("content:\n");
|
|
for (n = 0; n < length_query; n++) {
|
|
printf("%s%02hhx",
|
|
(n & 15) == 0 ? "" : " ",
|
|
template_query[n]);
|
|
if ((n & 15) == 15)
|
|
printf("\n");
|
|
}
|
|
if ((n & 15) != 15)
|
|
printf("\n");
|
|
printf("\n");
|
|
}
|
|
|
|
/* compute the exit code (and exit) */
|
|
if (fatal)
|
|
exit(1);
|
|
else if ((xccount == xscount) && (xscount == xrcount))
|
|
exit(0);
|
|
else
|
|
exit(3);
|
|
}
|