Adding upstream version 2.4.2+debian.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
0fae05cfb7
commit
153471ed4b
64 changed files with 9668 additions and 0 deletions
66
src/Makefile.am
Normal file
66
src/Makefile.am
Normal file
|
@ -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
|
116
src/buffer.h
Normal file
116
src/buffer.h
Normal file
|
@ -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
|
246
src/datafile.c
Normal file
246
src/datafile.c
Normal file
|
@ -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;
|
||||
}
|
52
src/datafile.h
Normal file
52
src/datafile.h
Normal file
|
@ -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
|
484
src/dns.c
Normal file
484
src/dns.c
Normal file
|
@ -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;
|
||||
}
|
43
src/dns.h
Normal file
43
src/dns.h
Normal file
|
@ -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
|
366
src/dnsperf.1.in
Normal file
366
src/dnsperf.1.in
Normal file
|
@ -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
|
1220
src/dnsperf.c
Normal file
1220
src/dnsperf.c
Normal file
File diff suppressed because it is too large
Load diff
158
src/edns.c
Normal file
158
src/edns.c
Normal file
|
@ -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;
|
||||
}
|
41
src/edns.h
Normal file
41
src/edns.h
Normal file
|
@ -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
|
46
src/gen-qtype.c.py
Executable file
46
src/gen-qtype.c.py
Executable file
|
@ -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 }
|
||||
};""")
|
91
src/list.h
Normal file
91
src/list.h
Normal file
|
@ -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
|
72
src/log.c
Normal file
72
src/log.c
Normal file
|
@ -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;
|
||||
}
|
28
src/log.h
Normal file
28
src/log.h
Normal file
|
@ -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
|
664
src/net.c
Normal file
664
src/net.c
Normal file
|
@ -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;
|
||||
}
|
84
src/net.h
Normal file
84
src/net.h
Normal file
|
@ -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
|
234
src/opt.c
Normal file
234
src/opt.c
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
39
src/opt.h
Normal file
39
src/opt.h
Normal file
|
@ -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
|
150
src/os.c
Normal file
150
src/os.c
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
44
src/os.h
Normal file
44
src/os.h
Normal file
|
@ -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
|
117
src/qtype.c
Normal file
117
src/qtype.c
Normal file
|
@ -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 }
|
||||
};
|
32
src/qtype.h
Normal file
32
src/qtype.h
Normal file
|
@ -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
|
112
src/resperf-report
Executable file
112
src/resperf-report
Executable file
|
@ -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
|
647
src/resperf.1.in
Normal file
647
src/resperf.1.in
Normal file
|
@ -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
|
816
src/resperf.c
Normal file
816
src/resperf.c
Normal file
|
@ -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;
|
||||
}
|
38
src/result.h
Normal file
38
src/result.h
Normal file
|
@ -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
|
37
src/strerror.c
Normal file
37
src/strerror.c
Normal file
|
@ -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
|
||||
}
|
27
src/strerror.h
Normal file
27
src/strerror.h
Normal file
|
@ -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
|
9
src/test/Makefile.am
Normal file
9
src/test/Makefile.am
Normal file
|
@ -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
|
2
src/test/datafile
Normal file
2
src/test/datafile
Normal file
|
@ -0,0 +1,2 @@
|
|||
google.com A
|
||||
google.com AAAA
|
560
src/test/datafile2
Normal file
560
src/test/datafile2
Normal file
|
@ -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
|
2
src/test/datafile3
Normal file
2
src/test/datafile3
Normal file
|
@ -0,0 +1,2 @@
|
|||
. A
|
||||
google.com. A
|
4
src/test/test1.sh
Executable file
4
src/test/test1.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh -xe
|
||||
|
||||
../dnsperf -h
|
||||
../resperf -h
|
108
src/test/test2.sh
Executable file
108
src/test/test2.sh
Executable file
|
@ -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"
|
46
src/test/test3.sh
Executable file
46
src/test/test3.sh
Executable file
|
@ -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
|
11
src/test/updatefile
Normal file
11
src/test/updatefile
Normal file
|
@ -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
|
326
src/tsig.c
Normal file
326
src/tsig.c
Normal file
|
@ -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;
|
||||
}
|
41
src/tsig.h
Normal file
41
src/tsig.h
Normal file
|
@ -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
|
146
src/util.h
Normal file
146
src/util.h
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue