summaryrefslogtreecommitdiffstats
path: root/contrib/perftcpdns/perftcpdns.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/perftcpdns/perftcpdns.c')
-rw-r--r--contrib/perftcpdns/perftcpdns.c2472
1 files changed, 2472 insertions, 0 deletions
diff --git a/contrib/perftcpdns/perftcpdns.c b/contrib/perftcpdns/perftcpdns.c
new file mode 100644
index 0000000..55486e4
--- /dev/null
+++ b/contrib/perftcpdns/perftcpdns.c
@@ -0,0 +1,2472 @@
+/*
+ * 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);
+}