summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--CHANGES36
-rwxr-xr-xconfigure20
-rw-r--r--configure.ac2
-rw-r--r--src/dnsperf.1.in25
-rw-r--r--src/dnsperf.c110
-rw-r--r--src/net_doh.c8
-rw-r--r--src/net_dot.c5
-rw-r--r--src/tsig.c43
-rw-r--r--src/tsig.h12
9 files changed, 233 insertions, 28 deletions
diff --git a/CHANGES b/CHANGES
index d9b3bad..e8b249e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,39 @@
+2023-05-21 Jerry Lundström
+
+ Release 2.12.0
+
+ This release fixes a segfault when doing DNS-over-HTTPS and changes the
+ way maximum queries per second are handled.
+
+ The DNS-over-HTTPS module handled reconnecting incorrectly and destroyed
+ the nghttp2 context during callbacks. Thanks to the help from
+ @kgillis2000 it was quickly found and fixed.
+
+ The way maximum QPS is handled has been changed by Petr Špaček @pspacek
+ (ISC). The new way solves an over-shoot problem that happened due to
+ max QPS being counted for the whole runtime and based on completed
+ queries, not just sent.
+
+ A new option `qps_threshold_wait` has also been added. This controls
+ the threshold for using `nanosleep()` between sending packet and the
+ default is measured on start-up. If the time between packets, based on
+ max QPS `-Q`, is smaller then no sleep will be performed. This improves
+ performance when doing high max QPS limiting.
+
+ Other changes:
+ - `dnsperf`: Statistics output
+ - Fixed missing connection statistics if only reconnections happened
+ - Flush output to allow pipe/grep processing
+ - Add percentage on reconnections based on total connections
+ - Support OpenSSL 3.0+
+
+ 9aca046 OpenSSL 3.0
+ 6d3d6b4 Stats, DoH
+ 316f901 qps_threshold_wait
+ ed52770 WIP: use busy wait only if necessary
+ 32229b6 WIP: import nanosleep benchmark
+ 1842b88 Fix dnsperf -Q to not overshot target value
+
2023-03-16 Jerry Lundström
Release 2.11.2
diff --git a/configure b/configure
index cb78bac..00ecbcc 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for dnsperf 2.11.2.
+# Generated by GNU Autoconf 2.69 for dnsperf 2.12.0.
#
# Report bugs to <admin@dns-oarc.net>.
#
@@ -590,8 +590,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='dnsperf'
PACKAGE_TARNAME='dnsperf'
-PACKAGE_VERSION='2.11.2'
-PACKAGE_STRING='dnsperf 2.11.2'
+PACKAGE_VERSION='2.12.0'
+PACKAGE_STRING='dnsperf 2.12.0'
PACKAGE_BUGREPORT='admin@dns-oarc.net'
PACKAGE_URL='https://github.com/DNS-OARC/dnsperf/issues'
@@ -1362,7 +1362,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures dnsperf 2.11.2 to adapt to many kinds of systems.
+\`configure' configures dnsperf 2.12.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1433,7 +1433,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of dnsperf 2.11.2:";;
+ short | recursive ) echo "Configuration of dnsperf 2.12.0:";;
esac
cat <<\_ACEOF
@@ -1572,7 +1572,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-dnsperf configure 2.11.2
+dnsperf configure 2.12.0
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1941,7 +1941,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by dnsperf $as_me 2.11.2, which was
+It was created by dnsperf $as_me 2.12.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -2804,7 +2804,7 @@ fi
# Define the identity of the package.
PACKAGE='dnsperf'
- VERSION='2.11.2'
+ VERSION='2.12.0'
cat >>confdefs.h <<_ACEOF
@@ -14436,7 +14436,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by dnsperf $as_me 2.11.2, which was
+This file was extended by dnsperf $as_me 2.12.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -14503,7 +14503,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-dnsperf config.status 2.11.2
+dnsperf config.status 2.12.0
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index b25c19f..dd84b36 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,7 +16,7 @@
# limitations under the License.
AC_PREREQ(2.64)
-AC_INIT([dnsperf], [2.11.2], [admin@dns-oarc.net], [dnsperf], [https://github.com/DNS-OARC/dnsperf/issues])
+AC_INIT([dnsperf], [2.12.0], [admin@dns-oarc.net], [dnsperf], [https://github.com/DNS-OARC/dnsperf/issues])
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
AC_CONFIG_SRCDIR([src/dnsperf.c])
AC_CONFIG_HEADER([src/config.h])
diff --git a/src/dnsperf.1.in b/src/dnsperf.1.in
index c44a5f3..76b694e 100644
--- a/src/dnsperf.1.in
+++ b/src/dnsperf.1.in
@@ -485,6 +485,8 @@ available in interval statistics.
Number of answers received within \fIstats_interval\fR can legitimately
exceed number of queries sent, depending on answer latency, configured
\fItimeout\fR, and \fIstats_interval\fR.
+
+Only available in \fBdnsperf\fR.
.RE
\fBlatency-histogram\fR
@@ -496,6 +498,29 @@ range for individual bins increases logarithmically.
This is done to to limit amount of memory required for histograms
and also allows to visualize latency using logarithmic percentile histograms
with minimal postprocessing.
+
+Only available in \fBdnsperf\fR and if compile time support was detected.
+.RE
+
+\fBqps-threshold-wait=<microseconds>\fR
+.br
+.RS
+When using \fB-Q\fR to rate limit queries sent, this option control the
+minimum time (in microseconds) there should be between queries to use
+\fInanosleep()\fR to wait until sending the next query.
+Setting to zero (0) will disable any call to \fInanosleep()\fR between
+sending queries.
+
+If the time between queries is lower then this, then no wait is performed
+in order to have more precision on when to send the next query.
+This is because during high QPS rate limiting it can take more time just
+calling the functions to wait for when to send the next query then the
+actually time between queries.
+
+If not set, the average call-time to \fInanosleep()\fR will be measured
+during startup.
+
+Only available in \fBdnsperf\fR.
.RE
.SH "SEE ALSO"
\fBresperf\fR(1)
diff --git a/src/dnsperf.c b/src/dnsperf.c
index 90b7874..02bdd2c 100644
--- a/src/dnsperf.c
+++ b/src/dnsperf.c
@@ -102,6 +102,7 @@ typedef struct {
#ifdef USE_HISTOGRAMS
bool latency_histogram;
#endif
+ int qps_threshold_wait;
} config_t;
typedef struct {
@@ -437,12 +438,16 @@ print_statistics(const config_t* config, const times_t* times, stats_t* stats, u
printf("\n");
- if (!stats->num_conn_completed) {
+ if (!stats->num_conn_completed && !stats->num_conn_reconnect) {
+ fflush(stdout);
return;
}
printf("Connection Statistics:\n\n");
- printf(" Reconnections: %" PRIu64 "\n\n", stats->num_conn_reconnect);
+ printf(" Reconnections: %" PRIu64 " (%.2lf%% of %" PRIu64 " connections)\n\n",
+ stats->num_conn_reconnect,
+ PERF_SAFE_DIV(100.0 * stats->num_conn_reconnect, stats->num_conn_completed),
+ stats->num_conn_completed);
latency_avg = PERF_SAFE_DIV(stats->conn_latency_sum, stats->num_conn_completed);
printf(" Average Latency (s): %u.%06u",
(unsigned int)(latency_avg / MILLION),
@@ -466,6 +471,7 @@ print_statistics(const config_t* config, const times_t* times, stats_t* stats, u
}
printf("\n");
+ fflush(stdout);
}
/*
@@ -532,6 +538,40 @@ stringify(unsigned int value)
return buf;
}
+static int
+measure_nanosleep(config_t* config)
+{
+ struct timespec start, stop, wait = { 0, 0 };
+ int err;
+
+ int i = 100;
+ if ((err = clock_gettime(CLOCK_REALTIME, &start))) {
+ return err;
+ }
+ for (; i; i--) {
+ if ((err = nanosleep(&wait, NULL))) {
+ return err;
+ }
+ }
+ if ((err = clock_gettime(CLOCK_REALTIME, &stop))) {
+ return err;
+ }
+
+ // Total time for 100 nanosleep() + 2 clock_gettime()
+ config->qps_threshold_wait = ((stop.tv_sec - start.tv_sec) * 1000000000 + stop.tv_nsec - start.tv_nsec)
+ // divided by 100 runs
+ / 100
+ // add fudge
+ * 3
+ // converted to microseconds
+ / 1000;
+ if (config->qps_threshold_wait < 0) {
+ config->qps_threshold_wait = 0;
+ }
+
+ return 0;
+}
+
static void
setup(int argc, char** argv, config_t* config)
{
@@ -559,6 +599,8 @@ setup(int argc, char** argv, config_t* config)
config->max_outstanding = DEFAULT_MAX_OUTSTANDING;
config->mode = sock_udp;
+ config->qps_threshold_wait = -1;
+
perf_opt_add('f', perf_opt_string, "family",
"address family of DNS transport, inet or inet6", "any",
&family);
@@ -637,6 +679,8 @@ setup(int argc, char** argv, config_t* config)
perf_long_opt_add("latency-histogram", perf_opt_boolean, NULL,
"collect and print detailed latency histograms", NULL, &config->latency_histogram);
#endif
+ perf_long_opt_add("qps-threshold-wait", perf_opt_zpint, "microseconds",
+ "minimum threshold for enabling wait in rate limiting", stringify(config->qps_threshold_wait), &config->qps_threshold_wait);
bool log_stdout = false;
perf_opt_add('W', perf_opt_boolean, NULL, "log warnings and errors to stdout instead of stderr", NULL, &log_stdout);
@@ -730,6 +774,14 @@ setup(int argc, char** argv, config_t* config)
perf_log_fatal("Unable to dynamic update, support not built in");
}
#endif
+
+ if (config->qps_threshold_wait < 0) {
+ int err = measure_nanosleep(config);
+ if (err) {
+ char __s[256];
+ perf_log_fatal("Unable to measure nanosleep(): %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+ }
}
static void
@@ -803,7 +855,7 @@ do_send(void* arg)
stats_t* stats;
unsigned int max_packet_size;
perf_buffer_t msg;
- uint64_t now, run_time, req_time;
+ uint64_t now, req_time, wait_us, q_sent = 0, q_step = 0, q_slice;
char input_data[MAX_INPUT_DATA];
perf_buffer_t lines;
perf_region_t used;
@@ -824,8 +876,13 @@ do_send(void* arg)
perf_buffer_init(&msg, packet_buffer, max_packet_size);
perf_buffer_init(&lines, input_data, sizeof(input_data));
+ if (tinfo->max_qps > 0) {
+ q_step = MILLION / tinfo->max_qps;
+ }
wait_for_start();
- now = perf_get_time();
+ now = perf_get_time();
+ req_time = now;
+ q_slice = now + MILLION;
while (!interrupted && now < times->stop_time) {
/* Avoid flooding the network too quickly. */
if (stats->num_sent < tinfo->max_outstanding && stats->num_sent % 2 == 1) {
@@ -838,13 +895,49 @@ do_send(void* arg)
/* Rate limiting */
if (tinfo->max_qps > 0) {
- run_time = now - times->start_time;
- req_time = (MILLION * stats->num_sent) / tinfo->max_qps;
- if (req_time > run_time) {
- usleep(req_time - run_time);
+ /* the 1 second time slice where q_sent is calculated over */
+ if (q_slice <= now) {
+ q_slice += MILLION;
+ q_sent = 0;
+ req_time = now; // reset stepping, in case of clock sliding
+ }
+ /* limit QPS over the 1 second slice */
+ if (q_sent >= tinfo->max_qps) {
+ wait_us = q_slice - now;
+ if (config->qps_threshold_wait && wait_us > config->qps_threshold_wait) {
+ wait_us -= config->qps_threshold_wait;
+ struct timespec ts = { 0, 0 };
+ if (wait_us >= MILLION) {
+ ts.tv_sec = wait_us / MILLION;
+ ts.tv_nsec = (wait_us % MILLION) * 1000;
+ } else {
+ ts.tv_sec = 0;
+ ts.tv_nsec = wait_us * 1000;
+ }
+ nanosleep(&ts, NULL);
+ }
+ now = perf_get_time();
+ continue;
+ }
+ /* handle stepping to the next window to send a query on */
+ if (req_time > now) {
+ wait_us = req_time - now;
+ if (config->qps_threshold_wait && wait_us > config->qps_threshold_wait) {
+ wait_us -= config->qps_threshold_wait;
+ struct timespec ts = { 0, 0 };
+ if (wait_us >= MILLION) {
+ ts.tv_sec = wait_us / MILLION;
+ ts.tv_nsec = (wait_us % MILLION) * 1000;
+ } else {
+ ts.tv_sec = 0;
+ ts.tv_nsec = wait_us * 1000;
+ }
+ nanosleep(&ts, NULL);
+ }
now = perf_get_time();
continue;
}
+ req_time += q_step;
}
PERF_LOCK(&tinfo->lock);
@@ -980,6 +1073,7 @@ do_send(void* arg)
continue;
}
stats->num_sent++;
+ q_sent++;
stats->total_request_size += length;
}
diff --git a/src/net_doh.c b/src/net_doh.c
index dafd736..2caeab8 100644
--- a/src/net_doh.c
+++ b/src/net_doh.c
@@ -100,7 +100,7 @@ struct perf__doh_socket {
bool is_ready, is_conn_ready, is_ssl_ready, is_sending, is_post_sending;
// bool have_more; TODO
- bool do_reconnect;
+ bool do_reconnect, do_connect;
perf_sockaddr_t server, local;
size_t bufsize;
@@ -254,7 +254,7 @@ static void perf__doh_reconnect(struct perf_net_socket* sock)
self->http2_code = 0;
self->http2_is_dns = false;
- perf__doh_connect(sock);
+ self->do_connect = true;
}
// static ssize_t _recv_callback(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *sock)
@@ -623,6 +623,10 @@ static int perf__doh_sockready(struct perf_net_socket* sock, int pipe_fd, int64_
perf__doh_reconnect(sock);
self->do_reconnect = false;
}
+ if (self->do_connect) {
+ perf__doh_connect(sock);
+ self->do_connect = false;
+ }
if (self->is_ready) {
// do nghttp2 I/O send to flush outstanding frames
diff --git a/src/net_dot.c b/src/net_dot.c
index 7e58a73..02b54e4 100644
--- a/src/net_dot.c
+++ b/src/net_dot.c
@@ -175,6 +175,11 @@ static ssize_t perf__dot_recv(struct perf_net_socket* sock, void* buf, size_t le
case SSL_ERROR_WANT_READ:
errno = EAGAIN;
break;
+#if OPENSSL_VERSION_NUMBER > 0x30000000L
+ case SSL_ERROR_SSL:
+ // OpenSSL 3.0+ returns this on EOF, treat everything as bad fd and reconnect
+ errno = EBADF;
+#endif
case SSL_ERROR_SYSCALL:
switch (errno) {
case EBADF:
diff --git a/src/tsig.c b/src/tsig.c
index b697141..9f48c63 100644
--- a/src/tsig.c
+++ b/src/tsig.c
@@ -183,15 +183,27 @@ perf_tsigkey_t* perf_tsig_parsekey(const char* arg)
perf_log_fatal("unable to setup TSIG, OpenSSL HMAC context failed to be created");
}
HMAC_CTX_init(tsigkey->hmac);
-#else
+#elif OPENSSL_VERSION_NUMBER < 0x30000000L
if (!(tsigkey->hmac = HMAC_CTX_new())) {
perf_log_fatal("unable to setup TSIG, OpenSSL HMAC context failed to be created");
}
+#else
+ if (!(tsigkey->pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, 0, key, keylen))) {
+ perf_log_fatal("unable to setup TSIG, OpenSSL EVP PKEY failed to be created");
+ }
+ if (!(tsigkey->mdctx = EVP_MD_CTX_create())) {
+ perf_log_fatal("unable to setup TSIG, OpenSSL EVP MD context failed to be created");
+ }
+ if (!EVP_DigestSignInit(tsigkey->mdctx, 0, md, 0, tsigkey->pkey)) {
+ perf_log_fatal("unable to setup TSIG, OpenSSL EVP DigestSign init failed");
+ }
#endif
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
if (!HMAC_Init_ex(tsigkey->hmac, key, keylen, md, 0)) {
perf_log_fatal("unable to setup TSIG, OpenSSL HMAC init failed");
}
+#endif
free(key);
@@ -206,8 +218,11 @@ void perf_tsig_destroykey(perf_tsigkey_t** tsigkeyp)
#if OPENSSL_VERSION_NUMBER < 0x10100000L
HMAC_CTX_cleanup((*tsigkeyp)->hmac);
free((*tsigkeyp)->hmac);
-#else
+#elif OPENSSL_VERSION_NUMBER < 0x30000000L
HMAC_CTX_free((*tsigkeyp)->hmac);
+#else
+ EVP_MD_CTX_free((*tsigkeyp)->mdctx);
+ EVP_PKEY_free((*tsigkeyp)->pkey);
#endif
free(*tsigkeyp);
@@ -222,20 +237,27 @@ perf_result_t perf_add_tsig(perf_buffer_t* packet, perf_tsigkey_t* tsigkey)
unsigned char* base;
size_t rdlen, totallen;
unsigned char tmpdata[512], md[EVP_MAX_MD_SIZE];
- unsigned int mdlen;
perf_buffer_t tmp;
uint32_t now;
perf_result_t result;
now = time(NULL);
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
if (!HMAC_Init_ex(tsigkey->hmac, 0, 0, 0, 0)) {
perf_log_fatal("adding TSIG: OpenSSL HMAC reinit failed");
}
-
if (!HMAC_Update(tsigkey->hmac, perf_buffer_base(packet), perf_buffer_usedlength(packet))) {
perf_log_fatal("adding TSIG: OpenSSL HMAC update failed");
}
+#else
+ if (!EVP_DigestSignInit(tsigkey->mdctx, 0, 0, 0, 0)) {
+ perf_log_fatal("adding TSIG: OpenSSL EVP DigestSign reinit failed");
+ }
+ if (!EVP_DigestSignUpdate(tsigkey->mdctx, perf_buffer_base(packet), perf_buffer_usedlength(packet))) {
+ perf_log_fatal("adding TSIG: OpenSSL EVP DigestSign update failed");
+ }
+#endif
/* Digest the TSIG record */
perf_buffer_init(&tmp, tmpdata, sizeof tmpdata);
@@ -267,14 +289,23 @@ perf_result_t perf_add_tsig(perf_buffer_t* packet, perf_tsigkey_t* tsigkey)
perf_buffer_putuint16(&tmp, 0); /* error */
perf_buffer_putuint16(&tmp, 0); /* other length */
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ unsigned int mdlen = sizeof(md);
if (!HMAC_Update(tsigkey->hmac, perf_buffer_base(&tmp), perf_buffer_usedlength(&tmp))) {
perf_log_fatal("adding TSIG: OpenSSL HMAC update failed");
}
-
- mdlen = sizeof(md);
if (!HMAC_Final(tsigkey->hmac, md, &mdlen)) {
perf_log_fatal("adding TSIG: OpenSSL HMAC final failed");
}
+#else
+ size_t mdlen = sizeof(md);
+ if (!EVP_DigestSignUpdate(tsigkey->mdctx, perf_buffer_base(&tmp), perf_buffer_usedlength(&tmp))) {
+ perf_log_fatal("adding TSIG: OpenSSL EVP DigestSign update failed");
+ }
+ if (!EVP_DigestSignFinal(tsigkey->mdctx, md, &mdlen)) {
+ perf_log_fatal("adding TSIG: OpenSSL EVP DigestSign final failed");
+ }
+#endif
/* Make sure everything will fit */
rdlen = tsigkey->alglen + 18 + mdlen;
diff --git a/src/tsig.h b/src/tsig.h
index b69c2d1..8ec108e 100644
--- a/src/tsig.h
+++ b/src/tsig.h
@@ -23,13 +23,23 @@
#ifndef PERF_TSIG_H
#define PERF_TSIG_H 1
+#include <openssl/opensslv.h>
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
#include <openssl/hmac.h>
+#else
+#include <openssl/evp.h>
+#endif
typedef struct perf_tsigkey {
char name[256];
size_t namelen, alglen;
const char* alg;
- HMAC_CTX* hmac;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_CTX* hmac;
+#else
+ EVP_PKEY* pkey;
+ EVP_MD_CTX* mdctx;
+#endif
} perf_tsigkey_t;
perf_tsigkey_t* perf_tsig_parsekey(const char* arg);