summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Makefile.am66
-rw-r--r--src/buffer.h116
-rw-r--r--src/datafile.c246
-rw-r--r--src/datafile.h52
-rw-r--r--src/dns.c484
-rw-r--r--src/dns.h43
-rw-r--r--src/dnsperf.1.in366
-rw-r--r--src/dnsperf.c1220
-rw-r--r--src/edns.c158
-rw-r--r--src/edns.h41
-rwxr-xr-xsrc/gen-qtype.c.py46
-rw-r--r--src/list.h91
-rw-r--r--src/log.c72
-rw-r--r--src/log.h28
-rw-r--r--src/net.c664
-rw-r--r--src/net.h84
-rw-r--r--src/opt.c234
-rw-r--r--src/opt.h39
-rw-r--r--src/os.c150
-rw-r--r--src/os.h44
-rw-r--r--src/qtype.c117
-rw-r--r--src/qtype.h32
-rwxr-xr-xsrc/resperf-report112
-rw-r--r--src/resperf.1.in647
-rw-r--r--src/resperf.c816
-rw-r--r--src/result.h38
-rw-r--r--src/strerror.c37
-rw-r--r--src/strerror.h27
-rw-r--r--src/test/Makefile.am9
-rw-r--r--src/test/datafile2
-rw-r--r--src/test/datafile2560
-rw-r--r--src/test/datafile32
-rwxr-xr-xsrc/test/test1.sh4
-rwxr-xr-xsrc/test/test2.sh108
-rwxr-xr-xsrc/test/test3.sh46
-rw-r--r--src/test/updatefile11
-rw-r--r--src/tsig.c326
-rw-r--r--src/tsig.h41
-rw-r--r--src/util.h146
39 files changed, 7325 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..35d10a3
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,66 @@
+# Copyright 2019-2021 OARC, Inc.
+# Copyright 2017-2018 Akamai Technologies
+# Copyright 2006-2016 Nominum, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in $(srcdir)/config.h.in
+CLEANFILES = dnsperf.1 resperf.1 *.gcda *.gcno *.gcov
+
+SUBDIRS = test
+
+AM_CFLAGS = -I$(srcdir) \
+ -I$(top_srcdir) \
+ $(PTHREAD_CFLAGS) $(libssl_CFLAGS) $(libcrypto_CFLAGS) $(libldns_CFLAGS)
+
+EXTRA_DIST = dnsperf.1.in resperf-report resperf.1.in
+
+bin_PROGRAMS = dnsperf resperf
+dist_bin_SCRIPTS = resperf-report
+
+_libperf_sources = datafile.c dns.c log.c net.c opt.c os.c strerror.c qtype.c \
+ edns.c tsig.c
+_libperf_headers = datafile.h dns.h log.h net.h opt.h os.h util.h strerror.h \
+ list.h result.h buffer.h qtype.h edns.h tsig.h
+
+dnsperf_SOURCES = $(_libperf_sources) dnsperf.c
+dist_dnsperf_SOURCES = $(_libperf_headers)
+dnsperf_LDADD = $(PTHREAD_LIBS) $(libssl_LIBS) $(libcrypto_LIBS) \
+ $(libldns_LIBS)
+
+resperf_SOURCES = $(_libperf_sources) resperf.c
+dist_resperf_SOURCES = $(_libperf_headers)
+resperf_LDADD = $(PTHREAD_LIBS) $(libssl_LIBS) $(libcrypto_LIBS) \
+ $(libldns_LIBS)
+
+man1_MANS = dnsperf.1 resperf.1
+
+dnsperf.1: dnsperf.1.in Makefile
+ sed -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \
+-e 's,[@]PACKAGE_URL[@],$(PACKAGE_URL),g' \
+-e 's,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g' \
+< $(srcdir)/dnsperf.1.in > dnsperf.1
+
+resperf.1: resperf.1.in Makefile
+ sed -e 's,[@]PACKAGE_VERSION[@],$(PACKAGE_VERSION),g' \
+-e 's,[@]PACKAGE_URL[@],$(PACKAGE_URL),g' \
+-e 's,[@]PACKAGE_BUGREPORT[@],$(PACKAGE_BUGREPORT),g' \
+< $(srcdir)/resperf.1.in > resperf.1
+
+if ENABLE_GCOV
+gcov-local:
+ for src in $(_libperf_sources) dnsperf.c resperf.c; do \
+ gcov -l -r -s "$(srcdir)" "$$src"; \
+ done
+endif
diff --git a/src/buffer.h b/src/buffer.h
new file mode 100644
index 0000000..ab46f9b
--- /dev/null
+++ b/src/buffer.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PERF_BUFFER_H
+#define PERF_BUFFER_H 1
+
+#include <stddef.h>
+
+typedef struct perf_region {
+ void* base;
+ size_t length;
+} perf_region_t;
+
+typedef struct perf_buffer {
+ void* base;
+ size_t length, used, current, active;
+} perf_buffer_t;
+
+#include <assert.h>
+
+#define perf_buffer_init(b, _base, _length) \
+ { \
+ (b)->base = _base; \
+ (b)->length = _length; \
+ (b)->used = 0; \
+ (b)->current = 0; \
+ (b)->active = 0; \
+ }
+
+#define perf_buffer_add(b, n) \
+ { \
+ assert((b)->used + n <= (b)->length); \
+ (b)->used += (n); \
+ }
+
+#define perf_buffer_length(b) ((b)->length)
+
+#define perf_buffer_availablelength(b) ((b)->length - (b)->used)
+
+#define perf_buffer_base(b) ((b)->base)
+
+#define perf_buffer_clear(b) \
+ { \
+ (b)->used = 0; \
+ (b)->current = 0; \
+ (b)->active = 0; \
+ }
+
+#define perf_buffer_putmem(b, base, length) \
+ { \
+ assert(perf_buffer_availablelength(b) >= length); \
+ memcpy(perf_buffer_used(b), base, length); \
+ perf_buffer_add(b, length); \
+ }
+
+#define perf_buffer_putuint8(b, _val) \
+ { \
+ unsigned char* _cp; \
+ uint8_t _val2 = (_val); \
+ assert(perf_buffer_availablelength(b) >= 1U); \
+ _cp = perf_buffer_used(b); \
+ (b)->used += 1U; \
+ _cp[0] = _val2; \
+ }
+
+#define perf_buffer_putuint16(b, _val) \
+ { \
+ unsigned char* _cp; \
+ uint16_t _val2 = (_val); \
+ assert(perf_buffer_availablelength(b) >= 2U); \
+ _cp = perf_buffer_used(b); \
+ (b)->used += 2U; \
+ _cp[0] = _val2 >> 8; \
+ _cp[1] = _val2; \
+ }
+
+#define perf_buffer_putuint32(b, _val) \
+ { \
+ unsigned char* _cp; \
+ uint32_t _val2 = (_val); \
+ assert(perf_buffer_availablelength(b) >= 4U); \
+ _cp = perf_buffer_used(b); \
+ (b)->used += 4U; \
+ _cp[0] = _val2 >> 24; \
+ _cp[1] = _val2 >> 16; \
+ _cp[2] = _val2 >> 8; \
+ _cp[3] = _val2; \
+ }
+
+#define perf_buffer_copyregion(b, r) perf_buffer_putmem(b, (r)->base, (r)->length)
+
+#define perf_buffer_used(b) ((void*)((unsigned char*)(b)->base + (b)->used))
+#define perf_buffer_usedlength(b) ((b)->used)
+#define perf_buffer_usedregion(b, r) \
+ { \
+ (r)->base = (b)->base; \
+ (r)->length = (b)->used; \
+ }
+
+#endif
diff --git a/src/datafile.c b/src/datafile.c
new file mode 100644
index 0000000..da2acac
--- /dev/null
+++ b/src/datafile.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "datafile.h"
+
+#include "log.h"
+#include "os.h"
+#include "util.h"
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <assert.h>
+
+perf_datafile_t* perf_datafile_open(const char* filename)
+{
+ perf_datafile_t* dfile;
+ struct stat buf;
+
+ dfile = calloc(1, sizeof(perf_datafile_t));
+ if (!dfile) {
+ perf_log_fatal("out of memory");
+ return 0; // fix clang scan-build
+ }
+
+ PERF_MUTEX_INIT(&dfile->lock);
+ dfile->pipe_fd = -1;
+ dfile->is_file = false;
+ dfile->size = 0;
+ dfile->cached = false;
+ dfile->maxruns = 1;
+ dfile->nruns = 0;
+ dfile->read_any = false;
+ if (!filename) {
+ dfile->fd = STDIN_FILENO;
+ } else {
+ dfile->fd = open(filename, O_RDONLY);
+ if (dfile->fd < 0)
+ perf_log_fatal("unable to open file: %s", filename);
+ if (fstat(dfile->fd, &buf) == 0 && S_ISREG(buf.st_mode)) {
+ dfile->is_file = true;
+ dfile->size = buf.st_size;
+ }
+ }
+
+ return dfile;
+}
+
+void perf_datafile_close(perf_datafile_t** dfilep)
+{
+ perf_datafile_t* dfile;
+
+ assert(dfilep);
+ assert(*dfilep);
+
+ dfile = *dfilep;
+ *dfilep = 0;
+
+ if (dfile->fd >= 0 && dfile->fd != STDIN_FILENO) {
+ close(dfile->fd);
+ }
+ PERF_MUTEX_DESTROY(&dfile->lock);
+ free(dfile);
+}
+
+void perf_datafile_setpipefd(perf_datafile_t* dfile, int pipe_fd)
+{
+ dfile->pipe_fd = pipe_fd;
+}
+
+void perf_datafile_setmaxruns(perf_datafile_t* dfile, unsigned int maxruns)
+{
+ dfile->maxruns = maxruns;
+}
+
+static void reopen_file(perf_datafile_t* dfile)
+{
+ if (dfile->cached) {
+ dfile->at = 0;
+ } else {
+ if (lseek(dfile->fd, 0L, SEEK_SET) < 0) {
+ perf_log_fatal("cannot reread input");
+ }
+ dfile->at = 0;
+ dfile->have = 0;
+ dfile->databuf[0] = 0;
+ }
+}
+
+static perf_result_t read_more(perf_datafile_t* dfile)
+{
+ ssize_t n;
+ perf_result_t result;
+ struct perf_net_socket sock = { .mode = sock_file, .fd = dfile->fd };
+
+ if (!dfile->is_file && dfile->pipe_fd >= 0) {
+ result = perf_os_waituntilreadable(&sock, dfile->pipe_fd, -1);
+ if (result != PERF_R_SUCCESS)
+ return (result);
+ }
+
+ if (dfile->at && dfile->at < dfile->have) {
+ memmove(dfile->databuf, &dfile->databuf[dfile->at], dfile->have - dfile->at);
+ dfile->have -= dfile->at;
+ dfile->at = 0;
+ } else if (dfile->at == dfile->have) {
+ dfile->have = 0;
+ dfile->at = 0;
+ }
+
+ n = read(dfile->fd, &dfile->databuf[dfile->have], sizeof(dfile->databuf) - dfile->have - 1);
+ if (n < 0) {
+ return (PERF_R_FAILURE);
+ }
+
+ dfile->have += n;
+ dfile->databuf[dfile->have] = 0;
+
+ if (dfile->is_file && dfile->have == dfile->size) {
+ dfile->cached = true;
+ }
+
+ return (PERF_R_SUCCESS);
+}
+
+static perf_result_t read_one_line(perf_datafile_t* dfile, perf_buffer_t* lines)
+{
+ const char* cur;
+ size_t length, curlen, nrem;
+ perf_result_t result;
+
+ while (true) {
+ /* Get the current line */
+ cur = &dfile->databuf[dfile->at];
+ curlen = strcspn(cur, "\n");
+
+ /*
+ * If the current line contains the rest of the buffer,
+ * we need to read more (unless the full file is cached).
+ */
+ nrem = dfile->have - dfile->at;
+ if (curlen == nrem) {
+ if (!dfile->cached) {
+ result = read_more(dfile);
+ if (result != PERF_R_SUCCESS)
+ return (result);
+ }
+ if (dfile->have - dfile->at == 0) {
+ dfile->nruns++;
+ return (PERF_R_EOF);
+ }
+ if (dfile->have - dfile->at > nrem)
+ continue;
+ }
+
+ /* We now have a line. Advance the buffer past it. */
+ dfile->at += curlen;
+ if (dfile->have - dfile->at > 0) {
+ dfile->at += 1;
+ }
+
+ /* If the line is empty or a comment, we need to try again. */
+ if (curlen > 0 && cur[0] != ';')
+ break;
+ }
+
+ length = perf_buffer_availablelength(lines);
+ if (curlen > length - 1)
+ curlen = length - 1;
+ perf_buffer_putmem(lines, (unsigned char*)cur, curlen);
+ perf_buffer_putuint8(lines, 0);
+
+ return (PERF_R_SUCCESS);
+}
+
+perf_result_t perf_datafile_next(perf_datafile_t* dfile, perf_buffer_t* lines, bool is_update)
+{
+ const char* current;
+ perf_result_t result;
+
+ PERF_LOCK(&dfile->lock);
+
+ if (dfile->maxruns > 0 && dfile->maxruns == dfile->nruns) {
+ result = PERF_R_EOF;
+ goto done;
+ }
+
+ result = read_one_line(dfile, lines);
+ if (result == PERF_R_EOF) {
+ if (!dfile->read_any) {
+ result = PERF_R_INVALIDFILE;
+ goto done;
+ }
+ if (dfile->maxruns != dfile->nruns) {
+ reopen_file(dfile);
+ result = read_one_line(dfile, lines);
+ }
+ }
+ if (result != PERF_R_SUCCESS) {
+ goto done;
+ }
+ dfile->read_any = true;
+
+ if (is_update) {
+ while (true) {
+ current = perf_buffer_used(lines);
+ result = read_one_line(dfile, lines);
+ if (result == PERF_R_EOF && dfile->maxruns != dfile->nruns) {
+ reopen_file(dfile);
+ }
+ if (result != PERF_R_SUCCESS || strcasecmp(current, "send") == 0)
+ break;
+ }
+ }
+
+ result = PERF_R_SUCCESS;
+done:
+ PERF_UNLOCK(&dfile->lock);
+ return (result);
+}
+
+unsigned int perf_datafile_nruns(const perf_datafile_t* dfile)
+{
+ return dfile->nruns;
+}
diff --git a/src/datafile.h b/src/datafile.h
new file mode 100644
index 0000000..7b6caf3
--- /dev/null
+++ b/src/datafile.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "result.h"
+#include "buffer.h"
+
+#ifndef PERF_DATAFILE_H
+#define PERF_DATAFILE_H 1
+
+#include <pthread.h>
+#include <stdbool.h>
+
+typedef struct perf_datafile {
+ pthread_mutex_t lock;
+ int pipe_fd;
+ int fd;
+ bool is_file;
+ size_t size, at, have;
+ bool cached;
+ char databuf[(64 * 1024) + 1];
+ unsigned int maxruns;
+ unsigned int nruns;
+ bool read_any;
+} perf_datafile_t;
+
+perf_datafile_t* perf_datafile_open(const char* filename);
+
+void perf_datafile_close(perf_datafile_t** dfilep);
+void perf_datafile_setmaxruns(perf_datafile_t* dfile, unsigned int maxruns);
+void perf_datafile_setpipefd(perf_datafile_t* dfile, int pipe_fd);
+
+perf_result_t perf_datafile_next(perf_datafile_t* dfile, perf_buffer_t* lines, bool is_update);
+
+unsigned int perf_datafile_nruns(const perf_datafile_t* dfile);
+
+#endif
diff --git a/src/dns.c b/src/dns.c
new file mode 100644
index 0000000..548a1a9
--- /dev/null
+++ b/src/dns.c
@@ -0,0 +1,484 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "dns.h"
+
+#include "log.h"
+#include "opt.h"
+#include "qtype.h"
+
+#include <ctype.h>
+#include <time.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#ifdef HAVE_LDNS
+#include <ldns/ldns.h>
+#endif
+
+#define WHITESPACE " \t\n"
+
+#define MAX_RDATA_LENGTH 65535
+#define EDNSLEN 11
+
+const char* perf_dns_rcode_strings[] = {
+ "NOERROR", "FORMERR", "SERVFAIL", "NXDOMAIN",
+ "NOTIMP", "REFUSED", "YXDOMAIN", "YXRRSET",
+ "NXRRSET", "NOTAUTH", "NOTZONE", "rcode11",
+ "rcode12", "rcode13", "rcode14", "rcode15"
+};
+
+perf_result_t perf_dname_fromstring(const char* str, size_t len, perf_buffer_t* target)
+{
+ size_t label_len;
+ const char* orig_str = str;
+
+ if (perf_buffer_availablelength(target) < len) {
+ return PERF_R_NOSPACE;
+ }
+
+ while (len) {
+ for (label_len = 0; label_len < len; label_len++) {
+ if (*(str + label_len) == '.') {
+ break;
+ }
+ }
+ if (!label_len) {
+ // Just a dot
+ if (len > 1) {
+ // a dot but with labels after it
+ return PERF_R_FAILURE;
+ } else if (str != orig_str) {
+ // a dot but with labels before it
+ return PERF_R_FAILURE;
+ }
+ perf_buffer_putuint8(target, 0);
+ break;
+ }
+ if (label_len > 63) {
+ return PERF_R_FAILURE;
+ }
+ perf_buffer_putuint8(target, label_len);
+ perf_buffer_putmem(target, str, label_len);
+ str += label_len;
+ len -= label_len;
+ if (len < 2) {
+ // Last label/dot
+ perf_buffer_putuint8(target, 0);
+ break;
+ }
+ // advance past dot
+ str++;
+ len--;
+ }
+
+ return PERF_R_SUCCESS;
+}
+
+perf_result_t perf_qtype_fromstring(const char* str, size_t len, perf_buffer_t* target)
+{
+ const perf_qtype_t* q = qtype_table;
+
+ while (q->type) {
+ if (!strncasecmp(q->type, str, len)) {
+ perf_buffer_putuint16(target, q->value);
+ return PERF_R_SUCCESS;
+ }
+ q++;
+ }
+
+ return PERF_R_FAILURE;
+}
+
+static perf_result_t build_query(const perf_region_t* line, perf_buffer_t* msg)
+{
+ char * domain_str, *qtype_str;
+ size_t domain_len, qtype_len;
+ perf_result_t result;
+
+ domain_str = line->base;
+ domain_len = strcspn(line->base, WHITESPACE);
+
+ if (!domain_len) {
+ perf_log_warning("invalid query input format: %s", (char*)line->base);
+ return PERF_R_FAILURE;
+ }
+
+ qtype_str = line->base + domain_len;
+ while (isspace(*qtype_str))
+ qtype_str++;
+ qtype_len = strcspn(qtype_str, WHITESPACE);
+
+ /* Create the question section */
+ result = perf_dname_fromstring(domain_str, domain_len, msg);
+ if (result != PERF_R_SUCCESS) {
+ perf_log_warning("invalid domain name (or out of space): %.*s", (int)domain_len, domain_str);
+ return result;
+ }
+
+ if (!qtype_len) {
+ perf_log_warning("invalid query input format: %s", (char*)line->base);
+ return PERF_R_FAILURE;
+ }
+
+ result = perf_qtype_fromstring(qtype_str, qtype_len, msg);
+ if (result != PERF_R_SUCCESS) {
+ perf_log_warning("invalid qtype: %.*s", (int)qtype_len, qtype_str);
+ return result;
+ }
+
+ perf_buffer_putuint16(msg, 1); // class IN
+
+ return PERF_R_SUCCESS;
+}
+
+#ifdef HAVE_LDNS
+static bool token_equals(const perf_region_t* token, const char* str)
+{
+ return (strlen(str) == token->length && strncasecmp(str, token->base, token->length) == 0);
+}
+
+/*
+ * Reads one line containing an individual update for a dynamic update message.
+ */
+static perf_result_t
+read_update_line(char* str, const ldns_rdf* origin,
+ bool want_ttl, bool need_type, bool want_rdata, bool need_rdata,
+ ldns_rr** rr, const char** errstr)
+{
+ char tmp[256], *str2;
+ size_t len;
+
+ while (isspace(*str & 0xff))
+ str++;
+ str2 = str;
+
+ /*
+ * Read the owner name
+ */
+ len = strcspn(str, WHITESPACE);
+ if (len > sizeof(tmp) - 1) {
+ *errstr = "domain name too large";
+ return PERF_R_NOSPACE;
+ }
+ memcpy(tmp, str, len);
+ tmp[len] = 0;
+
+ ldns_rdf* owner;
+ if (!(owner = ldns_dname_new_frm_str(tmp))) {
+ *errstr = "invalid name or out of memory";
+ return PERF_R_FAILURE;
+ }
+ ldns_rr_set_owner(*rr, owner);
+ if (!ldns_dname_str_absolute(tmp) && origin) {
+ if (ldns_dname_cat(ldns_rr_owner(*rr), origin) != LDNS_STATUS_OK) {
+ return PERF_R_FAILURE;
+ }
+ }
+
+ str += len;
+ while (isspace(*str & 0xff))
+ str++;
+
+ /*
+ * Read the ttl
+ */
+ if (want_ttl) {
+ len = strcspn(str, WHITESPACE);
+ if (len > sizeof(tmp) - 1) {
+ *errstr = "TTL string too large";
+ return PERF_R_NOSPACE;
+ }
+ memcpy(tmp, str, len);
+ tmp[len] = 0;
+
+ char* endptr = 0;
+ unsigned long int u = strtoul(tmp, &endptr, 10);
+ if (*endptr || u == ULONG_MAX) {
+ *errstr = "TTL invalid";
+ return PERF_R_INVALIDUPDATE;
+ }
+
+ ldns_rr_set_ttl(*rr, u);
+
+ str += len;
+ while (isspace(*str & 0xff))
+ str++;
+ }
+
+ /*
+ * Read the type
+ */
+ len = strcspn(str, WHITESPACE);
+ if (!len) {
+ if (!need_type)
+ return PERF_R_SUCCESS;
+
+ *errstr = "TYPE required";
+ return PERF_R_INVALIDUPDATE;
+ }
+ if (len > sizeof(tmp) - 1) {
+ *errstr = "TYPE string too large";
+ return PERF_R_NOSPACE;
+ }
+ memcpy(tmp, str, len);
+ tmp[len] = 0;
+
+ ldns_rr_type type = ldns_get_rr_type_by_name(tmp);
+ if (!type) {
+ *errstr = "TYPE invalid";
+ return PERF_R_INVALIDUPDATE;
+ }
+ ldns_rr_set_type(*rr, type);
+
+ str += len;
+ while (isspace(*str & 0xff))
+ str++;
+
+ if (!want_rdata)
+ return PERF_R_SUCCESS;
+
+ /*
+ * Read the rdata
+ */
+ if (*str == 0) {
+ if (!need_rdata)
+ return PERF_R_SUCCESS;
+
+ *errstr = "RDATA required";
+ return PERF_R_INVALIDUPDATE;
+ }
+
+ // Need to recreate ldns_rr because there is no new_frm_str function to
+ // correctly parse RDATA (quotes etc) for a RDF
+ ldns_rr* rr2 = 0;
+ if (ldns_rr_new_frm_str(&rr2, str2, 0, origin, 0) != LDNS_STATUS_OK) {
+ *errstr = "invalid RDATA or out of memory";
+ return PERF_R_INVALIDUPDATE;
+ }
+
+ // Force set TTL since if its missing in the input it will get the default
+ // 3600 and not 0 as it should
+ ldns_rr_set_ttl(rr2, ldns_rr_ttl(*rr));
+
+ ldns_rr_free(*rr);
+ *rr = rr2;
+
+ return PERF_R_SUCCESS;
+}
+
+static void compression_free(ldns_rbnode_t* node, void* arg)
+{
+ (void)arg;
+ ldns_rdf_deep_free((ldns_rdf*)node->key);
+ LDNS_FREE(node);
+}
+
+/*
+ * Reads a complete dynamic update message and sends it.
+ */
+static perf_result_t build_update(const perf_region_t* record, perf_buffer_t* msg)
+{
+ perf_region_t input, token;
+ char * msgbase, *str;
+ bool is_update;
+ int updates = 0;
+ int prereqs = 0;
+ perf_result_t result = PERF_R_FAILURE;
+ ldns_rdf* origin = 0;
+ ldns_rr* rr = 0;
+ ldns_buffer* lmsg = 0;
+ ldns_rbtree_t compression;
+ const char* errstr;
+
+ input = *record;
+ msgbase = perf_buffer_base(msg);
+ ldns_rbtree_init(&compression, ldns_dname_compare_v);
+
+ // Fill LDNS buffer with current message (DNS headers)
+ if (!(lmsg = ldns_buffer_new(perf_buffer_length(msg)))) {
+ perf_log_fatal("unable to create LDNS buffer for DNS message");
+ goto done; // for scan-build / sonarcloud
+ }
+ ldns_buffer_write(lmsg, perf_buffer_base(msg), perf_buffer_usedlength(msg));
+
+ if (!(origin = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, input.base))) {
+ perf_log_warning("Unable to parse domain name %s", (char*)input.base);
+ goto done;
+ }
+ if (ldns_dname2buffer_wire_compress(lmsg, origin, &compression) != LDNS_STATUS_OK) {
+ perf_log_warning("Unable to write domain name %s to wire format", (char*)input.base);
+ goto done;
+ }
+
+ ldns_buffer_write_u16(lmsg, 6); // SOA
+ ldns_buffer_write_u16(lmsg, 1); // IN
+
+ while (true) {
+ input.base += strlen(input.base) + 1;
+ if (input.base >= record->base + record->length) {
+ perf_log_warning("incomplete update: %s", (char*)record->base);
+ result = PERF_R_FAILURE;
+ goto done;
+ }
+
+ is_update = false;
+ token.base = input.base;
+ token.length = strcspn(token.base, WHITESPACE);
+ str = input.base + token.length;
+ errstr = 0;
+ if (token_equals(&token, "send")) {
+ break;
+ }
+
+ rr = ldns_rr_new();
+ ldns_rr_set_ttl(rr, 0);
+ ldns_rr_set_type(rr, LDNS_RR_TYPE_ANY);
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_IN);
+
+ if (token_equals(&token, "add")) {
+ result = read_update_line(str, origin, true, true, true, true, &rr, &errstr);
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_IN);
+ is_update = true;
+ } else if (token_equals(&token, "delete")) {
+ result = read_update_line(str, origin, false, false, true, false, &rr, &errstr);
+ if (ldns_rr_rd_count(rr)) {
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_NONE);
+ } else {
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_ANY);
+ }
+ is_update = true;
+ } else if (token_equals(&token, "require")) {
+ result = read_update_line(str, origin, false, false, true, false, &rr, &errstr);
+ if (ldns_rr_rd_count(rr)) {
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_IN);
+ } else {
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_ANY);
+ }
+ is_update = false;
+ } else if (token_equals(&token, "prohibit")) {
+ result = read_update_line(str, origin, false, false, false, false, &rr, &errstr);
+ ldns_rr_set_class(rr, LDNS_RR_CLASS_NONE);
+ is_update = false;
+ } else {
+ perf_log_warning("invalid update command: %s", (char*)input.base);
+ result = PERF_R_FAILURE;
+ }
+
+ if (result != PERF_R_SUCCESS) {
+ if (errstr) {
+ perf_log_warning("invalid update command, %s: %s", errstr, (char*)input.base);
+ } else if (result == PERF_R_INVALIDUPDATE) {
+ perf_log_warning("invalid update command: %s", (char*)input.base);
+ } else {
+ perf_log_warning("error processing update command: %s", (char*)input.base);
+ }
+ ldns_rr_free(rr);
+ goto done;
+ }
+
+ if (!is_update && updates > 0) {
+ perf_log_warning("prereqs must precede updates");
+ result = PERF_R_FAILURE;
+ ldns_rr_free(rr);
+ goto done;
+ }
+
+ if (ldns_rr2buffer_wire_compress(lmsg, rr, LDNS_SECTION_ANSWER, &compression) != LDNS_STATUS_OK) {
+ perf_log_warning("Unable to write update message to wire format");
+ ldns_rr_free(rr);
+ goto done;
+ }
+ ldns_rr_free(rr);
+
+ if (is_update)
+ updates++;
+ else
+ prereqs++;
+ }
+
+ if (ldns_buffer_position(lmsg) - perf_buffer_usedlength(msg) > perf_buffer_availablelength(msg)) {
+ perf_log_warning("out of space in message buffer");
+ result = PERF_R_NOSPACE;
+ goto done;
+ }
+ uint8_t* p = ldns_buffer_begin(lmsg) + perf_buffer_usedlength(msg);
+ perf_buffer_putmem(msg, p, ldns_buffer_position(lmsg) - perf_buffer_usedlength(msg));
+
+ msgbase[7] = prereqs; /* ANCOUNT = number of prereqs */
+ msgbase[9] = updates; /* AUCOUNT = number of updates */
+
+ result = PERF_R_SUCCESS;
+
+done:
+ ldns_buffer_free(lmsg);
+ ldns_rdf_deep_free(origin);
+ ldns_traverse_postorder(&compression, compression_free, 0);
+
+ return result;
+}
+#endif
+
+perf_result_t perf_dns_buildrequest(const perf_region_t* record, uint16_t qid,
+ bool edns, bool dnssec, bool is_update,
+ perf_tsigkey_t* tsigkey, perf_ednsoption_t* edns_option,
+ perf_buffer_t* msg)
+{
+ unsigned int flags;
+ perf_result_t result;
+
+ if (is_update)
+ flags = 5 << 11; // opcode UPDATE
+ else
+ flags = 0x0100U; // flag RD
+
+ /* Create the DNS packet header */
+ perf_buffer_putuint16(msg, qid);
+ perf_buffer_putuint16(msg, flags); /* flags */
+ perf_buffer_putuint16(msg, 1); /* qdcount */
+ perf_buffer_putuint16(msg, 0); /* ancount */
+ perf_buffer_putuint16(msg, 0); /* aucount */
+ perf_buffer_putuint16(msg, 0); /* arcount */
+
+ if (is_update) {
+#ifdef HAVE_LDNS
+ result = build_update(record, msg);
+#else
+ result = PERF_R_FAILURE;
+#endif
+ } else {
+ result = build_query(record, msg);
+ }
+
+ if (result == PERF_R_SUCCESS && edns) {
+ result = perf_add_edns(msg, dnssec, edns_option);
+ }
+
+ if (result == PERF_R_SUCCESS && tsigkey) {
+ result = perf_add_tsig(msg, tsigkey);
+ }
+
+ return result;
+}
diff --git a/src/dns.h b/src/dns.h
new file mode 100644
index 0000000..cb8cb49
--- /dev/null
+++ b/src/dns.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "result.h"
+#include "buffer.h"
+#include "edns.h"
+#include "tsig.h"
+
+#ifndef PERF_DNS_H
+#define PERF_DNS_H 1
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define MAX_UDP_PACKET 512
+
+extern const char* perf_dns_rcode_strings[];
+
+perf_result_t perf_dname_fromstring(const char* str, size_t len, perf_buffer_t* target);
+perf_result_t perf_qtype_fromstring(const char* str, size_t len, perf_buffer_t* target);
+
+perf_result_t perf_dns_buildrequest(const perf_region_t* record, uint16_t qid,
+ bool edns, bool dnssec, bool is_update,
+ perf_tsigkey_t* tsigkey, perf_ednsoption_t* edns_option,
+ perf_buffer_t* msg);
+
+#endif
diff --git a/src/dnsperf.1.in b/src/dnsperf.1.in
new file mode 100644
index 0000000..f6def3b
--- /dev/null
+++ b/src/dnsperf.1.in
@@ -0,0 +1,366 @@
+.\" Copyright 2019-2021 OARC, Inc.
+.\" Copyright 2017-2018 Akamai Technologies
+.\" Copyright 2006-2016 Nominum, Inc.
+.\" All rights reserved.
+.\"
+.\" Licensed under the Apache License, Version 2.0 (the "License");
+.\" you may not use this file except in compliance with the License.
+.\" You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing, software
+.\" distributed under the License is distributed on an "AS IS" BASIS,
+.\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.\" See the License for the specific language governing permissions and
+.\" limitations under the License.
+.TH dnsperf 1 "@PACKAGE_VERSION@" "dnsperf"
+.SH NAME
+dnsperf \- test the performance of a DNS server
+.SH SYNOPSIS
+.hy 0
+.ad l
+\fBdnsperf\fR\ [\fB\-a\ \fIlocal_addr\fB\fR]
+[\fB\-b\ \fIbufsize\fB\fR]
+[\fB\-c\ \fIclients\fB\fR]
+[\fB\-d\ \fIdatafile\fB\fR]
+[\fB\-D\fR]
+[\fB\-e\fR]
+[\fB\-E\ \fIcode:secret\fB\fR]
+[\fB\-f\ \fIfamily\fB\fR]
+[\fB\-h\fR]
+[\fB\-l\ \fIlimit\fB\fR]
+[\fB\-m\ \fImode\fB\fR]
+[\fB\-n\ \fIruns_through_file\fB\fR]
+[\fB\-p\ \fIport\fB\fR]
+[\fB\-q\ \fInum_queries\fB\fR]
+[\fB\-Q\ \fImax_qps\fB\fR]
+[\fB\-s\ \fIserver_addr\fB\fR]
+[\fB\-S\ \fIstats_interval\fB\fR]
+[\fB\-t\ \fItimeout\fB\fR]
+[\fB\-T\ \fIthreads\fB\fR]
+[\fB\-u\fR]
+[\fB\-v\fR]
+[\fB\-x\ \fIlocal_port\fB\fR]
+[\fB\-y\ \fI[alg:]name:secret\fB\fR]
+.ad
+.hy
+.SH DESCRIPTION
+\fBdnsperf\fR is a DNS server performance testing tool. It is primarily
+intended for measuring the performance of authoritative DNS servers, but it
+can also be used for measuring caching server performance in a closed
+laboratory environment. For testing caching servers resolving against the
+live Internet, the \fBresperf\fR program is preferred.
+
+It is recommended that \fBdnsperf\fR and the name server under test be run
+on separate machines, so that the CPU usage of \fBdnsperf\fR itself does not
+slow down the name server. The two machines should be connected with a fast
+network, preferably a dedicated Gigabit Ethernet segment. Testing through a
+router or firewall is not advisable.
+.SS "Configuring the name server"
+If using \fBdnsperf\fR to test an authoritative server, the name server
+under test should be set up to serve one or more zones similar in size and
+number to what the server is expected to serve in production.
+
+Also, be sure to turn off recursion in the server's configuration (in BIND
+8/9, specify "recursion no;" in the options block). In BIND 8, you should
+also specify "fetch-glue no;"; otherwise the server may attempt to retrieve
+glue information from the Internet during the test, slowing it down by an
+unpredictable factor.
+.SS "Constructing a query input file"
+A \fBdnsperf\fR input file should contain a large and realistic set of
+queries, on the order of ten thousand to a million. The input file contains
+one line per query, consisting of a domain name and an RR type name
+separated by a space. The class of the query is implicitly IN.
+
+When measuring the performance serving non-terminal zones such as the root
+zone or TLDs, note that such servers spend most of their time providing
+referral responses, not authoritative answers. Therefore, a realistic input
+file might consist mostly of queries for type A for names *below*, not at,
+the delegations present in the zone. For example, when testing the
+performance of a server configured to be authoritative for the top-level
+domain "fi.", which contains delegations for domains like "helsinki.fi" and
+"turku.fi", the input file could contain lines like
+.RS
+.hy 0
+
+.nf
+www.turku.fi A
+www.helsinki.fi A
+.fi
+.hy
+.RE
+
+where the "www" prefix ensures that the server will respond with a referral.
+Ideally, a realistic proportion of queries for nonexistent domains should be
+mixed in with those for existing ones, and the lines of the input file
+should be in a random order.
+.SS "Constructing a dynamic update input file"
+To test dynamic update performance, \fBdnsperf\fR is run with the \fB\-u\fR
+option, and the input file is constructed of blocks of lines describing
+dynamic update messages. The first line in a block contains the zone name:
+.RS
+.hy 0
+
+.nf
+example.com
+.fi
+.hy
+.RE
+
+Subsequent lines contain prerequisites, if there are any. Prerequisites can
+specify that a name may or may not exist, an rrset may or may not exist, or
+an rrset exists and its rdata matches all specified rdata for that name and
+type. The keywords "require" and "prohibit" are followed by the appropriate
+information. All relative names are considered to be relative to the zone
+name. The following lines show the 5 types of prerequisites.
+.RS
+.hy 0
+
+.nf
+require a
+require a A
+require a A 1.2.3.4
+prohibit x
+prohibit x A
+.fi
+.hy
+.RE
+
+Subsequent lines contain records to be added, records to be deleted, rrsets
+to be deleted, or names to be deleted. The keywords "add" or "delete" are
+followed by the appropriate information. All relative names are considered
+to be relative to the zone name. The following lines show the 4 types of
+updates.
+.RS
+.hy 0
+
+.nf
+add x 3600 A 10.1.2.3
+delete y A 10.1.2.3
+delete z A
+delete w
+.fi
+.hy
+.RE
+
+Each update message is terminated by a line containing the command:
+.RS
+.hy 0
+
+.nf
+send
+.fi
+.hy
+.RE
+.SS "Running the tests"
+When running \fBdnsperf\fR, a data file (the \fB\-d\fR option) and server
+(the \fB\-s\fR option) will normally be specified. The output of dnsperf is
+mostly self-explanatory. Pay attention to the number of dropped packets
+reported - when running the test over a local Ethernet connection, it should
+be zero. If one or more packets has been dropped, there may be a problem
+with the network connection. In that case, the results should be considered
+suspect and the test repeated.
+.SH OPTIONS
+
+\fB-a \fIlocal_addr\fB\fR
+.br
+.RS
+Specifies the local address from which to send requests. The default is the
+wildcard address.
+.RE
+
+\fB-b \fIbufsize\fB\fR
+.br
+.RS
+Sets the size of the socket's send and receive buffers, in kilobytes. If not
+specified, the operating system's default is used.
+.RE
+
+\fB-c \fIclients\fB\fR
+.br
+.RS
+Act as multiple clients. Requests are sent from multiple sockets. The
+default is to act as 1 client.
+.RE
+
+\fB-d \fIdatafile\fB\fR
+.br
+.RS
+Specifies the input data file. If not specified, \fBdnsperf\fR will read
+from standard input.
+.RE
+
+\fB-D\fR
+.br
+.RS
+Sets the DO (DNSSEC OK) bit [RFC3225] in all packets sent. This also enables
+EDNS0, which is required for DNSSEC.
+.RE
+
+\fB-e\fR
+.br
+.RS
+Enables EDNS0 [RFC2671], by adding an OPT record to all packets sent.
+.RE
+
+\fB-E \fIcode:value\fB\fR
+.br
+.RS
+Add an EDNS [RFC2671] option to all packets sent, using the specified
+numeric option code and value expressed as a a hex-encoded string. This also
+enables EDNS0.
+.RE
+
+\fB-f \fIfamily\fB\fR
+.br
+.RS
+Specifies the address family used for sending DNS packets. The possible
+values are "inet", "inet6", or "any". If "any" (the default value) is
+specified, \fBdnsperf\fR will use whichever address family is appropriate
+for the server it is sending packets to.
+.RE
+
+\fB-h\fR
+.br
+.RS
+Print a usage statement and exit.
+.RE
+
+\fB-l \fIlimit\fB\fR
+.br
+.RS
+Specifies a time limit for the run, in seconds. This may cause the input to
+be read multiple times, or only some of the input to be read. The default
+behavior is to read the input once, and have no specific time limit.
+.RE
+
+\fB-n \fIruns_through_file\fB\fR
+.br
+.RS
+Run through the input file at most this many times. If no time limit is set,
+the file will be read exactly this number of times; if a time limit is set,
+the file may be read fewer times.
+.RE
+
+\fB-p \fIport\fB\fR
+.br
+.RS
+Sets the port on which the DNS packets are sent. If not specified, the
+standard DNS port (udp/tcp 53, dot/tls 853) is used.
+.RE
+
+\fB-q \fInum_queries\fB\fR
+.br
+.RS
+Sets the maximum number of outstanding requests. When this value is reached,
+\fBdnsperf\fR will not send any more requests until either responses are
+received or requests time out. The default value is 100.
+.RE
+
+\fB-Q \fImax_qps\fB\fR
+.br
+.RS
+Limits the number of requests per second. There is no default limit.
+.RE
+
+\fB-m \fImode\fB\fR
+.br
+.RS
+Specifies the transport mode to use, "udp", "tcp" or "dot"/"tls".
+Default is "udp".
+.RE
+
+\fB-s \fIserver_addr\fB\fR
+.br
+.RS
+Specifies the name or address of the server to which requests will be sent.
+The default is the loopback address, 127.0.0.1.
+.RE
+
+\fB-S \fIstats_interval\fB\fR
+.br
+.RS
+If this parameter is specified, a count of the number of queries per second
+during the interval will be printed out every stats_interval seconds.
+.RE
+
+\fB-t \fItimeout\fB\fR
+.br
+.RS
+Specifies the request timeout value, in seconds. \fBdnsperf\fR will no
+longer wait for a response to a particular request after this many seconds
+have elapsed. The default is 5 seconds.
+.RE
+
+\fB-T \fIthreads\fB\fR
+.br
+.RS
+Run multiple client threads. By default, \fBdnsperf\fR uses one thread for
+sending requests and one thread for receiving responses. If this option is
+specified, \fBdnsperf\fR will instead use N pairs of send/receive threads.
+.RE
+
+\fB-u\fR
+.br
+.RS
+Instructs \fBdnsperf\fR to send DNS dynamic update messages, rather than
+queries. The format of the input file is different in this case; see the
+"Constructing a dynamic update input file" section for more details.
+.RE
+
+\fB-v\fR
+.br
+.RS
+Enables verbose mode. The DNS RCODE of each response will be reported to
+standard output when the response is received, as will the latency. If a
+query times out, it will be reported with the special string "T" instead of
+a normal DNS RCODE. If a query is interrupted, it will be reported with the
+special string "I". Additional information regarding network readiness and
+congestion will also be reported.
+.RE
+
+\fB-x \fIlocal_port\fB\fR
+.br
+.RS
+Specifies the local port from which to send requests. The default is the
+wildcard port (0).
+
+If acting as multiple clients and the wildcard port is used, each client
+will use a different random port. If a port is specified, the clients will
+use a range of ports starting with the specified one.
+.RE
+
+\fB-y \fI[alg:]name:secret\fB\fR
+.br
+.RS
+Add a TSIG record [RFC2845] to all packets sent, using the specified TSIG
+key algorithm, name and secret, where the algorithm defaults to hmac-md5 and
+the secret is expressed as a base-64 encoded string.
+Available algorithms are: hmac-md5, hmac-sha1, hmac-sha224, hmac-sha256,
+hmac-sha384 and hmac-sha512.
+.RE
+.SH "SEE ALSO"
+\fBresperf\fR(1)
+.SH AUTHOR
+Nominum, Inc.
+.LP
+Maintained by DNS-OARC
+.LP
+.RS
+.I https://www.dns-oarc.net/
+.RE
+.LP
+.SH BUGS
+For issues and feature requests please use:
+.LP
+.RS
+\fI@PACKAGE_URL@\fP
+.RE
+.LP
+For question and help please use:
+.LP
+.RS
+\fI@PACKAGE_BUGREPORT@\fP
+.RE
+.LP
diff --git a/src/dnsperf.c b/src/dnsperf.c
new file mode 100644
index 0000000..f001370
--- /dev/null
+++ b/src/dnsperf.c
@@ -0,0 +1,1220 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/***
+ *** DNS Performance Testing Tool
+ ***/
+
+#include "config.h"
+
+#include "net.h"
+#include "datafile.h"
+#include "dns.h"
+#include "log.h"
+#include "opt.h"
+#include "os.h"
+#include "util.h"
+#include "list.h"
+#include "buffer.h"
+
+#include <inttypes.h>
+#include <errno.h>
+#include <math.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <openssl/ssl.h>
+#include <openssl/conf.h>
+#include <openssl/err.h>
+
+#define DEFAULT_SERVER_NAME "127.0.0.1"
+#define DEFAULT_SERVER_PORT 53
+#define DEFAULT_SERVER_TLS_PORT 853
+#define DEFAULT_SERVER_PORTS "udp/tcp 53 or dot/tls 853"
+#define DEFAULT_LOCAL_PORT 0
+#define DEFAULT_MAX_OUTSTANDING 100
+#define DEFAULT_TIMEOUT 5
+
+#define TIMEOUT_CHECK_TIME 100000
+
+#define MAX_INPUT_DATA (64 * 1024)
+
+#define MAX_SOCKETS 256
+
+#define RECV_BATCH_SIZE 16
+
+typedef struct {
+ int argc;
+ char** argv;
+ int family;
+ uint32_t clients;
+ uint32_t threads;
+ uint32_t maxruns;
+ uint64_t timelimit;
+ perf_sockaddr_t server_addr;
+ perf_sockaddr_t local_addr;
+ uint64_t timeout;
+ uint32_t bufsize;
+ bool edns;
+ bool dnssec;
+ perf_tsigkey_t* tsigkey;
+ perf_ednsoption_t* edns_option;
+ uint32_t max_outstanding;
+ uint32_t max_qps;
+ uint64_t stats_interval;
+ bool updates;
+ bool verbose;
+ enum perf_net_mode mode;
+} config_t;
+
+typedef struct {
+ uint64_t start_time;
+ uint64_t end_time;
+ uint64_t stop_time;
+ struct timespec stop_time_ns;
+} times_t;
+
+typedef struct {
+ uint64_t rcodecounts[16];
+
+ uint64_t num_sent;
+ uint64_t num_interrupted;
+ uint64_t num_timedout;
+ uint64_t num_completed;
+
+ uint64_t total_request_size;
+ uint64_t total_response_size;
+
+ uint64_t latency_sum;
+ uint64_t latency_sum_squares;
+ uint64_t latency_min;
+ uint64_t latency_max;
+} stats_t;
+
+typedef perf_list(struct query_info) query_list;
+
+typedef struct query_info {
+ uint64_t timestamp;
+ query_list* list;
+ char* desc;
+ struct perf_net_socket* sock;
+ /*
+ * This link links the query into the list of outstanding
+ * queries or the list of available query IDs.
+ */
+ perf_link(struct query_info);
+} query_info;
+
+#define NQIDS 65536
+
+typedef struct {
+ query_info queries[NQIDS];
+ query_list outstanding_queries;
+ query_list unused_queries;
+
+ pthread_t sender;
+ pthread_t receiver;
+
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+
+ unsigned int nsocks;
+ int current_sock;
+ struct perf_net_socket* socks;
+
+ bool done_sending;
+ uint64_t done_send_time;
+
+ const config_t* config;
+ const times_t* times;
+ stats_t stats;
+
+ uint32_t max_outstanding;
+ uint32_t max_qps;
+
+ uint64_t last_recv;
+} threadinfo_t;
+
+static threadinfo_t* threads;
+
+static pthread_mutex_t start_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t start_cond = PTHREAD_COND_INITIALIZER;
+static bool started;
+
+static bool interrupted = false;
+
+static int threadpipe[2];
+static int mainpipe[2];
+static int intrpipe[2];
+
+static perf_datafile_t* input;
+
+const char* progname = "dnsperf";
+
+static void
+handle_sigint(int sig)
+{
+ (void)sig;
+ if (write(intrpipe[1], "", 1)) { // lgtm [cpp/empty-block]
+ }
+}
+
+static void
+print_initial_status(const config_t* config)
+{
+ time_t now;
+ char buf[255], ct[32];
+ int i;
+
+ printf("[Status] Command line: %s", progname);
+ for (i = 1; i < config->argc; i++)
+ printf(" %s", config->argv[i]);
+ printf("\n");
+
+ perf_sockaddr_format(&config->server_addr, buf, sizeof(buf));
+ printf("[Status] Sending %s (to %s)\n",
+ config->updates ? "updates" : "queries", buf);
+
+ now = time(NULL);
+ printf("[Status] Started at: %s", ctime_r(&now, ct));
+
+ printf("[Status] Stopping after ");
+ if (config->timelimit)
+ printf("%u.%06u seconds",
+ (unsigned int)(config->timelimit / MILLION),
+ (unsigned int)(config->timelimit % MILLION));
+ if (config->timelimit && config->maxruns)
+ printf(" or ");
+ if (config->maxruns)
+ printf("%u run%s through file", config->maxruns,
+ config->maxruns == 1 ? "" : "s");
+ printf("\n");
+}
+
+static void
+print_final_status(const config_t* config)
+{
+ const char* reason;
+
+ if (interrupted)
+ reason = "interruption";
+ else if (config->maxruns > 0 && perf_datafile_nruns(input) == config->maxruns)
+ reason = "end of file";
+ else
+ reason = "time limit";
+
+ printf("[Status] Testing complete (%s)\n", reason);
+ printf("\n");
+}
+
+static double
+stddev(uint64_t sum_of_squares, uint64_t sum, uint64_t total)
+{
+ double squared;
+
+ squared = (double)sum * (double)sum;
+ return sqrt((sum_of_squares - (squared / total)) / (total - 1));
+}
+
+static void
+print_statistics(const config_t* config, const times_t* times, stats_t* stats)
+{
+ const char* units;
+ uint64_t run_time;
+ bool first_rcode;
+ uint64_t latency_avg;
+ unsigned int i;
+
+ units = config->updates ? "Updates" : "Queries";
+
+ run_time = times->end_time - times->start_time;
+
+ printf("Statistics:\n\n");
+
+ printf(" %s sent: %" PRIu64 "\n",
+ units, stats->num_sent);
+ printf(" %s completed: %" PRIu64 " (%.2lf%%)\n",
+ units, stats->num_completed,
+ PERF_SAFE_DIV(100.0 * stats->num_completed, stats->num_sent));
+ printf(" %s lost: %" PRIu64 " (%.2lf%%)\n",
+ units, stats->num_timedout,
+ PERF_SAFE_DIV(100.0 * stats->num_timedout, stats->num_sent));
+ if (stats->num_interrupted > 0)
+ printf(" %s interrupted: %" PRIu64 " (%.2lf%%)\n",
+ units, stats->num_interrupted,
+ PERF_SAFE_DIV(100.0 * stats->num_interrupted, stats->num_sent));
+ printf("\n");
+
+ printf(" Response codes: ");
+ first_rcode = true;
+ for (i = 0; i < 16; i++) {
+ if (stats->rcodecounts[i] == 0)
+ continue;
+ if (first_rcode)
+ first_rcode = false;
+ else
+ printf(", ");
+ printf("%s %" PRIu64 " (%.2lf%%)",
+ perf_dns_rcode_strings[i], stats->rcodecounts[i],
+ (stats->rcodecounts[i] * 100.0) / stats->num_completed);
+ }
+ printf("\n");
+
+ printf(" Average packet size: request %u, response %u\n",
+ (unsigned int)PERF_SAFE_DIV(stats->total_request_size, stats->num_sent),
+ (unsigned int)PERF_SAFE_DIV(stats->total_response_size,
+ stats->num_completed));
+ printf(" Run time (s): %u.%06u\n",
+ (unsigned int)(run_time / MILLION),
+ (unsigned int)(run_time % MILLION));
+ printf(" %s per second: %.6lf\n", units,
+ PERF_SAFE_DIV(stats->num_completed, (((double)run_time) / MILLION)));
+
+ printf("\n");
+
+ latency_avg = PERF_SAFE_DIV(stats->latency_sum, stats->num_completed);
+ printf(" Average Latency (s): %u.%06u (min %u.%06u, max %u.%06u)\n",
+ (unsigned int)(latency_avg / MILLION),
+ (unsigned int)(latency_avg % MILLION),
+ (unsigned int)(stats->latency_min / MILLION),
+ (unsigned int)(stats->latency_min % MILLION),
+ (unsigned int)(stats->latency_max / MILLION),
+ (unsigned int)(stats->latency_max % MILLION));
+ if (stats->num_completed > 1) {
+ printf(" Latency StdDev (s): %f\n",
+ stddev(stats->latency_sum_squares, stats->latency_sum,
+ stats->num_completed)
+ / MILLION);
+ }
+
+ printf("\n");
+}
+
+static void
+sum_stats(const config_t* config, stats_t* total)
+{
+ unsigned int i, j;
+
+ memset(total, 0, sizeof(*total));
+
+ for (i = 0; i < config->threads; i++) {
+ stats_t* stats = &threads[i].stats;
+
+ for (j = 0; j < 16; j++)
+ total->rcodecounts[j] += stats->rcodecounts[j];
+
+ total->num_sent += stats->num_sent;
+ total->num_interrupted += stats->num_interrupted;
+ total->num_timedout += stats->num_timedout;
+ total->num_completed += stats->num_completed;
+
+ total->total_request_size += stats->total_request_size;
+ total->total_response_size += stats->total_response_size;
+
+ total->latency_sum += stats->latency_sum;
+ total->latency_sum_squares += stats->latency_sum_squares;
+ if (stats->latency_min < total->latency_min || i == 0)
+ total->latency_min = stats->latency_min;
+ if (stats->latency_max > total->latency_max)
+ total->latency_max = stats->latency_max;
+ }
+}
+
+static char*
+stringify(unsigned int value)
+{
+ static char buf[20];
+
+ snprintf(buf, sizeof(buf), "%u", value);
+ return buf;
+}
+
+static void
+setup(int argc, char** argv, config_t* config)
+{
+ const char* family = NULL;
+ const char* server_name = DEFAULT_SERVER_NAME;
+ in_port_t server_port = 0;
+ const char* local_name = NULL;
+ in_port_t local_port = DEFAULT_LOCAL_PORT;
+ const char* filename = NULL;
+ const char* edns_option = NULL;
+ const char* tsigkey = NULL;
+ const char* mode = 0;
+
+ memset(config, 0, sizeof(*config));
+ config->argc = argc;
+ config->argv = argv;
+
+ config->family = AF_UNSPEC;
+ config->clients = 1;
+ config->threads = 1;
+ config->timeout = DEFAULT_TIMEOUT * MILLION;
+ config->max_outstanding = DEFAULT_MAX_OUTSTANDING;
+ config->mode = sock_udp;
+
+ perf_opt_add('f', perf_opt_string, "family",
+ "address family of DNS transport, inet or inet6", "any",
+ &family);
+ perf_opt_add('m', perf_opt_string, "mode", "set transport mode: udp, tcp or dot/tls", "udp", &mode);
+ perf_opt_add('s', perf_opt_string, "server_addr",
+ "the server to query", DEFAULT_SERVER_NAME, &server_name);
+ perf_opt_add('p', perf_opt_port, "port",
+ "the port on which to query the server",
+ DEFAULT_SERVER_PORTS, &server_port);
+ perf_opt_add('a', perf_opt_string, "local_addr",
+ "the local address from which to send queries", NULL,
+ &local_name);
+ perf_opt_add('x', perf_opt_port, "local_port",
+ "the local port from which to send queries",
+ stringify(DEFAULT_LOCAL_PORT), &local_port);
+ perf_opt_add('d', perf_opt_string, "datafile",
+ "the input data file", "stdin", &filename);
+ perf_opt_add('c', perf_opt_uint, "clients",
+ "the number of clients to act as", NULL,
+ &config->clients);
+ perf_opt_add('T', perf_opt_uint, "threads",
+ "the number of threads to run", NULL,
+ &config->threads);
+ perf_opt_add('n', perf_opt_uint, "maxruns",
+ "run through input at most N times", NULL,
+ &config->maxruns);
+ perf_opt_add('l', perf_opt_timeval, "timelimit",
+ "run for at most this many seconds", NULL,
+ &config->timelimit);
+ perf_opt_add('b', perf_opt_uint, "buffer_size",
+ "socket send/receive buffer size in kilobytes", NULL,
+ &config->bufsize);
+ perf_opt_add('t', perf_opt_timeval, "timeout",
+ "the timeout for query completion in seconds",
+ stringify(DEFAULT_TIMEOUT), &config->timeout);
+ perf_opt_add('e', perf_opt_boolean, NULL,
+ "enable EDNS 0", NULL, &config->edns);
+ perf_opt_add('E', perf_opt_string, "code:value",
+ "send EDNS option", NULL, &edns_option);
+ perf_opt_add('D', perf_opt_boolean, NULL,
+ "set the DNSSEC OK bit (implies EDNS)", NULL,
+ &config->dnssec);
+ perf_opt_add('y', perf_opt_string, "[alg:]name:secret",
+ "the TSIG algorithm, name and secret (base64)", NULL,
+ &tsigkey);
+ perf_opt_add('q', perf_opt_uint, "num_queries",
+ "the maximum number of queries outstanding",
+ stringify(DEFAULT_MAX_OUTSTANDING),
+ &config->max_outstanding);
+ perf_opt_add('Q', perf_opt_uint, "max_qps",
+ "limit the number of queries per second", NULL,
+ &config->max_qps);
+ perf_opt_add('S', perf_opt_timeval, "stats_interval",
+ "print qps statistics every N seconds",
+ NULL, &config->stats_interval);
+ perf_opt_add('u', perf_opt_boolean, NULL,
+ "send dynamic updates instead of queries",
+ NULL, &config->updates);
+ perf_opt_add('v', perf_opt_boolean, NULL,
+ "verbose: report each query and additional information to stdout",
+ NULL, &config->verbose);
+ bool log_stdout = false;
+ perf_opt_add('W', perf_opt_boolean, NULL, "log warnings and errors to stdout instead of stderr", NULL, &log_stdout);
+
+ perf_opt_parse(argc, argv);
+
+ if (log_stdout) {
+ perf_log_tostdout();
+ }
+
+ if (mode != 0)
+ config->mode = perf_net_parsemode(mode);
+
+ if (!server_port) {
+ server_port = config->mode == sock_tls ? DEFAULT_SERVER_TLS_PORT : DEFAULT_SERVER_PORT;
+ }
+
+ if (family != NULL)
+ config->family = perf_net_parsefamily(family);
+ perf_net_parseserver(config->family, server_name, server_port,
+ &config->server_addr);
+ perf_net_parselocal(config->server_addr.sa.sa.sa_family,
+ local_name, local_port, &config->local_addr);
+
+ input = perf_datafile_open(filename);
+
+ if (config->maxruns == 0 && config->timelimit == 0)
+ config->maxruns = 1;
+ perf_datafile_setmaxruns(input, config->maxruns);
+
+ if (config->dnssec || edns_option != NULL)
+ config->edns = true;
+
+ if (tsigkey != NULL)
+ config->tsigkey = perf_tsig_parsekey(tsigkey);
+
+ if (edns_option != NULL)
+ config->edns_option = perf_edns_parseoption(edns_option);
+
+ /*
+ * If we run more threads than max-qps, some threads will have
+ * ->max_qps set to 0, and be unlimited.
+ */
+ if (config->max_qps > 0 && config->threads > config->max_qps)
+ config->threads = config->max_qps;
+
+ /*
+ * We also can't run more threads than clients.
+ */
+ if (config->threads > config->clients)
+ config->threads = config->clients;
+
+#ifndef HAVE_LDNS
+ if (config->updates) {
+ perf_log_fatal("Unable to dynamic update, support not built in");
+ }
+#endif
+}
+
+static void
+cleanup(config_t* config)
+{
+ unsigned int i;
+
+ perf_datafile_close(&input);
+ for (i = 0; i < 2; i++) {
+ close(threadpipe[i]);
+ close(mainpipe[i]);
+ close(intrpipe[i]);
+ }
+ if (config->tsigkey != NULL)
+ perf_tsig_destroykey(&config->tsigkey);
+ if (config->edns_option != NULL)
+ perf_edns_destroyoption(&config->edns_option);
+}
+
+typedef enum {
+ prepend_unused,
+ append_unused,
+ prepend_outstanding,
+} query_move_op;
+
+static inline void
+query_move(threadinfo_t* tinfo, query_info* q, query_move_op op)
+{
+ perf_list_unlink(*q->list, q);
+ switch (op) {
+ case prepend_unused:
+ q->list = &tinfo->unused_queries;
+ perf_list_prepend(tinfo->unused_queries, q);
+ break;
+ case append_unused:
+ q->list = &tinfo->unused_queries;
+ perf_list_append(tinfo->unused_queries, q);
+ break;
+ case prepend_outstanding:
+ q->list = &tinfo->outstanding_queries;
+ perf_list_prepend(tinfo->outstanding_queries, q);
+ break;
+ }
+}
+
+static inline uint64_t
+num_outstanding(const stats_t* stats)
+{
+ return stats->num_sent - stats->num_completed - stats->num_timedout;
+}
+
+static void
+wait_for_start(void)
+{
+ PERF_LOCK(&start_lock);
+ while (!started)
+ PERF_WAIT(&start_cond, &start_lock);
+ PERF_UNLOCK(&start_lock);
+}
+
+static void*
+do_send(void* arg)
+{
+ threadinfo_t* tinfo;
+ const config_t* config;
+ const times_t* times;
+ stats_t* stats;
+ unsigned int max_packet_size;
+ perf_buffer_t msg;
+ uint64_t now, run_time, req_time;
+ char input_data[MAX_INPUT_DATA];
+ perf_buffer_t lines;
+ perf_region_t used;
+ query_info* q;
+ int qid;
+ unsigned char packet_buffer[MAX_EDNS_PACKET];
+ unsigned char* base;
+ unsigned int length;
+ int n, i, any_inprogress = 0;
+ perf_result_t result;
+
+ tinfo = (threadinfo_t*)arg;
+ config = tinfo->config;
+ times = tinfo->times;
+ stats = &tinfo->stats;
+ max_packet_size = config->edns ? MAX_EDNS_PACKET : MAX_UDP_PACKET;
+ perf_buffer_init(&msg, packet_buffer, max_packet_size);
+ perf_buffer_init(&lines, input_data, sizeof(input_data));
+
+ wait_for_start();
+ now = perf_get_time();
+ while (!interrupted && now < times->stop_time) {
+ /* Avoid flooding the network too quickly. */
+ if (stats->num_sent < tinfo->max_outstanding && stats->num_sent % 2 == 1) {
+ if (stats->num_completed == 0)
+ usleep(1000);
+ else
+ sleep(0);
+ now = perf_get_time();
+ }
+
+ /* 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);
+ now = perf_get_time();
+ continue;
+ }
+ }
+
+ PERF_LOCK(&tinfo->lock);
+
+ /* Limit in-flight queries */
+ if (num_outstanding(stats) >= tinfo->max_outstanding) {
+ PERF_TIMEDWAIT(&tinfo->cond, &tinfo->lock, &times->stop_time_ns, NULL);
+ PERF_UNLOCK(&tinfo->lock);
+ now = perf_get_time();
+ continue;
+ }
+
+ q = perf_list_head(tinfo->unused_queries);
+ query_move(tinfo, q, prepend_outstanding);
+ q->timestamp = UINT64_MAX;
+
+ i = tinfo->nsocks * 2;
+ while (i--) {
+ q->sock = &tinfo->socks[tinfo->current_sock++ % tinfo->nsocks];
+ switch (perf_net_sockready(q->sock, threadpipe[0], TIMEOUT_CHECK_TIME)) {
+ case 0:
+ if (config->verbose) {
+ perf_log_warning("socket %p not ready", q->sock);
+ }
+ q->sock = 0;
+ continue;
+ case -1:
+ if (errno == EINPROGRESS) {
+ any_inprogress = 1;
+ q->sock = 0;
+ continue;
+ }
+ if (config->verbose) {
+ perf_log_warning("socket %p readiness check timed out", q->sock);
+ }
+ default:
+ break;
+ }
+ break;
+ };
+
+ if (!q->sock) {
+ query_move(tinfo, q, prepend_unused);
+ PERF_UNLOCK(&tinfo->lock);
+ now = perf_get_time();
+ continue;
+ }
+ PERF_UNLOCK(&tinfo->lock);
+
+ perf_buffer_clear(&lines);
+ result = perf_datafile_next(input, &lines, config->updates);
+ if (result != PERF_R_SUCCESS) {
+ if (result == PERF_R_INVALIDFILE)
+ perf_log_fatal("input file contains no data");
+ break;
+ }
+
+ qid = q - tinfo->queries;
+ perf_buffer_usedregion(&lines, &used);
+ perf_buffer_clear(&msg);
+ result = perf_dns_buildrequest(&used, qid,
+ config->edns, config->dnssec, config->updates,
+ config->tsigkey, config->edns_option,
+ &msg);
+ if (result != PERF_R_SUCCESS) {
+ PERF_LOCK(&tinfo->lock);
+ query_move(tinfo, q, prepend_unused);
+ PERF_UNLOCK(&tinfo->lock);
+ now = perf_get_time();
+ continue;
+ }
+
+ base = perf_buffer_base(&msg);
+ length = perf_buffer_usedlength(&msg);
+
+ now = perf_get_time();
+ if (config->verbose) {
+ free(q->desc);
+ q->desc = strdup(lines.base);
+ if (q->desc == NULL)
+ perf_log_fatal("out of memory");
+ }
+ q->timestamp = now;
+
+ n = perf_net_sendto(q->sock, base, length, 0, &config->server_addr.sa.sa,
+ config->server_addr.length);
+ if (n < 0) {
+ if (errno == EINPROGRESS) {
+ if (config->verbose) {
+ perf_log_warning("network congested, packet sending in progress");
+ }
+ any_inprogress = 1;
+ } else {
+ if (config->verbose) {
+ char __s[256];
+ perf_log_warning("failed to send packet: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+ PERF_LOCK(&tinfo->lock);
+ query_move(tinfo, q, prepend_unused);
+ PERF_UNLOCK(&tinfo->lock);
+ continue;
+ }
+ } else if ((unsigned int)n != length) {
+ perf_log_warning("failed to send full packet: only sent %d of %u", n, length);
+ PERF_LOCK(&tinfo->lock);
+ query_move(tinfo, q, prepend_unused);
+ PERF_UNLOCK(&tinfo->lock);
+ continue;
+ }
+ stats->num_sent++;
+
+ stats->total_request_size += length;
+ }
+
+ while (any_inprogress) {
+ any_inprogress = 0;
+ for (i = 0; i < tinfo->nsocks; i++) {
+ if (perf_net_sockready(&tinfo->socks[i], threadpipe[0], TIMEOUT_CHECK_TIME) == -1 && errno == EINPROGRESS) {
+ any_inprogress = 1;
+ }
+ }
+ }
+
+ tinfo->done_send_time = perf_get_time();
+ tinfo->done_sending = true;
+ if (write(mainpipe[1], "", 1)) { // lgtm [cpp/empty-block]
+ }
+ return NULL;
+}
+
+static void
+process_timeouts(threadinfo_t* tinfo, uint64_t now)
+{
+ struct query_info* q;
+ const config_t* config;
+
+ config = tinfo->config;
+
+ /* Avoid locking unless we need to. */
+ q = perf_list_tail(tinfo->outstanding_queries);
+ if (q == NULL || q->timestamp > now || now - q->timestamp < config->timeout)
+ return;
+
+ PERF_LOCK(&tinfo->lock);
+
+ do {
+ query_move(tinfo, q, append_unused);
+
+ tinfo->stats.num_timedout++;
+
+ if (q->desc != NULL) {
+ perf_log_printf("> T %s", q->desc);
+ } else {
+ perf_log_printf("[Timeout] %s timed out: msg id %u",
+ config->updates ? "Update" : "Query",
+ (unsigned int)(q - tinfo->queries));
+ }
+ q = perf_list_tail(tinfo->outstanding_queries);
+ } while (q != NULL && q->timestamp < now && now - q->timestamp >= config->timeout);
+
+ PERF_UNLOCK(&tinfo->lock);
+}
+
+typedef struct {
+ struct perf_net_socket* sock;
+ uint16_t qid;
+ uint16_t rcode;
+ unsigned int size;
+ uint64_t when;
+ uint64_t sent;
+ bool unexpected;
+ bool short_response;
+ char* desc;
+} received_query_t;
+
+static bool
+recv_one(threadinfo_t* tinfo, int which_sock,
+ unsigned char* packet_buffer, unsigned int packet_size,
+ received_query_t* recvd, int* saved_errnop)
+{
+ uint16_t* packet_header;
+ uint64_t now;
+ int n;
+
+ packet_header = (uint16_t*)packet_buffer;
+
+ n = perf_net_recv(&tinfo->socks[which_sock], packet_buffer, packet_size, 0);
+ now = perf_get_time();
+ if (n < 0) {
+ *saved_errnop = errno;
+ return false;
+ }
+ if (!n) {
+ // Treat connection closed like try again until reconnection features are in
+ *saved_errnop = EAGAIN;
+ return false;
+ }
+ recvd->sock = &tinfo->socks[which_sock];
+ recvd->qid = ntohs(packet_header[0]);
+ recvd->rcode = ntohs(packet_header[1]) & 0xF;
+ recvd->size = n;
+ recvd->when = now;
+ recvd->sent = 0;
+ recvd->unexpected = false;
+ recvd->short_response = (n < 4);
+ recvd->desc = NULL;
+ return true;
+}
+
+static inline void
+bit_set(unsigned char* bits, unsigned int bit)
+{
+ unsigned int shift, mask;
+
+ shift = 7 - (bit % 8);
+ mask = 1 << shift;
+
+ bits[bit / 8] |= mask;
+}
+
+static inline bool
+bit_check(unsigned char* bits, unsigned int bit)
+{
+ unsigned int shift;
+
+ shift = 7 - (bit % 8);
+
+ if ((bits[bit / 8] >> shift) & 0x01)
+ return true;
+ return false;
+}
+
+static void*
+do_recv(void* arg)
+{
+ threadinfo_t* tinfo;
+ stats_t* stats;
+ unsigned char packet_buffer[MAX_EDNS_PACKET];
+ received_query_t recvd[RECV_BATCH_SIZE] = { { 0, 0, 0, 0, 0, 0, false, false, 0 } };
+ unsigned int nrecvd;
+ int saved_errno;
+ unsigned char socketbits[MAX_SOCKETS / 8];
+ uint64_t now, latency;
+ query_info* q;
+ unsigned int current_socket, last_socket;
+ unsigned int i, j;
+
+ tinfo = (threadinfo_t*)arg;
+ stats = &tinfo->stats;
+
+ wait_for_start();
+ now = perf_get_time();
+ last_socket = 0;
+ while (!interrupted) {
+ process_timeouts(tinfo, now);
+
+ /*
+ * If we're done sending and either all responses have been
+ * received, stop.
+ */
+ if (tinfo->done_sending && num_outstanding(stats) == 0)
+ break;
+
+ /*
+ * Try to receive a few packets, so that we can process them
+ * atomically.
+ */
+ saved_errno = 0;
+ memset(socketbits, 0, sizeof(socketbits));
+ for (i = 0; i < RECV_BATCH_SIZE; i++) {
+ for (j = 0; j < tinfo->nsocks; j++) {
+ current_socket = (j + last_socket) % tinfo->nsocks;
+ if (bit_check(socketbits, current_socket))
+ continue;
+ if (recv_one(tinfo, current_socket, packet_buffer,
+ sizeof(packet_buffer), &recvd[i], &saved_errno)) {
+ last_socket = (current_socket + 1);
+ break;
+ }
+ bit_set(socketbits, current_socket);
+ if (saved_errno != EAGAIN)
+ break;
+ }
+ if (j == tinfo->nsocks)
+ break;
+ }
+ nrecvd = i;
+
+ /* Do all of the processing that requires the lock */
+ PERF_LOCK(&tinfo->lock);
+ for (i = 0; i < nrecvd; i++) {
+ if (recvd[i].short_response)
+ continue;
+
+ q = &tinfo->queries[recvd[i].qid];
+ if (q->list != &tinfo->outstanding_queries || q->timestamp == UINT64_MAX || !perf_net_sockeq(q->sock, recvd[i].sock)) {
+ recvd[i].unexpected = true;
+ continue;
+ }
+ query_move(tinfo, q, append_unused);
+ recvd[i].sent = q->timestamp;
+ recvd[i].desc = q->desc;
+ q->desc = NULL;
+ }
+ PERF_SIGNAL(&tinfo->cond);
+ PERF_UNLOCK(&tinfo->lock);
+
+ /* Now do the rest of the processing unlocked */
+ for (i = 0; i < nrecvd; i++) {
+ if (recvd[i].short_response) {
+ perf_log_warning("received short response");
+ continue;
+ }
+ if (recvd[i].unexpected) {
+ perf_log_warning("received a response with an "
+ "unexpected (maybe timed out) "
+ "id: %u",
+ recvd[i].qid);
+ continue;
+ }
+ latency = recvd[i].when - recvd[i].sent;
+ if (recvd[i].desc != NULL) {
+ perf_log_printf(
+ "> %s %s %u.%06u",
+ perf_dns_rcode_strings[recvd[i].rcode],
+ recvd[i].desc,
+ (unsigned int)(latency / MILLION),
+ (unsigned int)(latency % MILLION));
+ free(recvd[i].desc);
+ }
+
+ stats->num_completed++;
+ stats->total_response_size += recvd[i].size;
+ stats->rcodecounts[recvd[i].rcode]++;
+ stats->latency_sum += latency;
+ stats->latency_sum_squares += (latency * latency);
+ if (latency < stats->latency_min || stats->num_completed == 1)
+ stats->latency_min = latency;
+ if (latency > stats->latency_max)
+ stats->latency_max = latency;
+ }
+
+ if (nrecvd > 0)
+ tinfo->last_recv = recvd[nrecvd - 1].when;
+
+ /*
+ * If there was an error, handle it (by either ignoring it,
+ * blocking, or exiting).
+ */
+ if (nrecvd < RECV_BATCH_SIZE) {
+ if (saved_errno == EINTR) {
+ continue;
+ } else if (saved_errno == EAGAIN) {
+ perf_os_waituntilanyreadable(tinfo->socks, tinfo->nsocks,
+ threadpipe[0], TIMEOUT_CHECK_TIME);
+ now = perf_get_time();
+ continue;
+ } else {
+ char __s[256];
+ perf_log_fatal("failed to receive packet: %s", perf_strerror_r(saved_errno, __s, sizeof(__s)));
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static void*
+do_interval_stats(void* arg)
+{
+ threadinfo_t* tinfo;
+ stats_t total;
+ uint64_t now;
+ uint64_t last_interval_time;
+ uint64_t last_completed;
+ uint64_t interval_time;
+ uint64_t num_completed;
+ double qps;
+ struct perf_net_socket sock = { .mode = sock_pipe, .fd = threadpipe[0] };
+
+ tinfo = arg;
+ last_interval_time = tinfo->times->start_time;
+ last_completed = 0;
+
+ wait_for_start();
+ while (perf_os_waituntilreadable(&sock, threadpipe[0],
+ tinfo->config->stats_interval)
+ == PERF_R_TIMEDOUT) {
+ now = perf_get_time();
+ sum_stats(tinfo->config, &total);
+ interval_time = now - last_interval_time;
+ num_completed = total.num_completed - last_completed;
+ qps = num_completed / (((double)interval_time) / MILLION);
+ perf_log_printf("%u.%06u: %.6lf",
+ (unsigned int)(now / MILLION),
+ (unsigned int)(now % MILLION), qps);
+ last_interval_time = now;
+ last_completed = total.num_completed;
+ }
+
+ return NULL;
+}
+
+static void
+cancel_queries(threadinfo_t* tinfo)
+{
+ struct query_info* q;
+
+ while (true) {
+ q = perf_list_tail(tinfo->outstanding_queries);
+ if (q == NULL)
+ break;
+ query_move(tinfo, q, append_unused);
+
+ if (q->timestamp == UINT64_MAX)
+ continue;
+
+ tinfo->stats.num_interrupted++;
+ if (q->desc != NULL) {
+ perf_log_printf("> I %s", q->desc);
+ free(q->desc);
+ q->desc = NULL;
+ }
+ }
+}
+
+static uint32_t
+per_thread(uint32_t total, uint32_t nthreads, unsigned int offset)
+{
+ uint32_t value, temp_total;
+
+ value = total / nthreads;
+
+ /*
+ * work out if there's a shortfall and adjust if necessary
+ */
+ temp_total = value * nthreads;
+ if (temp_total < total && offset < total - temp_total)
+ value++;
+
+ return value;
+}
+
+static void
+threadinfo_init(threadinfo_t* tinfo, const config_t* config,
+ const times_t* times)
+{
+ unsigned int offset, socket_offset, i;
+
+ memset(tinfo, 0, sizeof(*tinfo));
+ PERF_MUTEX_INIT(&tinfo->lock);
+ PERF_COND_INIT(&tinfo->cond);
+
+ perf_list_init(tinfo->outstanding_queries);
+ perf_list_init(tinfo->unused_queries);
+ for (i = 0; i < NQIDS; i++) {
+ perf_link_init(&tinfo->queries[i]);
+ perf_list_append(tinfo->unused_queries, &tinfo->queries[i]);
+ tinfo->queries[i].list = &tinfo->unused_queries;
+ }
+
+ offset = tinfo - threads;
+
+ tinfo->config = config;
+ tinfo->times = times;
+
+ /*
+ * Compute per-thread limits based on global values.
+ */
+ tinfo->max_outstanding = per_thread(config->max_outstanding,
+ config->threads, offset);
+ tinfo->max_qps = per_thread(config->max_qps, config->threads, offset);
+ tinfo->nsocks = per_thread(config->clients, config->threads, offset);
+
+ /*
+ * We can't have more than 64k outstanding queries per thread.
+ */
+ if (tinfo->max_outstanding > NQIDS)
+ tinfo->max_outstanding = NQIDS;
+
+ if (tinfo->nsocks > MAX_SOCKETS)
+ tinfo->nsocks = MAX_SOCKETS;
+
+ if (!(tinfo->socks = calloc(tinfo->nsocks, sizeof(*tinfo->socks)))) {
+ perf_log_fatal("out of memory");
+ }
+ socket_offset = 0;
+ for (i = 0; i < offset; i++)
+ socket_offset += threads[i].nsocks;
+ for (i = 0; i < tinfo->nsocks; i++)
+ tinfo->socks[i] = perf_net_opensocket(config->mode, &config->server_addr,
+ &config->local_addr,
+ socket_offset++,
+ config->bufsize);
+ tinfo->current_sock = 0;
+
+ PERF_THREAD(&tinfo->receiver, do_recv, tinfo);
+ PERF_THREAD(&tinfo->sender, do_send, tinfo);
+}
+
+static void
+threadinfo_stop(threadinfo_t* tinfo)
+{
+ PERF_SIGNAL(&tinfo->cond);
+ PERF_JOIN(tinfo->sender, NULL);
+ PERF_JOIN(tinfo->receiver, NULL);
+}
+
+static void
+threadinfo_cleanup(threadinfo_t* tinfo, times_t* times)
+{
+ unsigned int i;
+
+ if (interrupted)
+ cancel_queries(tinfo);
+ for (i = 0; i < tinfo->nsocks; i++)
+ perf_net_close(&tinfo->socks[i]);
+ if (tinfo->last_recv > times->end_time)
+ times->end_time = tinfo->last_recv;
+}
+
+int main(int argc, char** argv)
+{
+ config_t config;
+ times_t times;
+ stats_t total_stats;
+ threadinfo_t stats_thread;
+ unsigned int i;
+ perf_result_t result;
+ struct perf_net_socket sock = { .mode = sock_pipe };
+
+ printf("DNS Performance Testing Tool\n"
+ "Version " PACKAGE_VERSION "\n\n");
+
+ (void)SSL_library_init();
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ SSL_load_error_strings();
+ OPENSSL_config(0);
+#endif
+
+ setup(argc, argv, &config);
+
+ if (pipe(threadpipe) < 0 || pipe(mainpipe) < 0 || pipe(intrpipe) < 0)
+ perf_log_fatal("creating pipe");
+
+ perf_datafile_setpipefd(input, threadpipe[0]);
+
+ perf_os_blocksignal(SIGINT, true);
+ switch (config.mode) {
+ case sock_tcp:
+ case sock_tls:
+ // block SIGPIPE for TCP/TLS mode, if connection is closed it will generate a signal
+ perf_os_blocksignal(SIGPIPE, true);
+ break;
+ default:
+ break;
+ }
+
+ print_initial_status(&config);
+
+ if (!(threads = calloc(config.threads, sizeof(threadinfo_t)))) {
+ perf_log_fatal("out of memory");
+ }
+ for (i = 0; i < config.threads; i++)
+ threadinfo_init(&threads[i], &config, &times);
+ if (config.stats_interval > 0) {
+ stats_thread.config = &config;
+ stats_thread.times = &times;
+ PERF_THREAD(&stats_thread.sender, do_interval_stats, &stats_thread);
+ }
+
+ times.start_time = perf_get_time();
+ if (config.timelimit > 0)
+ times.stop_time = times.start_time + config.timelimit;
+ else
+ times.stop_time = UINT64_MAX;
+ times.stop_time_ns.tv_sec = times.stop_time / MILLION;
+ times.stop_time_ns.tv_nsec = (times.stop_time % MILLION) * 1000;
+
+ PERF_LOCK(&start_lock);
+ started = true;
+ PERF_BROADCAST(&start_cond);
+ PERF_UNLOCK(&start_lock);
+
+ perf_os_handlesignal(SIGINT, handle_sigint);
+ perf_os_blocksignal(SIGINT, false);
+ sock.fd = mainpipe[0];
+ result = perf_os_waituntilreadable(&sock, intrpipe[0],
+ times.stop_time - times.start_time);
+ if (result == PERF_R_CANCELED)
+ interrupted = true;
+
+ times.end_time = perf_get_time();
+
+ if (write(threadpipe[1], "", 1)) { // lgtm [cpp/empty-block]
+ }
+ for (i = 0; i < config.threads; i++)
+ threadinfo_stop(&threads[i]);
+ if (config.stats_interval > 0)
+ PERF_JOIN(stats_thread.sender, NULL);
+
+ for (i = 0; i < config.threads; i++)
+ threadinfo_cleanup(&threads[i], &times);
+
+ print_final_status(&config);
+
+ sum_stats(&config, &total_stats);
+ print_statistics(&config, &times, &total_stats);
+
+ cleanup(&config);
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ ERR_free_strings();
+#endif
+
+ return (0);
+}
diff --git a/src/edns.c b/src/edns.c
new file mode 100644
index 0000000..9c04f9b
--- /dev/null
+++ b/src/edns.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "edns.h"
+
+#include "log.h"
+#include "opt.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+
+#define EDNSLEN 11
+
+perf_ednsoption_t* perf_edns_parseoption(const char* arg)
+{
+ char * copy, *sep, *value, *endptr, hex[3];
+ perf_ednsoption_t* option;
+ size_t data_len;
+ unsigned long int u;
+ perf_buffer_t save;
+
+ copy = strdup(arg);
+ if (!copy) {
+ perf_log_fatal("out of memory");
+ return 0; // fix clang scan-build
+ }
+
+ sep = strchr(copy, ':');
+ if (!sep) {
+ perf_log_warning("invalid EDNS Option, must be code:value");
+ perf_opt_usage();
+ exit(1);
+ }
+ *sep = '\0';
+ value = sep + 1;
+
+ data_len = strlen(value);
+ if (!data_len) {
+ perf_log_warning("invalid EDNS Option, value is empty");
+ perf_opt_usage();
+ exit(1);
+ }
+ if (data_len & 1) {
+ perf_log_warning("invalid EDNS Option, value must hex string (even number of characters)");
+ perf_opt_usage();
+ exit(1);
+ }
+ data_len /= 2;
+ data_len += 4; // code, len, data...
+
+ option = calloc(1, sizeof(perf_ednsoption_t) + data_len);
+ if (!option) {
+ perf_log_fatal("out of memory");
+ free(copy); // fix clang scan-build
+ return 0; // fix clang scan-build
+ }
+ perf_buffer_init(&option->buffer, &option->data[0], data_len);
+
+ endptr = 0;
+ u = strtoul(copy, &endptr, 10);
+ if (*endptr || u == ULONG_MAX) {
+ perf_log_warning("invalid EDNS Option code '%s'", copy);
+ perf_opt_usage();
+ exit(1);
+ }
+ perf_buffer_putuint16(&option->buffer, u & 0xffff);
+
+ save = option->buffer;
+ perf_buffer_add(&option->buffer, 2);
+ hex[2] = 0;
+ while (*value) {
+ memcpy(hex, value, 2);
+ endptr = 0;
+ u = strtoul(hex, &endptr, 16);
+ if (*endptr || u == ULONG_MAX) {
+ perf_log_warning("invalid EDNS Option hex value '%.*s'", 2, value);
+ perf_opt_usage();
+ exit(1);
+ }
+ perf_buffer_putuint8(&option->buffer, u & 0xff);
+ value += 2;
+ }
+ perf_buffer_putuint16(&save, perf_buffer_usedlength(&option->buffer) - 4);
+
+ free(copy);
+
+ return option;
+}
+
+void perf_edns_destroyoption(perf_ednsoption_t** optionp)
+{
+ assert(optionp);
+ assert(*optionp);
+
+ free(*optionp);
+ *optionp = 0;
+}
+
+/*
+ * Appends an OPT record to the packet.
+ */
+perf_result_t perf_add_edns(perf_buffer_t* packet, bool dnssec, perf_ednsoption_t* option)
+{
+ unsigned char* base;
+ size_t option_length = 0, total_length;
+
+ if (option) {
+ option_length = perf_buffer_usedlength(&option->buffer);
+ }
+ total_length = EDNSLEN + option_length;
+
+ if (perf_buffer_availablelength(packet) < total_length) {
+ perf_log_warning("failed to add OPT to query packet");
+ return PERF_R_NOSPACE;
+ }
+
+ base = perf_buffer_base(packet);
+
+ perf_buffer_putuint8(packet, 0); /* root name */
+ perf_buffer_putuint16(packet, 41); /* OPT record */
+ perf_buffer_putuint16(packet, MAX_EDNS_PACKET); /* class */
+ perf_buffer_putuint8(packet, 0); /* xrcode */
+ perf_buffer_putuint8(packet, 0); /* version */
+ if (dnssec) {
+ /* flags */
+ perf_buffer_putuint16(packet, 0x8000);
+ } else {
+ perf_buffer_putuint16(packet, 0);
+ }
+ perf_buffer_putuint16(packet, option_length); /* rdlen */
+ if (option) {
+ perf_buffer_putmem(packet, perf_buffer_base(&option->buffer), option_length);
+ }
+
+ base[11]++; /* increment additional record count */
+
+ return PERF_R_SUCCESS;
+}
diff --git a/src/edns.h b/src/edns.h
new file mode 100644
index 0000000..300d462
--- /dev/null
+++ b/src/edns.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "result.h"
+#include "buffer.h"
+
+#ifndef PERF_EDNS_H
+#define PERF_EDNS_H 1
+
+#include <stdbool.h>
+
+#define MAX_EDNS_PACKET 4096
+
+typedef struct perf_ednsoption {
+ perf_buffer_t buffer;
+ char data[];
+} perf_ednsoption_t;
+
+perf_ednsoption_t* perf_edns_parseoption(const char* arg);
+
+void perf_edns_destroyoption(perf_ednsoption_t** optionp);
+
+perf_result_t perf_add_edns(perf_buffer_t* packet, bool dnssec, perf_ednsoption_t* option);
+
+#endif
diff --git a/src/gen-qtype.c.py b/src/gen-qtype.c.py
new file mode 100755
index 0000000..f9735e5
--- /dev/null
+++ b/src/gen-qtype.c.py
@@ -0,0 +1,46 @@
+#!/usr/bin/python3
+
+import csv
+from urllib.request import Request, urlopen
+from io import StringIO
+
+qtype = {}
+
+for row in csv.reader(StringIO(urlopen(Request('https://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv')).read().decode('utf-8'))):
+ if row[0] == 'TYPE':
+ continue
+ try:
+ qtype[row[0]] = int(row[1])
+ except Exception:
+ continue
+
+print("""/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "qtype.h"
+
+const perf_qtype_t qtype_table[] = {""")
+
+for k, v in qtype.items():
+ print(" { \"%s\", %d }," % (k, v))
+
+print(""" { 0, 0 }
+};""")
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 0000000..f110b79
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PERF_LIST_H
+#define PERF_LIST_H 1
+
+#include <assert.h>
+
+#define perf_link(type) \
+ struct { \
+ type *prev, *next; \
+ } _link
+#define perf_link_init(link) \
+ { \
+ (link)->_link.prev = 0; \
+ (link)->_link.next = 0; \
+ }
+
+#define perf_list(type) \
+ struct { \
+ type *head, *tail; \
+ }
+#define perf_list_init(list) \
+ { \
+ (list).head = 0; \
+ (list).tail = 0; \
+ }
+
+#define perf_list_head(list) ((list).head)
+#define perf_list_tail(list) ((list).tail)
+#define perf_list_empty(list) (!(list).head)
+
+#define perf_list_append(list, link) \
+ { \
+ if ((list).tail) { \
+ (list).tail->_link.next = (link); \
+ } else { \
+ (list).head = (link); \
+ } \
+ (link)->_link.prev = (list).tail; \
+ (link)->_link.next = 0; \
+ (list).tail = (link); \
+ }
+#define perf_list_prepend(list, link) \
+ { \
+ if ((list).head) { \
+ (list).head->_link.prev = (link); \
+ } else { \
+ (list).tail = (link); \
+ } \
+ (link)->_link.prev = 0; \
+ (link)->_link.next = (list).head; \
+ (list).head = (link); \
+ }
+#define perf_list_unlink(list, link) \
+ { \
+ if ((link)->_link.next) { \
+ (link)->_link.next->_link.prev = (link)->_link.prev; \
+ } else { \
+ assert((list).tail == (link)); \
+ (list).tail = (link)->_link.prev; \
+ } \
+ if ((link)->_link.prev) { \
+ (link)->_link.prev->_link.next = (link)->_link.next; \
+ } else { \
+ assert((list).head == (link)); \
+ (list).head = (link)->_link.next; \
+ } \
+ (link)->_link.next = 0; \
+ (link)->_link.prev = 0; \
+ assert((list).head != (link)); \
+ assert((list).tail != (link)); \
+ }
+
+#endif
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..bba5ba6
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "log.h"
+
+#include "util.h"
+
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static bool log_err_stdout = false;
+
+pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static void
+vlog(FILE* stream, const char* prefix, const char* fmt, va_list args)
+{
+ PERF_LOCK(&log_lock);
+ fflush(stdout);
+ if (prefix != NULL)
+ fprintf(stream, "%s: ", prefix);
+ vfprintf(stream, fmt, args);
+ fprintf(stream, "\n");
+ PERF_UNLOCK(&log_lock);
+}
+
+void perf_log_printf(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vlog(stdout, NULL, fmt, args);
+}
+
+void perf_log_fatal(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vlog(log_err_stdout ? stdout : stderr, "Error", fmt, args);
+ exit(1);
+}
+
+void perf_log_warning(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vlog(log_err_stdout ? stdout : stderr, "Warning", fmt, args);
+}
+
+void perf_log_tostdout(void)
+{
+ log_err_stdout = true;
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..76ad7b7
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PERF_LOG_H
+#define PERF_LOG_H 1
+
+void perf_log_printf(const char* fmt, ...);
+void perf_log_fatal(const char* fmt, ...);
+void perf_log_warning(const char* fmt, ...);
+void perf_log_tostdout(void);
+
+#endif
diff --git a/src/net.c b/src/net.c
new file mode 100644
index 0000000..9c4a8e2
--- /dev/null
+++ b/src/net.c
@@ -0,0 +1,664 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "net.h"
+
+#include "log.h"
+#include "opt.h"
+#include "os.h"
+#include "strerror.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+#include <openssl/err.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#define TCP_RECV_BUF_SIZE (16 * 1024)
+#define TCP_SEND_BUF_SIZE (4 * 1024)
+
+static SSL_CTX* ssl_ctx = 0;
+
+int perf_net_parsefamily(const char* family)
+{
+ if (family == NULL || strcmp(family, "any") == 0)
+ return AF_UNSPEC;
+ else if (strcmp(family, "inet") == 0)
+ return AF_INET;
+#ifdef AF_INET6
+ else if (strcmp(family, "inet6") == 0)
+ return AF_INET6;
+#endif
+ else {
+ fprintf(stderr, "invalid family %s\n", family);
+ perf_opt_usage();
+ exit(1);
+ }
+}
+
+void perf_sockaddr_fromin(perf_sockaddr_t* sockaddr, const struct in_addr* in, in_port_t port)
+{
+ memset(sockaddr, 0, sizeof(*sockaddr));
+ sockaddr->sa.sin.sin_family = AF_INET;
+ sockaddr->sa.sin.sin_addr = *in;
+ sockaddr->sa.sin.sin_port = htons(port);
+ sockaddr->length = sizeof(sockaddr->sa.sin);
+}
+
+void perf_sockaddr_fromin6(perf_sockaddr_t* sockaddr, const struct in6_addr* in, in_port_t port)
+{
+ memset(sockaddr, 0, sizeof(*sockaddr));
+ sockaddr->sa.sin6.sin6_family = AF_INET6;
+ sockaddr->sa.sin6.sin6_addr = *in;
+ sockaddr->sa.sin6.sin6_port = htons(port);
+ sockaddr->length = sizeof(sockaddr->sa.sin6);
+}
+
+in_port_t perf_sockaddr_port(const perf_sockaddr_t* sockaddr)
+{
+ switch (sockaddr->sa.sa.sa_family) {
+ case AF_INET:
+ return sockaddr->sa.sin.sin_port;
+ case AF_INET6:
+ return sockaddr->sa.sin6.sin6_port;
+ default:
+ break;
+ }
+ return 0;
+}
+
+void perf_sockaddr_setport(perf_sockaddr_t* sockaddr, in_port_t port)
+{
+ switch (sockaddr->sa.sa.sa_family) {
+ case AF_INET:
+ sockaddr->sa.sin.sin_port = port;
+ break;
+ case AF_INET6:
+ sockaddr->sa.sin6.sin6_port = port;
+ break;
+ default:
+ break;
+ }
+}
+
+void perf_sockaddr_format(const perf_sockaddr_t* sockaddr, char* buf, size_t len)
+{
+ const void* src;
+
+ *buf = 0;
+
+ switch (sockaddr->sa.sa.sa_family) {
+ case AF_INET:
+ src = &sockaddr->sa.sin.sin_addr;
+ break;
+ case AF_INET6:
+ src = &sockaddr->sa.sin6.sin6_addr;
+ break;
+ default:
+ return;
+ }
+
+ (void)inet_ntop(sockaddr->sa.sa.sa_family, src, buf, len);
+}
+
+void perf_net_parseserver(int family, const char* name, unsigned int port, perf_sockaddr_t* addr)
+{
+ struct addrinfo* ai;
+
+ if (getaddrinfo(name, 0, 0, &ai) == 0) {
+ struct addrinfo* a;
+
+ for (a = ai; a; a = a->ai_next) {
+ if (a->ai_family == family || family == AF_UNSPEC) {
+ switch (a->ai_family) {
+ case AF_INET:
+ perf_sockaddr_fromin(addr, &((struct sockaddr_in*)a->ai_addr)->sin_addr, port);
+ break;
+ case AF_INET6:
+ perf_sockaddr_fromin6(addr, &((struct sockaddr_in6*)a->ai_addr)->sin6_addr, port);
+ break;
+ default:
+ continue;
+ }
+
+ freeaddrinfo(ai);
+ return;
+ }
+ }
+ freeaddrinfo(ai);
+ }
+
+ fprintf(stderr, "invalid server address %s\n", name);
+ perf_opt_usage();
+ exit(1);
+}
+
+void perf_net_parselocal(int family, const char* name, unsigned int port,
+ perf_sockaddr_t* addr)
+{
+ struct in_addr in4a;
+ struct in6_addr in6a;
+
+ if (name == NULL) {
+ switch (family) {
+ case AF_INET:
+ in4a.s_addr = INADDR_ANY;
+ perf_sockaddr_fromin(addr, &in4a, port);
+ return;
+ case AF_INET6:
+ perf_sockaddr_fromin6(addr, &in6addr_any, port);
+ return;
+ default:
+ break;
+ }
+ } else if (inet_pton(AF_INET, name, &in4a) == 1) {
+ perf_sockaddr_fromin(addr, &in4a, port);
+ return;
+ } else if (inet_pton(AF_INET6, name, &in6a) == 1) {
+ perf_sockaddr_fromin6(addr, &in6a, port);
+ return;
+ }
+
+ fprintf(stderr, "invalid local address %s\n", name);
+ perf_opt_usage();
+ exit(1);
+}
+
+struct perf_net_socket perf_net_opensocket(enum perf_net_mode mode, const perf_sockaddr_t* server, const perf_sockaddr_t* local, unsigned int offset, int bufsize)
+{
+ int family;
+ perf_sockaddr_t tmp;
+ int port;
+ int ret;
+ int flags;
+ struct perf_net_socket sock = { .mode = mode, .is_ready = 1 };
+
+ family = server->sa.sa.sa_family;
+
+ if (local->sa.sa.sa_family != family) {
+ perf_log_fatal("server and local addresses have different families");
+ }
+
+ switch (mode) {
+ case sock_udp:
+ sock.fd = socket(family, SOCK_DGRAM, 0);
+ break;
+ case sock_tls:
+ if (pthread_mutex_init(&sock.lock, 0)) {
+ perf_log_fatal("pthread_mutex_init() failed");
+ }
+ if ((sock.fd = socket(family, SOCK_STREAM, 0)) < 0) {
+ char __s[256];
+ perf_log_fatal("socket: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+ if (!ssl_ctx) {
+#ifdef HAVE_TLS_METHOD
+ if (!(ssl_ctx = SSL_CTX_new(TLS_method()))) {
+ perf_log_fatal("SSL_CTX_new(): %s", ERR_error_string(ERR_get_error(), 0));
+ }
+ if (!SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION)) {
+ perf_log_fatal("SSL_CTX_set_min_proto_version(TLS1_2_VERSION): %s", ERR_error_string(ERR_get_error(), 0));
+ }
+#else
+ if (!(ssl_ctx = SSL_CTX_new(SSLv23_client_method()))) {
+ perf_log_fatal("SSL_CTX_new(): %s", ERR_error_string(ERR_get_error(), 0));
+ }
+#endif
+ }
+ if (!(sock.ssl = SSL_new(ssl_ctx))) {
+ perf_log_fatal("SSL_new(): %s", ERR_error_string(ERR_get_error(), 0));
+ }
+ if (!(ret = SSL_set_fd(sock.ssl, sock.fd))) {
+ perf_log_fatal("SSL_set_fd(): %s", ERR_error_string(SSL_get_error(sock.ssl, ret), 0));
+ }
+ break;
+ case sock_tcp:
+ sock.fd = socket(family, SOCK_STREAM, 0);
+ break;
+ default:
+ perf_log_fatal("perf_net_opensocket(): invalid mode");
+ }
+
+ if (sock.fd == -1) {
+ char __s[256];
+ perf_log_fatal("socket: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+
+#if defined(AF_INET6) && defined(IPV6_V6ONLY)
+ if (family == AF_INET6) {
+ int on = 1;
+
+ if (setsockopt(sock.fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
+ perf_log_warning("setsockopt(IPV6_V6ONLY) failed");
+ }
+ }
+#endif
+
+ tmp = *local;
+ port = perf_sockaddr_port(&tmp);
+ if (port != 0 && offset != 0) {
+ port += offset;
+ if (port >= 0xFFFF)
+ perf_log_fatal("port %d out of range", port);
+ perf_sockaddr_setport(&tmp, port);
+ }
+
+ if (bind(sock.fd, &tmp.sa.sa, tmp.length) == -1) {
+ char __s[256];
+ perf_log_fatal("bind: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+
+ if (bufsize > 0) {
+ bufsize *= 1024;
+
+ ret = setsockopt(sock.fd, SOL_SOCKET, SO_RCVBUF,
+ &bufsize, sizeof(bufsize));
+ if (ret < 0)
+ perf_log_warning("setsockbuf(SO_RCVBUF) failed");
+
+ ret = setsockopt(sock.fd, SOL_SOCKET, SO_SNDBUF,
+ &bufsize, sizeof(bufsize));
+ if (ret < 0)
+ perf_log_warning("setsockbuf(SO_SNDBUF) failed");
+ }
+
+ flags = fcntl(sock.fd, F_GETFL, 0);
+ if (flags < 0)
+ perf_log_fatal("fcntl(F_GETFL)");
+ ret = fcntl(sock.fd, F_SETFL, flags | O_NONBLOCK);
+ if (ret < 0)
+ perf_log_fatal("fcntl(F_SETFL)");
+
+ if (mode == sock_tcp || mode == sock_tls) {
+ if (connect(sock.fd, &server->sa.sa, server->length)) {
+ if (errno == EINPROGRESS) {
+ sock.is_ready = 0;
+ } else {
+ char __s[256];
+ perf_log_fatal("connect() failed: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+ }
+ sock.recvbuf = malloc(TCP_RECV_BUF_SIZE);
+ sock.at = 0;
+ sock.have_more = 0;
+ sock.sendbuf = malloc(TCP_SEND_BUF_SIZE);
+ if (!sock.recvbuf || !sock.sendbuf) {
+ perf_log_fatal("perf_net_opensocket() failed: unable to allocate buffers");
+ }
+ }
+
+ return sock;
+}
+
+ssize_t perf_net_recv(struct perf_net_socket* sock, void* buf, size_t len, int flags)
+{
+ switch (sock->mode) {
+ case sock_tls: {
+ ssize_t n;
+ uint16_t dnslen, dnslen2;
+
+ if (!sock->have_more) {
+ if (pthread_mutex_lock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_lock() failed");
+ }
+ if (!sock->is_ready) {
+ if (pthread_mutex_unlock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_unlock() failed");
+ }
+ errno = EAGAIN;
+ return -1;
+ }
+
+ n = SSL_read(sock->ssl, sock->recvbuf + sock->at, TCP_RECV_BUF_SIZE - sock->at);
+ if (n < 0) {
+ int err = SSL_get_error(sock->ssl, n);
+ if (pthread_mutex_unlock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_unlock() failed");
+ }
+ if (err == SSL_ERROR_WANT_READ) {
+ errno = EAGAIN;
+ } else {
+ errno = EBADF;
+ }
+ return -1;
+ }
+ if (pthread_mutex_unlock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_unlock() failed");
+ }
+
+ sock->at += n;
+ if (sock->at < 3) {
+ errno = EAGAIN;
+ return -1;
+ }
+ }
+
+ memcpy(&dnslen, sock->recvbuf, 2);
+ dnslen = ntohs(dnslen);
+ if (sock->at < dnslen + 2) {
+ errno = EAGAIN;
+ return -1;
+ }
+ memcpy(buf, sock->recvbuf + 2, len < dnslen ? len : dnslen);
+ memmove(sock->recvbuf, sock->recvbuf + 2 + dnslen, sock->at - 2 - dnslen);
+ sock->at -= 2 + dnslen;
+
+ if (sock->at > 2) {
+ memcpy(&dnslen2, sock->recvbuf, 2);
+ dnslen2 = ntohs(dnslen2);
+ if (sock->at >= dnslen2 + 2) {
+ sock->have_more = 1;
+ return dnslen;
+ }
+ }
+
+ sock->have_more = 0;
+ return dnslen;
+ }
+ case sock_tcp: {
+ ssize_t n;
+ uint16_t dnslen, dnslen2;
+
+ if (!sock->have_more) {
+ n = recv(sock->fd, sock->recvbuf + sock->at, TCP_RECV_BUF_SIZE - sock->at, flags);
+ if (n < 0) {
+ if (errno == ECONNRESET) {
+ // Treat connection reset like try again until reconnection features are in
+ errno = EAGAIN;
+ }
+ return n;
+ }
+ sock->at += n;
+ if (sock->at < 3) {
+ errno = EAGAIN;
+ return -1;
+ }
+ }
+
+ memcpy(&dnslen, sock->recvbuf, 2);
+ dnslen = ntohs(dnslen);
+ if (sock->at < dnslen + 2) {
+ errno = EAGAIN;
+ return -1;
+ }
+ memcpy(buf, sock->recvbuf + 2, len < dnslen ? len : dnslen);
+ memmove(sock->recvbuf, sock->recvbuf + 2 + dnslen, sock->at - 2 - dnslen);
+ sock->at -= 2 + dnslen;
+
+ if (sock->at > 2) {
+ memcpy(&dnslen2, sock->recvbuf, 2);
+ dnslen2 = ntohs(dnslen2);
+ if (sock->at >= dnslen2 + 2) {
+ sock->have_more = 1;
+ return dnslen;
+ }
+ }
+
+ sock->have_more = 0;
+ return dnslen;
+ }
+ default:
+ break;
+ }
+
+ return recv(sock->fd, buf, len, flags);
+}
+
+ssize_t perf_net_sendto(struct perf_net_socket* sock, const void* buf, size_t len, int flags,
+ const struct sockaddr* dest_addr, socklen_t addrlen)
+{
+ switch (sock->mode) {
+ case sock_tls: {
+ size_t send = len < TCP_SEND_BUF_SIZE - 2 ? len : (TCP_SEND_BUF_SIZE - 2);
+ // TODO: We only send what we can send, because we can't continue sending
+ uint16_t dnslen = htons(send);
+ ssize_t n;
+
+ memcpy(sock->sendbuf, &dnslen, 2);
+ memcpy(sock->sendbuf + 2, buf, send);
+ if (pthread_mutex_lock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_lock() failed");
+ }
+ n = SSL_write(sock->ssl, sock->sendbuf, send + 2);
+ if (n < 0) {
+ perf_log_warning("SSL_write(): %s", ERR_error_string(SSL_get_error(sock->ssl, n), 0));
+ errno = EBADF;
+ }
+ if (pthread_mutex_unlock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_unlock() failed");
+ }
+
+ if (n > 0 && n < send + 2) {
+ sock->sending = n;
+ sock->flags = flags;
+ memcpy(&sock->dest_addr, dest_addr, addrlen);
+ sock->addrlen = addrlen;
+ sock->is_ready = 0;
+ errno = EINPROGRESS;
+ return -1;
+ }
+
+ return n > 0 ? n - 2 : n;
+ }
+ case sock_tcp: {
+ size_t send = len < TCP_SEND_BUF_SIZE - 2 ? len : (TCP_SEND_BUF_SIZE - 2);
+ // TODO: We only send what we can send, because we can't continue sending
+ uint16_t dnslen = htons(send);
+ ssize_t n;
+
+ memcpy(sock->sendbuf, &dnslen, 2);
+ memcpy(sock->sendbuf + 2, buf, send);
+ n = sendto(sock->fd, sock->sendbuf, send + 2, flags, dest_addr, addrlen);
+
+ if (n > 0 && n < send + 2) {
+ sock->sending = n;
+ sock->flags = flags;
+ memcpy(&sock->dest_addr, dest_addr, addrlen);
+ sock->addrlen = addrlen;
+ sock->is_ready = 0;
+ errno = EINPROGRESS;
+ return -1;
+ }
+
+ return n > 0 ? n - 2 : n;
+ }
+ default:
+ break;
+ }
+ return sendto(sock->fd, buf, len, flags, dest_addr, addrlen);
+}
+
+int perf_net_close(struct perf_net_socket* sock)
+{
+ return close(sock->fd);
+}
+
+int perf_net_sockeq(struct perf_net_socket* sock_a, struct perf_net_socket* sock_b)
+{
+ return sock_a->fd == sock_b->fd;
+}
+
+enum perf_net_mode perf_net_parsemode(const char* mode)
+{
+ if (!strcmp(mode, "udp")) {
+ return sock_udp;
+ } else if (!strcmp(mode, "tcp")) {
+ return sock_tcp;
+ } else if (!strcmp(mode, "tls") || !strcmp(mode, "dot")) {
+ return sock_tls;
+ }
+
+ perf_log_warning("invalid socket mode");
+ perf_opt_usage();
+ exit(1);
+}
+
+int perf_net_sockready(struct perf_net_socket* sock, int pipe_fd, int64_t timeout)
+{
+ if (sock->is_ready) {
+ return 1;
+ }
+
+ switch (sock->mode) {
+ case sock_tls: {
+ int ret;
+
+ if (sock->sending) {
+ uint16_t dnslen;
+ ssize_t n;
+
+ memcpy(&dnslen, sock->sendbuf, 2);
+ dnslen = ntohs(dnslen);
+ if (pthread_mutex_lock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_lock() failed");
+ }
+ n = SSL_write(sock->ssl, sock->sendbuf + sock->sending, dnslen + 2 - sock->sending);
+ if (n < 1) {
+ if (n < 0) {
+ perf_log_warning("SSL_write(): %s", ERR_error_string(SSL_get_error(sock->ssl, n), 0));
+ errno = EBADF;
+ }
+ if (pthread_mutex_unlock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_unlock() failed");
+ }
+ return -1;
+ }
+ if (pthread_mutex_unlock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_unlock() failed");
+ }
+ sock->sending += n;
+ if (sock->sending < dnslen + 2) {
+ errno = EINPROGRESS;
+ return -1;
+ }
+ sock->sending = 0;
+ sock->is_ready = 1;
+ return 1;
+ }
+
+ if (!sock->is_ssl_ready) {
+ switch (perf_os_waituntilanywritable(sock, 1, pipe_fd, timeout)) {
+ case PERF_R_TIMEDOUT:
+ return -1;
+ case PERF_R_SUCCESS: {
+ int error = 0;
+ socklen_t len = (socklen_t)sizeof(error);
+
+ getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &len);
+ if (error != 0) {
+ if (error == EINPROGRESS
+#if EWOULDBLOCK != EAGAIN
+ || error == EWOULDBLOCK
+#endif
+ || error == EAGAIN) {
+ return 0;
+ }
+ return -1;
+ }
+ }
+ }
+ sock->is_ssl_ready = 1;
+ }
+
+ if (pthread_mutex_lock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_lock() failed");
+ }
+ ret = SSL_connect(sock->ssl);
+ if (!ret) {
+ perf_log_warning("SSL_connect(): %s", ERR_error_string(SSL_get_error(sock->ssl, ret), 0));
+ if (pthread_mutex_unlock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_unlock() failed");
+ }
+ return -1;
+ }
+ if (ret < 0) {
+ int err = SSL_get_error(sock->ssl, ret);
+ if (pthread_mutex_unlock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_unlock() failed");
+ }
+ if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
+ return 0;
+ }
+ perf_log_warning("SSL_connect(): %s", ERR_error_string(err, 0));
+ return -1;
+ }
+ sock->is_ready = 1;
+ if (pthread_mutex_unlock(&sock->lock)) {
+ perf_log_fatal("pthread_mutex_unlock() failed");
+ }
+ return 1;
+ }
+ case sock_tcp:
+ if (sock->sending) {
+ uint16_t dnslen;
+ ssize_t n;
+
+ memcpy(&dnslen, sock->sendbuf, 2);
+ dnslen = ntohs(dnslen);
+ n = sendto(sock->fd, sock->sendbuf + sock->sending, dnslen + 2 - sock->sending, sock->flags, (struct sockaddr*)&sock->dest_addr, sock->addrlen);
+ if (n < 1) {
+ return -1;
+ }
+ sock->sending += n;
+ if (sock->sending < dnslen + 2) {
+ errno = EINPROGRESS;
+ return -1;
+ }
+ sock->sending = 0;
+ sock->is_ready = 1;
+ return 1;
+ }
+
+ switch (perf_os_waituntilanywritable(sock, 1, pipe_fd, timeout)) {
+ case PERF_R_TIMEDOUT:
+ return -1;
+ case PERF_R_SUCCESS: {
+ int error = 0;
+ socklen_t len = (socklen_t)sizeof(error);
+
+ getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &len);
+ if (error != 0) {
+ if (error == EINPROGRESS
+#if EWOULDBLOCK != EAGAIN
+ || error == EWOULDBLOCK
+#endif
+ || error == EAGAIN) {
+ return 0;
+ }
+ return -1;
+ }
+ sock->is_ready = 1;
+ return 1;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return -1;
+}
diff --git a/src/net.h b/src/net.h
new file mode 100644
index 0000000..1339af6
--- /dev/null
+++ b/src/net.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PERF_NET_H
+#define PERF_NET_H 1
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <openssl/ssl.h>
+#include <pthread.h>
+#include <netinet/in.h>
+
+struct perf_sockaddr {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ } sa;
+ socklen_t length;
+};
+typedef struct perf_sockaddr perf_sockaddr_t;
+
+enum perf_net_mode {
+ sock_none,
+ sock_file,
+ sock_pipe,
+ sock_udp,
+ sock_tcp,
+ sock_tls
+};
+
+struct perf_net_socket {
+ enum perf_net_mode mode;
+ int fd, have_more, is_ready, flags, is_ssl_ready;
+ char* recvbuf;
+ size_t at, sending;
+ char* sendbuf;
+ struct sockaddr_storage dest_addr;
+ socklen_t addrlen;
+ SSL* ssl;
+ pthread_mutex_t lock;
+};
+
+void perf_sockaddr_fromin(perf_sockaddr_t* sockaddr, const struct in_addr* in, in_port_t port);
+void perf_sockaddr_fromin6(perf_sockaddr_t* sockaddr, const struct in6_addr* in, in_port_t port);
+in_port_t perf_sockaddr_port(const perf_sockaddr_t* sockaddr);
+void perf_sockaddr_setport(perf_sockaddr_t* sockaddr, in_port_t port);
+void perf_sockaddr_format(const perf_sockaddr_t* sockaddr, char* buf, size_t len);
+
+ssize_t perf_net_recv(struct perf_net_socket* sock, void* buf, size_t len, int flags);
+ssize_t perf_net_sendto(struct perf_net_socket* sock, const void* buf, size_t len, int flags,
+ const struct sockaddr* dest_addr, socklen_t addrlen);
+
+int perf_net_close(struct perf_net_socket* sock);
+int perf_net_sockeq(struct perf_net_socket* sock_a, struct perf_net_socket* sock_b);
+
+int perf_net_parsefamily(const char* family);
+
+void perf_net_parseserver(int family, const char* name, unsigned int port, perf_sockaddr_t* addr);
+void perf_net_parselocal(int family, const char* name, unsigned int port, perf_sockaddr_t* addr);
+
+struct perf_net_socket perf_net_opensocket(enum perf_net_mode mode, const perf_sockaddr_t* server, const perf_sockaddr_t* local, unsigned int offset, int bufsize);
+
+enum perf_net_mode perf_net_parsemode(const char* mode);
+
+int perf_net_sockready(struct perf_net_socket* sock, int pipe_fd, int64_t timeout);
+
+#endif
diff --git a/src/opt.c b/src/opt.c
new file mode 100644
index 0000000..41dcd5e
--- /dev/null
+++ b/src/opt.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "opt.h"
+
+#include "log.h"
+#include "util.h"
+#include "result.h"
+
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <netinet/in.h>
+
+#define MAX_OPTS 64
+#define LINE_LENGTH 80
+
+typedef struct {
+ char c;
+ perf_opttype_t type;
+ const char* desc;
+ const char* help;
+ const char* defval;
+ char defvalbuf[32];
+ union {
+ void* valp;
+ char** stringp;
+ bool* boolp;
+ unsigned int* uintp;
+ uint64_t* uint64p;
+ double* doublep;
+ in_port_t* portp;
+ } u;
+} opt_t;
+
+static opt_t opts[MAX_OPTS];
+static unsigned int nopts;
+static char optstr[MAX_OPTS * 2 + 2 + 1] = { 0 };
+extern const char* progname;
+
+void perf_opt_add(char c, perf_opttype_t type, const char* desc, const char* help,
+ const char* defval, void* valp)
+{
+ opt_t* opt;
+
+ if (nopts == MAX_OPTS) {
+ perf_log_fatal("too many defined options");
+ return;
+ }
+ opt = &opts[nopts++];
+ opt->c = c;
+ opt->type = type;
+ opt->desc = desc;
+ opt->help = help;
+ if (defval != NULL) {
+ opt->defvalbuf[sizeof(opt->defvalbuf) - 1] = 0;
+ strncpy(opt->defvalbuf, defval, sizeof(opt->defvalbuf));
+ if (opt->defvalbuf[sizeof(opt->defvalbuf) - 1]) {
+ perf_log_fatal("perf_opt_add(): defval too large");
+ return;
+ }
+ opt->defval = opt->defvalbuf;
+ } else {
+ opt->defval = NULL;
+ }
+ opt->u.valp = valp;
+
+ char newoptstr[sizeof(optstr) + 2];
+ snprintf(newoptstr, sizeof(newoptstr), "%s%c%s", optstr, c, (type == perf_opt_boolean ? "" : ":"));
+ memcpy(optstr, newoptstr, sizeof(optstr) - 1);
+ optstr[sizeof(optstr) - 1] = 0;
+}
+
+void perf_opt_usage(void)
+{
+ unsigned int prefix_len, position, arg_len, i, j;
+
+ prefix_len = fprintf(stderr, "Usage: %s", progname);
+ position = prefix_len;
+ for (i = 0; i < nopts; i++) {
+ arg_len = 6;
+ if (opts[i].desc != NULL)
+ arg_len += strlen(opts[i].desc) + 1;
+ if (LINE_LENGTH - position - 1 < arg_len) {
+ fprintf(stderr, "\n");
+ for (j = 0; j < prefix_len; j++)
+ fprintf(stderr, " ");
+ position = prefix_len;
+ }
+ fprintf(stderr, " [-%c", opts[i].c);
+ if (opts[i].desc != NULL)
+ fprintf(stderr, " %s", opts[i].desc);
+ fprintf(stderr, "]");
+ position += arg_len;
+ }
+ fprintf(stderr, "\n");
+
+ for (i = 0; i < nopts; i++) {
+ fprintf(stderr, " -%c %s", opts[i].c, opts[i].help);
+ if (opts[i].defval)
+ fprintf(stderr, " (default: %s)", opts[i].defval);
+ fprintf(stderr, "\n");
+ }
+}
+
+static uint32_t
+parse_uint(const char* desc, const char* str,
+ unsigned int min, unsigned int max)
+{
+ unsigned long int val;
+ uint32_t ret;
+ char* endptr = 0;
+
+ errno = 0;
+ val = strtoul(str, &endptr, 10);
+ if (!errno && str && *str && endptr && !*endptr && val <= UINT32_MAX) {
+ ret = (uint32_t)val;
+ if (ret >= min && ret <= max) {
+ return ret;
+ }
+ }
+
+ fprintf(stderr, "invalid %s: %s\n", desc, str);
+ perf_opt_usage();
+ exit(1);
+}
+
+static double
+parse_double(const char* desc, const char* str)
+{
+ const char* s;
+ char c;
+ bool seen_dot = false;
+
+ s = str;
+ while (*s != 0) {
+ c = *s++;
+ if (c == '.') {
+ if (seen_dot)
+ goto fail;
+ seen_dot = true;
+ } else if (c < '0' || c > '9') {
+ goto fail;
+ }
+ }
+
+ return atof(str);
+
+fail:
+ fprintf(stderr, "invalid %s: %s\n", desc, str);
+ perf_opt_usage();
+ exit(1);
+}
+
+static uint64_t
+parse_timeval(const char* desc, const char* str)
+{
+ return MILLION * parse_double(desc, str);
+}
+
+void perf_opt_parse(int argc, char** argv)
+{
+ int c;
+ opt_t* opt;
+ unsigned int i;
+
+ perf_opt_add('h', perf_opt_boolean, NULL, "print this help", NULL, NULL);
+
+ while ((c = getopt(argc, argv, optstr)) != -1) {
+ for (i = 0; i < nopts; i++) {
+ if (opts[i].c == c)
+ break;
+ }
+ if (i == nopts) {
+ perf_opt_usage();
+ exit(1);
+ }
+ if (c == 'h') {
+ perf_opt_usage();
+ exit(0);
+ }
+ opt = &opts[i];
+ switch (opt->type) {
+ case perf_opt_string:
+ *opt->u.stringp = optarg;
+ break;
+ case perf_opt_boolean:
+ *opt->u.boolp = true;
+ break;
+ case perf_opt_uint:
+ *opt->u.uintp = parse_uint(opt->desc, optarg,
+ 1, 0xFFFFFFFF);
+ break;
+ case perf_opt_timeval:
+ *opt->u.uint64p = parse_timeval(opt->desc, optarg);
+ break;
+ case perf_opt_double:
+ *opt->u.doublep = parse_double(opt->desc, optarg);
+ break;
+ case perf_opt_port:
+ *opt->u.portp = parse_uint(opt->desc, optarg,
+ 0, 0xFFFF);
+ break;
+ }
+ }
+ if (optind != argc) {
+ fprintf(stderr, "unexpected argument %s\n", argv[optind]);
+ perf_opt_usage();
+ exit(1);
+ }
+}
diff --git a/src/opt.h b/src/opt.h
new file mode 100644
index 0000000..829ba5c
--- /dev/null
+++ b/src/opt.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PERF_OPT_H
+#define PERF_OPT_H 1
+
+typedef enum {
+ perf_opt_string,
+ perf_opt_boolean,
+ perf_opt_uint,
+ perf_opt_timeval,
+ perf_opt_double,
+ perf_opt_port,
+} perf_opttype_t;
+
+void perf_opt_add(char c, perf_opttype_t type, const char* desc, const char* help,
+ const char* defval, void* valp);
+
+void perf_opt_usage(void);
+
+void perf_opt_parse(int argc, char** argv);
+
+#endif
diff --git a/src/os.c b/src/os.c
new file mode 100644
index 0000000..addcdac
--- /dev/null
+++ b/src/os.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "os.h"
+
+#include "log.h"
+#include "util.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <poll.h>
+
+void perf_os_blocksignal(int sig, bool block)
+{
+ sigset_t sset;
+ int op;
+
+ op = block ? SIG_BLOCK : SIG_UNBLOCK;
+
+ if (sigemptyset(&sset) < 0 || sigaddset(&sset, sig) < 0 || pthread_sigmask(op, &sset, NULL) < 0) {
+ char __s[256];
+ perf_log_fatal("pthread_sigmask: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+}
+
+void perf_os_handlesignal(int sig, void (*handler)(int))
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = handler;
+
+ if (sigfillset(&sa.sa_mask) < 0 || sigaction(sig, &sa, NULL) < 0) {
+ char __s[256];
+ perf_log_fatal("sigaction: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+}
+
+perf_result_t
+perf_os_waituntilreadable(struct perf_net_socket* sock, int pipe_fd, int64_t timeout)
+{
+ return perf_os_waituntilanyreadable(sock, 1, pipe_fd, timeout);
+}
+
+perf_result_t
+perf_os_waituntilanyreadable(struct perf_net_socket* socks, unsigned int nfds, int pipe_fd,
+ int64_t timeout)
+{
+ struct pollfd fds[nfds + 1];
+ size_t i;
+ int to, n;
+
+ for (i = 0; i < nfds; i++) {
+ if (socks[i].have_more)
+ return (PERF_R_SUCCESS);
+
+ fds[i].fd = socks[i].fd;
+ fds[i].events = POLLIN;
+ }
+
+ fds[nfds].fd = pipe_fd;
+ fds[nfds].events = POLLIN;
+
+ if (timeout < 0) {
+ to = -1;
+ } else {
+ to = timeout / 1000;
+ if (timeout && !to) {
+ to = 1;
+ }
+ }
+
+ n = poll(fds, nfds + 1, to);
+ if (n < 0) {
+ if (errno != EINTR) {
+ char __s[256];
+ perf_log_fatal("select(): %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+ return (PERF_R_CANCELED);
+ } else if (n == 0) {
+ return (PERF_R_TIMEDOUT);
+ } else if (fds[nfds].revents & POLLIN) {
+ return (PERF_R_CANCELED);
+ } else {
+ return (PERF_R_SUCCESS);
+ }
+}
+
+perf_result_t
+perf_os_waituntilanywritable(struct perf_net_socket* socks, unsigned int nfds, int pipe_fd,
+ int64_t timeout)
+{
+ struct pollfd fds[nfds + 1];
+ size_t i;
+ int to, n;
+
+ for (i = 0; i < nfds; i++) {
+ fds[i].fd = socks[i].fd;
+ fds[i].events = POLLOUT;
+ }
+
+ fds[nfds].fd = pipe_fd;
+ fds[nfds].events = POLLIN;
+
+ if (timeout < 0) {
+ to = -1;
+ } else {
+ to = timeout / 1000;
+ if (timeout && !to) {
+ to = 1;
+ }
+ }
+
+ n = poll(fds, nfds + 1, to);
+ if (n < 0) {
+ if (errno != EINTR) {
+ char __s[256];
+ perf_log_fatal("select(): %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+ return (PERF_R_CANCELED);
+ } else if (n == 0) {
+ return (PERF_R_TIMEDOUT);
+ } else if (fds[nfds].revents & POLLIN) {
+ return (PERF_R_CANCELED);
+ } else {
+ return (PERF_R_SUCCESS);
+ }
+}
diff --git a/src/os.h b/src/os.h
new file mode 100644
index 0000000..90ecfaf
--- /dev/null
+++ b/src/os.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "net.h"
+#include "result.h"
+
+#ifndef PERF_OS_H
+#define PERF_OS_H 1
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+void perf_os_blocksignal(int sig, bool block);
+
+void perf_os_handlesignal(int sig, void (*handler)(int));
+
+perf_result_t
+perf_os_waituntilreadable(struct perf_net_socket* sock, int pipe_fd, int64_t timeout);
+
+perf_result_t
+perf_os_waituntilanyreadable(struct perf_net_socket* socks, unsigned int nfds, int pipe_fd,
+ int64_t timeout);
+
+perf_result_t
+perf_os_waituntilanywritable(struct perf_net_socket* socks, unsigned int nfds, int pipe_fd,
+ int64_t timeout);
+
+#endif
diff --git a/src/qtype.c b/src/qtype.c
new file mode 100644
index 0000000..78aadcf
--- /dev/null
+++ b/src/qtype.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "qtype.h"
+
+const perf_qtype_t qtype_table[] = {
+ { "A", 1 },
+ { "NS", 2 },
+ { "MD", 3 },
+ { "MF", 4 },
+ { "CNAME", 5 },
+ { "SOA", 6 },
+ { "MB", 7 },
+ { "MG", 8 },
+ { "MR", 9 },
+ { "NULL", 10 },
+ { "WKS", 11 },
+ { "PTR", 12 },
+ { "HINFO", 13 },
+ { "MINFO", 14 },
+ { "MX", 15 },
+ { "TXT", 16 },
+ { "RP", 17 },
+ { "AFSDB", 18 },
+ { "X25", 19 },
+ { "ISDN", 20 },
+ { "RT", 21 },
+ { "NSAP", 22 },
+ { "NSAP-PTR", 23 },
+ { "SIG", 24 },
+ { "KEY", 25 },
+ { "PX", 26 },
+ { "GPOS", 27 },
+ { "AAAA", 28 },
+ { "LOC", 29 },
+ { "NXT", 30 },
+ { "EID", 31 },
+ { "NIMLOC", 32 },
+ { "SRV", 33 },
+ { "ATMA", 34 },
+ { "NAPTR", 35 },
+ { "KX", 36 },
+ { "CERT", 37 },
+ { "A6", 38 },
+ { "DNAME", 39 },
+ { "SINK", 40 },
+ { "OPT", 41 },
+ { "APL", 42 },
+ { "DS", 43 },
+ { "SSHFP", 44 },
+ { "IPSECKEY", 45 },
+ { "RRSIG", 46 },
+ { "NSEC", 47 },
+ { "DNSKEY", 48 },
+ { "DHCID", 49 },
+ { "NSEC3", 50 },
+ { "NSEC3PARAM", 51 },
+ { "TLSA", 52 },
+ { "SMIMEA", 53 },
+ { "Unassigned", 54 },
+ { "HIP", 55 },
+ { "NINFO", 56 },
+ { "RKEY", 57 },
+ { "TALINK", 58 },
+ { "CDS", 59 },
+ { "CDNSKEY", 60 },
+ { "OPENPGPKEY", 61 },
+ { "CSYNC", 62 },
+ { "ZONEMD", 63 },
+ { "SVCB", 64 },
+ { "HTTPS", 65 },
+ { "SPF", 99 },
+ { "UINFO", 100 },
+ { "UID", 101 },
+ { "GID", 102 },
+ { "UNSPEC", 103 },
+ { "NID", 104 },
+ { "L32", 105 },
+ { "L64", 106 },
+ { "LP", 107 },
+ { "EUI48", 108 },
+ { "EUI64", 109 },
+ { "TKEY", 249 },
+ { "TSIG", 250 },
+ { "IXFR", 251 },
+ { "AXFR", 252 },
+ { "MAILB", 253 },
+ { "MAILA", 254 },
+ { "*", 255 },
+ { "URI", 256 },
+ { "CAA", 257 },
+ { "AVC", 258 },
+ { "DOA", 259 },
+ { "AMTRELAY", 260 },
+ { "TA", 32768 },
+ { "DLV", 32769 },
+ { "Reserved", 65535 },
+ { 0, 0 }
+};
diff --git a/src/qtype.h b/src/qtype.h
new file mode 100644
index 0000000..8c1a17d
--- /dev/null
+++ b/src/qtype.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PERF_QTYPE_H
+#define PERF_QTYPE_H 1
+
+#include <stdint.h>
+
+typedef struct perf_qtype {
+ char* type;
+ uint16_t value;
+} perf_qtype_t;
+
+extern const perf_qtype_t qtype_table[];
+
+#endif
diff --git a/src/resperf-report b/src/resperf-report
new file mode 100755
index 0000000..b3b7697
--- /dev/null
+++ b/src/resperf-report
@@ -0,0 +1,112 @@
+#!/bin/sh
+#
+# Copyright 2019-2021 OARC, Inc.
+# Copyright 2017-2018 Akamai Technologies
+# Copyright 2006-2016 Nominum, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Driver script to run resperf and generate an HTML report of
+# the results, with graphs.
+#
+
+# Program locations - change these if not in $PATH
+resperf=resperf
+gnuplot=gnuplot
+
+# The gnuplot terminal type. This determines the image format for the
+# plots; "png" or "gif" will both work as long as the corresponding
+# terminal support is compiled into your copy of gnuplot.
+terminal=png
+
+# Create a unique ID for this report
+id=`date '+%Y%m%d-%H%M'`
+
+# Set up file names
+reportfile="$id.html"
+outputfile="$id.output"
+plotfile="$id.gnuplot"
+rate_graph="$id.rate.$terminal"
+latency_graph="$id.latency.$terminal"
+
+# Run the test
+$resperf -P "$plotfile" "$@" >"$outputfile" 2>&1 ||
+ { echo "`basename $0`: error running resperf:" >&2;
+ cat $outputfile >&2;
+ exit 1;
+ }
+
+# Create plots
+
+if
+ $gnuplot <<EOF
+set terminal $terminal
+set output "$rate_graph"
+set title "Query / response / failure rate"
+set key top left
+set xlabel "Time (seconds)"
+set yrange [0:]
+plot \
+"$plotfile" using 1:3 title "Queries sent per second" with lines, \
+"$plotfile" using 1:4 title "Total responses received per second" with lines, \
+"$plotfile" using 1:5 title "Failure responses received per second" with lines
+EOF
+then
+ :
+else
+ echo "`basename $0`: error running gnuplot" >&2; exit 1;
+fi
+
+if
+ $gnuplot <<EOF
+set terminal $terminal
+set output "$latency_graph"
+set title "Latency"
+set key top left
+set xlabel "Time (seconds)"
+set yrange [0:]
+plot \
+"$plotfile" using 1:6 title "Average latency (seconds)" with lines
+EOF
+then
+ :
+else
+ echo "`basename $0`: error running gnuplot" >&2; exit 1;
+fi
+
+# Generate the report
+
+exec >"$reportfile"
+
+cat <<EOF
+<html><head></head><body>
+<h1>Resperf report $id</h1>
+<h2>Resperf output</h2>
+<pre>
+EOF
+cat "$outputfile"
+cat <<EOF
+</pre>
+EOF
+
+cat <<EOF
+<h2>Plots</h2>
+<p>
+<img src="$rate_graph" />
+<img src="$latency_graph" />
+</p>
+</body></html>
+EOF
+
+echo "Done, report is in $reportfile" >&2
diff --git a/src/resperf.1.in b/src/resperf.1.in
new file mode 100644
index 0000000..463a5ac
--- /dev/null
+++ b/src/resperf.1.in
@@ -0,0 +1,647 @@
+.\" Copyright 2019-2021 OARC, Inc.
+.\" Copyright 2017-2018 Akamai Technologies
+.\" Copyright 2006-2016 Nominum, Inc.
+.\" All rights reserved.
+.\"
+.\" Licensed under the Apache License, Version 2.0 (the "License");
+.\" you may not use this file except in compliance with the License.
+.\" You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing, software
+.\" distributed under the License is distributed on an "AS IS" BASIS,
+.\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.\" See the License for the specific language governing permissions and
+.\" limitations under the License.
+.TH resperf 1 "@PACKAGE_VERSION@" "resperf"
+.SH NAME
+resperf \- test the resolution performance of a caching DNS server
+.SH SYNOPSIS
+.hy 0
+.ad l
+\fBresperf\-report\fR\ [\fB\-a\ \fIlocal_addr\fB\fR]
+[\fB\-d\ \fIdatafile\fB\fR]
+[\fB\-M\ \fImode\fB\fR]
+[\fB\-s\ \fIserver_addr\fB\fR]
+[\fB\-p\ \fIport\fB\fR]
+[\fB\-x\ \fIlocal_port\fB\fR]
+[\fB\-t\ \fItimeout\fB\fR]
+[\fB\-b\ \fIbufsize\fB\fR]
+[\fB\-f\ \fIfamily\fB\fR]
+[\fB\-e\fR]
+[\fB\-D\fR]
+[\fB\-y\ \fI[alg:]name:secret\fB\fR]
+[\fB\-h\fR]
+[\fB\-i\ \fIinterval\fB\fR]
+[\fB\-m\ \fImax_qps\fB\fR]
+[\fB\-r\ \fIrampup_time\fB\fR]
+[\fB\-c\ \fIconstant_traffic_time\fB\fR]
+[\fB\-L\ \fImax_loss\fB\fR]
+[\fB\-C\ \fIclients\fB\fR]
+[\fB\-q\ \fImax_outstanding\fB\fR]
+[\fB\-v\fR]
+.ad
+.hy
+.hy 0
+.ad l
+
+\fBresperf\fR\ [\fB\-a\ \fIlocal_addr\fB\fR]
+[\fB\-d\ \fIdatafile\fB\fR]
+[\fB\-M\ \fImode\fB\fR]
+[\fB\-s\ \fIserver_addr\fB\fR]
+[\fB\-p\ \fIport\fB\fR]
+[\fB\-x\ \fIlocal_port\fB\fR]
+[\fB\-t\ \fItimeout\fB\fR]
+[\fB\-b\ \fIbufsize\fB\fR]
+[\fB\-f\ \fIfamily\fB\fR]
+[\fB\-e\fR]
+[\fB\-D\fR]
+[\fB\-y\ \fI[alg:]name:secret\fB\fR]
+[\fB\-h\fR]
+[\fB\-i\ \fIinterval\fB\fR]
+[\fB\-m\ \fImax_qps\fB\fR]
+[\fB\-P\ \fIplot_data_file\fB\fR]
+[\fB\-r\ \fIrampup_time\fB\fR]
+[\fB\-c\ \fIconstant_traffic_time\fB\fR]
+[\fB\-L\ \fImax_loss\fB\fR]
+[\fB\-C\ \fIclients\fB\fR]
+[\fB\-q\ \fImax_outstanding\fB\fR]
+[\fB\-v\fR]
+.ad
+.hy
+.SH DESCRIPTION
+\fBresperf\fR is a companion tool to \fBdnsperf\fR. \fBdnsperf\fR was
+primarily designed for benchmarking authoritative servers, and it does not
+work well with caching servers that are talking to the live Internet. One
+reason for this is that dnsperf uses a "self-pacing" approach, which is
+based on the assumption that you can keep the server 100% busy simply by
+sending it a small burst of back-to-back queries to fill up network buffers,
+and then send a new query whenever you get a response back. This approach
+works well for authoritative servers that process queries in order and one
+at a time; it also works pretty well for a caching server in a closed
+laboratory environment talking to a simulated Internet that's all on the
+same LAN. Unfortunately, it does not work well with a caching server talking
+to the actual Internet, which may need to work on thousands of queries in
+parallel to achieve its maximum throughput. There have been numerous
+attempts to use dnsperf (or its predecessor, queryperf) for benchmarking
+live caching servers, usually with poor results. Therefore, a separate tool
+designed specifically for caching servers is needed.
+.SS "How resperf works"
+Unlike the "self-pacing" approach of dnsperf, resperf works by sending DNS
+queries at a controlled, steadily increasing rate. By default, resperf will
+send traffic for 60 seconds, linearly increasing the amount of traffic from
+zero to 100,000 queries per second.
+
+During the test, resperf listens for responses from the server and keeps
+track of response rates, failure rates, and latencies. It will also continue
+listening for responses for an additional 40 seconds after it has stopped
+sending traffic, so that there is time for the server to respond to the last
+queries sent. This time period was chosen to be longer than the overall
+query timeout of both Nominum CacheServe and current versions of BIND.
+
+If the test is successful, the query rate will at some point exceed the
+capacity of the server and queries will be dropped, causing the response
+rate to stop growing or even decrease as the query rate increases.
+
+The result of the test is a set of measurements of the query rate, response
+rate, failure response rate, and average query latency as functions of time.
+.SS "What you will need"
+Benchmarking a live caching server is serious business. A fast caching
+server like Nominum CacheServe, resolving a mix of cacheable and
+non-cacheable queries typical of ISP customer traffic, is capable of
+resolving well over 1,000,000 queries per second. In the process, it will
+send more than 40,000 queries per second to authoritative servers on the
+Internet, and receive responses to most of them. Assuming an average request
+size of 50 bytes and a response size of 150 bytes, this amounts to some 1216
+Mbps of outgoing and 448 Mbps of incoming traffic. If your Internet
+connection can't handle the bandwidth, you will end up measuring the speed
+of the connection, not the server, and may saturate the connection causing a
+degradation in service for other users.
+
+Make sure there is no stateful firewall between the server and the Internet,
+because most of them can't handle the amount of UDP traffic the test will
+generate and will end up dropping packets, skewing the test results. Some
+will even lock up or crash.
+
+You should run resperf on a machine separate from the server under test, on
+the same LAN. Preferably, this should be a Gigabit Ethernet network. The
+machine running resperf should be at least as fast as the machine being
+tested; otherwise, it may end up being the bottleneck.
+
+There should be no other applications running on the machine running
+resperf. Performance testing at the traffic levels involved is essentially a
+hard real-time application - consider the fact that at a query rate of
+100,000 queries per second, if resperf gets delayed by just 1/100 of a
+second, 1000 incoming UDP packets will arrive in the meantime. This is more
+than most operating systems will buffer, which means packets will be
+dropped.
+
+Because the granularity of the timers provided by operating systems is
+typically too coarse to accurately schedule packet transmissions at
+sub-millisecond intervals, resperf will busy-wait between packet
+transmissions, constantly polling for responses in the meantime. Therefore,
+it is normal for resperf to consume 100% CPU during the whole test run, even
+during periods where query rates are relatively low.
+
+You will also need a set of test queries in the \fBdnsperf\fR file format.
+See the \fBdnsperf\fR man page for instructions on how to construct this
+query file. To make the test as realistic as possible, the queries should be
+derived from recorded production client DNS traffic, without removing
+duplicate queries or other filtering. With the default settings, resperf
+will use up to 3 million queries in each test run.
+
+If the caching server to be tested has a configurable limit on the number of
+simultaneous resolutions, like the \fBmax\-recursive\-clients\fR statement
+in Nominum CacheServe or the \fBrecursive\-clients\fR option in BIND 9, you
+will probably have to increase it. As a starting point, we recommend a value
+of 10000 for Nominum CacheServe and 100000 for BIND 9. Should the limit be
+reached, it will show up in the plots as an increase in the number of
+failure responses.
+
+The server being tested should be restarted at the beginning of each test to
+make sure it is starting with an empty cache. If the cache already contains
+data from a previous test run that used the same set of queries, almost all
+queries will be answered from the cache, yielding inflated performance
+numbers.
+
+To use the \fBresperf\-report\fR script, you need to have \fBgnuplot\fR
+installed. Make sure your installed version of \fBgnuplot\fR supports the
+png terminal driver. If your \fBgnuplot\fR doesn't support png but does
+support gif, you can change the line saying terminal=png in the
+\fBresperf\-report\fR script to terminal=gif.
+.SS "Running the test"
+Resperf is typically invoked via the \fBresperf\-report\fR script, which
+will run \fBresperf\fR with its output redirected to a file and then
+automatically generate an illustrated report in HTML format. Command line
+arguments given to resperf-report will be passed on unchanged to resperf.
+
+When running resperf-report, you will need to specify at least the server IP
+address and the query data file. A typical invocation will look like
+.RS
+.hy 0
+
+.nf
+resperf\-report \-s 10.0.0.2 \-d queryfile
+.fi
+.hy
+.RE
+
+With default settings, the test run will take at most 100 seconds (60
+seconds of ramping up traffic and then 40 seconds of waiting for responses),
+but in practice, the 60-second traffic phase will usually be cut short. To
+be precise, resperf can transition from the traffic-sending phase to the
+waiting-for-responses phase in three different ways:
+.IP \(bu 2
+Running for the full allotted time and successfully reaching the maximum
+query rate (by default, 60 seconds and 100,000 qps, respectively). Since
+this is a very high query rate, this will rarely happen (with today's
+hardware); one of the other two conditions listed below will usually occur
+first.
+.IP \(bu 2
+Exceeding 65,536 outstanding queries. This often happens as a result of
+(successfully) exceeding the capacity of the server being tested, causing
+the excess queries to be dropped. The limit of 65,536 queries comes from the
+number of possible values for the ID field in the DNS packet. Resperf needs
+to allocate a unique ID for each outstanding query, and is therefore unable
+to send further queries if the set of possible IDs is exhausted.
+.IP \(bu 2
+When resperf finds itself unable to send queries fast enough. Resperf will
+notice if it is falling behind in its scheduled query transmissions, and if
+this backlog reaches 1000 queries, it will print a message like "Fell behind
+by 1000 queries" (or whatever the actual number is at the time) and stop
+sending traffic.
+.PP
+Regardless of which of the above conditions caused the traffic-sending phase
+of the test to end, you should examine the resulting plots to make sure the
+server's response rate is flattening out toward the end of the test. If it
+is not, then you are not loading the server enough. If you are getting the
+"Fell behind" message, make sure that the machine running resperf is fast
+enough and has no other applications running.
+
+You should also monitor the CPU usage of the server under test. It should
+reach close to 100% CPU at the point of maximum traffic; if it does not, you
+most likely have a bottleneck in some other part of your test setup, for
+example, your external Internet connection.
+
+The report generated by \fBresperf\-report\fR will be stored with a unique
+file name based on the current date and time, e.g.,
+\fI20060812-1550.html\fR. The PNG images of the plots and other auxiliary
+files will be stored in separate files beginning with the same date-time
+string. To view the report, simply open the \fI.html\fR file in a web
+browser.
+
+If you need to copy the report to a separate machine for viewing, make sure
+to copy the .png files along with the .html file (or simply copy all the
+files, e.g., using scp 20060812-1550.* host:directory/).
+.SS "Interpreting the report"
+The \fI.html\fR file produced by \fBresperf\-report\fR consists of two
+sections. The first section, "Resperf output", contains output from the
+\fBresperf\fR program such as progress messages, a summary of the command
+line arguments, and summary statistics. The second section, "Plots",
+contains two plots generated by \fBgnuplot\fR: "Query/response/failure rate"
+and "Latency".
+
+The "Query/response/failure rate" plot contains three graphs. The "Queries
+sent per second" graph shows the amount of traffic being sent to the server;
+this should be very close to a straight diagonal line, reflecting the linear
+ramp-up of traffic.
+
+The "Total responses received per second" graph shows how many of the
+queries received a response from the server. All responses are counted,
+whether successful (NOERROR or NXDOMAIN) or not (e.g., SERVFAIL).
+
+The "Failure responses received per second" graph shows how many of the
+queries received a failure response. A response is considered to be a
+failure if its RCODE is neither NOERROR nor NXDOMAIN.
+
+By visually inspecting the graphs, you can get an idea of how the server
+behaves under increasing load. The "Total responses received per second"
+graph will initially closely follow the "Queries sent per second" graph
+(often rendering it invisible in the plot as the two graphs are plotted on
+top of one another), but when the load exceeds the server's capacity, the
+"Total responses received per second" graph may diverge from the "Queries
+sent per second" graph and flatten out, indicating that some of the queries
+are being dropped.
+
+The "Failure responses received per second" graph will normally show a
+roughly linear ramp close to the bottom of the plot with some random
+fluctuation, since typical query traffic will contain some small percentage
+of failing queries randomly interspersed with the successful ones. As the
+total traffic increases, the number of failures will increase
+proportionally.
+
+If the "Failure responses received per second" graph turns sharply upwards,
+this can be another indication that the load has exceeded the server's
+capacity. This will happen if the server reacts to overload by sending
+SERVFAIL responses rather than by dropping queries. Since Nominum CacheServe
+and BIND 9 will both respond with SERVFAIL when they exceed their
+\fBmax\-recursive\-clients\fR or \fBrecursive\-clients\fR limit,
+respectively, a sudden increase in the number of failures could mean that
+the limit needs to be increased.
+
+The "Latency" plot contains a single graph marked "Average latency". This
+shows how the latency varies during the course of the test. Typically, the
+latency graph will exhibit a downwards trend because the cache hit rate
+improves as ever more responses are cached during the test, and the latency
+for a cache hit is much smaller than for a cache miss. The latency graph is
+provided as an aid in determining the point where the server gets
+overloaded, which can be seen as a sharp upwards turn in the graph. The
+latency graph is not intended for making absolute latency measurements or
+comparisons between servers; the latencies shown in the graph are not
+representative of production latencies due to the initially empty cache and
+the deliberate overloading of the server towards the end of the test.
+
+Note that all measurements are displayed on the plot at the horizontal
+position corresponding to the point in time when the query was sent, not
+when the response (if any) was received. This makes it it easy to compare
+the query and response rates; for example, if no queries are dropped, the
+query and response graphs will be identical. As another example, if the plot
+shows 10% failure responses at t=5 seconds, this means that 10% of the
+queries sent at t=5 seconds eventually failed, not that 10% of the responses
+received at t=5 seconds were failures.
+.SS "Determining the server's maximum throughput"
+Often, the goal of running \fBresperf\fR is to determine the server's
+maximum throughput, in other words, the number of queries per second it is
+capable of handling. This is not always an easy task, because as a server is
+driven into overload, the service it provides may deteriorate gradually, and
+this deterioration can manifest itself either as queries being dropped, as
+an increase in the number of SERVFAIL responses, or an increase in latency.
+The maximum throughput may be defined as the highest level of traffic at
+which the server still provides an acceptable level of service, but that
+means you first need to decide what an acceptable level of service means in
+terms of packet drop percentage, SERVFAIL percentage, and latency.
+
+The summary statistics in the "Resperf output" section of the report
+contains a "Maximum throughput" value which by default is determined from
+the maximum rate at which the server was able to return responses, without
+regard to the number of queries being dropped or failing at that point. This
+method of throughput measurement has the advantage of simplicity, but it may
+or may not be appropriate for your needs; the reported value should always
+be validated by a visual inspection of the graphs to ensure that service has
+not already deteriorated unacceptably before the maximum response rate is
+reached. It may also be helpful to look at the "Lost at that point" value in
+the summary statistics; this indicates the percentage of the queries that
+was being dropped at the point in the test when the maximum throughput was
+reached.
+
+Alternatively, you can make resperf report the throughput at the point in
+the test where the percentage of queries dropped exceeds a given limit (or
+the maximum as above if the limit is never exceeded). This can be a more
+realistic indication of how much the server can be loaded while still
+providing an acceptable level of service. This is done using the \fB\-L\fR
+command line option; for example, specifying \fB\-L 10\fR makes resperf
+report the highest throughput reached before the server starts dropping more
+than 10% of the queries.
+
+There is no corresponding way of automatically constraining results based on
+the number of failed queries, because unlike dropped queries, resolution
+failures will occur even when the the server is not overloaded, and the
+number of such failures is heavily dependent on the query data and network
+conditions. Therefore, the plots should be manually inspected to ensure that
+there is not an abnormal number of failures.
+.SH "GENERATING CONSTANT TRAFFIC"
+In addition to ramping up traffic linearly, \fBresperf\fR also has the
+capability to send a constant stream of traffic. This can be useful when
+using \fBresperf\fR for tasks other than performance measurement; for
+example, it can be used to "soak test" a server by subjecting it to a
+sustained load for an extended period of time.
+
+To generate a constant traffic load, use the \fB\-c\fR command line option,
+together with the \fB\-m\fR option which specifies the desired constant
+query rate. For example, to send 10000 queries per second for an hour, use
+\fB\-m 10000 \-c 3600\fR. This will include the usual 30-second gradual
+ramp-up of traffic at the beginning, which may be useful to avoid initially
+overwhelming a server that is starting with an empty cache. To start the
+onslaught of traffic instantly, use \fB\-m 10000 \-c 3600 \-r 0\fR.
+
+To be precise, \fBresperf\fR will do a linear ramp-up of traffic from 0 to
+\fB\-m\fR queries per second over a period of \fB\-r\fR seconds, followed by
+a plateau of steady traffic at \fB\-m\fR queries per second lasting for
+\fB\-c\fR seconds, followed by waiting for responses for an extra 40
+seconds. Either the ramp-up or the plateau can be suppressed by supplying a
+duration of zero seconds with \fB\-r 0\fR and \fB\-c 0\fR, respectively. The
+latter is the default.
+
+Sending traffic at high rates for hours on end will of course require very
+large amounts of input data. Also, a long-running test will generate a large
+amount of plot data, which is kept in memory for the duration of the test.
+To reduce the memory usage and the size of the plot file, consider
+increasing the interval between measurements from the default of 0.5 seconds
+using the \fB\-i\fR option in long-running tests.
+
+When using \fBresperf\fR for long-running tests, it is important that the
+traffic rate specified using the \fB\-m\fR is one that both \fBresperf\fR
+itself and the server under test can sustain. Otherwise, the test is likely
+to be cut short as a result of either running out of query IDs (because of
+large numbers of dropped queries) or of resperf falling behind its
+transmission schedule.
+.SH OPTIONS
+Because the \fBresperf\-report\fR script passes its command line options
+directly to the \fBresperf\fR programs, they both accept the same set of
+options, with one exception: \fBresperf\-report\fR automatically adds an
+appropriate \fB\-P\fR to the \fBresperf\fR command line, and therefore does
+not itself take a \fB\-P\fR option.
+
+\fB-d \fIdatafile\fB\fR
+.br
+.RS
+Specifies the input data file. If not specified, \fBresperf\fR will read
+from standard input.
+.RE
+
+\fB-M \fImode\fB\fR
+.br
+.RS
+Specifies the transport mode to use, "udp", "tcp" or "tls". Default is "udp".
+.RE
+
+\fB-s \fIserver_addr\fB\fR
+.br
+.RS
+Specifies the name or address of the server to which requests will be sent.
+The default is the loopback address, 127.0.0.1.
+.RE
+
+\fB-p \fIport\fB\fR
+.br
+.RS
+Sets the port on which the DNS packets are sent. If not specified, the
+standard DNS port (udp/tcp 53, tls 853) is used.
+.RE
+
+\fB-a \fIlocal_addr\fB\fR
+.br
+.RS
+Specifies the local address from which to send requests. The default is the
+wildcard address.
+.RE
+
+\fB-x \fIlocal_port\fB\fR
+.br
+.RS
+Specifies the local port from which to send requests. The default is the
+wildcard port (0).
+
+If acting as multiple clients and the wildcard port is used, each client
+will use a different random port. If a port is specified, the clients will
+use a range of ports starting with the specified one.
+.RE
+
+\fB-t \fItimeout\fB\fR
+.br
+.RS
+Specifies the request timeout value, in seconds. \fBresperf\fR will no
+longer wait for a response to a particular request after this many seconds
+have elapsed. The default is 45 seconds.
+
+\fBresperf\fR times out unanswered requests in order to reclaim query IDs so
+that the query ID space will not be exhausted in a long-running test, such
+as when "soak testing" a server for an day with \fB\-m 10000 \-c 86400\fR.
+The timeouts and the ability to tune them are of little use in the more
+typical use case of a performance test lasting only a minute or two.
+
+The default timeout of 45 seconds was chosen to be longer than the query
+timeout of current caching servers. Note that this is longer than the
+corresponding default in \fBdnsperf\fR, because caching servers can take
+many orders of magnitude longer to answer a query than authoritative servers
+do.
+
+If a short timeout is used, there is a possibility that \fBresperf\fR will
+receive a response after the corresponding request has timed out; in this
+case, a message like Warning: Received a response with an unexpected id: 141
+will be printed.
+.RE
+
+\fB-b \fIbufsize\fB\fR
+.br
+.RS
+Sets the size of the socket's send and receive buffers, in kilobytes. If not
+specified, the operating system's default is used.
+.RE
+
+\fB-f \fIfamily\fB\fR
+.br
+.RS
+Specifies the address family used for sending DNS packets. The possible
+values are "inet", "inet6", or "any". If "any" (the default value) is
+specified, \fBresperf\fR will use whichever address family is appropriate
+for the server it is sending packets to.
+.RE
+
+\fB-e\fR
+.br
+.RS
+Enables EDNS0 [RFC2671], by adding an OPT record to all packets sent.
+.RE
+
+\fB-D\fR
+.br
+.RS
+Sets the DO (DNSSEC OK) bit [RFC3225] in all packets sent. This also enables
+EDNS0, which is required for DNSSEC.
+.RE
+
+\fB-y \fI[alg:]name:secret\fB\fR
+.br
+.RS
+Add a TSIG record [RFC2845] to all packets sent, using the specified TSIG
+key algorithm, name and secret, where the algorithm defaults to hmac-md5 and
+the secret is expressed as a base-64 encoded string.
+.RE
+
+\fB-h\fR
+.br
+.RS
+Print a usage statement and exit.
+.RE
+
+\fB-i \fIinterval\fB\fR
+.br
+.RS
+Specifies the time interval between data points in the plot file. The
+default is 0.5 seconds.
+.RE
+
+\fB-m \fImax_qps\fB\fR
+.br
+.RS
+Specifies the target maximum query rate (in queries per second). This should
+be higher than the expected maximum throughput of the server being tested.
+Traffic will be ramped up at a linearly increasing rate until this value is
+reached, or until one of the other conditions described in the section
+"Running the test" occurs. The default is 100000 queries per second.
+.RE
+
+\fB-P \fIplot_data_file\fB\fR
+.br
+.RS
+Specifies the name of the plot data file. The default is
+\fIresperf.gnuplot\fR.
+.RE
+
+\fB-r \fIrampup_time\fB\fR
+.br
+.RS
+Specifies the length of time over which traffic will be ramped up. The
+default is 60 seconds.
+.RE
+
+\fB-c \fIconstant_traffic_time\fB\fR
+.br
+.RS
+Specifies the length of time for which traffic will be sent at a constant
+rate following the initial ramp-up. The default is 0 seconds, meaning no
+sending of traffic at a constant rate will be done.
+.RE
+
+\fB-L \fImax_loss\fB\fR
+.br
+.RS
+Specifies the maximum acceptable query loss percentage for purposes of
+determining the maximum throughput value. The default is 100%, meaning that
+\fBresperf\fR will measure the maximum throughput without regard to query
+loss.
+.RE
+
+\fB-C \fIclients\fB\fR
+.br
+.RS
+Act as multiple clients. Requests are sent from multiple sockets. The
+default is to act as 1 client.
+.RE
+
+\fB-q \fImax_outstanding\fB\fR
+.br
+.RS
+Sets the maximum number of outstanding requests. \fBresperf\fR will stop
+ramping up traffic when this many queries are outstanding. The default is
+64k, and the limit is 64k per client.
+.RE
+
+\fB-v\fR
+.br
+.RS
+Enables verbose mode to report about network readiness and congestion.
+.RE
+.SH "THE PLOT DATA FILE"
+The plot data file is written by the \fBresperf\fR program and contains the
+data to be plotted using \fBgnuplot\fR. When running \fBresperf\fR via the
+\fBresperf\-report\fR script, there is no need for the user to deal with
+this file directly, but its format and contents are documented here for
+completeness and in case you wish to run \fBresperf\fR directly and use its
+output for purposes other than viewing it with \fBgnuplot\fR.
+
+The first line of the file is a comment identifying the fields. It may be
+recognized as a comment by its leading hash sign (#).
+
+Subsequent lines contain the actual plot data. For purposes of generating
+the plot data file, the test run is divided into time intervals of 0.5
+seconds (or some other length of time specified with the \fB\-i\fR command
+line option). Each line corresponds to one such interval, and contains the
+following values as floating-point numbers:
+
+\fBTime\fR
+.br
+.RS
+The midpoint of this time interval, in seconds since the beginning of the
+run
+.RE
+
+\fBTarget queries per second\fR
+.br
+.RS
+The number of queries per second scheduled to be sent in this time interval
+.RE
+
+\fBActual queries per second\fR
+.br
+.RS
+The number of queries per second actually sent in this time interval
+.RE
+
+\fBResponses per second\fR
+.br
+.RS
+The number of responses received corresponding to queries sent in this time
+interval, divided by the length of the interval
+.RE
+
+\fBFailures per second\fR
+.br
+.RS
+The number of responses received corresponding to queries sent in this time
+interval and having an RCODE other than NOERROR or NXDOMAIN, divided by the
+length of the interval
+.RE
+
+\fBAverage latency\fR
+.br
+.RS
+The average time between sending the query and receiving a response, for
+queries sent in this time interval
+.RE
+.SH "SEE ALSO"
+\fBdnsperf\fR(1)
+.SH AUTHOR
+Nominum, Inc.
+.LP
+Maintained by DNS-OARC
+.LP
+.RS
+.I https://www.dns-oarc.net/
+.RE
+.LP
+.SH BUGS
+For issues and feature requests please use:
+.LP
+.RS
+\fI@PACKAGE_URL@\fP
+.RE
+.LP
+For question and help please use:
+.LP
+.RS
+\fI@PACKAGE_BUGREPORT@\fP
+.RE
+.LP
diff --git a/src/resperf.c b/src/resperf.c
new file mode 100644
index 0000000..97c998d
--- /dev/null
+++ b/src/resperf.c
@@ -0,0 +1,816 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/***
+ *** DNS Resolution Performance Testing Tool
+ ***/
+
+#include "config.h"
+
+#include "datafile.h"
+#include "dns.h"
+#include "log.h"
+#include "net.h"
+#include "opt.h"
+#include "util.h"
+#include "os.h"
+#include "list.h"
+#include "result.h"
+#include "buffer.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <openssl/ssl.h>
+#include <openssl/conf.h>
+#include <openssl/err.h>
+#include <signal.h>
+
+/*
+ * Global stuff
+ */
+
+#define DEFAULT_SERVER_NAME "127.0.0.1"
+#define DEFAULT_SERVER_PORT 53
+#define DEFAULT_SERVER_TLS_PORT 853
+#define DEFAULT_SERVER_PORTS "udp/tcp 53 or dot/tls 853"
+#define DEFAULT_LOCAL_PORT 0
+#define DEFAULT_SOCKET_BUFFER 32
+#define DEFAULT_TIMEOUT 45
+#define DEFAULT_MAX_OUTSTANDING (64 * 1024)
+
+#define MAX_INPUT_DATA (64 * 1024)
+
+#define TIMEOUT_CHECK_TIME 5000000
+
+#define DNS_RCODE_NOERROR 0
+#define DNS_RCODE_NXDOMAIN 3
+
+struct query_info;
+
+typedef perf_list(struct query_info) query_list;
+
+typedef struct query_info {
+ uint64_t sent_timestamp;
+ /*
+ * This link links the query into the list of outstanding
+ * queries or the list of available query IDs.
+ */
+ perf_link(struct query_info);
+ /*
+ * The list this query is on.
+ */
+ query_list* list;
+} query_info;
+
+static query_list outstanding_list;
+static query_list instanding_list;
+
+static query_info* queries;
+
+static perf_sockaddr_t server_addr;
+static perf_sockaddr_t local_addr;
+static unsigned int nsocks;
+static struct perf_net_socket* socks;
+static enum perf_net_mode mode;
+
+static int dummypipe[2];
+
+static uint64_t query_timeout;
+static bool edns;
+static bool dnssec;
+
+static perf_datafile_t* input;
+
+/* The target traffic level at the end of the ramp-up */
+double max_qps = 100000.0;
+
+/* The time period over which we ramp up traffic */
+#define DEFAULT_RAMP_TIME 60
+static uint64_t ramp_time;
+
+/* How long to send constant traffic after the initial ramp-up */
+#define DEFAULT_SUSTAIN_TIME 0
+static uint64_t sustain_time;
+
+/* How long to wait for responses after sending traffic */
+static uint64_t wait_time = 40 * MILLION;
+
+/* Total duration of the traffic-sending part of the test */
+static uint64_t traffic_time;
+
+/* Total duration of the test */
+static uint64_t end_time;
+
+/* Interval between plot data points, in microseconds */
+#define DEFAULT_BUCKET_INTERVAL 0.5
+static uint64_t bucket_interval;
+
+/* The number of plot data points */
+static int n_buckets;
+
+/* The plot data file */
+static const char* plotfile = "resperf.gnuplot";
+
+/* The largest acceptable query loss when reporting max throughput */
+static double max_loss_percent = 100.0;
+
+/* The maximum number of outstanding queries */
+static unsigned int max_outstanding;
+
+static uint64_t num_queries_sent;
+static uint64_t num_queries_outstanding;
+static uint64_t num_responses_received;
+static uint64_t num_queries_timed_out;
+static uint64_t rcodecounts[16];
+
+static uint64_t time_now;
+static uint64_t time_of_program_start;
+static uint64_t time_of_end_of_run;
+
+/*
+ * The last plot data point containing actual data; this can
+ * be less than than (n_buckets - 1) if the traffic sending
+ * phase is cut short
+ */
+static int last_bucket_used;
+
+/*
+ * The statistics for queries sent during one bucket_interval
+ * of the traffic sending phase.
+ */
+typedef struct {
+ int queries;
+ int responses;
+ int failures;
+ double latency_sum;
+} ramp_bucket;
+
+/* Pointer to array of n_buckets ramp_bucket structures */
+static ramp_bucket* buckets;
+
+enum phase {
+ /*
+ * The ramp-up phase: we are steadily increasing traffic.
+ */
+ PHASE_RAMP,
+ /*
+ * The sustain phase: we are sending traffic at a constant
+ * rate.
+ */
+ PHASE_SUSTAIN,
+ /*
+ * The wait phase: we have stopped sending queries and are
+ * just waiting for any remaining responses.
+ */
+ PHASE_WAIT
+};
+static enum phase phase = PHASE_RAMP;
+
+/* The time when the sustain/wait phase began */
+static uint64_t sustain_phase_began, wait_phase_began;
+
+static perf_tsigkey_t* tsigkey;
+
+static bool verbose;
+
+const char* progname = "resperf";
+
+static char*
+stringify(double value, int precision)
+{
+ static char buf[20];
+
+ snprintf(buf, sizeof(buf), "%.*f", precision, value);
+ return buf;
+}
+
+static void
+setup(int argc, char** argv)
+{
+ const char* family = NULL;
+ const char* server_name = DEFAULT_SERVER_NAME;
+ in_port_t server_port = 0;
+ const char* local_name = NULL;
+ in_port_t local_port = DEFAULT_LOCAL_PORT;
+ const char* filename = NULL;
+ const char* tsigkey_str = NULL;
+ int sock_family;
+ unsigned int bufsize;
+ unsigned int i;
+ const char* _mode = 0;
+
+ sock_family = AF_UNSPEC;
+ server_port = 0;
+ local_port = DEFAULT_LOCAL_PORT;
+ bufsize = DEFAULT_SOCKET_BUFFER;
+ query_timeout = DEFAULT_TIMEOUT * MILLION;
+ ramp_time = DEFAULT_RAMP_TIME * MILLION;
+ sustain_time = DEFAULT_SUSTAIN_TIME * MILLION;
+ bucket_interval = DEFAULT_BUCKET_INTERVAL * MILLION;
+ max_outstanding = DEFAULT_MAX_OUTSTANDING;
+ nsocks = 1;
+ mode = sock_udp;
+ verbose = false;
+
+ perf_opt_add('f', perf_opt_string, "family",
+ "address family of DNS transport, inet or inet6", "any",
+ &family);
+ perf_opt_add('M', perf_opt_string, "mode", "set transport mode: udp, tcp or dot/tls", "udp", &_mode);
+ perf_opt_add('s', perf_opt_string, "server_addr",
+ "the server to query", DEFAULT_SERVER_NAME, &server_name);
+ perf_opt_add('p', perf_opt_port, "port",
+ "the port on which to query the server",
+ DEFAULT_SERVER_PORTS, &server_port);
+ perf_opt_add('a', perf_opt_string, "local_addr",
+ "the local address from which to send queries", NULL,
+ &local_name);
+ perf_opt_add('x', perf_opt_port, "local_port",
+ "the local port from which to send queries",
+ stringify(DEFAULT_LOCAL_PORT, 0), &local_port);
+ perf_opt_add('d', perf_opt_string, "datafile",
+ "the input data file", "stdin", &filename);
+ perf_opt_add('t', perf_opt_timeval, "timeout",
+ "the timeout for query completion in seconds",
+ stringify(DEFAULT_TIMEOUT, 0), &query_timeout);
+ perf_opt_add('b', perf_opt_uint, "buffer_size",
+ "socket send/receive buffer size in kilobytes", NULL,
+ &bufsize);
+ perf_opt_add('e', perf_opt_boolean, NULL,
+ "enable EDNS 0", NULL, &edns);
+ perf_opt_add('D', perf_opt_boolean, NULL,
+ "set the DNSSEC OK bit (implies EDNS)", NULL, &dnssec);
+ perf_opt_add('y', perf_opt_string, "[alg:]name:secret",
+ "the TSIG algorithm, name and secret", NULL, &tsigkey_str);
+ perf_opt_add('i', perf_opt_timeval, "plot_interval",
+ "the time interval between plot data points, in seconds",
+ stringify(DEFAULT_BUCKET_INTERVAL, 1), &bucket_interval);
+ perf_opt_add('m', perf_opt_double, "max_qps",
+ "the maximum number of queries per second",
+ stringify(max_qps, 0), &max_qps);
+ perf_opt_add('P', perf_opt_string, "plotfile",
+ "the name of the plot data file", plotfile, &plotfile);
+ perf_opt_add('r', perf_opt_timeval, "ramp_time",
+ "the ramp-up time in seconds",
+ stringify(DEFAULT_RAMP_TIME, 0), &ramp_time);
+ perf_opt_add('c', perf_opt_timeval, "constant_traffic_time",
+ "how long to send constant traffic, in seconds",
+ stringify(DEFAULT_SUSTAIN_TIME, 0), &sustain_time);
+ perf_opt_add('L', perf_opt_double, "max_query_loss",
+ "the maximum acceptable query loss, in percent",
+ stringify(max_loss_percent, 0), &max_loss_percent);
+ perf_opt_add('C', perf_opt_uint, "clients",
+ "the number of clients to act as", NULL, &nsocks);
+ perf_opt_add('q', perf_opt_uint, "num_outstanding",
+ "the maximum number of queries outstanding",
+ stringify(DEFAULT_MAX_OUTSTANDING, 0), &max_outstanding);
+ perf_opt_add('v', perf_opt_boolean, NULL,
+ "verbose: report additional information to stdout",
+ NULL, &verbose);
+ bool log_stdout = false;
+ perf_opt_add('W', perf_opt_boolean, NULL, "log warnings and errors to stdout instead of stderr", NULL, &log_stdout);
+
+ perf_opt_parse(argc, argv);
+
+ if (log_stdout) {
+ perf_log_tostdout();
+ }
+
+ if (_mode != 0)
+ mode = perf_net_parsemode(_mode);
+
+ if (!server_port) {
+ server_port = mode == sock_tls ? DEFAULT_SERVER_TLS_PORT : DEFAULT_SERVER_PORT;
+ }
+
+ if (max_outstanding > nsocks * DEFAULT_MAX_OUTSTANDING)
+ perf_log_fatal("number of outstanding packets (%u) must not "
+ "be more than 64K per client",
+ max_outstanding);
+
+ if (ramp_time + sustain_time == 0)
+ perf_log_fatal("rampup_time and constant_traffic_time must not "
+ "both be 0");
+
+ perf_list_init(outstanding_list);
+ perf_list_init(instanding_list);
+ if (!(queries = calloc(max_outstanding, sizeof(query_info)))) {
+ perf_log_fatal("out of memory");
+ }
+ for (i = 0; i < max_outstanding; i++) {
+ perf_link_init(&queries[i]);
+ perf_list_append(instanding_list, &queries[i]);
+ queries[i].list = &instanding_list;
+ }
+
+ if (family != NULL)
+ sock_family = perf_net_parsefamily(family);
+ perf_net_parseserver(sock_family, server_name, server_port, &server_addr);
+ perf_net_parselocal(server_addr.sa.sa.sa_family, local_name,
+ local_port, &local_addr);
+
+ input = perf_datafile_open(filename);
+
+ if (dnssec)
+ edns = true;
+
+ if (tsigkey_str != NULL)
+ tsigkey = perf_tsig_parsekey(tsigkey_str);
+
+ if (!(socks = calloc(nsocks, sizeof(*socks)))) {
+ perf_log_fatal("out of memory");
+ }
+ for (i = 0; i < nsocks; i++)
+ socks[i] = perf_net_opensocket(mode, &server_addr, &local_addr, i, bufsize);
+}
+
+static void
+cleanup(void)
+{
+ unsigned int i;
+
+ perf_datafile_close(&input);
+ for (i = 0; i < nsocks; i++)
+ (void)perf_net_close(&socks[i]);
+ close(dummypipe[0]);
+ close(dummypipe[1]);
+}
+
+/* Find the ramp_bucket for queries sent at time "when" */
+
+static ramp_bucket*
+find_bucket(uint64_t when)
+{
+ uint64_t sent_at = when - time_of_program_start;
+ int i = (int)((n_buckets * sent_at) / traffic_time);
+ /*
+ * Guard against array bounds violations due to roundoff
+ * errors or scheduling jitter
+ */
+ if (i < 0)
+ i = 0;
+ if (i > n_buckets - 1)
+ i = n_buckets - 1;
+ return &buckets[i];
+}
+
+/*
+ * print_statistics:
+ * Print out statistics based on the results of the test
+ */
+static void
+print_statistics(void)
+{
+ int i;
+ double max_throughput;
+ double loss_at_max_throughput;
+ bool first_rcode;
+ uint64_t run_time = time_of_end_of_run - time_of_program_start;
+
+ printf("\nStatistics:\n\n");
+
+ printf(" Queries sent: %" PRIu64 "\n",
+ num_queries_sent);
+ printf(" Queries completed: %" PRIu64 "\n",
+ num_responses_received);
+ printf(" Queries lost: %" PRIu64 "\n",
+ num_queries_sent - num_responses_received);
+ printf(" Response codes: ");
+ first_rcode = true;
+ for (i = 0; i < 16; i++) {
+ if (rcodecounts[i] == 0)
+ continue;
+ if (first_rcode)
+ first_rcode = false;
+ else
+ printf(", ");
+ printf("%s %" PRIu64 " (%.2lf%%)",
+ perf_dns_rcode_strings[i], rcodecounts[i],
+ (rcodecounts[i] * 100.0) / num_responses_received);
+ }
+ printf("\n");
+ printf(" Run time (s): %u.%06u\n",
+ (unsigned int)(run_time / MILLION),
+ (unsigned int)(run_time % MILLION));
+
+ /* Find the maximum throughput, subject to the -L option */
+ max_throughput = 0.0;
+ loss_at_max_throughput = 0.0;
+ for (i = 0; i <= last_bucket_used; i++) {
+ ramp_bucket* b = &buckets[i];
+ double responses_per_sec = b->responses / (bucket_interval / (double)MILLION);
+ double loss = b->queries ? (b->queries - b->responses) / (double)b->queries : 0.0;
+ double loss_percent = loss * 100.0;
+ if (loss_percent > max_loss_percent)
+ break;
+ if (responses_per_sec > max_throughput) {
+ max_throughput = responses_per_sec;
+ loss_at_max_throughput = loss_percent;
+ }
+ }
+ printf(" Maximum throughput: %.6lf qps\n", max_throughput);
+ printf(" Lost at that point: %.2f%%\n", loss_at_max_throughput);
+}
+
+static ramp_bucket*
+init_buckets(int n)
+{
+ ramp_bucket* p;
+ int i;
+
+ if (!(p = calloc(n, sizeof(*p)))) {
+ perf_log_fatal("out of memory");
+ return 0; // fix clang scan-build
+ }
+ for (i = 0; i < n; i++) {
+ p[i].queries = p[i].responses = p[i].failures = 0;
+ p[i].latency_sum = 0.0;
+ }
+ return p;
+}
+
+/*
+ * Send a query based on a line of input.
+ * Return PERF_R_NOMORE if we ran out of query IDs.
+ */
+static perf_result_t
+do_one_line(perf_buffer_t* lines, perf_buffer_t* msg)
+{
+ query_info* q;
+ unsigned int qid;
+ unsigned int sock;
+ perf_region_t used;
+ unsigned char* base;
+ unsigned int length;
+ perf_result_t result;
+
+ q = perf_list_head(instanding_list);
+ if (!q)
+ return (PERF_R_NOMORE);
+ qid = (q - queries) / nsocks;
+ sock = (q - queries) % nsocks;
+
+ if (socks[sock].sending) {
+ if (perf_net_sockready(&socks[sock], dummypipe[0], TIMEOUT_CHECK_TIME) == -1) {
+ if (errno == EINPROGRESS) {
+ if (verbose) {
+ perf_log_warning("network congested, packet sending in progress");
+ }
+ } else {
+ if (verbose) {
+ char __s[256];
+ perf_log_warning("failed to send packet: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+ }
+ return (PERF_R_FAILURE);
+ }
+
+ perf_list_unlink(instanding_list, q);
+ perf_list_prepend(outstanding_list, q);
+ q->list = &outstanding_list;
+
+ num_queries_sent++;
+ num_queries_outstanding++;
+
+ q = perf_list_head(instanding_list);
+ if (!q)
+ return (PERF_R_NOMORE);
+ qid = (q - queries) / nsocks;
+ sock = (q - queries) % nsocks;
+ }
+
+ switch (perf_net_sockready(&socks[sock], dummypipe[0], TIMEOUT_CHECK_TIME)) {
+ case 0:
+ if (verbose) {
+ perf_log_warning("failed to send packet: socket %d not ready", sock);
+ }
+ return (PERF_R_FAILURE);
+ case -1:
+ perf_log_warning("failed to send packet: socket %d readiness check timed out", sock);
+ return (PERF_R_FAILURE);
+ default:
+ break;
+ }
+
+ perf_buffer_clear(lines);
+ result = perf_datafile_next(input, lines, false);
+ if (result != PERF_R_SUCCESS)
+ perf_log_fatal("ran out of query data");
+ perf_buffer_usedregion(lines, &used);
+
+ perf_buffer_clear(msg);
+ result = perf_dns_buildrequest(&used, qid,
+ edns, dnssec, false,
+ tsigkey, 0,
+ msg);
+ if (result != PERF_R_SUCCESS)
+ return (result);
+
+ q->sent_timestamp = time_now;
+
+ base = perf_buffer_base(msg);
+ length = perf_buffer_usedlength(msg);
+ if (perf_net_sendto(&socks[sock], base, length, 0,
+ &server_addr.sa.sa, server_addr.length)
+ < 1) {
+ if (errno == EINPROGRESS) {
+ if (verbose) {
+ perf_log_warning("network congested, packet sending in progress");
+ }
+ } else {
+ if (verbose) {
+ char __s[256];
+ perf_log_warning("failed to send packet: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+ }
+ return (PERF_R_FAILURE);
+ }
+
+ perf_list_unlink(instanding_list, q);
+ perf_list_prepend(outstanding_list, q);
+ q->list = &outstanding_list;
+
+ num_queries_sent++;
+ num_queries_outstanding++;
+
+ return PERF_R_SUCCESS;
+}
+
+static void
+enter_sustain_phase(void)
+{
+ phase = PHASE_SUSTAIN;
+ if (sustain_time != 0.0)
+ printf("[Status] Ramp-up done, sending constant traffic\n");
+ sustain_phase_began = time_now;
+}
+
+static void
+enter_wait_phase(void)
+{
+ phase = PHASE_WAIT;
+ printf("[Status] Waiting for more responses\n");
+ wait_phase_began = time_now;
+}
+
+/*
+ * try_process_response:
+ *
+ * Receive from the given socket & process an individual response packet.
+ * Remove it from the list of open queries (status[]) and decrement the
+ * number of outstanding queries if it matches an open query.
+ */
+static void
+try_process_response(unsigned int sockindex)
+{
+ unsigned char packet_buffer[MAX_EDNS_PACKET];
+ uint16_t* packet_header;
+ uint16_t qid, rcode;
+ query_info* q;
+ double latency;
+ ramp_bucket* b;
+ int n;
+
+ packet_header = (uint16_t*)packet_buffer;
+ n = perf_net_recv(&socks[sockindex], packet_buffer, sizeof(packet_buffer), 0);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ return;
+ } else {
+ char __s[256];
+ perf_log_fatal("failed to receive packet: %s", perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+ } else if (!n) {
+ // Treat connection closed like try again until reconnection features are in
+ return;
+ } else if (n < 4) {
+ perf_log_warning("received short response");
+ return;
+ }
+
+ qid = ntohs(packet_header[0]);
+ rcode = ntohs(packet_header[1]) & 0xF;
+
+ q = &queries[qid * nsocks + sockindex];
+ if (q->list != &outstanding_list) {
+ perf_log_warning("received a response with an unexpected id: %u", qid);
+ return;
+ }
+
+ perf_list_unlink(outstanding_list, q);
+ perf_list_append(instanding_list, q);
+ q->list = &instanding_list;
+
+ num_queries_outstanding--;
+
+ latency = (time_now - q->sent_timestamp) / (double)MILLION;
+ b = find_bucket(q->sent_timestamp);
+ b->responses++;
+ if (!(rcode == DNS_RCODE_NOERROR || rcode == DNS_RCODE_NXDOMAIN))
+ b->failures++;
+ b->latency_sum += latency;
+ num_responses_received++;
+ rcodecounts[rcode]++;
+}
+
+static void
+retire_old_queries(void)
+{
+ query_info* q;
+
+ while (true) {
+ q = perf_list_tail(outstanding_list);
+ if (q == NULL || (time_now - q->sent_timestamp) < query_timeout)
+ break;
+ perf_list_unlink(outstanding_list, q);
+ perf_list_append(instanding_list, q);
+ q->list = &instanding_list;
+
+ num_queries_outstanding--;
+ num_queries_timed_out++;
+ }
+}
+
+static inline int
+num_scheduled(uint64_t time_since_start)
+{
+ if (phase == PHASE_RAMP) {
+ return 0.5 * max_qps * (double)time_since_start * time_since_start / (ramp_time * MILLION);
+ } else { /* PHASE_SUSTAIN */
+ return 0.5 * max_qps * (ramp_time / (double)MILLION) + max_qps * (time_since_start - ramp_time) / (double)MILLION;
+ }
+}
+
+static void
+handle_sigpipe(int sig)
+{
+ (void)sig;
+ switch (mode) {
+ case sock_tcp:
+ case sock_tls:
+ // if connection is closed it will generate a signal
+ perf_log_fatal("SIGPIPE received, connection(s) likely closed, can't continue");
+ break;
+ default:
+ break;
+ }
+}
+
+int main(int argc, char** argv)
+{
+ int i;
+ FILE* plotf;
+ perf_buffer_t lines, msg;
+ char input_data[MAX_INPUT_DATA];
+ unsigned char outpacket_buffer[MAX_EDNS_PACKET];
+ unsigned int max_packet_size;
+ unsigned int current_sock;
+ perf_result_t result;
+
+ printf("DNS Resolution Performance Testing Tool\n"
+ "Version " PACKAGE_VERSION "\n\n");
+
+ (void)SSL_library_init();
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ SSL_load_error_strings();
+ OPENSSL_config(0);
+#endif
+
+ setup(argc, argv);
+
+ if (pipe(dummypipe) < 0)
+ perf_log_fatal("creating pipe");
+
+ perf_os_handlesignal(SIGPIPE, handle_sigpipe);
+
+ perf_buffer_init(&lines, input_data, sizeof(input_data));
+
+ max_packet_size = edns ? MAX_EDNS_PACKET : MAX_UDP_PACKET;
+ perf_buffer_init(&msg, outpacket_buffer, max_packet_size);
+
+ traffic_time = ramp_time + sustain_time;
+ end_time = traffic_time + wait_time;
+
+ n_buckets = (traffic_time + bucket_interval - 1) / bucket_interval;
+ buckets = init_buckets(n_buckets);
+
+ time_now = perf_get_time();
+ time_of_program_start = time_now;
+
+ printf("[Status] Command line: %s", progname);
+ for (i = 1; i < argc; i++) {
+ printf(" %s", argv[i]);
+ }
+ printf("\n");
+
+ printf("[Status] Sending\n");
+
+ current_sock = 0;
+ for (;;) {
+ int should_send;
+ uint64_t time_since_start = time_now - time_of_program_start;
+ switch (phase) {
+ case PHASE_RAMP:
+ if (time_since_start >= ramp_time)
+ enter_sustain_phase();
+ break;
+ case PHASE_SUSTAIN:
+ if (time_since_start >= traffic_time)
+ enter_wait_phase();
+ break;
+ case PHASE_WAIT:
+ if (time_since_start >= end_time || perf_list_empty(outstanding_list))
+ goto end_loop;
+ break;
+ }
+ if (phase != PHASE_WAIT) {
+ should_send = num_scheduled(time_since_start) - num_queries_sent;
+ if (should_send >= 1000) {
+ printf("[Status] Fell behind by %d queries, "
+ "ending test at %.0f qps\n",
+ should_send, (max_qps * time_since_start) / ramp_time);
+ enter_wait_phase();
+ }
+ if (should_send > 0) {
+ result = do_one_line(&lines, &msg);
+ if (result == PERF_R_SUCCESS)
+ find_bucket(time_now)->queries++;
+ if (result == PERF_R_NOMORE) {
+ printf("[Status] Reached %u outstanding queries\n",
+ max_outstanding);
+ enter_wait_phase();
+ }
+ }
+ }
+ try_process_response(current_sock++);
+ current_sock = current_sock % nsocks;
+ retire_old_queries();
+ time_now = perf_get_time();
+ }
+end_loop:
+ time_now = perf_get_time();
+ time_of_end_of_run = time_now;
+
+ printf("[Status] Testing complete\n");
+
+ plotf = fopen(plotfile, "w");
+ if (!plotf) {
+ char __s[256];
+ perf_log_fatal("could not open %s: %s", plotfile, perf_strerror_r(errno, __s, sizeof(__s)));
+ }
+
+ /* Print column headers */
+ fprintf(plotf, "# time target_qps actual_qps "
+ "responses_per_sec failures_per_sec avg_latency\n");
+
+ /* Don't print unused buckets */
+ last_bucket_used = find_bucket(wait_phase_began) - buckets;
+
+ /* Don't print a partial bucket at the end */
+ if (last_bucket_used > 0)
+ --last_bucket_used;
+
+ for (i = 0; i <= last_bucket_used; i++) {
+ double t = (i + 0.5) * traffic_time / (n_buckets * (double)MILLION);
+ double ramp_dtime = ramp_time / (double)MILLION;
+ double target_qps = t <= ramp_dtime ? (t / ramp_dtime) * max_qps : max_qps;
+ double latency = buckets[i].responses ? buckets[i].latency_sum / buckets[i].responses : 0;
+ double interval = bucket_interval / (double)MILLION;
+ fprintf(plotf, "%7.3f %8.2f %8.2f %8.2f %8.2f %8.6f\n",
+ t,
+ target_qps,
+ buckets[i].queries / interval,
+ buckets[i].responses / interval,
+ buckets[i].failures / interval,
+ latency);
+ }
+
+ fclose(plotf);
+ print_statistics();
+ cleanup();
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ ERR_free_strings();
+#endif
+
+ return 0;
+}
diff --git a/src/result.h b/src/result.h
new file mode 100644
index 0000000..076fcc5
--- /dev/null
+++ b/src/result.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PERF_RESULT_H
+#define PERF_RESULT_H 1
+
+#include <assert.h>
+
+typedef unsigned int perf_result_t;
+
+#define PERF_R_SUCCESS 0
+#define PERF_R_FAILURE 1
+#define PERF_R_CANCELED 2
+#define PERF_R_EOF 3
+#define PERF_R_INVALIDFILE 4
+#define PERF_R_NOMORE 5
+#define PERF_R_NOSPACE 6
+#define PERF_R_TIMEDOUT 7
+
+#define PERF_R_INVALIDUPDATE 100
+
+#endif
diff --git a/src/strerror.c b/src/strerror.c
new file mode 100644
index 0000000..a283bc8
--- /dev/null
+++ b/src/strerror.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "strerror.h"
+
+#include <string.h>
+#include <stdio.h>
+
+const char* perf_strerror_r(int errnum, char* str, size_t len)
+{
+#if ((_POSIX_C_SOURCE >= 200112L) && !_GNU_SOURCE) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
+ if (strerror_r(errnum, str, len)) {
+ (void)snprintf(str, len, "Error %d", errnum);
+ }
+ return str;
+#else
+ return strerror_r(errnum, str, len);
+#endif
+}
diff --git a/src/strerror.h b/src/strerror.h
new file mode 100644
index 0000000..72bca6d
--- /dev/null
+++ b/src/strerror.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef PERF_STRERROR_H
+#define PERF_STRERROR_H 1
+
+#include <stddef.h>
+
+const char* perf_strerror_r(int errnum, char* str, size_t len);
+
+#endif
diff --git a/src/test/Makefile.am b/src/test/Makefile.am
new file mode 100644
index 0000000..9b3e7c2
--- /dev/null
+++ b/src/test/Makefile.am
@@ -0,0 +1,9 @@
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+CLEANFILES = test*.log test*.trs \
+ test2.out
+
+TESTS = test1.sh test2.sh test3.sh
+
+EXTRA_DIST = $(TESTS) \
+ datafile datafile2 updatefile
diff --git a/src/test/datafile b/src/test/datafile
new file mode 100644
index 0000000..883156e
--- /dev/null
+++ b/src/test/datafile
@@ -0,0 +1,2 @@
+google.com A
+google.com AAAA
diff --git a/src/test/datafile2 b/src/test/datafile2
new file mode 100644
index 0000000..96acd2d
--- /dev/null
+++ b/src/test/datafile2
@@ -0,0 +1,560 @@
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
+google.com A
+google.com AAAA
diff --git a/src/test/datafile3 b/src/test/datafile3
new file mode 100644
index 0000000..ce49d49
--- /dev/null
+++ b/src/test/datafile3
@@ -0,0 +1,2 @@
+. A
+google.com. A
diff --git a/src/test/test1.sh b/src/test/test1.sh
new file mode 100755
index 0000000..945aab3
--- /dev/null
+++ b/src/test/test1.sh
@@ -0,0 +1,4 @@
+#!/bin/sh -xe
+
+../dnsperf -h
+../resperf -h
diff --git a/src/test/test2.sh b/src/test/test2.sh
new file mode 100755
index 0000000..a4cfce0
--- /dev/null
+++ b/src/test/test2.sh
@@ -0,0 +1,108 @@
+#!/bin/sh -xe
+
+test "$TEST_DNSPERF_WITH_NETWORK" = "1" || exit 0
+
+for ip in 1.1.1.1 2606:4700:4700::1111; do
+
+echo "google.com A" | ../dnsperf -vvv -s $ip -m udp >test2.out
+cat test2.out
+grep -q "Queries sent: *1" test2.out
+echo "google.com A" | ../dnsperf -vvv -s $ip -e -E 12345:0a0a0a0a -m udp >test2.out
+cat test2.out
+grep -q "Queries sent: *1" test2.out
+../dnsperf -vvv -s $ip -d "$srcdir/datafile" -n 2 -m udp >test2.out
+cat test2.out
+grep -q "Queries sent: *4" test2.out
+../dnsperf -s $ip -d "$srcdir/datafile" -n 1 -m tcp >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+../dnsperf -s $ip -d "$srcdir/datafile" -n 1 -m tls >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+../dnsperf -s $ip -d "$srcdir/datafile" -n 1 -m dot >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+
+../dnsperf -s $ip -d "$srcdir/datafile3" -n 1 -m dot >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+
+../dnsperf -s $ip -d "$srcdir/datafile" -n 1 -e >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+../dnsperf -s $ip -d "$srcdir/datafile" -n 1 -e -D >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+
+../dnsperf -d "$srcdir/updatefile" -u -s $ip -y hmac-md5:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8= >test2.out
+cat test2.out
+grep -q "Updates sent: *1" test2.out
+../dnsperf -d "$srcdir/updatefile" -u -s $ip -y hmac-sha1:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8= >test2.out
+cat test2.out
+grep -q "Updates sent: *1" test2.out
+../dnsperf -d "$srcdir/updatefile" -u -s $ip -y hmac-sha224:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8= >test2.out
+cat test2.out
+grep -q "Updates sent: *1" test2.out
+../dnsperf -d "$srcdir/updatefile" -u -s $ip -y hmac-sha256:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8= >test2.out
+cat test2.out
+grep -q "Updates sent: *1" test2.out
+../dnsperf -d "$srcdir/updatefile" -u -s $ip -y hmac-sha384:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8= >test2.out
+cat test2.out
+grep -q "Updates sent: *1" test2.out
+../dnsperf -d "$srcdir/updatefile" -u -s $ip -y hmac-sha512:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8= >test2.out
+cat test2.out
+grep -q "Updates sent: *1" test2.out
+
+../resperf -s $ip -m 1 -d "$srcdir/datafile2" -r 2 -c 2 -M udp >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+../resperf -s $ip -m 1 -d "$srcdir/datafile2" -r 2 -c 2 -M tcp >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+
+../resperf -s $ip -m 1 -d "$srcdir/datafile2" -r 2 -c 2 -M udp -D >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+# Disabled until https://github.com/DNS-OARC/dnsperf/issues/92 is fixed
+../resperf -s $ip -m 1 -d "$srcdir/datafile2" -r 2 -c 2 -M udp -y hmac-sha256:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8= >test2.out
+cat test2.out
+grep -q "Queries sent: *2" test2.out
+
+# Ignore failure until https://github.com/DNS-OARC/dnsperf/issues/88 is fixed
+# May work on slower systems
+../resperf -s $ip -m 1 -d "$srcdir/datafile2" -r 2 -c 2 -M tls || true
+
+done # for ip
+
+../dnsperf -s 127.66.66.66 -d "$srcdir/datafile" -vvvv -m tcp -n 1 &
+sleep 2
+pkill -KILL -u `id -u` dnsperf || true
+
+../dnsperf -s 127.66.66.66 -d "$srcdir/datafile" -vvvv -m tls -n 1 &
+sleep 2
+pkill -KILL -u `id -u` dnsperf || true
+
+! echo "invalid" | ../dnsperf -s 127.66.66.66 -m tcp
+! echo "invalid invalid" | ../dnsperf -s 127.66.66.66 -m tcp
+echo "invalid" | ../dnsperf -u -s 127.66.66.66 -m tcp &
+sleep 2
+pkill -KILL -u `id -u` dnsperf || true
+echo "invalid\ninvalid" | ../dnsperf -u -s 127.66.66.66 -m tcp &
+sleep 2
+pkill -KILL -u `id -u` dnsperf || true
+
+! echo "google.com A" \
+ | ../dnsperf -W -s 1.1.1.1 -y tooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8= \
+ | grep "adding TSIG: invalid owner name"
+echo ".google.com A" | ../dnsperf -W -s 1.1.1.1 \
+ | grep "invalid domain name"
+echo "google.com.. A" | ../dnsperf -W -s 1.1.1.1 \
+ | grep "invalid domain name"
+echo " A" | ../dnsperf -W -s 1.1.1.1 \
+ | grep "invalid query input format"
+echo "toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolongname" \
+ | ../dnsperf -W -s 1.1.1.1 -u \
+ | grep "Unable to parse domain name"
+echo -e "test\ndelete toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolongname" \
+ | ../dnsperf -W -s 1.1.1.1 -u \
+ | grep "invalid update command, domain name too large"
diff --git a/src/test/test3.sh b/src/test/test3.sh
new file mode 100755
index 0000000..3c0e44f
--- /dev/null
+++ b/src/test/test3.sh
@@ -0,0 +1,46 @@
+#!/bin/sh -xe
+
+! ../dnsperf -d does_not_exist
+! ../resperf -d does_not_exist
+! ../dnsperf -f invalid
+! ../dnsperf -f any -s 256.256.256.256
+! ../dnsperf -f inet -s 256.256.256.256
+! ../dnsperf -f inet6 -s 256.256.256.256
+! ../dnsperf -a 127.0.0.1 -d does_not_exist
+! ../dnsperf -a ::1 -d does_not_exist
+! ../dnsperf -a 256.256.256.256
+! ../dnsperf -m invalid
+! ../dnsperf -n 43f8huishfs
+! ../dnsperf -p 12345 unexpected argument
+! ../dnsperf -p 65536
+
+! echo "" | ../dnsperf -y test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8=
+! echo "" | ../dnsperf -y hmac-md5:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8=
+! echo "" | ../dnsperf -y hmac-sha1:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8=
+! echo "" | ../dnsperf -y hmac-sha224:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8=
+! echo "" | ../dnsperf -y hmac-sha256:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8=
+! echo "" | ../dnsperf -y hmac-sha384:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8=
+! echo "" | ../dnsperf -y hmac-sha512:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8=
+! echo "" | ../dnsperf -y invalid:test:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8=
+! echo "" | ../dnsperf -y test:invalid
+! echo "" | ../dnsperf -y test
+echo "" | ../dnsperf -W -y toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolongname:Ax42vsuHBjQOKlVHO8yU1zGuQ5hjeSz01LXiNze8pb8= \
+ | grep "unable to setup TSIG, name too long"
+echo "" | ../dnsperf -W -y test: | grep "unable to setup TSIG, secret empty"
+
+! ../dnsperf -e -E invalid
+! ../dnsperf -e -E 9999999:invalid
+! ../dnsperf -e -E 123:invalid
+! ../dnsperf -e -E 123:fa0
+../dnsperf -W -E a: | grep "invalid EDNS Option, value is empty"
+../dnsperf -W -E a:a | grep "invalid EDNS Option, value must hex string (even number of characters)"
+../dnsperf -W -E a:aa | grep "invalid EDNS Option code 'a'"
+../dnsperf -W -E 1:xx | grep "invalid EDNS Option hex value 'xx'"
+
+! ../resperf -d does_not_exist
+! ../resperf -r 0 -c 0
+! ../resperf -f invalid
+! ../resperf -q 256000
+! ../resperf -m 123.45 unexpected argument
+! ../resperf -m 123..
+! ../resperf -m 123a
diff --git a/src/test/updatefile b/src/test/updatefile
new file mode 100644
index 0000000..1a61edf
--- /dev/null
+++ b/src/test/updatefile
@@ -0,0 +1,11 @@
+example.com
+require a
+require a A
+require a A 1.2.3.4
+prohibit x
+prohibit x A
+add x 3600 A 10.1.2.3
+delete y A 10.1.2.3
+delete z A
+delete w
+send
diff --git a/src/tsig.c b/src/tsig.c
new file mode 100644
index 0000000..0b660c1
--- /dev/null
+++ b/src/tsig.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+
+#include "tsig.h"
+
+#include "log.h"
+#include "opt.h"
+#include "dns.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <openssl/evp.h>
+#include <time.h>
+
+#define TSIG_HMACMD5_NAME "hmac-md5.sig-alg.reg.int"
+#define TSIG_HMACSHA1_NAME "hmac-sha1"
+#define TSIG_HMACSHA224_NAME "hmac-sha224"
+#define TSIG_HMACSHA256_NAME "hmac-sha256"
+#define TSIG_HMACSHA384_NAME "hmac-sha384"
+#define TSIG_HMACSHA512_NAME "hmac-sha512"
+
+static unsigned char* decode64(const void* base64, int* len)
+{
+ unsigned char* out;
+
+ assert(base64);
+ assert(len);
+ assert(*len);
+
+ out = calloc(1, *len);
+ assert(out);
+
+ int olen = *len;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ EVP_ENCODE_CTX evp;
+ EVP_DecodeInit(&evp);
+ if (EVP_DecodeUpdate(&evp, out, &olen, base64, *len)) {
+ free(out);
+ return 0;
+ }
+#else
+ EVP_ENCODE_CTX* evp = EVP_ENCODE_CTX_new();
+ if (!evp) {
+ free(out);
+ return 0;
+ }
+ EVP_DecodeInit(evp);
+ if (EVP_DecodeUpdate(evp, out, &olen, base64, *len)) {
+ free(out);
+ EVP_ENCODE_CTX_free(evp);
+ return 0;
+ }
+ EVP_ENCODE_CTX_free(evp);
+#endif
+
+ *len = olen;
+
+ return out;
+}
+
+perf_tsigkey_t* perf_tsig_parsekey(const char* arg)
+{
+ perf_tsigkey_t* tsigkey;
+ const char * sep1, *sep2, *alg, *name, *secret;
+ size_t alglen, namelen, secretlen;
+ int keylen;
+ const EVP_MD* md = 0;
+
+ tsigkey = calloc(1, sizeof(*tsigkey));
+ if (!tsigkey) {
+ perf_log_fatal("out of memory");
+ return 0; // fix clang scan-build
+ }
+
+ sep1 = strchr(arg, ':');
+ if (sep1 == NULL) {
+ perf_log_warning("invalid TSIG [alg:]name:secret");
+ perf_opt_usage();
+ exit(1);
+ }
+
+ sep2 = strchr(sep1 + 1, ':');
+ if (sep2 == NULL) {
+ /* name:key */
+ alg = NULL;
+ alglen = 0;
+ name = arg;
+ namelen = sep1 - arg;
+ secret = sep1 + 1;
+ } else {
+ /* [alg:]name:secret */
+ alg = arg;
+ alglen = sep1 - arg;
+ name = sep1 + 1;
+ namelen = sep2 - sep1 - 1;
+ secret = sep2 + 1;
+ }
+
+ /* Algorithm */
+
+ if (!alg || !strncasecmp(alg, "hmac-md5:", 9)) {
+ md = EVP_md5();
+ tsigkey->alg = TSIG_HMACMD5_NAME;
+ tsigkey->alglen = sizeof(TSIG_HMACMD5_NAME) - 1;
+ } else if (!strncasecmp(alg, "hmac-sha1:", 10)) {
+ md = EVP_sha1();
+ tsigkey->alg = TSIG_HMACSHA1_NAME;
+ tsigkey->alglen = sizeof(TSIG_HMACSHA1_NAME) - 1;
+ } else if (!strncasecmp(alg, "hmac-sha224:", 12)) {
+ md = EVP_sha224();
+ tsigkey->alg = TSIG_HMACSHA224_NAME;
+ tsigkey->alglen = sizeof(TSIG_HMACSHA224_NAME) - 1;
+ } else if (!strncasecmp(alg, "hmac-sha256:", 12)) {
+ md = EVP_sha256();
+ tsigkey->alg = TSIG_HMACSHA256_NAME;
+ tsigkey->alglen = sizeof(TSIG_HMACSHA256_NAME) - 1;
+ } else if (!strncasecmp(alg, "hmac-sha384:", 12)) {
+ md = EVP_sha384();
+ tsigkey->alg = TSIG_HMACSHA384_NAME;
+ tsigkey->alglen = sizeof(TSIG_HMACSHA384_NAME) - 1;
+ } else if (!strncasecmp(alg, "hmac-sha512:", 12)) {
+ md = EVP_sha512();
+ tsigkey->alg = TSIG_HMACSHA512_NAME;
+ tsigkey->alglen = sizeof(TSIG_HMACSHA512_NAME) - 1;
+ } else {
+ perf_log_warning("invalid TSIG algorithm %.*s", (int)alglen, alg);
+ perf_opt_usage();
+ exit(1);
+ }
+
+ if (namelen > sizeof(tsigkey->name)) {
+ perf_log_fatal("unable to setup TSIG, name too long");
+ // fix clang scan-build / sonarcloud:
+ free(tsigkey);
+ return 0;
+ }
+ memcpy(tsigkey->name, name, namelen);
+ tsigkey->namelen = namelen;
+ for (namelen = 0; namelen < tsigkey->namelen; namelen++) {
+ tsigkey->name[namelen] = tolower(tsigkey->name[namelen]);
+ }
+
+ /* Secret */
+
+ secretlen = strlen(secret);
+ if (!secretlen) {
+ perf_log_warning("unable to setup TSIG, secret empty");
+ perf_opt_usage();
+ exit(1);
+ }
+
+ keylen = secretlen;
+ unsigned char* key = decode64(secret, &keylen);
+ if (!key) {
+ perf_log_fatal("unable to setup TSIG, invalid base64 secret");
+ }
+
+ /* Setup HMAC */
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ if (!(tsigkey->hmac = calloc(1, sizeof(*tsigkey->hmac)))) {
+ perf_log_fatal("unable to setup TSIG, OpenSSL HMAC context failed to be created");
+ }
+ HMAC_CTX_init(tsigkey->hmac);
+#else
+ if (!(tsigkey->hmac = HMAC_CTX_new())) {
+ perf_log_fatal("unable to setup TSIG, OpenSSL HMAC context failed to be created");
+ }
+#endif
+
+ if (!HMAC_Init_ex(tsigkey->hmac, key, keylen, md, 0)) {
+ perf_log_fatal("unable to setup TSIG, OpenSSL HMAC init failed");
+ }
+
+ free(key);
+
+ return tsigkey;
+}
+
+void perf_tsig_destroykey(perf_tsigkey_t** tsigkeyp)
+{
+ assert(tsigkeyp);
+ assert(*tsigkeyp);
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ HMAC_CTX_cleanup((*tsigkeyp)->hmac);
+ free((*tsigkeyp)->hmac);
+#else
+ HMAC_CTX_free((*tsigkeyp)->hmac);
+#endif
+
+ free(*tsigkeyp);
+ *tsigkeyp = 0;
+}
+
+/*
+ * Appends a TSIG record to the packet.
+ */
+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 (!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");
+ }
+
+ /* Digest the TSIG record */
+ perf_buffer_init(&tmp, tmpdata, sizeof tmpdata);
+ switch ((result = perf_dname_fromstring(tsigkey->name, tsigkey->namelen, &tmp))) {
+ case PERF_R_SUCCESS:
+ break;
+ case PERF_R_NOSPACE:
+ perf_log_warning("adding TSIG: out of space in digest record");
+ return result;
+ default:
+ perf_log_warning("adding TSIG: invalid owner name");
+ return result;
+ }
+ perf_buffer_putuint16(&tmp, 255); /* class ANY */
+ perf_buffer_putuint32(&tmp, 0); /* ttl */
+ switch ((result = perf_dname_fromstring(tsigkey->alg, tsigkey->alglen, &tmp))) {
+ case PERF_R_SUCCESS:
+ break;
+ case PERF_R_NOSPACE:
+ perf_log_warning("adding TSIG: out of space in digest record");
+ return result;
+ default:
+ perf_log_warning("adding TSIG: invalid algorithm name");
+ return result;
+ }
+ perf_buffer_putuint16(&tmp, 0); /* time high */
+ perf_buffer_putuint32(&tmp, now); /* time low */
+ perf_buffer_putuint16(&tmp, 300); /* fudge */
+ perf_buffer_putuint16(&tmp, 0); /* error */
+ perf_buffer_putuint16(&tmp, 0); /* other length */
+
+ 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");
+ }
+
+ /* Make sure everything will fit */
+ rdlen = tsigkey->alglen + 18 + mdlen;
+ totallen = tsigkey->namelen + 12 + rdlen;
+ if (totallen > perf_buffer_availablelength(packet)) {
+ perf_log_warning("adding TSIG: out of space");
+ return PERF_R_NOSPACE;
+ }
+
+ base = perf_buffer_base(packet);
+
+ /* Add the TSIG record. */
+ switch ((result = perf_dname_fromstring(tsigkey->name, tsigkey->namelen, packet))) {
+ case PERF_R_SUCCESS:
+ break;
+ case PERF_R_NOSPACE:
+ perf_log_warning("adding TSIG: out of space");
+ return result;
+ default:
+ perf_log_warning("adding TSIG: invalid owner name");
+ return result;
+ }
+ perf_buffer_putuint16(packet, 250); /* type TSIG */
+ perf_buffer_putuint16(packet, 255); /* class ANY */
+ perf_buffer_putuint32(packet, 0); /* ttl */
+ perf_buffer_putuint16(packet, rdlen); /* rdlen */
+ switch ((result = perf_dname_fromstring(tsigkey->alg, tsigkey->alglen, packet))) {
+ case PERF_R_SUCCESS:
+ break;
+ case PERF_R_NOSPACE:
+ perf_log_warning("adding TSIG: out of space");
+ return result;
+ default:
+ perf_log_warning("adding TSIG: invalid algorithm name");
+ return result;
+ }
+ perf_buffer_putuint16(packet, 0); /* time high */
+ perf_buffer_putuint32(packet, now); /* time low */
+ perf_buffer_putuint16(packet, 300); /* fudge */
+ perf_buffer_putuint16(packet, mdlen); /* digest len */
+ perf_buffer_putmem(packet, md, mdlen); /* digest */
+ perf_buffer_putmem(packet, base, 2); /* orig ID */
+ perf_buffer_putuint16(packet, 0); /* error */
+ perf_buffer_putuint16(packet, 0); /* other len */
+
+ base[11]++; /* increment additional record count */
+
+ return PERF_R_SUCCESS;
+}
diff --git a/src/tsig.h b/src/tsig.h
new file mode 100644
index 0000000..ca4cf85
--- /dev/null
+++ b/src/tsig.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "result.h"
+#include "buffer.h"
+
+#ifndef PERF_TSIG_H
+#define PERF_TSIG_H 1
+
+#include <openssl/hmac.h>
+
+typedef struct perf_tsigkey {
+ char name[256];
+ size_t namelen, alglen;
+ const char* alg;
+ HMAC_CTX* hmac;
+} perf_tsigkey_t;
+
+perf_tsigkey_t* perf_tsig_parsekey(const char* arg);
+
+void perf_tsig_destroykey(perf_tsigkey_t** tsigkeyp);
+
+perf_result_t perf_add_tsig(perf_buffer_t* packet, perf_tsigkey_t* tsigkey);
+
+#endif
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..245c984
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2019-2021 OARC, Inc.
+ * Copyright 2017-2018 Akamai Technologies
+ * Copyright 2006-2016 Nominum, Inc.
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "log.h"
+#include "strerror.h"
+
+#ifndef PERF_UTIL_H
+#define PERF_UTIL_H 1
+
+#include <pthread.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/time.h>
+
+#define MILLION ((uint64_t)1000000)
+
+#define PERF_THREAD(thread, start, arg) \
+ do { \
+ int __n = pthread_create((thread), NULL, (start), (arg)); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_create failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_JOIN(thread, valuep) \
+ do { \
+ int __n = pthread_join((thread), (valuep)); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_join failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_MUTEX_INIT(mutex) \
+ do { \
+ int __n = pthread_mutex_init((mutex), NULL); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_mutex_init failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_MUTEX_DESTROY(mutex) \
+ do { \
+ int __n = pthread_mutex_destroy((mutex)); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_mutex_destroy failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_LOCK(mutex) \
+ do { \
+ int __n = pthread_mutex_lock((mutex)); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_mutex_lock failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_UNLOCK(mutex) \
+ do { \
+ int __n = pthread_mutex_unlock((mutex)); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_mutex_unlock failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_COND_INIT(cond) \
+ do { \
+ int __n = pthread_cond_init((cond), NULL); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_cond_init failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_SIGNAL(cond) \
+ do { \
+ int __n = pthread_cond_signal((cond)); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_cond_signal failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_BROADCAST(cond) \
+ do { \
+ int __n = pthread_cond_broadcast((cond)); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_cond_broadcast failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_WAIT(cond, mutex) \
+ do { \
+ int __n = pthread_cond_wait((cond), (mutex)); \
+ if (__n != 0) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_cond_wait failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ } while (0)
+
+#define PERF_TIMEDWAIT(cond, mutex, when, timedout) \
+ do { \
+ int __n = pthread_cond_timedwait((cond), (mutex), (when)); \
+ bool* res = (timedout); \
+ if (__n != 0 && __n != ETIMEDOUT) { \
+ char __s[256]; \
+ perf_log_fatal("pthread_cond_timedwait failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); \
+ } \
+ if (res != NULL) { \
+ *res = (__n != 0); \
+ } \
+ } while (0)
+
+static __inline__ uint64_t perf_get_time(void)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return tv.tv_sec * MILLION + tv.tv_usec;
+}
+
+#define PERF_SAFE_DIV(n, d) ((d) == 0 ? 0 : (n) / (d))
+
+#endif