Adding upstream version 2.5.0+debian.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
153471ed4b
commit
5d84f21420
24 changed files with 2037 additions and 1001 deletions
|
@ -9,6 +9,7 @@ extraction:
|
|||
- pkg-config
|
||||
- libssl-dev
|
||||
- libldns-dev
|
||||
- libck-dev
|
||||
configure:
|
||||
command:
|
||||
- ./autogen.sh
|
||||
|
|
56
CHANGES
56
CHANGES
|
@ -1,3 +1,59 @@
|
|||
2021-03-12 Jerry Lundström
|
||||
|
||||
Release 2.5.0
|
||||
|
||||
This release adds re-connection support for TCP and DoT protocol,
|
||||
new options to `resperf` and fixes a few bugs.
|
||||
|
||||
`dnsperf` and `resperf` will now try to re-connect when they lose a TCP
|
||||
or DoT connection, and with that comes a few new statistics metrics.
|
||||
For `dnsperf`, if a connection oriented protocol is used, it will now
|
||||
show the total number of re-connections made and the connection latency.
|
||||
For `resperf` it also shows the total number of re-connections made and
|
||||
the gnuplot data now contains the total number of connections made and
|
||||
the connection latency for each interval.
|
||||
Beside re-connection support, improvements have been made when it comes
|
||||
to tracking socket readiness while connections are established which
|
||||
should generate less warnings about "socket not ready".
|
||||
|
||||
New `resperf` options:
|
||||
- `-R`: Reopen the datafile if it runs out of data before the testing
|
||||
is completed. This allows for long running tests on very small and
|
||||
simple query datafile.
|
||||
- `-F <fall_behind>`: Sets the maximum number of queries that can fall
|
||||
behind being sent. `resperf` will stop when this many queries should
|
||||
have been sent and it can be relative easy to hit if `-m <max_qps>`
|
||||
is set too high.
|
||||
The default is 1000 and setting it to zero (0) disables the check.
|
||||
|
||||
Bugfixes:
|
||||
- Fixed port handling for host/network format when setting client side
|
||||
port with `-x`
|
||||
- Fix support for quoted characters, `\000` and `\.`, in domain names,
|
||||
this was lost when removing BIND's internal development libraries
|
||||
- Fix issue in `dnsperf`, it would loop forever if no connection could
|
||||
be established
|
||||
- Fix potential buffer overrun in `resperf` when using response id
|
||||
for `queries[]`
|
||||
- DoT: Fix bug when sending from buffer
|
||||
|
||||
Other changes:
|
||||
- Always use `IPV6_V6ONLY` socket option for IPv6
|
||||
- Add man-page on `-W` option added in v2.4.0
|
||||
- Reformat man-pages
|
||||
- `resperf`:
|
||||
- Try and process more request each run to hopefully not hit max
|
||||
outstanding so easy when high QPS
|
||||
- Add default value to `-C` so it shows in help
|
||||
|
||||
9308361 man-page format
|
||||
0e52fb4 man-page, opts, tuneups
|
||||
e36211d stats
|
||||
d9b9ba3 Response qid to index
|
||||
2b2c37e fixes, reconnection
|
||||
acd31e5 dname quote
|
||||
de8f049 net
|
||||
|
||||
2021-02-23 Jerry Lundström
|
||||
|
||||
Release 2.4.2
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# dnsperf
|
||||
|
||||
[![Build Status](https://travis-ci.com/DNS-OARC/dnsperf.svg?branch=develop)](https://travis-ci.com/DNS-OARC/dnsperf) [![Total alerts](https://img.shields.io/lgtm/alerts/g/DNS-OARC/dnsperf.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/DNS-OARC/dnsperf/alerts/) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=dns-oarc%3Adnsperf&metric=bugs)](https://sonarcloud.io/dashboard?id=dns-oarc%3Adnsperf) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=dns-oarc%3Adnsperf&metric=security_rating)](https://sonarcloud.io/dashboard?id=dns-oarc%3Adnsperf)
|
||||
[![Total alerts](https://img.shields.io/lgtm/alerts/g/DNS-OARC/dnsperf.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/DNS-OARC/dnsperf/alerts/) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=dns-oarc%3Adnsperf&metric=bugs)](https://sonarcloud.io/dashboard?id=dns-oarc%3Adnsperf) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=dns-oarc%3Adnsperf&metric=security_rating)](https://sonarcloud.io/dashboard?id=dns-oarc%3Adnsperf)
|
||||
|
||||
`dnsperf` and `resperf` are free tools developed by Nominum/Akamai (2006-2018)
|
||||
and DNS-OARC (since 2019) that make it simple to gather accurate latency and
|
||||
|
@ -44,21 +44,22 @@ those queries to DNS servers to measure performance.
|
|||
environment with autoconf, automake, libtool and pkgconfig.
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/) - for TSIG support
|
||||
- [Concurrency Kit](http://concurrencykit.org/) - for atomic operations
|
||||
- [LDNS](https://nlnetlabs.nl/projects/ldns/about/) - optional for dynamic update support
|
||||
|
||||
To install the dependencies under Debian/Ubuntu:
|
||||
```
|
||||
apt-get install -y libssl-dev libldns-dev
|
||||
apt-get install -y libssl-dev libldns-dev libck-dev
|
||||
```
|
||||
|
||||
To install the dependencies under CentOS (with EPEL enabled):
|
||||
```
|
||||
yum install -y openssl-devel ldns-devel
|
||||
yum install -y openssl-devel ldns-devel ck-devel
|
||||
```
|
||||
|
||||
To install the dependencies under FreeBSD 12+ using `pkg`:
|
||||
```
|
||||
pkg install -y openssl ldns
|
||||
pkg install -y openssl ldns concurrencykit
|
||||
```
|
||||
|
||||
To install the dependencies under OpenBSD 6+ using `pkg_add`:
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
AC_PREREQ(2.64)
|
||||
AC_INIT([dnsperf], [2.4.2], [admin@dns-oarc.net], [dnsperf], [https://github.com/DNS-OARC/dnsperf/issues])
|
||||
AC_INIT([dnsperf], [2.5.0], [admin@dns-oarc.net], [dnsperf], [https://github.com/DNS-OARC/dnsperf/issues])
|
||||
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
|
||||
AC_CONFIG_SRCDIR([src/dnsperf.c])
|
||||
AC_CONFIG_HEADER([src/config.h])
|
||||
|
@ -62,6 +62,13 @@ PKG_CHECK_MODULES([libssl], [libssl])
|
|||
PKG_CHECK_MODULES([libcrypto], [libcrypto])
|
||||
AC_CHECK_LIB([ssl], [TLS_method],
|
||||
[AC_DEFINE([HAVE_TLS_METHOD], [1], [Define to 1 if you have the 'TLS_method' function])])
|
||||
PKG_CHECK_MODULES([ck], [ck >= 0], [
|
||||
AS_VAR_APPEND([CFLAGS], [" $ck_CFLAGS"])
|
||||
AS_VAR_APPEND([LIBS], [" $ck_LIBS"])
|
||||
], [
|
||||
AC_CHECK_HEADERS([ck_ring.h ck_pr.h],, [AC_MSG_ERROR([libck headers not found])])
|
||||
AC_CHECK_LIB([ck], [ck_array_init],, [AC_MSG_ERROR([libck not found])])
|
||||
])
|
||||
|
||||
# Check for LDNS
|
||||
PKG_CHECK_MODULES([libldns], [libldns >= 1.7.0], [AC_DEFINE([HAVE_LDNS], [1], [Define to 1 if you have the LDNS library])], [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Name: dnsperf
|
||||
Version: 2.4.2
|
||||
Version: 2.5.0
|
||||
Release: 1%{?dist}
|
||||
Summary: DNS Performance Testing Tool
|
||||
Group: Productivity/Networking/DNS/Utilities
|
||||
|
@ -16,6 +16,7 @@ BuildRequires: libtool
|
|||
BuildRequires: openssl-devel
|
||||
BuildRequires: pkgconfig
|
||||
BuildRequires: ldns-devel
|
||||
BuildRequires: ck-devel
|
||||
|
||||
%description
|
||||
dnsperf and resperf are free tools developed by Nominum/Akamai (2006-2018)
|
||||
|
@ -93,6 +94,55 @@ rm -rf $RPM_BUILD_ROOT
|
|||
|
||||
|
||||
%changelog
|
||||
* Fri Mar 12 2021 Jerry Lundström <lundstrom.jerry@gmail.com> 2.5.0-1
|
||||
- Release 2.5.0
|
||||
* This release adds re-connection support for TCP and DoT protocol,
|
||||
new options to `resperf` and fixes a few bugs.
|
||||
* `dnsperf` and `resperf` will now try to re-connect when they lose a TCP
|
||||
or DoT connection, and with that comes a few new statistics metrics.
|
||||
For `dnsperf`, if a connection oriented protocol is used, it will now
|
||||
show the total number of re-connections made and the connection latency.
|
||||
For `resperf` it also shows the total number of re-connections made and
|
||||
the gnuplot data now contains the total number of connections made and
|
||||
the connection latency for each interval.
|
||||
Beside re-connection support, improvements have been made when it comes
|
||||
to tracking socket readiness while connections are established which
|
||||
should generate less warnings about "socket not ready".
|
||||
* New `resperf` options:
|
||||
- `-R`: Reopen the datafile if it runs out of data before the testing
|
||||
is completed. This allows for long running tests on very small and
|
||||
simple query datafile.
|
||||
- `-F <fall_behind>`: Sets the maximum number of queries that can fall
|
||||
behind being sent. `resperf` will stop when this many queries should
|
||||
have been sent and it can be relative easy to hit if `-m <max_qps>`
|
||||
is set too high.
|
||||
The default is 1000 and setting it to zero (0) disables the check.
|
||||
* Bugfixes:
|
||||
- Fixed port handling for host/network format when setting client side
|
||||
port with `-x`
|
||||
- Fix support for quoted characters, `\000` and `\.`, in domain names,
|
||||
this was lost when removing BIND's internal development libraries
|
||||
- Fix issue in `dnsperf`, it would loop forever if no connection could
|
||||
be established
|
||||
- Fix potential buffer overrun in `resperf` when using response id
|
||||
for `queries[]`
|
||||
- DoT: Fix bug when sending from buffer
|
||||
* Other changes:
|
||||
- Always use `IPV6_V6ONLY` socket option for IPv6
|
||||
- Add man-page on `-W` option added in v2.4.0
|
||||
- Reformat man-pages
|
||||
- `resperf`:
|
||||
- Try and process more request each run to hopefully not hit max
|
||||
outstanding so easy when high QPS
|
||||
- Add default value to `-C` so it shows in help
|
||||
* Commits:
|
||||
9308361 man-page format
|
||||
0e52fb4 man-page, opts, tuneups
|
||||
e36211d stats
|
||||
d9b9ba3 Response qid to index
|
||||
2b2c37e fixes, reconnection
|
||||
acd31e5 dname quote
|
||||
de8f049 net
|
||||
* Tue Feb 23 2021 Jerry Lundström <lundstrom.jerry@gmail.com> 2.4.2-1
|
||||
- Release 2.4.2
|
||||
* This release fixes a few issues with reading of the datafile which
|
||||
|
|
|
@ -30,7 +30,7 @@ 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
|
||||
edns.c tsig.c net_udp.c net_tcp.c net_dot.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
|
||||
|
||||
|
|
57
src/dns.c
57
src/dns.c
|
@ -52,18 +52,39 @@ const char* perf_dns_rcode_strings[] = {
|
|||
|
||||
perf_result_t perf_dname_fromstring(const char* str, size_t len, perf_buffer_t* target)
|
||||
{
|
||||
size_t label_len;
|
||||
size_t label_len, at;
|
||||
const char* orig_str = str;
|
||||
bool is_quoted;
|
||||
|
||||
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) == '.') {
|
||||
is_quoted = false;
|
||||
for (label_len = 0, at = 0; at < len;) {
|
||||
if (*(str + at) == '\\') {
|
||||
is_quoted = true;
|
||||
at++;
|
||||
if (at >= len)
|
||||
return PERF_R_FAILURE;
|
||||
if (*(str + at) >= '0' && *(str + at) <= '9') {
|
||||
at++;
|
||||
if (at >= len)
|
||||
return PERF_R_FAILURE;
|
||||
if (*(str + at) < '0' || *(str + at) > '9')
|
||||
return PERF_R_FAILURE;
|
||||
at++;
|
||||
if (at >= len)
|
||||
return PERF_R_FAILURE;
|
||||
if (*(str + at) < '0' || *(str + at) > '9')
|
||||
return PERF_R_FAILURE;
|
||||
}
|
||||
} else if (*(str + at) == '.') {
|
||||
break;
|
||||
}
|
||||
label_len++;
|
||||
at++;
|
||||
}
|
||||
if (!label_len) {
|
||||
// Just a dot
|
||||
|
@ -81,9 +102,33 @@ perf_result_t perf_dname_fromstring(const char* str, size_t len, perf_buffer_t*
|
|||
return PERF_R_FAILURE;
|
||||
}
|
||||
perf_buffer_putuint8(target, label_len);
|
||||
perf_buffer_putmem(target, str, label_len);
|
||||
str += label_len;
|
||||
len -= label_len;
|
||||
if (is_quoted) {
|
||||
for (at = 0; at < len; at++) {
|
||||
if (*(str + at) == '\\') {
|
||||
at++;
|
||||
if (at >= len)
|
||||
return PERF_R_FAILURE;
|
||||
if (*(str + at) >= '0' && *(str + at) <= '9') {
|
||||
char b[4];
|
||||
long v;
|
||||
memcpy(b, str, 3);
|
||||
b[3] = 0;
|
||||
v = strtol(b, 0, 7);
|
||||
if (v < 0 || v > 255)
|
||||
return PERF_R_FAILURE;
|
||||
perf_buffer_putuint8(target, (uint8_t)v);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
perf_buffer_putmem(target, str + at, 1);
|
||||
}
|
||||
str += at;
|
||||
len -= at;
|
||||
} else {
|
||||
perf_buffer_putmem(target, str, label_len);
|
||||
str += label_len;
|
||||
len -= label_len;
|
||||
}
|
||||
if (len < 2) {
|
||||
// Last label/dot
|
||||
perf_buffer_putuint8(target, 0);
|
||||
|
|
272
src/dnsperf.1.in
272
src/dnsperf.1.in
|
@ -20,67 +20,72 @@ 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]
|
||||
\fBdnsperf\fR\ [\fB\-a\ \fIlocal_addr\fR]
|
||||
[\fB\-b\ \fIbufsize\fR]
|
||||
[\fB\-c\ \fIclients\fR]
|
||||
[\fB\-d\ \fIdatafile\fR]
|
||||
[\fB\-D\fR]
|
||||
[\fB\-e\fR]
|
||||
[\fB\-E\ \fIcode:secret\fB\fR]
|
||||
[\fB\-f\ \fIfamily\fB\fR]
|
||||
[\fB\-E\ \fIcode:secret\fR]
|
||||
[\fB\-f\ \fIfamily\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\-l\ \fIlimit\fR]
|
||||
[\fB\-m\ \fImode\fR]
|
||||
[\fB\-n\ \fIruns_through_file\fR]
|
||||
[\fB\-p\ \fIport\fR]
|
||||
[\fB\-q\ \fInum_queries\fR]
|
||||
[\fB\-Q\ \fImax_qps\fR]
|
||||
[\fB\-s\ \fIserver_addr\fR]
|
||||
[\fB\-S\ \fIstats_interval\fR]
|
||||
[\fB\-t\ \fItimeout\fR]
|
||||
[\fB\-T\ \fIthreads\fR]
|
||||
[\fB\-u\fR]
|
||||
[\fB\-v\fR]
|
||||
[\fB\-x\ \fIlocal_port\fB\fR]
|
||||
[\fB\-y\ \fI[alg:]name:secret\fB\fR]
|
||||
[\fB\-W\fR]
|
||||
[\fB\-x\ \fIlocal_port\fR]
|
||||
[\fB\-y\ \fI[alg:]name:secret\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.
|
||||
\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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
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
|
||||
|
||||
|
@ -98,7 +103,8 @@ 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:
|
||||
dynamic update messages.
|
||||
The first line in a block contains the zone name:
|
||||
.RS
|
||||
.hy 0
|
||||
|
||||
|
@ -108,12 +114,14 @@ example.com
|
|||
.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.
|
||||
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
|
||||
|
||||
|
@ -128,10 +136,10 @@ prohibit x A
|
|||
.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.
|
||||
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
|
||||
|
||||
|
@ -155,47 +163,49 @@ send
|
|||
.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.
|
||||
(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
|
||||
\fB-a \fIlocal_addr\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the local address from which to send requests. The default is the
|
||||
wildcard address.
|
||||
Specifies the local address from which to send requests.
|
||||
The default is the wildcard address.
|
||||
.RE
|
||||
|
||||
\fB-b \fIbufsize\fB\fR
|
||||
\fB-b \fIbufsize\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.
|
||||
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
|
||||
\fB-c \fIclients\fR
|
||||
.br
|
||||
.RS
|
||||
Act as multiple clients. Requests are sent from multiple sockets. The
|
||||
default is to act as 1 client.
|
||||
Act as multiple clients.
|
||||
Requests are sent from multiple sockets.
|
||||
The default is to act as 1 client.
|
||||
.RE
|
||||
|
||||
\fB-d \fIdatafile\fB\fR
|
||||
\fB-d \fIdatafile\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the input data file. If not specified, \fBdnsperf\fR will read
|
||||
from standard input.
|
||||
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.
|
||||
Sets the DO (DNSSEC OK) bit [RFC3225] in all packets sent.
|
||||
This also enables EDNS0, which is required for DNSSEC.
|
||||
.RE
|
||||
|
||||
\fB-e\fR
|
||||
|
@ -204,21 +214,21 @@ EDNS0, which is required for DNSSEC.
|
|||
Enables EDNS0 [RFC2671], by adding an OPT record to all packets sent.
|
||||
.RE
|
||||
|
||||
\fB-E \fIcode:value\fB\fR
|
||||
\fB-E \fIcode:value\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.
|
||||
numeric option code and value expressed as a a hex-encoded string.
|
||||
This also enables EDNS0.
|
||||
.RE
|
||||
|
||||
\fB-f \fIfamily\fB\fR
|
||||
\fB-f \fIfamily\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.
|
||||
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
|
||||
|
@ -227,111 +237,129 @@ for the server it is sending packets to.
|
|||
Print a usage statement and exit.
|
||||
.RE
|
||||
|
||||
\fB-l \fIlimit\fB\fR
|
||||
\fB-l \fIlimit\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.
|
||||
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
|
||||
\fB-n \fIruns_through_file\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.
|
||||
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
|
||||
\fB-p \fIport\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.
|
||||
Sets the port on which the DNS packets are sent.
|
||||
If not specified, the standard DNS port (udp/tcp 53, DoT 853) is used.
|
||||
.RE
|
||||
|
||||
\fB-q \fInum_queries\fB\fR
|
||||
\fB-q \fInum_queries\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.
|
||||
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
|
||||
\fB-Q \fImax_qps\fR
|
||||
.br
|
||||
.RS
|
||||
Limits the number of requests per second. There is no default limit.
|
||||
Limits the number of requests per second.
|
||||
There is no default limit.
|
||||
.RE
|
||||
|
||||
\fB-m \fImode\fB\fR
|
||||
\fB-m \fImode\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the transport mode to use, "udp", "tcp" or "dot"/"tls".
|
||||
Specifies the transport mode to use, "udp", "tcp" or "dot".
|
||||
Default is "udp".
|
||||
.RE
|
||||
|
||||
\fB-s \fIserver_addr\fB\fR
|
||||
\fB-s \fIserver_addr\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
|
||||
\fB-S \fIstats_interval\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
|
||||
\fB-t \fItimeout\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.
|
||||
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
|
||||
\fB-T \fIthreads\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.
|
||||
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
|
||||
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.
|
||||
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
|
||||
\fB-W\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.
|
||||
Log warnings and errors to standard output instead of standard error making
|
||||
it easier for script, test and automation to capture all output.
|
||||
.RE
|
||||
|
||||
\fB-y \fI[alg:]name:secret\fB\fR
|
||||
\fB-x \fIlocal_port\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\fR
|
||||
.br
|
||||
.RS
|
||||
Add a TSIG record [RFC2845] to all packets sent, using the specified TSIG
|
||||
|
|
131
src/dnsperf.c
131
src/dnsperf.c
|
@ -51,8 +51,8 @@
|
|||
|
||||
#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_SERVER_DOT_PORT 853
|
||||
#define DEFAULT_SERVER_PORTS "udp/tcp 53 or DoT 853"
|
||||
#define DEFAULT_LOCAL_PORT 0
|
||||
#define DEFAULT_MAX_OUTSTANDING 100
|
||||
#define DEFAULT_TIMEOUT 5
|
||||
|
@ -111,6 +111,14 @@ typedef struct {
|
|||
uint64_t latency_sum_squares;
|
||||
uint64_t latency_min;
|
||||
uint64_t latency_max;
|
||||
|
||||
uint64_t num_conn_reconnect;
|
||||
uint64_t num_conn_completed;
|
||||
|
||||
uint64_t conn_latency_sum;
|
||||
uint64_t conn_latency_sum_squares;
|
||||
uint64_t conn_latency_min;
|
||||
uint64_t conn_latency_max;
|
||||
} stats_t;
|
||||
|
||||
typedef perf_list(struct query_info) query_list;
|
||||
|
@ -140,9 +148,9 @@ typedef struct {
|
|||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond;
|
||||
|
||||
unsigned int nsocks;
|
||||
int current_sock;
|
||||
struct perf_net_socket* socks;
|
||||
unsigned int nsocks;
|
||||
int current_sock;
|
||||
struct perf_net_socket** socks;
|
||||
|
||||
bool done_sending;
|
||||
uint64_t done_send_time;
|
||||
|
@ -194,8 +202,13 @@ print_initial_status(const config_t* config)
|
|||
printf("\n");
|
||||
|
||||
perf_sockaddr_format(&config->server_addr, buf, sizeof(buf));
|
||||
printf("[Status] Sending %s (to %s)\n",
|
||||
config->updates ? "updates" : "queries", buf);
|
||||
if (perf_sockaddr_isinet6(&config->server_addr)) {
|
||||
printf("[Status] Sending %s (to [%s]:%d)\n",
|
||||
config->updates ? "updates" : "queries", buf, perf_sockaddr_port(&config->server_addr));
|
||||
} else {
|
||||
printf("[Status] Sending %s (to %s:%d)\n",
|
||||
config->updates ? "updates" : "queries", buf, perf_sockaddr_port(&config->server_addr));
|
||||
}
|
||||
|
||||
now = time(NULL);
|
||||
printf("[Status] Started at: %s", ctime_r(&now, ct));
|
||||
|
@ -310,6 +323,27 @@ print_statistics(const config_t* config, const times_t* times, stats_t* stats)
|
|||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
if (!stats->num_conn_completed) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Connection Statistics:\n\n");
|
||||
printf(" Reconnections: %" PRIu64 "\n\n", stats->num_conn_reconnect);
|
||||
latency_avg = PERF_SAFE_DIV(stats->conn_latency_sum, stats->num_conn_completed);
|
||||
printf(" Average Latency (s): %u.%06u (min %u.%06u, max %u.%06u)\n",
|
||||
(unsigned int)(latency_avg / MILLION),
|
||||
(unsigned int)(latency_avg % MILLION),
|
||||
(unsigned int)(stats->conn_latency_min / MILLION),
|
||||
(unsigned int)(stats->conn_latency_min % MILLION),
|
||||
(unsigned int)(stats->conn_latency_max / MILLION),
|
||||
(unsigned int)(stats->conn_latency_max % MILLION));
|
||||
if (stats->num_conn_completed > 1) {
|
||||
printf(" Latency StdDev (s): %f\n",
|
||||
stddev(stats->conn_latency_sum_squares, stats->conn_latency_sum, stats->num_conn_completed) / MILLION);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -339,6 +373,16 @@ sum_stats(const config_t* config, stats_t* total)
|
|||
total->latency_min = stats->latency_min;
|
||||
if (stats->latency_max > total->latency_max)
|
||||
total->latency_max = stats->latency_max;
|
||||
|
||||
total->num_conn_completed += stats->num_conn_completed;
|
||||
total->num_conn_reconnect += stats->num_conn_reconnect;
|
||||
|
||||
total->conn_latency_sum += stats->conn_latency_sum;
|
||||
total->conn_latency_sum_squares += stats->conn_latency_sum_squares;
|
||||
if (stats->conn_latency_min < total->conn_latency_min || i == 0)
|
||||
total->conn_latency_min = stats->conn_latency_min;
|
||||
if (stats->conn_latency_max > total->conn_latency_max)
|
||||
total->conn_latency_max = stats->conn_latency_max;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,7 +422,7 @@ setup(int argc, char** argv, config_t* config)
|
|||
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('m', perf_opt_string, "mode", "set transport mode: udp, tcp or dot", "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",
|
||||
|
@ -449,7 +493,7 @@ setup(int argc, char** argv, config_t* config)
|
|||
config->mode = perf_net_parsemode(mode);
|
||||
|
||||
if (!server_port) {
|
||||
server_port = config->mode == sock_tls ? DEFAULT_SERVER_TLS_PORT : DEFAULT_SERVER_PORT;
|
||||
server_port = config->mode == sock_dot ? DEFAULT_SERVER_DOT_PORT : DEFAULT_SERVER_PORT;
|
||||
}
|
||||
|
||||
if (family != NULL)
|
||||
|
@ -572,6 +616,7 @@ do_send(void* arg)
|
|||
unsigned int length;
|
||||
int n, i, any_inprogress = 0;
|
||||
perf_result_t result;
|
||||
bool all_fail;
|
||||
|
||||
tinfo = (threadinfo_t*)arg;
|
||||
config = tinfo->config;
|
||||
|
@ -618,15 +663,17 @@ do_send(void* arg)
|
|||
query_move(tinfo, q, prepend_outstanding);
|
||||
q->timestamp = UINT64_MAX;
|
||||
|
||||
i = tinfo->nsocks * 2;
|
||||
i = tinfo->nsocks * 2;
|
||||
all_fail = true;
|
||||
while (i--) {
|
||||
q->sock = &tinfo->socks[tinfo->current_sock++ % tinfo->nsocks];
|
||||
q->sock = tinfo->socks[tinfo->current_sock++ % tinfo->nsocks];
|
||||
switch (perf_net_sockready(q->sock, threadpipe[0], TIMEOUT_CHECK_TIME)) {
|
||||
case 0:
|
||||
if (config->verbose) {
|
||||
perf_log_warning("socket %p not ready", q->sock);
|
||||
}
|
||||
q->sock = 0;
|
||||
q->sock = 0;
|
||||
all_fail = false;
|
||||
continue;
|
||||
case -1:
|
||||
if (errno == EINPROGRESS) {
|
||||
|
@ -637,12 +684,19 @@ do_send(void* arg)
|
|||
if (config->verbose) {
|
||||
perf_log_warning("socket %p readiness check timed out", q->sock);
|
||||
}
|
||||
q->sock = 0;
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
all_fail = false;
|
||||
break;
|
||||
};
|
||||
|
||||
if (all_fail) {
|
||||
perf_log_fatal("all sockets reported failure, can not continue");
|
||||
}
|
||||
|
||||
if (!q->sock) {
|
||||
query_move(tinfo, q, prepend_unused);
|
||||
PERF_UNLOCK(&tinfo->lock);
|
||||
|
@ -686,7 +740,7 @@ do_send(void* arg)
|
|||
}
|
||||
q->timestamp = now;
|
||||
|
||||
n = perf_net_sendto(q->sock, base, length, 0, &config->server_addr.sa.sa,
|
||||
n = perf_net_sendto(q->sock, qid, base, length, 0, &config->server_addr.sa.sa,
|
||||
config->server_addr.length);
|
||||
if (n < 0) {
|
||||
if (errno == EINPROGRESS) {
|
||||
|
@ -719,7 +773,7 @@ do_send(void* arg)
|
|||
while (any_inprogress) {
|
||||
any_inprogress = 0;
|
||||
for (i = 0; i < tinfo->nsocks; i++) {
|
||||
if (perf_net_sockready(&tinfo->socks[i], threadpipe[0], TIMEOUT_CHECK_TIME) == -1 && errno == EINPROGRESS) {
|
||||
if (perf_net_sockready(tinfo->socks[i], threadpipe[0], TIMEOUT_CHECK_TIME) == -1 && errno == EINPROGRESS) {
|
||||
any_inprogress = 1;
|
||||
}
|
||||
}
|
||||
|
@ -788,7 +842,7 @@ recv_one(threadinfo_t* tinfo, int which_sock,
|
|||
|
||||
packet_header = (uint16_t*)packet_buffer;
|
||||
|
||||
n = perf_net_recv(&tinfo->socks[which_sock], packet_buffer, packet_size, 0);
|
||||
n = perf_net_recv(tinfo->socks[which_sock], packet_buffer, packet_size, 0);
|
||||
now = perf_get_time();
|
||||
if (n < 0) {
|
||||
*saved_errnop = errno;
|
||||
|
@ -799,7 +853,7 @@ recv_one(threadinfo_t* tinfo, int which_sock,
|
|||
*saved_errnop = EAGAIN;
|
||||
return false;
|
||||
}
|
||||
recvd->sock = &tinfo->socks[which_sock];
|
||||
recvd->sock = tinfo->socks[which_sock];
|
||||
recvd->qid = ntohs(packet_header[0]);
|
||||
recvd->rcode = ntohs(packet_header[1]) & 0xF;
|
||||
recvd->size = n;
|
||||
|
@ -1045,6 +1099,36 @@ per_thread(uint32_t total, uint32_t nthreads, unsigned int offset)
|
|||
return value;
|
||||
}
|
||||
|
||||
static void perf__net_sent(struct perf_net_socket* sock, uint16_t qid)
|
||||
{
|
||||
threadinfo_t* tinfo = (threadinfo_t*)sock->data;
|
||||
query_info* q;
|
||||
|
||||
q = &tinfo->queries[qid];
|
||||
if (q->timestamp != UINT64_MAX) {
|
||||
q->timestamp = perf_get_time();
|
||||
}
|
||||
}
|
||||
|
||||
static void perf__net_event(struct perf_net_socket* sock, perf_socket_event_t event, uint64_t elapsed_time)
|
||||
{
|
||||
stats_t* stats = &((threadinfo_t*)sock->data)->stats;
|
||||
|
||||
switch (event) {
|
||||
case perf_socket_event_reconnect:
|
||||
stats->num_conn_reconnect++;
|
||||
case perf_socket_event_connect:
|
||||
stats->num_conn_completed++;
|
||||
|
||||
stats->conn_latency_sum += elapsed_time;
|
||||
stats->conn_latency_sum_squares += (elapsed_time * elapsed_time);
|
||||
if (elapsed_time < stats->conn_latency_min || stats->num_conn_completed == 1)
|
||||
stats->conn_latency_min = elapsed_time;
|
||||
if (elapsed_time > stats->conn_latency_max)
|
||||
stats->conn_latency_max = elapsed_time;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
threadinfo_init(threadinfo_t* tinfo, const config_t* config,
|
||||
const times_t* times)
|
||||
|
@ -1091,11 +1175,18 @@ threadinfo_init(threadinfo_t* tinfo, const config_t* config,
|
|||
socket_offset = 0;
|
||||
for (i = 0; i < offset; i++)
|
||||
socket_offset += threads[i].nsocks;
|
||||
for (i = 0; i < tinfo->nsocks; i++)
|
||||
for (i = 0; i < tinfo->nsocks; i++) {
|
||||
tinfo->socks[i] = perf_net_opensocket(config->mode, &config->server_addr,
|
||||
&config->local_addr,
|
||||
socket_offset++,
|
||||
config->bufsize);
|
||||
if (!tinfo->socks[i]) {
|
||||
perf_log_fatal("perf_net_opensocket(): no socket returned, out of memory?");
|
||||
}
|
||||
tinfo->socks[i]->data = tinfo;
|
||||
tinfo->socks[i]->sent = perf__net_sent;
|
||||
tinfo->socks[i]->event = perf__net_event;
|
||||
}
|
||||
tinfo->current_sock = 0;
|
||||
|
||||
PERF_THREAD(&tinfo->receiver, do_recv, tinfo);
|
||||
|
@ -1118,7 +1209,7 @@ threadinfo_cleanup(threadinfo_t* tinfo, times_t* times)
|
|||
if (interrupted)
|
||||
cancel_queries(tinfo);
|
||||
for (i = 0; i < tinfo->nsocks; i++)
|
||||
perf_net_close(&tinfo->socks[i]);
|
||||
perf_net_close(tinfo->socks[i]);
|
||||
if (tinfo->last_recv > times->end_time)
|
||||
times->end_time = tinfo->last_recv;
|
||||
}
|
||||
|
@ -1152,8 +1243,8 @@ int main(int argc, char** argv)
|
|||
perf_os_blocksignal(SIGINT, true);
|
||||
switch (config.mode) {
|
||||
case sock_tcp:
|
||||
case sock_tls:
|
||||
// block SIGPIPE for TCP/TLS mode, if connection is closed it will generate a signal
|
||||
case sock_dot:
|
||||
// block SIGPIPE for TCP/DOT mode, if connection is closed it will generate a signal
|
||||
perf_os_blocksignal(SIGPIPE, true);
|
||||
break;
|
||||
default:
|
||||
|
|
500
src/net.c
500
src/net.c
|
@ -23,25 +23,28 @@
|
|||
|
||||
#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)
|
||||
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_dot;
|
||||
}
|
||||
|
||||
static SSL_CTX* ssl_ctx = 0;
|
||||
perf_log_warning("invalid socket mode");
|
||||
perf_opt_usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int perf_net_parsefamily(const char* family)
|
||||
{
|
||||
|
@ -49,10 +52,8 @@ int perf_net_parsefamily(const char* family)
|
|||
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();
|
||||
|
@ -82,9 +83,9 @@ 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;
|
||||
return ntohs(sockaddr->sa.sin.sin_port);
|
||||
case AF_INET6:
|
||||
return sockaddr->sa.sin6.sin6_port;
|
||||
return ntohs(sockaddr->sa.sin6.sin6_port);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -95,10 +96,10 @@ 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;
|
||||
sockaddr->sa.sin.sin_port = htons(port);
|
||||
break;
|
||||
case AF_INET6:
|
||||
sockaddr->sa.sin6.sin6_port = port;
|
||||
sockaddr->sa.sin6.sin6_port = htons(port);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -188,76 +189,15 @@ void perf_net_parselocal(int family, const char* name, unsigned int port,
|
|||
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)
|
||||
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, size_t bufsize)
|
||||
{
|
||||
int family;
|
||||
perf_sockaddr_t tmp;
|
||||
int port;
|
||||
int ret;
|
||||
int flags;
|
||||
struct perf_net_socket sock = { .mode = mode, .is_ready = 1 };
|
||||
int port;
|
||||
perf_sockaddr_t tmp;
|
||||
|
||||
family = server->sa.sa.sa_family;
|
||||
|
||||
if (local->sa.sa.sa_family != family) {
|
||||
if (server->sa.sa.sa_family != local->sa.sa.sa_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) {
|
||||
|
@ -267,398 +207,16 @@ struct perf_net_socket perf_net_opensocket(enum perf_net_mode mode, const perf_s
|
|||
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;
|
||||
}
|
||||
switch (mode) {
|
||||
case sock_udp:
|
||||
return perf_net_udp_opensocket(server, &tmp, bufsize);
|
||||
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;
|
||||
return perf_net_tcp_opensocket(server, &tmp, bufsize);
|
||||
case sock_dot:
|
||||
return perf_net_dot_opensocket(server, &tmp, bufsize);
|
||||
default:
|
||||
break;
|
||||
perf_log_fatal("perf_net_opensocket(): invalid mode");
|
||||
}
|
||||
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
|
132
src/net.h
132
src/net.h
|
@ -25,6 +25,11 @@
|
|||
#include <openssl/ssl.h>
|
||||
#include <pthread.h>
|
||||
#include <netinet/in.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define TCP_RECV_BUF_SIZE (16 * 1024)
|
||||
#define TCP_SEND_BUF_SIZE (4 * 1024)
|
||||
|
||||
struct perf_sockaddr {
|
||||
union {
|
||||
|
@ -42,43 +47,120 @@ enum perf_net_mode {
|
|||
sock_pipe,
|
||||
sock_udp,
|
||||
sock_tcp,
|
||||
sock_tls
|
||||
sock_dot
|
||||
};
|
||||
|
||||
struct perf_net_socket;
|
||||
|
||||
typedef ssize_t (*perf_net_recv_t)(struct perf_net_socket* sock, void* buf, size_t len, int flags);
|
||||
typedef ssize_t (*perf_net_sendto_t)(struct perf_net_socket* sock, uint16_t qid, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
|
||||
typedef int (*perf_net_close_t)(struct perf_net_socket* sock);
|
||||
typedef int (*perf_net_sockeq_t)(struct perf_net_socket* sock, struct perf_net_socket* other);
|
||||
|
||||
/* sockready return:
|
||||
* -1: An error occurred, see errno
|
||||
* - EINPROGRESS: socket is still sending
|
||||
* 0: Socket is not ready, may still be connecting or negotiating
|
||||
* 1: Socket is ready and can be used for sending to
|
||||
*/
|
||||
typedef int (*perf_net_sockready_t)(struct perf_net_socket* sock, int pipe_fd, int64_t timeout);
|
||||
|
||||
/* Indicates if there are more data to be read in buffers of the transport */
|
||||
typedef bool (*perf_net_have_more_t)(struct perf_net_socket* sock);
|
||||
|
||||
/* Callback for when a query has been sent if it was delayed due to partily sent or reconnection */
|
||||
typedef void (*perf_net_sent_cb_t)(struct perf_net_socket* sock, uint16_t qid);
|
||||
|
||||
typedef enum perf_socket_event {
|
||||
perf_socket_event_connect,
|
||||
perf_socket_event_reconnect
|
||||
} perf_socket_event_t;
|
||||
/* Callback for socket events related to connection oriented protocols, for statistics */
|
||||
typedef void (*perf_net_event_cb_t)(struct perf_net_socket* sock, perf_socket_event_t event, uint64_t elapsed_time);
|
||||
|
||||
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* data; /* user data */
|
||||
|
||||
enum perf_net_mode mode;
|
||||
perf_net_recv_t recv;
|
||||
perf_net_sendto_t sendto;
|
||||
perf_net_close_t close;
|
||||
perf_net_sockeq_t sockeq;
|
||||
perf_net_sockready_t sockready;
|
||||
perf_net_have_more_t have_more;
|
||||
|
||||
/*
|
||||
* Not set by protocol, set by caller.
|
||||
* May be 0 if caller don't care.
|
||||
* MUST NOT be called from sendto(), only called if query is delayed in some way.
|
||||
*/
|
||||
perf_net_sent_cb_t sent;
|
||||
|
||||
/* Used if caller want info on connection oriented events */
|
||||
perf_net_event_cb_t event;
|
||||
|
||||
/*
|
||||
* The system file descriptor that is used for transport, this is used
|
||||
* in os functions to poll/wait for read/write.
|
||||
*/
|
||||
int fd;
|
||||
};
|
||||
|
||||
static inline ssize_t perf_net_recv(struct perf_net_socket* sock, void* buf, size_t len, int flags)
|
||||
{
|
||||
return sock->recv(sock, buf, len, flags);
|
||||
}
|
||||
|
||||
static inline ssize_t perf_net_sendto(struct perf_net_socket* sock, uint16_t qid, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen)
|
||||
{
|
||||
return sock->sendto(sock, qid, buf, len, flags, dest_addr, addrlen);
|
||||
}
|
||||
|
||||
static inline int perf_net_close(struct perf_net_socket* sock)
|
||||
{
|
||||
return sock->close(sock);
|
||||
}
|
||||
|
||||
static inline int perf_net_sockeq(struct perf_net_socket* sock_a, struct perf_net_socket* sock_b)
|
||||
{
|
||||
assert(sock_a);
|
||||
assert(sock_b);
|
||||
assert(sock_a->mode == sock_b->mode);
|
||||
return sock_a->sockeq(sock_a, sock_b);
|
||||
}
|
||||
|
||||
static inline int perf_net_sockready(struct perf_net_socket* sock, int pipe_fd, int64_t timeout)
|
||||
{
|
||||
return sock->sockready(sock, pipe_fd, timeout);
|
||||
}
|
||||
|
||||
static inline int perf_net_have_more(struct perf_net_socket* sock)
|
||||
{
|
||||
return sock->have_more ? sock->have_more(sock) : false;
|
||||
}
|
||||
|
||||
enum perf_net_mode perf_net_parsemode(const char* mode);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
static inline int perf_sockaddr_isinet6(const perf_sockaddr_t* sockaddr)
|
||||
{
|
||||
return sockaddr->sa.sa.sa_family == AF_INET6;
|
||||
}
|
||||
|
||||
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);
|
||||
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, size_t bufsize);
|
||||
|
||||
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);
|
||||
struct perf_net_socket* perf_net_udp_opensocket(const perf_sockaddr_t*, const perf_sockaddr_t*, size_t);
|
||||
struct perf_net_socket* perf_net_tcp_opensocket(const perf_sockaddr_t*, const perf_sockaddr_t*, size_t);
|
||||
struct perf_net_socket* perf_net_dot_opensocket(const perf_sockaddr_t*, const perf_sockaddr_t*, size_t);
|
||||
|
||||
#endif
|
||||
|
|
468
src/net_dot.c
Normal file
468
src/net_dot.c
Normal file
|
@ -0,0 +1,468 @@
|
|||
/*
|
||||
* 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 "strerror.h"
|
||||
#include "util.h"
|
||||
#include "os.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <openssl/err.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <ck_pr.h>
|
||||
|
||||
static SSL_CTX* ssl_ctx = 0;
|
||||
|
||||
#define self ((struct perf__dot_socket*)sock)
|
||||
|
||||
struct perf__dot_socket {
|
||||
struct perf_net_socket base;
|
||||
|
||||
pthread_mutex_t lock;
|
||||
SSL* ssl;
|
||||
|
||||
char recvbuf[TCP_RECV_BUF_SIZE], sendbuf[TCP_SEND_BUF_SIZE];
|
||||
size_t at, sending;
|
||||
bool is_ready, is_conn_ready, have_more, is_sending, do_reconnect;
|
||||
|
||||
perf_sockaddr_t server, local;
|
||||
size_t bufsize;
|
||||
|
||||
uint16_t qid;
|
||||
|
||||
uint64_t conn_ts;
|
||||
perf_socket_event_t conn_event;
|
||||
};
|
||||
|
||||
static void perf__dot_connect(struct perf_net_socket* sock)
|
||||
{
|
||||
int ret;
|
||||
|
||||
self->is_ready = true;
|
||||
|
||||
int fd = socket(self->server.sa.sa.sa_family, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
char __s[256];
|
||||
perf_log_fatal("socket: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
}
|
||||
ck_pr_store_int(&sock->fd, fd);
|
||||
|
||||
if (self->ssl) {
|
||||
SSL_free(self->ssl);
|
||||
}
|
||||
if (!(self->ssl = SSL_new(ssl_ctx))) {
|
||||
perf_log_fatal("SSL_new(): %s", ERR_error_string(ERR_get_error(), 0));
|
||||
}
|
||||
if (!(ret = SSL_set_fd(self->ssl, sock->fd))) {
|
||||
perf_log_fatal("SSL_set_fd(): %s", ERR_error_string(SSL_get_error(self->ssl, ret), 0));
|
||||
}
|
||||
|
||||
if (self->server.sa.sa.sa_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");
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(sock->fd, &self->local.sa.sa, self->local.length) == -1) {
|
||||
char __s[256];
|
||||
perf_log_fatal("bind: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
}
|
||||
|
||||
if (self->bufsize > 0) {
|
||||
ret = setsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF,
|
||||
&self->bufsize, sizeof(self->bufsize));
|
||||
if (ret < 0)
|
||||
perf_log_warning("setsockbuf(SO_RCVBUF) failed");
|
||||
|
||||
ret = setsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF,
|
||||
&self->bufsize, sizeof(self->bufsize));
|
||||
if (ret < 0)
|
||||
perf_log_warning("setsockbuf(SO_SNDBUF) failed");
|
||||
}
|
||||
|
||||
int 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)");
|
||||
|
||||
self->conn_ts = perf_get_time();
|
||||
if (connect(sock->fd, &self->server.sa.sa, self->server.length)) {
|
||||
if (errno == EINPROGRESS) {
|
||||
self->is_ready = false;
|
||||
} else {
|
||||
char __s[256];
|
||||
perf_log_fatal("connect() failed: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void perf__dot_reconnect(struct perf_net_socket* sock)
|
||||
{
|
||||
close(sock->fd);
|
||||
self->have_more = false;
|
||||
self->at = 0;
|
||||
if (self->sending) {
|
||||
self->sending = 0;
|
||||
self->is_sending = false;
|
||||
}
|
||||
self->is_conn_ready = false;
|
||||
perf__dot_connect(sock);
|
||||
}
|
||||
|
||||
static ssize_t perf__dot_recv(struct perf_net_socket* sock, void* buf, size_t len, int flags)
|
||||
{
|
||||
ssize_t n;
|
||||
uint16_t dnslen, dnslen2;
|
||||
|
||||
if (!self->have_more) {
|
||||
PERF_LOCK(&self->lock);
|
||||
if (!self->is_ready) {
|
||||
PERF_UNLOCK(&self->lock);
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
n = SSL_read(self->ssl, self->recvbuf + self->at, TCP_RECV_BUF_SIZE - self->at);
|
||||
if (!n) {
|
||||
perf__dot_reconnect(sock);
|
||||
PERF_UNLOCK(&self->lock);
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
if (n < 0) {
|
||||
int err = SSL_get_error(self->ssl, n);
|
||||
switch (err) {
|
||||
case SSL_ERROR_WANT_READ:
|
||||
errno = EAGAIN;
|
||||
break;
|
||||
case SSL_ERROR_SYSCALL:
|
||||
switch (errno) {
|
||||
case ECONNREFUSED:
|
||||
case ECONNRESET:
|
||||
case ENOTCONN:
|
||||
perf__dot_reconnect(sock);
|
||||
errno = EAGAIN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
errno = EBADF;
|
||||
break;
|
||||
}
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return -1;
|
||||
}
|
||||
PERF_UNLOCK(&self->lock);
|
||||
|
||||
self->at += n;
|
||||
if (self->at < 3) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(&dnslen, self->recvbuf, 2);
|
||||
dnslen = ntohs(dnslen);
|
||||
if (self->at < dnslen + 2) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
memcpy(buf, self->recvbuf + 2, len < dnslen ? len : dnslen);
|
||||
memmove(self->recvbuf, self->recvbuf + 2 + dnslen, self->at - 2 - dnslen);
|
||||
self->at -= 2 + dnslen;
|
||||
|
||||
if (self->at > 2) {
|
||||
memcpy(&dnslen2, self->recvbuf, 2);
|
||||
dnslen2 = ntohs(dnslen2);
|
||||
if (self->at >= dnslen2 + 2) {
|
||||
self->have_more = true;
|
||||
return dnslen;
|
||||
}
|
||||
}
|
||||
|
||||
self->have_more = false;
|
||||
return dnslen;
|
||||
}
|
||||
|
||||
static ssize_t perf__dot_sendto(struct perf_net_socket* sock, uint16_t qid, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen)
|
||||
{
|
||||
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;
|
||||
|
||||
PERF_LOCK(&self->lock);
|
||||
if (!self->is_ready) {
|
||||
PERF_UNLOCK(&self->lock);
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(self->sendbuf, &dnslen, 2);
|
||||
memcpy(self->sendbuf + 2, buf, send);
|
||||
self->qid = qid;
|
||||
|
||||
n = SSL_write(self->ssl, self->sendbuf, send + 2);
|
||||
if (n < 1) {
|
||||
switch (SSL_get_error(self->ssl, n)) {
|
||||
case SSL_ERROR_SYSCALL:
|
||||
switch (errno) {
|
||||
case ECONNREFUSED:
|
||||
case ECONNRESET:
|
||||
case ENOTCONN:
|
||||
case EPIPE:
|
||||
perf__dot_reconnect(sock);
|
||||
self->is_sending = true;
|
||||
self->sending = 0;
|
||||
PERF_UNLOCK(&self->lock);
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return -1;
|
||||
case SSL_ERROR_WANT_READ:
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
self->is_sending = true;
|
||||
self->sending = 0;
|
||||
PERF_UNLOCK(&self->lock);
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
perf_log_warning("SSL_write(): %s", ERR_error_string(SSL_get_error(self->ssl, n), 0));
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (n < send + 2) {
|
||||
self->sending = n;
|
||||
self->is_sending = true;
|
||||
PERF_UNLOCK(&self->lock);
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
}
|
||||
PERF_UNLOCK(&self->lock);
|
||||
|
||||
return n - 2;
|
||||
}
|
||||
|
||||
static int perf__dot_close(struct perf_net_socket* sock)
|
||||
{
|
||||
// TODO
|
||||
return close(sock->fd);
|
||||
}
|
||||
|
||||
static int perf__dot_sockeq(struct perf_net_socket* sock_a, struct perf_net_socket* sock_b)
|
||||
{
|
||||
return sock_a->fd == sock_b->fd;
|
||||
}
|
||||
|
||||
static int perf__dot_sockready(struct perf_net_socket* sock, int pipe_fd, int64_t timeout)
|
||||
{
|
||||
PERF_LOCK(&self->lock);
|
||||
if (self->do_reconnect) {
|
||||
perf__dot_reconnect(sock);
|
||||
self->do_reconnect = false;
|
||||
}
|
||||
|
||||
if (self->is_ready) {
|
||||
if (self->is_sending) {
|
||||
uint16_t dnslen;
|
||||
ssize_t n;
|
||||
|
||||
memcpy(&dnslen, self->sendbuf, 2);
|
||||
dnslen = ntohs(dnslen);
|
||||
|
||||
n = SSL_write(self->ssl, self->sendbuf + self->sending, dnslen + 2 - self->sending);
|
||||
if (n < 1) {
|
||||
switch (SSL_get_error(self->ssl, n)) {
|
||||
case SSL_ERROR_SYSCALL:
|
||||
switch (errno) {
|
||||
case ECONNREFUSED:
|
||||
case ECONNRESET:
|
||||
case ENOTCONN:
|
||||
case EPIPE:
|
||||
perf__dot_reconnect(sock);
|
||||
PERF_UNLOCK(&self->lock);
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return -1;
|
||||
case SSL_ERROR_WANT_READ:
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
PERF_UNLOCK(&self->lock);
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
perf_log_warning("SSL_write(): %s", ERR_error_string(SSL_get_error(self->ssl, n), 0));
|
||||
errno = EBADF;
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return -1;
|
||||
}
|
||||
PERF_UNLOCK(&self->lock);
|
||||
|
||||
self->sending += n;
|
||||
if (self->sending < dnslen + 2) {
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
}
|
||||
self->sending = 0;
|
||||
self->is_sending = false;
|
||||
if (sock->sent) {
|
||||
sock->sent(sock, self->qid);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!self->is_conn_ready) {
|
||||
switch (perf_os_waituntilanywritable(&sock, 1, pipe_fd, timeout)) {
|
||||
case PERF_R_TIMEDOUT:
|
||||
PERF_UNLOCK(&self->lock);
|
||||
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) {
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return 0;
|
||||
}
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return -1;
|
||||
}
|
||||
self->is_conn_ready = true;
|
||||
}
|
||||
|
||||
int ret = SSL_connect(self->ssl);
|
||||
if (!ret) {
|
||||
perf_log_warning("SSL_connect(): %s", ERR_error_string(SSL_get_error(self->ssl, ret), 0));
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return -1;
|
||||
}
|
||||
if (ret < 0) {
|
||||
int err = SSL_get_error(self->ssl, ret);
|
||||
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
|
||||
PERF_UNLOCK(&self->lock);
|
||||
return 0;
|
||||
}
|
||||
self->do_reconnect = true;
|
||||
PERF_UNLOCK(&self->lock);
|
||||
perf_log_warning("SSL_connect(): %s", ERR_error_string(err, 0));
|
||||
return -1;
|
||||
}
|
||||
self->is_ready = true;
|
||||
PERF_UNLOCK(&self->lock);
|
||||
if (sock->event) {
|
||||
sock->event(sock, self->conn_event, perf_get_time() - self->conn_ts);
|
||||
self->conn_event = perf_socket_event_reconnect;
|
||||
}
|
||||
if (self->is_sending) {
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool perf__dot_have_more(struct perf_net_socket* sock)
|
||||
{
|
||||
return self->have_more;
|
||||
}
|
||||
|
||||
struct perf_net_socket* perf_net_dot_opensocket(const perf_sockaddr_t* server, const perf_sockaddr_t* local, size_t bufsize)
|
||||
{
|
||||
struct perf__dot_socket* tmp = calloc(1, sizeof(struct perf__dot_socket)); // clang scan-build
|
||||
struct perf_net_socket* sock = (struct perf_net_socket*)tmp;
|
||||
|
||||
if (!sock) {
|
||||
perf_log_fatal("perf_net_dot_opensocket() out of memory");
|
||||
return 0; // needed for clang scan build
|
||||
}
|
||||
|
||||
sock->recv = perf__dot_recv;
|
||||
sock->sendto = perf__dot_sendto;
|
||||
sock->close = perf__dot_close;
|
||||
sock->sockeq = perf__dot_sockeq;
|
||||
sock->sockready = perf__dot_sockready;
|
||||
sock->have_more = perf__dot_have_more;
|
||||
|
||||
self->server = *server;
|
||||
self->local = *local;
|
||||
self->bufsize = bufsize;
|
||||
if (self->bufsize > 0) {
|
||||
self->bufsize *= 1024;
|
||||
}
|
||||
self->conn_event = perf_socket_event_connect;
|
||||
PERF_MUTEX_INIT(&self->lock);
|
||||
|
||||
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
|
||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||
}
|
||||
|
||||
perf__dot_connect(sock);
|
||||
|
||||
return sock;
|
||||
}
|
348
src/net_tcp.c
Normal file
348
src/net_tcp.c
Normal file
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* 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 "strerror.h"
|
||||
#include "os.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <ck_pr.h>
|
||||
|
||||
#define self ((struct perf__tcp_socket*)sock)
|
||||
|
||||
struct perf__tcp_socket {
|
||||
struct perf_net_socket base;
|
||||
|
||||
char recvbuf[TCP_RECV_BUF_SIZE], sendbuf[TCP_SEND_BUF_SIZE];
|
||||
size_t at, sending;
|
||||
bool is_ready, need_reconnect, have_more, is_sending;
|
||||
|
||||
int flags;
|
||||
struct sockaddr_storage dest_addr;
|
||||
socklen_t addrlen;
|
||||
|
||||
perf_sockaddr_t server, local;
|
||||
size_t bufsize;
|
||||
|
||||
int recvfd;
|
||||
|
||||
uint16_t qid;
|
||||
|
||||
uint64_t conn_ts;
|
||||
perf_socket_event_t conn_event;
|
||||
};
|
||||
|
||||
static int perf__tcp_connect(struct perf_net_socket* sock)
|
||||
{
|
||||
int fd;
|
||||
|
||||
self->is_ready = true;
|
||||
|
||||
fd = socket(self->server.sa.sa.sa_family, SOCK_STREAM, 0);
|
||||
if (fd == -1) {
|
||||
char __s[256];
|
||||
perf_log_fatal("socket: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
}
|
||||
|
||||
if (self->server.sa.sa.sa_family == AF_INET6) {
|
||||
int on = 1;
|
||||
|
||||
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
|
||||
perf_log_warning("setsockopt(IPV6_V6ONLY) failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(fd, &self->local.sa.sa, self->local.length) == -1) {
|
||||
char __s[256];
|
||||
perf_log_fatal("bind: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
}
|
||||
|
||||
if (self->bufsize) {
|
||||
int ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF,
|
||||
&self->bufsize, sizeof(self->bufsize));
|
||||
if (ret < 0)
|
||||
perf_log_warning("setsockbuf(SO_RCVBUF) failed");
|
||||
|
||||
ret = setsockopt(fd, SOL_SOCKET, SO_SNDBUF,
|
||||
&self->bufsize, sizeof(self->bufsize));
|
||||
if (ret < 0)
|
||||
perf_log_warning("setsockbuf(SO_SNDBUF) failed");
|
||||
}
|
||||
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0)
|
||||
perf_log_fatal("fcntl(F_GETFL)");
|
||||
int ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
if (ret < 0)
|
||||
perf_log_fatal("fcntl(F_SETFL)");
|
||||
|
||||
self->conn_ts = perf_get_time();
|
||||
if (connect(fd, &self->server.sa.sa, self->server.length)) {
|
||||
if (errno == EINPROGRESS) {
|
||||
self->is_ready = false;
|
||||
} else {
|
||||
char __s[256];
|
||||
perf_log_fatal("connect() failed: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
}
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static ssize_t perf__tcp_recv(struct perf_net_socket* sock, void* buf, size_t len, int flags)
|
||||
{
|
||||
ssize_t n;
|
||||
uint16_t dnslen, dnslen2;
|
||||
|
||||
int fd = ck_pr_load_int(&sock->fd);
|
||||
if (fd != self->recvfd) {
|
||||
/* reconnecting happened, reset buffers */
|
||||
self->have_more = false;
|
||||
self->at = 0;
|
||||
self->recvfd = fd;
|
||||
}
|
||||
|
||||
if (!self->have_more) {
|
||||
n = recv(fd, self->recvbuf + self->at, TCP_RECV_BUF_SIZE - self->at, flags);
|
||||
if (!n) {
|
||||
return 0;
|
||||
} else if (n < 0) {
|
||||
switch (errno) {
|
||||
case ECONNREFUSED:
|
||||
case ECONNRESET:
|
||||
case ENOTCONN:
|
||||
errno = EAGAIN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
self->at += n;
|
||||
if (self->at < 3) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(&dnslen, self->recvbuf, 2);
|
||||
dnslen = ntohs(dnslen);
|
||||
if (self->at < dnslen + 2) {
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
memcpy(buf, self->recvbuf + 2, len < dnslen ? len : dnslen);
|
||||
memmove(self->recvbuf, self->recvbuf + 2 + dnslen, self->at - 2 - dnslen);
|
||||
self->at -= 2 + dnslen;
|
||||
|
||||
if (self->at > 2) {
|
||||
memcpy(&dnslen2, self->recvbuf, 2);
|
||||
dnslen2 = ntohs(dnslen2);
|
||||
if (self->at >= dnslen2 + 2) {
|
||||
self->have_more = true;
|
||||
return dnslen;
|
||||
}
|
||||
}
|
||||
|
||||
self->have_more = false;
|
||||
return dnslen;
|
||||
}
|
||||
|
||||
static ssize_t perf__tcp_sendto(struct perf_net_socket* sock, uint16_t qid, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen)
|
||||
{
|
||||
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(self->sendbuf, &dnslen, 2);
|
||||
memcpy(self->sendbuf + 2, buf, send);
|
||||
self->qid = qid;
|
||||
n = sendto(sock->fd, self->sendbuf, send + 2, flags, dest_addr, addrlen);
|
||||
|
||||
if (n < 0) {
|
||||
switch (errno) {
|
||||
case ECONNREFUSED:
|
||||
case ECONNRESET:
|
||||
case ENOTCONN:
|
||||
case EPIPE:
|
||||
self->need_reconnect = true;
|
||||
self->is_sending = true;
|
||||
self->sending = 0;
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (n < send + 2) {
|
||||
self->is_sending = true;
|
||||
self->sending = n;
|
||||
self->flags = flags;
|
||||
memcpy(&self->dest_addr, dest_addr, addrlen);
|
||||
self->addrlen = addrlen;
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return n - 2;
|
||||
}
|
||||
|
||||
static int perf__tcp_close(struct perf_net_socket* sock)
|
||||
{
|
||||
return close(sock->fd);
|
||||
}
|
||||
|
||||
static int perf__tcp_sockeq(struct perf_net_socket* sock_a, struct perf_net_socket* sock_b)
|
||||
{
|
||||
return sock_a->fd == sock_b->fd;
|
||||
}
|
||||
|
||||
static int perf__tcp_sockready(struct perf_net_socket* sock, int pipe_fd, int64_t timeout)
|
||||
{
|
||||
if (self->need_reconnect) {
|
||||
int fd = perf__tcp_connect(sock), oldfd = ck_pr_load_int(&sock->fd);
|
||||
ck_pr_store_int(&sock->fd, fd);
|
||||
close(oldfd);
|
||||
self->need_reconnect = false;
|
||||
}
|
||||
|
||||
if (self->is_ready) {
|
||||
if (self->is_sending) {
|
||||
uint16_t dnslen;
|
||||
ssize_t n;
|
||||
|
||||
memcpy(&dnslen, self->sendbuf, 2);
|
||||
dnslen = ntohs(dnslen);
|
||||
n = sendto(sock->fd, self->sendbuf + self->sending, dnslen + 2 - self->sending, self->flags, (struct sockaddr*)&self->dest_addr, self->addrlen);
|
||||
if (n < 1) {
|
||||
switch (errno) {
|
||||
case ECONNREFUSED:
|
||||
case ECONNRESET:
|
||||
case ENOTCONN:
|
||||
case EPIPE:
|
||||
self->need_reconnect = true;
|
||||
if (self->sending) {
|
||||
self->sending = 0;
|
||||
self->is_sending = false;
|
||||
}
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
self->sending += n;
|
||||
if (self->sending < dnslen + 2) {
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
}
|
||||
self->sending = 0;
|
||||
self->is_sending = false;
|
||||
if (sock->sent) {
|
||||
sock->sent(sock, self->qid);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
self->is_ready = true;
|
||||
if (sock->event) {
|
||||
sock->event(sock, self->conn_event, perf_get_time() - self->conn_ts);
|
||||
self->conn_event = perf_socket_event_reconnect;
|
||||
}
|
||||
if (self->is_sending) {
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool perf__tcp_have_more(struct perf_net_socket* sock)
|
||||
{
|
||||
return self->have_more;
|
||||
}
|
||||
|
||||
struct perf_net_socket* perf_net_tcp_opensocket(const perf_sockaddr_t* server, const perf_sockaddr_t* local, size_t bufsize)
|
||||
{
|
||||
struct perf__tcp_socket* tmp = calloc(1, sizeof(struct perf__tcp_socket)); // clang scan-build
|
||||
struct perf_net_socket* sock = (struct perf_net_socket*)tmp;
|
||||
|
||||
if (!sock) {
|
||||
perf_log_fatal("perf_net_tcp_opensocket() out of memory");
|
||||
return 0; // needed for clang scan build
|
||||
}
|
||||
|
||||
sock->recv = perf__tcp_recv;
|
||||
sock->sendto = perf__tcp_sendto;
|
||||
sock->close = perf__tcp_close;
|
||||
sock->sockeq = perf__tcp_sockeq;
|
||||
sock->sockready = perf__tcp_sockready;
|
||||
sock->have_more = perf__tcp_have_more;
|
||||
|
||||
self->server = *server;
|
||||
self->local = *local;
|
||||
self->bufsize = bufsize;
|
||||
if (self->bufsize > 0) {
|
||||
self->bufsize *= 1024;
|
||||
}
|
||||
self->conn_event = perf_socket_event_connect;
|
||||
|
||||
sock->fd = perf__tcp_connect(sock);
|
||||
self->recvfd = sock->fd;
|
||||
|
||||
return sock;
|
||||
}
|
123
src/net_udp.c
Normal file
123
src/net_udp.c
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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 "strerror.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#define self ((struct perf__udp_socket*)sock)
|
||||
|
||||
struct perf__udp_socket {
|
||||
struct perf_net_socket base;
|
||||
};
|
||||
|
||||
static ssize_t perf__udp_recv(struct perf_net_socket* sock, void* buf, size_t len, int flags)
|
||||
{
|
||||
return recv(sock->fd, buf, len, flags);
|
||||
}
|
||||
|
||||
static ssize_t perf__udp_sendto(struct perf_net_socket* sock, uint16_t qid, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen)
|
||||
{
|
||||
return sendto(sock->fd, buf, len, flags, dest_addr, addrlen);
|
||||
}
|
||||
|
||||
static int perf__udp_close(struct perf_net_socket* sock)
|
||||
{
|
||||
return close(sock->fd);
|
||||
}
|
||||
|
||||
static int perf__udp_sockeq(struct perf_net_socket* sock_a, struct perf_net_socket* sock_b)
|
||||
{
|
||||
return sock_a->fd == sock_b->fd;
|
||||
}
|
||||
|
||||
static int perf__udp_sockready(struct perf_net_socket* sock, int pipe_fd, int64_t timeout)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct perf_net_socket* perf_net_udp_opensocket(const perf_sockaddr_t* server, const perf_sockaddr_t* local, size_t bufsize)
|
||||
{
|
||||
struct perf__udp_socket* tmp = calloc(1, sizeof(struct perf__udp_socket)); // clang scan-build
|
||||
struct perf_net_socket* sock = (struct perf_net_socket*)tmp;
|
||||
|
||||
int ret, flags;
|
||||
|
||||
if (!sock) {
|
||||
perf_log_fatal("perf_net_udp_opensocket() out of memory");
|
||||
return 0; // needed for clang scan build
|
||||
}
|
||||
|
||||
sock->recv = perf__udp_recv;
|
||||
sock->sendto = perf__udp_sendto;
|
||||
sock->close = perf__udp_close;
|
||||
sock->sockeq = perf__udp_sockeq;
|
||||
sock->sockready = perf__udp_sockready;
|
||||
|
||||
sock->fd = socket(server->sa.sa.sa_family, SOCK_DGRAM, 0);
|
||||
if (sock->fd == -1) {
|
||||
char __s[256];
|
||||
perf_log_fatal("socket: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
}
|
||||
|
||||
if (server->sa.sa.sa_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");
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(sock->fd, &local->sa.sa, local->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)");
|
||||
|
||||
return sock;
|
||||
}
|
|
@ -214,6 +214,10 @@ void perf_opt_parse(int argc, char** argv)
|
|||
*opt->u.uintp = parse_uint(opt->desc, optarg,
|
||||
1, 0xFFFFFFFF);
|
||||
break;
|
||||
case perf_opt_zpint:
|
||||
*opt->u.uintp = parse_uint(opt->desc, optarg,
|
||||
0, 0xFFFFFFFF);
|
||||
break;
|
||||
case perf_opt_timeval:
|
||||
*opt->u.uint64p = parse_timeval(opt->desc, optarg);
|
||||
break;
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
typedef enum {
|
||||
perf_opt_string,
|
||||
perf_opt_boolean,
|
||||
perf_opt_uint,
|
||||
perf_opt_uint, // can not be zero
|
||||
perf_opt_zpint, // zero or positive
|
||||
perf_opt_timeval,
|
||||
perf_opt_double,
|
||||
perf_opt_port,
|
||||
|
|
13
src/os.c
13
src/os.c
|
@ -61,11 +61,12 @@ 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)
|
||||
{
|
||||
return perf_os_waituntilanyreadable(sock, 1, pipe_fd, timeout);
|
||||
struct perf_net_socket* socks[] = { sock };
|
||||
return perf_os_waituntilanyreadable(socks, 1, pipe_fd, timeout);
|
||||
}
|
||||
|
||||
perf_result_t
|
||||
perf_os_waituntilanyreadable(struct perf_net_socket* socks, unsigned int nfds, int pipe_fd,
|
||||
perf_os_waituntilanyreadable(struct perf_net_socket** socks, unsigned int nfds, int pipe_fd,
|
||||
int64_t timeout)
|
||||
{
|
||||
struct pollfd fds[nfds + 1];
|
||||
|
@ -73,10 +74,10 @@ perf_os_waituntilanyreadable(struct perf_net_socket* socks, unsigned int nfds, i
|
|||
int to, n;
|
||||
|
||||
for (i = 0; i < nfds; i++) {
|
||||
if (socks[i].have_more)
|
||||
if (perf_net_have_more(socks[i]))
|
||||
return (PERF_R_SUCCESS);
|
||||
|
||||
fds[i].fd = socks[i].fd;
|
||||
fds[i].fd = socks[i]->fd;
|
||||
fds[i].events = POLLIN;
|
||||
}
|
||||
|
||||
|
@ -109,7 +110,7 @@ perf_os_waituntilanyreadable(struct perf_net_socket* socks, unsigned int nfds, i
|
|||
}
|
||||
|
||||
perf_result_t
|
||||
perf_os_waituntilanywritable(struct perf_net_socket* socks, unsigned int nfds, int pipe_fd,
|
||||
perf_os_waituntilanywritable(struct perf_net_socket** socks, unsigned int nfds, int pipe_fd,
|
||||
int64_t timeout)
|
||||
{
|
||||
struct pollfd fds[nfds + 1];
|
||||
|
@ -117,7 +118,7 @@ perf_os_waituntilanywritable(struct perf_net_socket* socks, unsigned int nfds, i
|
|||
int to, n;
|
||||
|
||||
for (i = 0; i < nfds; i++) {
|
||||
fds[i].fd = socks[i].fd;
|
||||
fds[i].fd = socks[i]->fd;
|
||||
fds[i].events = POLLOUT;
|
||||
}
|
||||
|
||||
|
|
4
src/os.h
4
src/os.h
|
@ -34,11 +34,11 @@ 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,
|
||||
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,
|
||||
perf_os_waituntilanywritable(struct perf_net_socket** socks, unsigned int nfds, int pipe_fd,
|
||||
int64_t timeout);
|
||||
|
||||
#endif
|
||||
|
|
661
src/resperf.1.in
661
src/resperf.1.in
|
@ -20,85 +20,96 @@ 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]
|
||||
\fBresperf\-report\fR\ [\fB\-a\ \fIlocal_addr\fR]
|
||||
[\fB\-d\ \fIdatafile\fR]
|
||||
[\fB\-R\fR]
|
||||
[\fB\-M\ \fImode\fR]
|
||||
[\fB\-s\ \fIserver_addr\fR]
|
||||
[\fB\-p\ \fIport\fR]
|
||||
[\fB\-x\ \fIlocal_port\fR]
|
||||
[\fB\-t\ \fItimeout\fR]
|
||||
[\fB\-b\ \fIbufsize\fR]
|
||||
[\fB\-f\ \fIfamily\fR]
|
||||
[\fB\-e\fR]
|
||||
[\fB\-D\fR]
|
||||
[\fB\-y\ \fI[alg:]name:secret\fB\fR]
|
||||
[\fB\-y\ \fI[alg:]name:secret\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\-i\ \fIinterval\fR]
|
||||
[\fB\-m\ \fImax_qps\fR]
|
||||
[\fB\-r\ \fIrampup_time\fR]
|
||||
[\fB\-c\ \fIconstant_traffic_time\fR]
|
||||
[\fB\-L\ \fImax_loss\fR]
|
||||
[\fB\-C\ \fIclients\fR]
|
||||
[\fB\-q\ \fImax_outstanding\fR]
|
||||
[\fB\-F\ \fIfall_behind\fR]
|
||||
[\fB\-v\fR]
|
||||
[\fB\-W\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]
|
||||
\fBresperf\fR\ [\fB\-a\ \fIlocal_addr\fR]
|
||||
[\fB\-d\ \fIdatafile\fR]
|
||||
[\fB\-R\fR]
|
||||
[\fB\-M\ \fImode\fR]
|
||||
[\fB\-s\ \fIserver_addr\fR]
|
||||
[\fB\-p\ \fIport\fR]
|
||||
[\fB\-x\ \fIlocal_port\fR]
|
||||
[\fB\-t\ \fItimeout\fR]
|
||||
[\fB\-b\ \fIbufsize\fR]
|
||||
[\fB\-f\ \fIfamily\fR]
|
||||
[\fB\-e\fR]
|
||||
[\fB\-D\fR]
|
||||
[\fB\-y\ \fI[alg:]name:secret\fB\fR]
|
||||
[\fB\-y\ \fI[alg:]name:secret\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\-i\ \fIinterval\fR]
|
||||
[\fB\-m\ \fImax_qps\fR]
|
||||
[\fB\-P\ \fIplot_data_file\fR]
|
||||
[\fB\-r\ \fIrampup_time\fR]
|
||||
[\fB\-c\ \fIconstant_traffic_time\fR]
|
||||
[\fB\-L\ \fImax_loss\fR]
|
||||
[\fB\-C\ \fIclients\fR]
|
||||
[\fB\-q\ \fImax_outstanding\fR]
|
||||
[\fB\-F\ \fIfall_behind\fR]
|
||||
[\fB\-v\fR]
|
||||
[\fB\-W\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
|
||||
\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
|
||||
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.
|
||||
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.
|
||||
Unlike the "self-pacing" approach of dnsperf, \fBresperf\fR works by sending
|
||||
DNS queries at a controlled, steadily increasing rate.
|
||||
By default, \fBresperf\fR will send traffic for 60 seconds, linearly
|
||||
increasing the amount of traffic from zero to 100,000 queries per second (or
|
||||
\fImax_qps\fR).
|
||||
|
||||
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.
|
||||
During the test, \fBresperf\fR 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
|
||||
|
@ -107,77 +118,87 @@ 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.
|
||||
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.
|
||||
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.
|
||||
You should run \fBresperf\fR on a machine separate from the server under test,
|
||||
on the same LAN.
|
||||
Preferably, this should be a Gigabit Ethernet network.
|
||||
The machine running \fBresperf\fR 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
|
||||
\fBresperf\fR.
|
||||
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.
|
||||
100,000 queries per second, if \fBresperf\fR 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.
|
||||
sub-millisecond intervals, \fBresperf\fR will busy-wait between packet
|
||||
transmissions, constantly polling for responses in the meantime.
|
||||
Therefore, it is normal for \fBresperf\fR 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.
|
||||
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, \fBresperf\fR 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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
\fBresperf\fR 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 \fBresperf\-report\fR will be passed on
|
||||
unchanged to \fBresperf\fR.
|
||||
|
||||
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
|
||||
When running \fBresperf\-report\fR, 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
|
||||
|
||||
|
@ -189,124 +210,132 @@ resperf\-report \-s 10.0.0.2 \-d queryfile
|
|||
|
||||
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:
|
||||
but in practice, the 60-second traffic phase will usually be cut short.
|
||||
To be precise, \fBresperf\fR 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
|
||||
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.
|
||||
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.
|
||||
\fBresperf\fR 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.
|
||||
When \fBresperf\fR finds itself unable to send queries fast enough.
|
||||
\fBresperf\fR 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.
|
||||
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 \fBresperf\fR 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.
|
||||
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.
|
||||
\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".
|
||||
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 "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).
|
||||
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.
|
||||
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.
|
||||
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
|
||||
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.
|
||||
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
|
||||
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.
|
||||
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.
|
||||
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
|
||||
|
@ -315,22 +344,24 @@ 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
|
||||
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
|
||||
Alternatively, you can make \fBresperf\fR 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 \fBresperf\fR
|
||||
report the highest throughput reached before the server starts dropping more
|
||||
than 10% of the queries.
|
||||
|
||||
|
@ -338,44 +369,50 @@ 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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
\fBresperf\fR 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
|
||||
|
@ -383,57 +420,67 @@ 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
|
||||
\fB-d \fIdatafile\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the input data file. If not specified, \fBresperf\fR will read
|
||||
from standard input.
|
||||
Specifies the input data file.
|
||||
If not specified, \fBresperf\fR will read from standard input.
|
||||
.RE
|
||||
|
||||
\fB-M \fImode\fB\fR
|
||||
\fB-R\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the transport mode to use, "udp", "tcp" or "tls". Default is "udp".
|
||||
Reopen the datafile if it runs out of data before the testing is completed.
|
||||
This allows for long running tests on very small and simple query datafile.
|
||||
.RE
|
||||
|
||||
\fB-s \fIserver_addr\fB\fR
|
||||
\fB-M \fImode\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the transport mode to use, "udp", "tcp" or "dot".
|
||||
Default is "udp".
|
||||
.RE
|
||||
|
||||
\fB-s \fIserver_addr\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
|
||||
\fB-p \fIport\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.
|
||||
Sets the port on which the DNS packets are sent.
|
||||
If not specified, the standard DNS port (udp/tcp 53, DoT 853) is used.
|
||||
.RE
|
||||
|
||||
\fB-a \fIlocal_addr\fB\fR
|
||||
\fB-a \fIlocal_addr\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the local address from which to send requests. The default is the
|
||||
wildcard address.
|
||||
Specifies the local address from which to send requests.
|
||||
The default is the wildcard address.
|
||||
.RE
|
||||
|
||||
\fB-x \fIlocal_port\fB\fR
|
||||
\fB-x \fIlocal_port\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the local port from which to send requests. The default is the
|
||||
wildcard port (0).
|
||||
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.
|
||||
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
|
||||
\fB-t \fItimeout\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.
|
||||
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
|
||||
|
@ -442,9 +489,10 @@ 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
|
||||
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
|
||||
|
@ -453,20 +501,20 @@ case, a message like Warning: Received a response with an unexpected id: 141
|
|||
will be printed.
|
||||
.RE
|
||||
|
||||
\fB-b \fIbufsize\fB\fR
|
||||
\fB-b \fIbufsize\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.
|
||||
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
|
||||
\fB-f \fIfamily\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.
|
||||
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
|
||||
|
@ -478,11 +526,11 @@ Enables EDNS0 [RFC2671], by adding an OPT record to all packets sent.
|
|||
\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.
|
||||
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
|
||||
\fB-y \fI[alg:]name:secret\fR
|
||||
.br
|
||||
.RS
|
||||
Add a TSIG record [RFC2845] to all packets sent, using the specified TSIG
|
||||
|
@ -496,67 +544,82 @@ the secret is expressed as a base-64 encoded string.
|
|||
Print a usage statement and exit.
|
||||
.RE
|
||||
|
||||
\fB-i \fIinterval\fB\fR
|
||||
\fB-i \fIinterval\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the time interval between data points in the plot file. The
|
||||
default is 0.5 seconds.
|
||||
Specifies the time interval between data points in the plot file.
|
||||
The default is 0.5 seconds.
|
||||
.RE
|
||||
|
||||
\fB-m \fImax_qps\fB\fR
|
||||
\fB-m \fImax_qps\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.
|
||||
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.
|
||||
"Running the test" occurs.
|
||||
The default is 100000 queries per second.
|
||||
.RE
|
||||
|
||||
\fB-P \fIplot_data_file\fB\fR
|
||||
\fB-P \fIplot_data_file\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the name of the plot data file. The default is
|
||||
\fIresperf.gnuplot\fR.
|
||||
Specifies the name of the plot data file.
|
||||
The default is \fIresperf.gnuplot\fR.
|
||||
.RE
|
||||
|
||||
\fB-r \fIrampup_time\fB\fR
|
||||
\fB-r \fIrampup_time\fR
|
||||
.br
|
||||
.RS
|
||||
Specifies the length of time over which traffic will be ramped up. The
|
||||
default is 60 seconds.
|
||||
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
|
||||
\fB-c \fIconstant_traffic_time\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.
|
||||
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
|
||||
\fB-L \fImax_loss\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
|
||||
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
|
||||
\fB-C \fIclients\fR
|
||||
.br
|
||||
.RS
|
||||
Act as multiple clients. Requests are sent from multiple sockets. The
|
||||
default is to act as 1 client.
|
||||
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
|
||||
\fB-q \fImax_outstanding\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.
|
||||
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-F \fIfall_behind\fR
|
||||
.br
|
||||
.RS
|
||||
Sets the maximum number of queries that can fall behind being sent.
|
||||
\fBresperf\fR will stop when this many queries should have been sent and it
|
||||
can be relative easy to hit if \fImax_qps\fR is set too high.
|
||||
The default is 1000 and setting it to zero (0) disables the check.
|
||||
.RE
|
||||
|
||||
\fB-v\fR
|
||||
|
@ -564,22 +627,31 @@ ramping up traffic when this many queries are outstanding. The default is
|
|||
.RS
|
||||
Enables verbose mode to report about network readiness and congestion.
|
||||
.RE
|
||||
|
||||
\fB-W\fR
|
||||
.br
|
||||
.RS
|
||||
Log warnings and errors to standard output instead of standard error making
|
||||
it easier for script, test and automation to capture all output.
|
||||
.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.
|
||||
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 (#).
|
||||
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:
|
||||
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
|
||||
|
@ -621,6 +693,23 @@ length of the interval
|
|||
The average time between sending the query and receiving a response, for
|
||||
queries sent in this time interval
|
||||
.RE
|
||||
|
||||
\fBConnections\fR
|
||||
.br
|
||||
.RS
|
||||
The number of connections done, including re-connections, during this time
|
||||
interval.
|
||||
This is only relevant to connection oriented protocols, such as TCP and DoT.
|
||||
.RE
|
||||
|
||||
\fBAverage connection latency\fR
|
||||
.br
|
||||
.RS
|
||||
The average time between starting to connect and having the connection ready
|
||||
for sending queries to, for this time interval.
|
||||
This is only relevant to connection oriented protocols, such as TCP and DoT.
|
||||
.RE
|
||||
|
||||
.SH "SEE ALSO"
|
||||
\fBdnsperf\fR(1)
|
||||
.SH AUTHOR
|
||||
|
|
169
src/resperf.c
169
src/resperf.c
|
@ -52,12 +52,13 @@
|
|||
|
||||
#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_SERVER_DOT_PORT 853
|
||||
#define DEFAULT_SERVER_PORTS "udp/tcp 53 or DoT 853"
|
||||
#define DEFAULT_LOCAL_PORT 0
|
||||
#define DEFAULT_SOCKET_BUFFER 32
|
||||
#define DEFAULT_TIMEOUT 45
|
||||
#define DEFAULT_MAX_OUTSTANDING (64 * 1024)
|
||||
#define DEFAULT_MAX_FALL_BEHIND 1000
|
||||
|
||||
#define MAX_INPUT_DATA (64 * 1024)
|
||||
|
||||
|
@ -72,6 +73,8 @@ typedef perf_list(struct query_info) query_list;
|
|||
|
||||
typedef struct query_info {
|
||||
uint64_t sent_timestamp;
|
||||
bool is_inprogress;
|
||||
|
||||
/*
|
||||
* This link links the query into the list of outstanding
|
||||
* queries or the list of available query IDs.
|
||||
|
@ -88,11 +91,11 @@ 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 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];
|
||||
|
||||
|
@ -143,6 +146,7 @@ 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 num_reconnections;
|
||||
|
||||
static uint64_t time_now;
|
||||
static uint64_t time_of_program_start;
|
||||
|
@ -164,6 +168,9 @@ typedef struct {
|
|||
int responses;
|
||||
int failures;
|
||||
double latency_sum;
|
||||
|
||||
int connections;
|
||||
double conn_latency_sum;
|
||||
} ramp_bucket;
|
||||
|
||||
/* Pointer to array of n_buckets ramp_bucket structures */
|
||||
|
@ -192,7 +199,8 @@ static uint64_t sustain_phase_began, wait_phase_began;
|
|||
|
||||
static perf_tsigkey_t* tsigkey;
|
||||
|
||||
static bool verbose;
|
||||
static bool verbose;
|
||||
static unsigned int max_fall_behind;
|
||||
|
||||
const char* progname = "resperf";
|
||||
|
||||
|
@ -205,6 +213,9 @@ stringify(double value, int precision)
|
|||
return buf;
|
||||
}
|
||||
|
||||
static void perf__net_event(struct perf_net_socket* sock, perf_socket_event_t event, uint64_t elapsed_time);
|
||||
static void perf__net_sent(struct perf_net_socket* sock, uint16_t qid);
|
||||
|
||||
static void
|
||||
setup(int argc, char** argv)
|
||||
{
|
||||
|
@ -232,11 +243,12 @@ setup(int argc, char** argv)
|
|||
nsocks = 1;
|
||||
mode = sock_udp;
|
||||
verbose = false;
|
||||
max_fall_behind = DEFAULT_MAX_FALL_BEHIND;
|
||||
|
||||
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('M', perf_opt_string, "mode", "set transport mode: udp, tcp or dot", "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",
|
||||
|
@ -280,7 +292,7 @@ setup(int argc, char** argv)
|
|||
"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);
|
||||
"the number of clients to act as", stringify(1, 0), &nsocks);
|
||||
perf_opt_add('q', perf_opt_uint, "num_outstanding",
|
||||
"the maximum number of queries outstanding",
|
||||
stringify(DEFAULT_MAX_OUTSTANDING, 0), &max_outstanding);
|
||||
|
@ -289,6 +301,10 @@ setup(int argc, char** argv)
|
|||
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);
|
||||
bool reopen_datafile = false;
|
||||
perf_opt_add('R', perf_opt_boolean, NULL, "reopen datafile on end, allow for infinit use of it", NULL, &reopen_datafile);
|
||||
perf_opt_add('F', perf_opt_zpint, "fall_behind", "the maximum number of queries that is allowed to fall behind, zero to disable",
|
||||
stringify(DEFAULT_MAX_FALL_BEHIND, 0), &max_fall_behind);
|
||||
|
||||
perf_opt_parse(argc, argv);
|
||||
|
||||
|
@ -300,7 +316,7 @@ setup(int argc, char** argv)
|
|||
mode = perf_net_parsemode(_mode);
|
||||
|
||||
if (!server_port) {
|
||||
server_port = mode == sock_tls ? DEFAULT_SERVER_TLS_PORT : DEFAULT_SERVER_PORT;
|
||||
server_port = mode == sock_dot ? DEFAULT_SERVER_DOT_PORT : DEFAULT_SERVER_PORT;
|
||||
}
|
||||
|
||||
if (max_outstanding > nsocks * DEFAULT_MAX_OUTSTANDING)
|
||||
|
@ -330,6 +346,9 @@ setup(int argc, char** argv)
|
|||
local_port, &local_addr);
|
||||
|
||||
input = perf_datafile_open(filename);
|
||||
if (reopen_datafile) {
|
||||
perf_datafile_setmaxruns(input, -1);
|
||||
}
|
||||
|
||||
if (dnssec)
|
||||
edns = true;
|
||||
|
@ -340,8 +359,15 @@ setup(int argc, char** argv)
|
|||
if (!(socks = calloc(nsocks, sizeof(*socks)))) {
|
||||
perf_log_fatal("out of memory");
|
||||
}
|
||||
for (i = 0; i < nsocks; i++)
|
||||
for (i = 0; i < nsocks; i++) {
|
||||
socks[i] = perf_net_opensocket(mode, &server_addr, &local_addr, i, bufsize);
|
||||
if (!socks[i]) {
|
||||
perf_log_fatal("perf_net_opensocket(): no socket returned, out of memory?");
|
||||
}
|
||||
socks[i]->data = (void*)(intptr_t)i;
|
||||
socks[i]->sent = perf__net_sent;
|
||||
socks[i]->event = perf__net_event;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -351,7 +377,7 @@ cleanup(void)
|
|||
|
||||
perf_datafile_close(&input);
|
||||
for (i = 0; i < nsocks; i++)
|
||||
(void)perf_net_close(&socks[i]);
|
||||
(void)perf_net_close(socks[i]);
|
||||
close(dummypipe[0]);
|
||||
close(dummypipe[1]);
|
||||
}
|
||||
|
@ -374,6 +400,30 @@ find_bucket(uint64_t when)
|
|||
return &buckets[i];
|
||||
}
|
||||
|
||||
static void perf__net_event(struct perf_net_socket* sock, perf_socket_event_t event, uint64_t elapsed_time)
|
||||
{
|
||||
ramp_bucket* b = find_bucket(time_now);
|
||||
|
||||
switch (event) {
|
||||
case perf_socket_event_reconnect:
|
||||
num_reconnections++;
|
||||
case perf_socket_event_connect:
|
||||
b->connections++;
|
||||
b->conn_latency_sum += elapsed_time / (double)MILLION;
|
||||
}
|
||||
}
|
||||
|
||||
static void perf__net_sent(struct perf_net_socket* sock, uint16_t qid)
|
||||
{
|
||||
ramp_bucket* b = find_bucket(time_now);
|
||||
|
||||
b->queries++;
|
||||
|
||||
size_t idx = (size_t)qid * nsocks + (intptr_t)sock->data;
|
||||
assert(idx < max_outstanding);
|
||||
queries[idx].sent_timestamp = time_now;
|
||||
}
|
||||
|
||||
/*
|
||||
* print_statistics:
|
||||
* Print out statistics based on the results of the test
|
||||
|
@ -409,6 +459,7 @@ print_statistics(void)
|
|||
(rcodecounts[i] * 100.0) / num_responses_received);
|
||||
}
|
||||
printf("\n");
|
||||
printf(" Reconnection(s): %" PRIu64 "\n", num_reconnections);
|
||||
printf(" Run time (s): %u.%06u\n",
|
||||
(unsigned int)(run_time / MILLION),
|
||||
(unsigned int)(run_time % MILLION));
|
||||
|
@ -470,8 +521,8 @@ do_one_line(perf_buffer_t* lines, perf_buffer_t* msg)
|
|||
qid = (q - queries) / nsocks;
|
||||
sock = (q - queries) % nsocks;
|
||||
|
||||
if (socks[sock].sending) {
|
||||
if (perf_net_sockready(&socks[sock], dummypipe[0], TIMEOUT_CHECK_TIME) == -1) {
|
||||
while (q->is_inprogress) {
|
||||
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");
|
||||
|
@ -479,12 +530,13 @@ do_one_line(perf_buffer_t* lines, perf_buffer_t* msg)
|
|||
} else {
|
||||
if (verbose) {
|
||||
char __s[256];
|
||||
perf_log_warning("failed to send packet: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
perf_log_warning("failed to check socket readiness: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
}
|
||||
}
|
||||
return (PERF_R_FAILURE);
|
||||
}
|
||||
|
||||
q->is_inprogress = false;
|
||||
perf_list_unlink(instanding_list, q);
|
||||
perf_list_prepend(outstanding_list, q);
|
||||
q->list = &outstanding_list;
|
||||
|
@ -499,14 +551,20 @@ do_one_line(perf_buffer_t* lines, perf_buffer_t* msg)
|
|||
sock = (q - queries) % nsocks;
|
||||
}
|
||||
|
||||
switch (perf_net_sockready(&socks[sock], dummypipe[0], TIMEOUT_CHECK_TIME)) {
|
||||
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);
|
||||
if (errno == EINPROGRESS) {
|
||||
if (verbose) {
|
||||
perf_log_warning("network congested, packet sending in progress");
|
||||
}
|
||||
} else {
|
||||
perf_log_warning("failed to send packet: socket %d not ready", sock);
|
||||
}
|
||||
return (PERF_R_FAILURE);
|
||||
default:
|
||||
break;
|
||||
|
@ -530,13 +588,14 @@ do_one_line(perf_buffer_t* lines, perf_buffer_t* msg)
|
|||
|
||||
base = perf_buffer_base(msg);
|
||||
length = perf_buffer_usedlength(msg);
|
||||
if (perf_net_sendto(&socks[sock], base, length, 0,
|
||||
if (perf_net_sendto(socks[sock], qid, 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");
|
||||
}
|
||||
q->is_inprogress = true;
|
||||
} else {
|
||||
if (verbose) {
|
||||
char __s[256];
|
||||
|
@ -591,8 +650,17 @@ try_process_response(unsigned int sockindex)
|
|||
ramp_bucket* b;
|
||||
int n;
|
||||
|
||||
if (perf_net_sockready(socks[sockindex], dummypipe[0], TIMEOUT_CHECK_TIME) == -1) {
|
||||
if (errno != EINPROGRESS) {
|
||||
if (verbose) {
|
||||
char __s[256];
|
||||
perf_log_warning("failed to check socket readiness: %s", perf_strerror_r(errno, __s, sizeof(__s)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packet_header = (uint16_t*)packet_buffer;
|
||||
n = perf_net_recv(&socks[sockindex], packet_buffer, sizeof(packet_buffer), 0);
|
||||
n = perf_net_recv(socks[sockindex], packet_buffer, sizeof(packet_buffer), 0);
|
||||
if (n < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR) {
|
||||
return;
|
||||
|
@ -611,11 +679,12 @@ try_process_response(unsigned int sockindex)
|
|||
qid = ntohs(packet_header[0]);
|
||||
rcode = ntohs(packet_header[1]) & 0xF;
|
||||
|
||||
q = &queries[qid * nsocks + sockindex];
|
||||
if (q->list != &outstanding_list) {
|
||||
size_t idx = qid * nsocks + sockindex;
|
||||
if (idx >= max_outstanding || queries[idx].list != &outstanding_list) {
|
||||
perf_log_warning("received a response with an unexpected id: %u", qid);
|
||||
return;
|
||||
}
|
||||
q = &queries[idx];
|
||||
|
||||
perf_list_unlink(outstanding_list, q);
|
||||
perf_list_append(instanding_list, q);
|
||||
|
@ -661,21 +730,6 @@ num_scheduled(uint64_t time_since_start)
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -701,7 +755,15 @@ int main(int argc, char** argv)
|
|||
if (pipe(dummypipe) < 0)
|
||||
perf_log_fatal("creating pipe");
|
||||
|
||||
perf_os_handlesignal(SIGPIPE, handle_sigpipe);
|
||||
switch (mode) {
|
||||
case sock_tcp:
|
||||
case sock_dot:
|
||||
// block SIGPIPE for TCP/DOT mode, if connection is closed it will generate a signal
|
||||
perf_os_blocksignal(SIGPIPE, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
perf_buffer_init(&lines, input_data, sizeof(input_data));
|
||||
|
||||
|
@ -725,7 +787,8 @@ int main(int argc, char** argv)
|
|||
|
||||
printf("[Status] Sending\n");
|
||||
|
||||
current_sock = 0;
|
||||
int try_responses = (max_qps / max_outstanding) + 1;
|
||||
current_sock = 0;
|
||||
for (;;) {
|
||||
int should_send;
|
||||
uint64_t time_since_start = time_now - time_of_program_start;
|
||||
|
@ -745,7 +808,7 @@ int main(int argc, char** argv)
|
|||
}
|
||||
if (phase != PHASE_WAIT) {
|
||||
should_send = num_scheduled(time_since_start) - num_queries_sent;
|
||||
if (should_send >= 1000) {
|
||||
if (max_fall_behind && should_send >= max_fall_behind) {
|
||||
printf("[Status] Fell behind by %d queries, "
|
||||
"ending test at %.0f qps\n",
|
||||
should_send, (max_qps * time_since_start) / ramp_time);
|
||||
|
@ -762,8 +825,11 @@ int main(int argc, char** argv)
|
|||
}
|
||||
}
|
||||
}
|
||||
try_process_response(current_sock++);
|
||||
current_sock = current_sock % nsocks;
|
||||
for (i = try_responses; i--;) {
|
||||
try_process_response(current_sock++);
|
||||
if (current_sock >= nsocks)
|
||||
current_sock = 0;
|
||||
}
|
||||
retire_old_queries();
|
||||
time_now = perf_get_time();
|
||||
}
|
||||
|
@ -780,8 +846,8 @@ end_loop:
|
|||
}
|
||||
|
||||
/* Print column headers */
|
||||
fprintf(plotf, "# time target_qps actual_qps "
|
||||
"responses_per_sec failures_per_sec avg_latency\n");
|
||||
fprintf(plotf, "# time target_qps actual_qps responses_per_sec failures_per_sec avg_latency"
|
||||
" connections conn_avg_latency\n");
|
||||
|
||||
/* Don't print unused buckets */
|
||||
last_bucket_used = find_bucket(wait_phase_began) - buckets;
|
||||
|
@ -796,13 +862,18 @@ end_loop:
|
|||
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",
|
||||
|
||||
double conn_latency = buckets[i].connections ? buckets[i].conn_latency_sum / buckets[i].connections : 0;
|
||||
|
||||
fprintf(plotf, "%7.3f %8.2f %8.2f %8.2f %8.2f %8.6f %8.2f %8.6f\n",
|
||||
t,
|
||||
target_qps,
|
||||
buckets[i].queries / interval,
|
||||
buckets[i].responses / interval,
|
||||
buckets[i].failures / interval,
|
||||
latency);
|
||||
(double)buckets[i].queries / interval,
|
||||
(double)buckets[i].responses / interval,
|
||||
(double)buckets[i].failures / interval,
|
||||
latency,
|
||||
(double)buckets[i].connections / interval,
|
||||
conn_latency);
|
||||
}
|
||||
|
||||
fclose(plotf);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
|
||||
|
||||
CLEANFILES = test*.log test*.trs \
|
||||
test2.out
|
||||
test2.out test4.out test4err.out
|
||||
|
||||
TESTS = test1.sh test2.sh test3.sh
|
||||
TESTS = test1.sh test2.sh test3.sh test4.sh
|
||||
|
||||
EXTRA_DIST = $(TESTS) \
|
||||
datafile datafile2 updatefile
|
||||
datafile datafile2 updatefile datafile3 datafile4
|
||||
|
|
3
src/test/datafile4
Normal file
3
src/test/datafile4
Normal file
|
@ -0,0 +1,3 @@
|
|||
api-read.facebook.com.\002\004\003\002\002\002\002\005\004\004\003\004\006\005\006\006\006\005\006\006\006\007\009\008\006\007\009\007\006\006\008\011\008\009\010\010\010\010\010\006\008\011\012\011\010\012\009\010\010\010\255\219. A
|
||||
valid\.quote.com A
|
||||
invalid\0quote.com A
|
|
@ -16,7 +16,7 @@ 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
|
||||
../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/datafile" -n 1 -m dot >test2.out
|
||||
|
@ -70,7 +70,7 @@ 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
|
||||
../resperf -s $ip -m 1 -d "$srcdir/datafile2" -r 2 -c 2 -M dot || true
|
||||
|
||||
done # for ip
|
||||
|
||||
|
@ -78,7 +78,7 @@ done # for ip
|
|||
sleep 2
|
||||
pkill -KILL -u `id -u` dnsperf || true
|
||||
|
||||
../dnsperf -s 127.66.66.66 -d "$srcdir/datafile" -vvvv -m tls -n 1 &
|
||||
../dnsperf -s 127.66.66.66 -d "$srcdir/datafile" -vvvv -m dot -n 1 &
|
||||
sleep 2
|
||||
pkill -KILL -u `id -u` dnsperf || true
|
||||
|
||||
|
|
9
src/test/test4.sh
Executable file
9
src/test/test4.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh -xe
|
||||
|
||||
test "$TEST_DNSPERF_WITH_NETWORK" = "1" || exit 0
|
||||
|
||||
../dnsperf -vvv -d "$srcdir/datafile4" -t 0 -s 127.0.0.1 >test4.out 2>test4err.out
|
||||
|
||||
grep 'api-read.facebook.com.\\002\\004\\003\\002\\002\\002\\002\\005\\004\\004\\003\\004\\006\\005\\006\\006\\006\\005\\006\\006\\006\\007\\009\\008\\006\\007\\009\\007\\006\\006\\008\\011\\008\\009\\010\\010\\010\\010\\010\\006\\008\\011\\012\\011\\010\\012\\009\\010\\010\\010\\255\\219. A' test4.out
|
||||
grep 'T valid\\.quote.com A' test4.out
|
||||
grep 'Warning: invalid domain name (or out of space): invalid\\0quote.com' test4err.out
|
Loading…
Add table
Reference in a new issue