diff options
-rw-r--r-- | CHANGES | 43 | ||||
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | README.md | 9 | ||||
-rwxr-xr-x | configure | 230 | ||||
-rw-r--r-- | configure.ac | 11 | ||||
-rw-r--r-- | src/Makefile.am | 8 | ||||
-rw-r--r-- | src/Makefile.in | 32 | ||||
-rw-r--r-- | src/config.h.in | 6 | ||||
-rw-r--r-- | src/dns.c | 5 | ||||
-rw-r--r-- | src/dnsperf.1.in | 33 | ||||
-rw-r--r-- | src/dnsperf.c | 50 | ||||
-rw-r--r-- | src/net.c | 42 | ||||
-rw-r--r-- | src/net.h | 26 | ||||
-rw-r--r-- | src/net_doh.c | 1017 | ||||
-rw-r--r-- | src/net_dot.c | 13 | ||||
-rw-r--r-- | src/net_tcp.c | 6 | ||||
-rw-r--r-- | src/net_udp.c | 6 | ||||
-rw-r--r-- | src/opt.c | 140 | ||||
-rw-r--r-- | src/opt.h | 5 | ||||
-rw-r--r-- | src/parse_uri.c | 112 | ||||
-rw-r--r-- | src/parse_uri.h | 41 | ||||
-rw-r--r-- | src/resperf.1.in | 19 | ||||
-rw-r--r-- | src/resperf.c | 98 | ||||
-rw-r--r-- | src/test/Makefile.in | 2 | ||||
-rwxr-xr-x | src/test/test2.sh | 3 | ||||
-rwxr-xr-x | src/test/test5.sh | 6 | ||||
-rw-r--r-- | src/util.h | 14 |
27 files changed, 1877 insertions, 102 deletions
@@ -1,3 +1,46 @@ +2021-08-09 Jerry Lundström + + Release 2.7.0 + + This release adds DNS-over-HTTPS support! + + DNS-over-HTTPS can be used by specifying transport mode `doh` and you + should also look at the dnsperf(1) man-page (or `-H`) for the extended + options `doh-uri` and `doh-method`, which controls aspects of DoH/HTTP/2 + that you might want to set. + + Other fixes: + - Add check when constructing DNS packet so that total length of labels + does not exceed 255 bytes + - Fix connection/reconnection state handling for DoT transport + - Fix event handling by initializing them directly when opening the + sockets, otherwise events could have been missed which would give + incorrect statistics + + 61b5eac Tests with dumdumd + d71071c Tests with dumdumd + b42f92e DoH sending + 2fa40bb Net stats + f7f8692 DoH fixes + ea62b49 DoH concurrent streams + 91929f1 DoH reconnect + 17660e6 DoH fixes + 5276aa6 resperf buckets + 585860e Packages + 3ffc601 Fixes + 1570609 Man-page + 6bcadc7 README + f81adf1 Fixes + 1acd71f Code structure + 6c47876 Fixes + 2d379f4 Fixes + 4d5384b Fixes + cee93b3 Initial DNS-over-HTTPS support implementation + 4ff3ebc Events + 8b24bbf DoT state + 6a5b5ef Fix too long name + 71fa09f long opts + 2021-05-31 Jerry Lundström Release 2.6.0 diff --git a/Makefile.in b/Makefile.in index e34d9f8..3169099 100644 --- a/Makefile.in +++ b/Makefile.in @@ -365,6 +365,8 @@ libdir = @libdir@ libexecdir = @libexecdir@ libldns_CFLAGS = @libldns_CFLAGS@ libldns_LIBS = @libldns_LIBS@ +libnghttp2_CFLAGS = @libnghttp2_CFLAGS@ +libnghttp2_LIBS = @libnghttp2_LIBS@ libssl_CFLAGS = @libssl_CFLAGS@ libssl_LIBS = @libssl_LIBS@ localedir = @localedir@ @@ -46,25 +46,26 @@ 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 +- [nghttp2](https://nghttp2.org) - for DNS-over-HTTPS support using HTTP/2 To install the dependencies under Debian/Ubuntu: ``` -apt-get install -y libssl-dev libldns-dev libck-dev +apt-get install -y libssl-dev libldns-dev libck-dev libnghttp2-dev ``` To install the dependencies under CentOS (with EPEL enabled): ``` -yum install -y openssl-devel ldns-devel ck-devel +yum install -y openssl-devel ldns-devel ck-devel libnghttp2-devel ``` To install the dependencies under FreeBSD 12+ using `pkg`: ``` -pkg install -y openssl ldns concurrencykit +pkg install -y openssl ldns concurrencykit libnghttp2 ``` To install the dependencies under OpenBSD 6+ using `pkg_add`: ``` -pkg_add libldns +pkg_add libldns nghttp2 ``` ## Building from source tarball @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for dnsperf 2.6.0. +# Generated by GNU Autoconf 2.69 for dnsperf 2.7.0. # # Report bugs to <admin@dns-oarc.net>. # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='dnsperf' PACKAGE_TARNAME='dnsperf' -PACKAGE_VERSION='2.6.0' -PACKAGE_STRING='dnsperf 2.6.0' +PACKAGE_VERSION='2.7.0' +PACKAGE_STRING='dnsperf 2.7.0' PACKAGE_BUGREPORT='admin@dns-oarc.net' PACKAGE_URL='https://github.com/DNS-OARC/dnsperf/issues' @@ -636,6 +636,8 @@ ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS +libnghttp2_LIBS +libnghttp2_CFLAGS libldns_LIBS libldns_CFLAGS ck_LIBS @@ -805,7 +807,9 @@ libcrypto_LIBS ck_CFLAGS ck_LIBS libldns_CFLAGS -libldns_LIBS' +libldns_LIBS +libnghttp2_CFLAGS +libnghttp2_LIBS' # Initialize some variables set by options. @@ -1356,7 +1360,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures dnsperf 2.6.0 to adapt to many kinds of systems. +\`configure' configures dnsperf 2.7.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1427,7 +1431,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of dnsperf 2.6.0:";; + short | recursive ) echo "Configuration of dnsperf 2.7.0:";; esac cat <<\_ACEOF @@ -1494,6 +1498,10 @@ Some influential environment variables: C compiler flags for libldns, overriding pkg-config libldns_LIBS linker flags for libldns, overriding pkg-config + libnghttp2_CFLAGS + C compiler flags for libnghttp2, overriding pkg-config + libnghttp2_LIBS + linker flags for libnghttp2, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. @@ -1562,7 +1570,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -dnsperf configure 2.6.0 +dnsperf configure 2.7.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1931,7 +1939,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by dnsperf $as_me 2.6.0, which was +It was created by dnsperf $as_me 2.7.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2794,7 +2802,7 @@ fi # Define the identity of the package. PACKAGE='dnsperf' - VERSION='2.6.0' + VERSION='2.7.0' cat >>confdefs.h <<_ACEOF @@ -13665,6 +13673,206 @@ $as_echo "#define HAVE_LDNS 1" >>confdefs.h fi +# Check for nghttp2 + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libnghttp2" >&5 +$as_echo_n "checking for libnghttp2... " >&6; } + +if test -n "$libnghttp2_CFLAGS"; then + pkg_cv_libnghttp2_CFLAGS="$libnghttp2_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libnghttp2 >= 0\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libnghttp2 >= 0") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_libnghttp2_CFLAGS=`$PKG_CONFIG --cflags "libnghttp2 >= 0" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$libnghttp2_LIBS"; then + pkg_cv_libnghttp2_LIBS="$libnghttp2_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libnghttp2 >= 0\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libnghttp2 >= 0") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_libnghttp2_LIBS=`$PKG_CONFIG --libs "libnghttp2 >= 0" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + libnghttp2_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libnghttp2 >= 0" 2>&1` + else + libnghttp2_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libnghttp2 >= 0" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$libnghttp2_PKG_ERRORS" >&5 + + + for ac_header in nghttp2.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "nghttp2.h" "ac_cv_header_nghttp2_h" "$ac_includes_default" +if test "x$ac_cv_header_nghttp2_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_NGHTTP2_H 1 +_ACEOF + +else + as_fn_error $? "nghttp2 headers not found" "$LINENO" 5 +fi + +done + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for nghttp2_session in -lnghttp2" >&5 +$as_echo_n "checking for nghttp2_session in -lnghttp2... " >&6; } +if ${ac_cv_lib_nghttp2_nghttp2_session+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lnghttp2 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char nghttp2_session (); +int +main () +{ +return nghttp2_session (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_nghttp2_nghttp2_session=yes +else + ac_cv_lib_nghttp2_nghttp2_session=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nghttp2_nghttp2_session" >&5 +$as_echo "$ac_cv_lib_nghttp2_nghttp2_session" >&6; } +if test "x$ac_cv_lib_nghttp2_nghttp2_session" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBNGHTTP2 1 +_ACEOF + + LIBS="-lnghttp2 $LIBS" + +else + as_fn_error $? "nghttp2 not found" "$LINENO" 5 +fi + + +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + + for ac_header in nghttp2.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "nghttp2.h" "ac_cv_header_nghttp2_h" "$ac_includes_default" +if test "x$ac_cv_header_nghttp2_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_NGHTTP2_H 1 +_ACEOF + +else + as_fn_error $? "nghttp2 headers not found" "$LINENO" 5 +fi + +done + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for nghttp2_session in -lnghttp2" >&5 +$as_echo_n "checking for nghttp2_session in -lnghttp2... " >&6; } +if ${ac_cv_lib_nghttp2_nghttp2_session+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lnghttp2 $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char nghttp2_session (); +int +main () +{ +return nghttp2_session (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_nghttp2_nghttp2_session=yes +else + ac_cv_lib_nghttp2_nghttp2_session=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nghttp2_nghttp2_session" >&5 +$as_echo "$ac_cv_lib_nghttp2_nghttp2_session" >&6; } +if test "x$ac_cv_lib_nghttp2_nghttp2_session" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBNGHTTP2 1 +_ACEOF + + LIBS="-lnghttp2 $LIBS" + +else + as_fn_error $? "nghttp2 not found" "$LINENO" 5 +fi + + +else + libnghttp2_CFLAGS=$pkg_cv_libnghttp2_CFLAGS + libnghttp2_LIBS=$pkg_cv_libnghttp2_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + as_fn_append CFLAGS " $nghttp2_CFLAGS" + as_fn_append LIBS " $nghttp2_LIBS" + +fi + # Output Makefiles ac_config_files="$ac_config_files Makefile src/Makefile src/test/Makefile" @@ -14202,7 +14410,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by dnsperf $as_me 2.6.0, which was +This file was extended by dnsperf $as_me 2.7.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -14269,7 +14477,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -dnsperf config.status 2.6.0 +dnsperf config.status 2.7.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 4d5bb80..c8f6029 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ # limitations under the License. AC_PREREQ(2.64) -AC_INIT([dnsperf], [2.6.0], [admin@dns-oarc.net], [dnsperf], [https://github.com/DNS-OARC/dnsperf/issues]) +AC_INIT([dnsperf], [2.7.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]) @@ -77,6 +77,15 @@ PKG_CHECK_MODULES([libldns], [libldns >= 1.7.0], [AC_DEFINE([HAVE_LDNS], [1], [D ]) ]) +# Check for nghttp2 +PKG_CHECK_MODULES([libnghttp2], [libnghttp2 >= 0], [ + AS_VAR_APPEND([CFLAGS], [" $nghttp2_CFLAGS"]) + AS_VAR_APPEND([LIBS], [" $nghttp2_LIBS"]) +], [ + AC_CHECK_HEADERS([nghttp2.h],, [AC_MSG_ERROR([nghttp2 headers not found])]) + AC_CHECK_LIB([nghttp2], [nghttp2_session],, [AC_MSG_ERROR([nghttp2 not found])]) +]) + # Output Makefiles AC_CONFIG_FILES([ Makefile diff --git a/src/Makefile.am b/src/Makefile.am index d08b272..45d2dec 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -30,19 +30,19 @@ 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 net_udp.c net_tcp.c net_dot.c + edns.c tsig.c net_udp.c net_tcp.c net_dot.c net_doh.c parse_uri.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 + list.h result.h buffer.h qtype.h edns.h tsig.h parse_uri.h dnsperf_SOURCES = $(_libperf_sources) dnsperf.c dist_dnsperf_SOURCES = $(_libperf_headers) dnsperf_LDADD = $(PTHREAD_LIBS) $(libssl_LIBS) $(libcrypto_LIBS) \ - $(libldns_LIBS) + $(libldns_LIBS) $(libnghttp2_LIBS) resperf_SOURCES = $(_libperf_sources) resperf.c dist_resperf_SOURCES = $(_libperf_headers) resperf_LDADD = $(PTHREAD_LIBS) $(libssl_LIBS) $(libcrypto_LIBS) \ - $(libldns_LIBS) + $(libldns_LIBS) $(libnghttp2_LIBS) man1_MANS = dnsperf.1 resperf.1 diff --git a/src/Makefile.in b/src/Makefile.in index d129516..335c659 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -130,14 +130,16 @@ PROGRAMS = $(bin_PROGRAMS) am__objects_1 = datafile.$(OBJEXT) dns.$(OBJEXT) log.$(OBJEXT) \ net.$(OBJEXT) opt.$(OBJEXT) os.$(OBJEXT) strerror.$(OBJEXT) \ qtype.$(OBJEXT) edns.$(OBJEXT) tsig.$(OBJEXT) \ - net_udp.$(OBJEXT) net_tcp.$(OBJEXT) net_dot.$(OBJEXT) + net_udp.$(OBJEXT) net_tcp.$(OBJEXT) net_dot.$(OBJEXT) \ + net_doh.$(OBJEXT) parse_uri.$(OBJEXT) am_dnsperf_OBJECTS = $(am__objects_1) dnsperf.$(OBJEXT) am__objects_2 = dist_dnsperf_OBJECTS = $(am__objects_2) dnsperf_OBJECTS = $(am_dnsperf_OBJECTS) $(dist_dnsperf_OBJECTS) am__DEPENDENCIES_1 = dnsperf_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) AM_V_lt = $(am__v_lt_@AM_V@) am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) am__v_lt_0 = --silent @@ -146,7 +148,8 @@ am_resperf_OBJECTS = $(am__objects_1) resperf.$(OBJEXT) dist_resperf_OBJECTS = $(am__objects_2) resperf_OBJECTS = $(am_resperf_OBJECTS) $(dist_resperf_OBJECTS) resperf_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ @@ -192,9 +195,10 @@ depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles am__depfiles_remade = ./$(DEPDIR)/datafile.Po ./$(DEPDIR)/dns.Po \ ./$(DEPDIR)/dnsperf.Po ./$(DEPDIR)/edns.Po ./$(DEPDIR)/log.Po \ - ./$(DEPDIR)/net.Po ./$(DEPDIR)/net_dot.Po \ - ./$(DEPDIR)/net_tcp.Po ./$(DEPDIR)/net_udp.Po \ - ./$(DEPDIR)/opt.Po ./$(DEPDIR)/os.Po ./$(DEPDIR)/qtype.Po \ + ./$(DEPDIR)/net.Po ./$(DEPDIR)/net_doh.Po \ + ./$(DEPDIR)/net_dot.Po ./$(DEPDIR)/net_tcp.Po \ + ./$(DEPDIR)/net_udp.Po ./$(DEPDIR)/opt.Po ./$(DEPDIR)/os.Po \ + ./$(DEPDIR)/parse_uri.Po ./$(DEPDIR)/qtype.Po \ ./$(DEPDIR)/resperf.Po ./$(DEPDIR)/strerror.Po \ ./$(DEPDIR)/tsig.Po am__mv = mv -f @@ -405,6 +409,8 @@ libdir = @libdir@ libexecdir = @libexecdir@ libldns_CFLAGS = @libldns_CFLAGS@ libldns_LIBS = @libldns_LIBS@ +libnghttp2_CFLAGS = @libnghttp2_CFLAGS@ +libnghttp2_LIBS = @libnghttp2_LIBS@ libssl_CFLAGS = @libssl_CFLAGS@ libssl_LIBS = @libssl_LIBS@ localedir = @localedir@ @@ -435,20 +441,20 @@ AM_CFLAGS = -I$(srcdir) \ EXTRA_DIST = dnsperf.1.in resperf-report resperf.1.in 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 net_udp.c net_tcp.c net_dot.c + edns.c tsig.c net_udp.c net_tcp.c net_dot.c net_doh.c parse_uri.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 + list.h result.h buffer.h qtype.h edns.h tsig.h parse_uri.h dnsperf_SOURCES = $(_libperf_sources) dnsperf.c dist_dnsperf_SOURCES = $(_libperf_headers) dnsperf_LDADD = $(PTHREAD_LIBS) $(libssl_LIBS) $(libcrypto_LIBS) \ - $(libldns_LIBS) + $(libldns_LIBS) $(libnghttp2_LIBS) resperf_SOURCES = $(_libperf_sources) resperf.c dist_resperf_SOURCES = $(_libperf_headers) resperf_LDADD = $(PTHREAD_LIBS) $(libssl_LIBS) $(libcrypto_LIBS) \ - $(libldns_LIBS) + $(libldns_LIBS) $(libnghttp2_LIBS) man1_MANS = dnsperf.1 resperf.1 all: config.h @@ -605,11 +611,13 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edns.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net_doh.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net_dot.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net_tcp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net_udp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/opt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/os.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse_uri.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/qtype.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/resperf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strerror.Po@am__quote@ # am--include-marker @@ -902,11 +910,13 @@ distclean: distclean-recursive -rm -f ./$(DEPDIR)/edns.Po -rm -f ./$(DEPDIR)/log.Po -rm -f ./$(DEPDIR)/net.Po + -rm -f ./$(DEPDIR)/net_doh.Po -rm -f ./$(DEPDIR)/net_dot.Po -rm -f ./$(DEPDIR)/net_tcp.Po -rm -f ./$(DEPDIR)/net_udp.Po -rm -f ./$(DEPDIR)/opt.Po -rm -f ./$(DEPDIR)/os.Po + -rm -f ./$(DEPDIR)/parse_uri.Po -rm -f ./$(DEPDIR)/qtype.Po -rm -f ./$(DEPDIR)/resperf.Po -rm -f ./$(DEPDIR)/strerror.Po @@ -966,11 +976,13 @@ maintainer-clean: maintainer-clean-recursive -rm -f ./$(DEPDIR)/edns.Po -rm -f ./$(DEPDIR)/log.Po -rm -f ./$(DEPDIR)/net.Po + -rm -f ./$(DEPDIR)/net_doh.Po -rm -f ./$(DEPDIR)/net_dot.Po -rm -f ./$(DEPDIR)/net_tcp.Po -rm -f ./$(DEPDIR)/net_udp.Po -rm -f ./$(DEPDIR)/opt.Po -rm -f ./$(DEPDIR)/os.Po + -rm -f ./$(DEPDIR)/parse_uri.Po -rm -f ./$(DEPDIR)/qtype.Po -rm -f ./$(DEPDIR)/resperf.Po -rm -f ./$(DEPDIR)/strerror.Po diff --git a/src/config.h.in b/src/config.h.in index 6ddcd00..49e5f44 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -21,9 +21,15 @@ /* Define to 1 if you have the `m' library (-lm). */ #undef HAVE_LIBM +/* Define to 1 if you have the `nghttp2' library (-lnghttp2). */ +#undef HAVE_LIBNGHTTP2 + /* Define to 1 if you have the <memory.h> header file. */ #undef HAVE_MEMORY_H +/* Define to 1 if you have the <nghttp2.h> header file. */ +#undef HAVE_NGHTTP2_H + /* Define if you have POSIX threads libraries and header files. */ #undef HAVE_PTHREAD @@ -53,6 +53,7 @@ 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, at; + ssize_t max = 255; const char* orig_str = str; bool is_quoted; @@ -101,6 +102,10 @@ perf_result_t perf_dname_fromstring(const char* str, size_t len, perf_buffer_t* if (label_len > 63) { return PERF_R_FAILURE; } + max -= label_len + 1; + if (max < 0) { + return PERF_R_FAILURE; + } perf_buffer_putuint8(target, label_len); if (is_quoted) { for (at = 0; at < len; at++) { diff --git a/src/dnsperf.1.in b/src/dnsperf.1.in index e38aec1..55cbdf1 100644 --- a/src/dnsperf.1.in +++ b/src/dnsperf.1.in @@ -44,6 +44,7 @@ dnsperf \- test the performance of a DNS server [\fB\-W\fR] [\fB\-x\ \fIlocal_port\fR] [\fB\-y\ \fI[alg:]name:secret\fR] +[\fB\-O\ \fIoption=value\fR] .ad .hy .SH DESCRIPTION @@ -170,6 +171,11 @@ 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. +.SS "Using DNS-over-HTTPS" +When using DNS-over-HTTPS you must set the \fB-O doh\-uri=...\fR to something +that works with the server you're sending to. +Also note that the value for maximum outstanding queries will be used to +control the maximum concurrent streams within the HTTP/2 connection. .SH OPTIONS \fB-a \fIlocal_addr\fR @@ -259,7 +265,7 @@ times; if a time limit is set, the file may be read fewer times. .br .RS Sets the port on which the DNS packets are sent. -If not specified, the standard DNS port (udp/tcp 53, DoT 853) is used. +If not specified, the standard DNS port (udp/tcp 53, DoT 853, DoH 443) is used. .RE \fB-q \fInum_queries\fR @@ -281,7 +287,7 @@ There is no default limit. \fB-m \fImode\fR .br .RS -Specifies the transport mode to use, "udp", "tcp" or "dot". +Specifies the transport mode to use, "udp", "tcp", "dot" or "doh". Default is "udp". .RE @@ -368,6 +374,29 @@ the secret is expressed as a base-64 encoded string. Available algorithms are: hmac-md5, hmac-sha1, hmac-sha224, hmac-sha256, hmac-sha384 and hmac-sha512. .RE + +\fB-O \fIoption=value\fR +.br +.RS +Set an extended long option for various things to control different aspects +of testing or protocol modules, see EXTENDED OPTIONS for list of available +options. +.RE +.SH "EXTENDED OPTIONS" + +\fBdoh-uri=\fIURI\fR +.br +.RS +The URI to use for DNS-over-HTTPS, default value is +"https://localhost/dns-query". +.RE + +\fBdoh-method=\fIHTTP_METHOD\fR +.br +.RS +The HTTP method to use when querying with DNS-over-HTTPS, default is GET. +Available methods are: GET, POST. +.RE .SH "SEE ALSO" \fBresperf\fR(1) .SH AUTHOR diff --git a/src/dnsperf.c b/src/dnsperf.c index c3466ee..0f312ce 100644 --- a/src/dnsperf.c +++ b/src/dnsperf.c @@ -52,7 +52,8 @@ #define DEFAULT_SERVER_NAME "127.0.0.1" #define DEFAULT_SERVER_PORT 53 #define DEFAULT_SERVER_DOT_PORT 853 -#define DEFAULT_SERVER_PORTS "udp/tcp 53 or DoT 853" +#define DEFAULT_SERVER_DOH_PORT 443 +#define DEFAULT_SERVER_PORTS "udp/tcp 53, DoT 853 or DoH 443" #define DEFAULT_LOCAL_PORT 0 #define DEFAULT_MAX_OUTSTANDING 100 #define DEFAULT_TIMEOUT 5 @@ -407,6 +408,8 @@ setup(int argc, char** argv, config_t* config) const char* edns_option = NULL; const char* tsigkey = NULL; const char* mode = 0; + const char* doh_uri = DEFAULT_DOH_URI; + const char* doh_method = DEFAULT_DOH_METHOD; memset(config, 0, sizeof(*config)); config->argc = argc; @@ -422,7 +425,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", "udp", &mode); + perf_opt_add('m', perf_opt_string, "mode", "set transport mode: udp, tcp, dot or doh", "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", @@ -480,6 +483,11 @@ setup(int argc, char** argv, config_t* config) perf_opt_add('v', perf_opt_boolean, NULL, "verbose: report each query and additional information to stdout", NULL, &config->verbose); + perf_long_opt_add("doh-uri", perf_opt_string, "doh_uri", + "the URI to use for DNS-over-HTTPS", DEFAULT_DOH_URI, &doh_uri); + perf_long_opt_add("doh-method", perf_opt_string, "doh_method", + "the HTTP method to use for DNS-over-HTTPS: GET or POST", DEFAULT_DOH_METHOD, &doh_method); + bool log_stdout = false; perf_opt_add('W', perf_opt_boolean, NULL, "log warnings and errors to stdout instead of stderr", NULL, &log_stdout); @@ -493,9 +501,27 @@ setup(int argc, char** argv, config_t* config) config->mode = perf_net_parsemode(mode); if (!server_port) { - server_port = config->mode == sock_dot ? DEFAULT_SERVER_DOT_PORT : DEFAULT_SERVER_PORT; + switch (config->mode) { + case sock_doh: + server_port = DEFAULT_SERVER_DOH_PORT; + break; + case sock_dot: + server_port = DEFAULT_SERVER_DOT_PORT; + break; + default: + server_port = DEFAULT_SERVER_PORT; + break; + } } + if (doh_uri) { + perf_net_doh_parse_uri(doh_uri); + } + if (doh_method) { + perf_net_doh_parse_method(doh_method); + } + perf_net_doh_set_max_concurrent_streams(config->max_outstanding); + if (family != NULL) config->family = perf_net_parsefamily(family); perf_net_parseserver(config->family, server_name, server_port, @@ -1181,13 +1207,11 @@ threadinfo_init(threadinfo_t* tinfo, const config_t* config, tinfo->socks[i] = perf_net_opensocket(config->mode, &config->server_addr, &config->local_addr, socket_offset++, - config->bufsize); + config->bufsize, + tinfo, perf__net_sent, perf__net_event); 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; @@ -1204,14 +1228,16 @@ threadinfo_stop(threadinfo_t* tinfo) } static void -threadinfo_cleanup(threadinfo_t* tinfo, times_t* times) +threadinfo_cleanup(config_t* config, threadinfo_t* tinfo, times_t* times) { unsigned int i; if (interrupted) cancel_queries(tinfo); - for (i = 0; i < tinfo->nsocks; i++) + for (i = 0; i < tinfo->nsocks; i++) { + perf_net_stats_compile(config->mode, tinfo->socks[i]); perf_net_close(tinfo->socks[i]); + } if (tinfo->last_recv > times->end_time) times->end_time = tinfo->last_recv; } @@ -1246,6 +1272,7 @@ int main(int argc, char** argv) switch (config.mode) { case sock_tcp: case sock_dot: + case sock_doh: // block SIGPIPE for TCP/DOT mode, if connection is closed it will generate a signal perf_os_blocksignal(SIGPIPE, true); break; @@ -1296,13 +1323,16 @@ int main(int argc, char** argv) if (config.stats_interval > 0) PERF_JOIN(stats_thread.sender, NULL); + perf_net_stats_init(config.mode); + for (i = 0; i < config.threads; i++) - threadinfo_cleanup(&threads[i], ×); + threadinfo_cleanup(&config, &threads[i], ×); print_final_status(&config); sum_stats(&config, &total_stats); print_statistics(&config, ×, &total_stats); + perf_net_stats_print(config.mode); cleanup(&config); #if OPENSSL_VERSION_NUMBER < 0x10100000L @@ -39,6 +39,8 @@ enum perf_net_mode perf_net_parsemode(const char* mode) return sock_tcp; } else if (!strcmp(mode, "tls") || !strcmp(mode, "dot")) { return sock_dot; + } else if (!strcmp(mode, "doh")) { + return sock_doh; } perf_log_warning("invalid socket mode"); @@ -189,7 +191,7 @@ 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, size_t 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, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event) { int port; perf_sockaddr_t tmp; @@ -209,14 +211,46 @@ struct perf_net_socket* perf_net_opensocket(enum perf_net_mode mode, const perf_ switch (mode) { case sock_udp: - return perf_net_udp_opensocket(server, &tmp, bufsize); + return perf_net_udp_opensocket(server, &tmp, bufsize, data, sent, event); case sock_tcp: - return perf_net_tcp_opensocket(server, &tmp, bufsize); + return perf_net_tcp_opensocket(server, &tmp, bufsize, data, sent, event); case sock_dot: - return perf_net_dot_opensocket(server, &tmp, bufsize); + return perf_net_dot_opensocket(server, &tmp, bufsize, data, sent, event); + case sock_doh: + return perf_net_doh_opensocket(server, &tmp, bufsize, data, sent, event); default: perf_log_fatal("perf_net_opensocket(): invalid mode"); } return 0; } + +void perf_net_stats_init(enum perf_net_mode mode) +{ + switch (mode) { + case sock_doh: + perf_net_doh_stats_init(); + default: + break; + } +} + +void perf_net_stats_compile(enum perf_net_mode mode, struct perf_net_socket* sock) +{ + switch (mode) { + case sock_doh: + perf_net_doh_stats_compile(sock); + default: + break; + } +} + +void perf_net_stats_print(enum perf_net_mode mode) +{ + switch (mode) { + case sock_doh: + perf_net_doh_stats_print(); + default: + break; + } +} @@ -47,7 +47,8 @@ enum perf_net_mode { sock_pipe, sock_udp, sock_tcp, - sock_dot + sock_dot, + sock_doh }; struct perf_net_socket; @@ -158,10 +159,25 @@ static inline int perf_sockaddr_isinet6(const perf_sockaddr_t* sockaddr) return sockaddr->sa.sa.sa_family == AF_INET6; } -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); +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, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event); -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); +struct perf_net_socket* perf_net_udp_opensocket(const perf_sockaddr_t*, const perf_sockaddr_t*, size_t, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event); +struct perf_net_socket* perf_net_tcp_opensocket(const perf_sockaddr_t*, const perf_sockaddr_t*, size_t, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event); +struct perf_net_socket* perf_net_dot_opensocket(const perf_sockaddr_t*, const perf_sockaddr_t*, size_t, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event); +struct perf_net_socket* perf_net_doh_opensocket(const perf_sockaddr_t*, const perf_sockaddr_t*, size_t, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event); + +#define DEFAULT_DOH_URI "https://localhost/dns-query" +#define DEFAULT_DOH_METHOD "GET" + +void perf_net_doh_parse_uri(const char*); +void perf_net_doh_parse_method(const char*); +void perf_net_doh_set_max_concurrent_streams(size_t); + +void perf_net_stats_init(enum perf_net_mode); +void perf_net_stats_compile(enum perf_net_mode, struct perf_net_socket*); +void perf_net_stats_print(enum perf_net_mode); +void perf_net_doh_stats_init(); +void perf_net_doh_stats_compile(struct perf_net_socket*); +void perf_net_doh_stats_print(); #endif diff --git a/src/net_doh.c b/src/net_doh.c new file mode 100644 index 0000000..0689fc6 --- /dev/null +++ b/src/net_doh.c @@ -0,0 +1,1017 @@ +/* + * 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. + */ +/* + * Based on HTTP/2 module in DNS shotgun by Tomáš KřÞek (CZ.NIC) + * https://gitlab.nic.cz/knot/shotgun/-/blob/master/replay/dnssim/src/output/dnssim/https2.c + * + * Initial PoC implementation by Atanas Argirov (PeeriX) + * https://github.com/m0rcq + */ + +#include "config.h" + +#include "net.h" +#include "edns.h" +#include "parse_uri.h" +#include "log.h" +#include "strerror.h" +#include "util.h" +#include "os.h" +#include "opt.h" + +#include <errno.h> +#include <assert.h> +#include <fcntl.h> +#include <unistd.h> +#include <openssl/err.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <ck_pr.h> +#include <nghttp2/nghttp2.h> +#include <openssl/bio.h> +#include <openssl/evp.h> + +#define DNS_GET_REQUEST_VAR "?dns=" +#define DNS_MSG_MAX_SIZE 65535 + +static SSL_CTX* ssl_ctx = 0; +static struct URI doh_uri; +enum perf_doh_method { + doh_method_get, + doh_method_post +}; +static enum perf_doh_method doh_method = doh_method_get; +static size_t doh_max_concurr = 100; + +#define self ((struct perf__doh_socket*)sock) + +#define MAKE_NV(NAME, VALUE) \ + { \ + (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +#define MAKE_NV_LEN(NAME, VALUE, VALUELEN) \ + { \ + (uint8_t*)NAME, (uint8_t*)VALUE, sizeof(NAME) - 1, VALUELEN, \ + NGHTTP2_NV_FLAG_NONE \ + } + +typedef struct { + uint8_t *buf, *bufp; + size_t len, buf_len; +} http2_data_provider_t; + +typedef struct { + nghttp2_session* session; + http2_data_provider_t payload; + bool settings_sent; + char dnsmsg[DNS_MSG_MAX_SIZE]; + size_t dnsmsg_at; + bool dnsmsg_completed; +} http2_session_t; + +struct _doh_stats { + size_t code[500]; + size_t unknown_code; +}; + +struct perf__doh_socket { + struct perf_net_socket base; + + pthread_mutex_t lock; + SSL* ssl; + + bool is_ready, is_conn_ready, is_ssl_ready, is_sending, is_post_sending; + // bool have_more; TODO + bool do_reconnect; + + perf_sockaddr_t server, local; + size_t bufsize; + + uint16_t qid; + + uint64_t conn_ts; + perf_socket_event_t conn_event, conning_event; + + http2_session_t http2; // http2 session data + int http2_code; + bool http2_is_dns; + + struct _doh_stats stats; +}; + +static pthread_mutex_t _nghttp2_lock = PTHREAD_MUTEX_INITIALIZER; +static nghttp2_session_callbacks* _nghttp2_callbacks = 0; +static nghttp2_option* _nghttp2_option = 0; + +void perf_net_doh_parse_uri(const char* uri) +{ + if (parse_uri(&doh_uri, uri)) { + perf_log_warning("invalid DNS-over-HTTPS URI"); + perf_opt_usage(); + exit(1); + } +} + +void perf_net_doh_parse_method(const char* method) +{ + if (!strcmp(method, "GET")) { + doh_method = doh_method_get; + return; + } else if (!strcmp(method, "POST")) { + doh_method = doh_method_post; + return; + } + + perf_log_warning("invalid DNS-over-HTTPS method"); + perf_opt_usage(); + exit(1); +} + +void perf_net_doh_set_max_concurrent_streams(size_t max_concurr) +{ + doh_max_concurr = max_concurr; +} + +static void perf__doh_connect(struct perf_net_socket* sock) +{ + int ret; + + nghttp2_session_del(self->http2.session); + ret = nghttp2_session_client_new2(&self->http2.session, _nghttp2_callbacks, sock, _nghttp2_option); + if (ret < 0) { + perf_log_fatal("Failed to initialize http2 session: %s", nghttp2_strerror(ret)); + } + + 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 (sock->event) { + sock->event(sock, self->conning_event, self->conn_ts); + self->conning_event = perf_socket_event_reconnecting; + } + if (connect(sock->fd, &self->server.sa.sa, self->server.length)) { + if (errno == EINPROGRESS) { + return; + } else { + char __s[256]; + perf_log_fatal("connect() failed: %s", perf_strerror_r(errno, __s, sizeof(__s))); + } + } + + self->is_conn_ready = true; +} + +static void perf__doh_reconnect(struct perf_net_socket* sock) +{ + close(sock->fd); + // self->have_more = false; TODO + + self->http2.settings_sent = false; + self->is_ready = false; + self->is_conn_ready = false; + self->is_ssl_ready = false; + self->is_sending = false; + self->is_post_sending = false; + + self->http2.dnsmsg_at = 0; + self->http2.dnsmsg_completed = false; + self->http2_code = 0; + self->http2_is_dns = false; + + perf__doh_connect(sock); +} + +// static ssize_t _recv_callback(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *sock) +// { +// if (self->http2.dnsmsg_completed) { +// return NGHTTP2_ERR_WOULDBLOCK; +// } +// +// ssize_t n = SSL_read(self->ssl, buf, length); +// if (!n) { +// perf__doh_reconnect(sock); +// return NGHTTP2_ERR_WOULDBLOCK; +// } +// if (n < 0) { +// int err = SSL_get_error(self->ssl, n); +// switch (err) { +// case SSL_ERROR_WANT_READ: +// return NGHTTP2_ERR_WOULDBLOCK; +// case SSL_ERROR_SYSCALL: +// switch (errno) { +// case ECONNREFUSED: +// case ECONNRESET: +// case ENOTCONN: +// perf__doh_reconnect(sock); +// return NGHTTP2_ERR_WOULDBLOCK; +// default: +// break; +// } +// break; +// default: +// break; +// } +// return NGHTTP2_ERR_CALLBACK_FAILURE; +// } +// return n; +// } + +static ssize_t perf__doh_recv(struct perf_net_socket* sock, void* buf, size_t len, int flags) +{ + // read TLS data here instead of nghttp2_recv_callback + PERF_LOCK(&self->lock); + if (!self->is_ready) { + PERF_UNLOCK(&self->lock); + errno = EAGAIN; + return -1; + } + + uint8_t recvbuf[TCP_RECV_BUF_SIZE]; + ssize_t n = SSL_read(self->ssl, recvbuf, TCP_RECV_BUF_SIZE); + if (!n) { + perf__doh_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__doh_reconnect(sock); + errno = EAGAIN; + break; + default: + break; + } + break; + default: + errno = EBADF; + break; + } + PERF_UNLOCK(&self->lock); + return -1; + } + + // this will be processed by nghttp2 callbacks + int ret = nghttp2_session_mem_recv(self->http2.session, recvbuf, n); + if (ret < 0) { + perf_log_warning("nghttp2_session_mem_recv failed: %s", nghttp2_strerror(ret)); + PERF_UNLOCK(&self->lock); + return -1; + } + // TODO: handle partial mem_recv + if (ret != n) { + perf_log_fatal("perf__doh_recv() mem_recv did not take all"); + } + + // TODO: is this faster then mem_recv? + // int ret = nghttp2_session_recv(self->http2.session); + // if (ret < 0) { + // perf_log_warning("nghttp2_session_recv failed: %s", nghttp2_strerror(ret)); + // PERF_UNLOCK(&self->lock); + // return -1; + // } + + if (self->http2.dnsmsg_completed) { + if (self->http2_code > 99 && self->http2_code < 600) { + self->stats.code[self->http2_code - 100]++; + } else { + self->stats.unknown_code++; + } + if (!self->http2_is_dns) { + // TODO: store non-dns for stats + self->http2.dnsmsg_completed = false; + self->http2.dnsmsg_at = 0; + self->http2_code = 0; + self->http2_is_dns = false; + PERF_UNLOCK(&self->lock); + errno = EAGAIN; + return -1; + } + if (self->http2_code < 200 || self->http2_code > 299) { + // TODO: store return code for stats + self->http2.dnsmsg_completed = false; + self->http2.dnsmsg_at = 0; + self->http2_code = 0; + PERF_UNLOCK(&self->lock); + errno = EAGAIN; + return -1; + } + if (self->http2.dnsmsg_at < len) { + len = self->http2.dnsmsg_at; + } + memcpy(buf, self->http2.dnsmsg, len); + self->http2.dnsmsg_completed = false; + self->http2.dnsmsg_at = 0; + + // self->have_more = false; TODO + PERF_UNLOCK(&self->lock); + return len; + } + + // self->have_more = true; TODO + PERF_UNLOCK(&self->lock); + errno = EAGAIN; + return -1; +} + +static void _submit_dns_query_get(struct perf_net_socket* sock, const void* buf, size_t len) +{ + const size_t path_len = doh_uri.pathlen + + sizeof(DNS_GET_REQUEST_VAR) - 1 + + (4 * ((len + 2) / 3)) + 1; + char full_path[path_len]; + char* p = &full_path[0]; + + memcpy(p, doh_uri.path, doh_uri.pathlen); + p += doh_uri.pathlen; + + memcpy(p, DNS_GET_REQUEST_VAR, sizeof(DNS_GET_REQUEST_VAR) - 1); + p += sizeof(DNS_GET_REQUEST_VAR) - 1; + + EVP_EncodeBlock((unsigned char*)p, buf, len); + // RFC8484 requires base64url (RFC4648) + // and Padding characters (=) for base64url MUST NOT be included. + // base64url alphabet is the same as base64 except + is - and / is _ + while (*p) { + switch (*p) { + case '+': + *p++ = '-'; + break; + case '/': + *p++ = '_'; + break; + case '=': + *p = 0; + break; + default: + p++; + } + } + + const nghttp2_nv hdrs[] = { + MAKE_NV(":method", "GET"), + MAKE_NV(":scheme", "https"), + MAKE_NV_LEN(":authority", doh_uri.hostport, doh_uri.hostportlen), + MAKE_NV_LEN(":path", full_path, p - full_path), + MAKE_NV("accept", "application/dns-message"), + MAKE_NV("user-agent", "dnsperf/" PACKAGE_VERSION " (nghttp2/" NGHTTP2_VERSION ")") + }; + + int32_t stream_id = nghttp2_submit_request(self->http2.session, + NULL, + hdrs, + sizeof(hdrs) / sizeof(hdrs[0]), + NULL, + sock); + if (stream_id < 0) { + perf_log_fatal("Failed to submit HTTP2 request: %s", nghttp2_strerror(stream_id)); + } +} + +static ssize_t _payload_read_cb(nghttp2_session* session, + int32_t stream_id, uint8_t* buf, + size_t length, uint32_t* data_flags, + nghttp2_data_source* source, + void* sock) +{ + http2_data_provider_t* payload = source->ptr; + + ssize_t payload_size = length < payload->len ? length : payload->len; + + memcpy(buf, payload->bufp, payload_size); + payload->bufp += payload_size; + payload->len -= payload_size; + // check for EOF + if (payload->len == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + self->is_post_sending = false; + } + + return payload_size; +} + +static void _submit_dns_query_post(struct perf_net_socket* sock, const void* buf, size_t len) +{ + // POST requires DATA flow-controlled payload that local endpoint + // can send across without issuing WINDOW_UPDATE + // we need to check for this and bounce back the request if the + // payload > remote window size + // TODO: are below needed? can they be checked on connect? + // int remote_window_size = nghttp2_session_get_remote_window_size(self->http2.session); + // if (remote_window_size < 0) { + // perf_log_fatal("failed to get http2 session remote window size"); + // } + // if (len > remote_window_size) { + // perf_log_fatal("remote window size is too small for POST payload"); + // } + + // compose content-length + char payload_size[20]; + int payload_size_len = snprintf(payload_size, sizeof(payload_size), "%zu", len); + // TODO: check snprintf() + + const nghttp2_nv hdrs[] = { + MAKE_NV(":method", "POST"), + MAKE_NV(":scheme", "https"), + MAKE_NV_LEN(":authority", doh_uri.hostport, doh_uri.hostportlen), + MAKE_NV_LEN(":path", doh_uri.path, doh_uri.pathlen), + MAKE_NV("accept", "application/dns-message"), + MAKE_NV("content-type", "application/dns-message"), + MAKE_NV_LEN("content-length", payload_size, payload_size_len), + MAKE_NV("user-agent", "dnsperf/" PACKAGE_VERSION " (nghttp2/" NGHTTP2_VERSION ")") + }; + + if (len > self->http2.payload.buf_len) { + self->http2.payload.buf_len = ((len / MAX_EDNS_PACKET) + 1) * MAX_EDNS_PACKET; + if (!(self->http2.payload.buf = realloc(self->http2.payload.buf, self->http2.payload.buf_len))) { + perf_log_fatal("perf_net_doh: out of memory"); + } + } + if (self && self->http2.payload.buf && buf) { // fix clang scan-build + memcpy(self->http2.payload.buf, buf, len); + } else { + perf_log_fatal("_submit_dns_query_post(): payload.buf is null"); + } + self->http2.payload.bufp = self->http2.payload.buf; + self->http2.payload.len = len; + self->is_post_sending = true; + + // we need data provider to pass to submit() + + nghttp2_data_provider data_provider = { + .source.ptr = &self->http2.payload, + .read_callback = _payload_read_cb + }; + int32_t stream_id = nghttp2_submit_request(self->http2.session, + NULL, + hdrs, + sizeof(hdrs) / sizeof(hdrs[0]), + &data_provider, + sock); + if (stream_id < 0) { + perf_log_fatal("Failed to submit HTTP2 request: %s", nghttp2_strerror(stream_id)); + } +} + +static ssize_t perf__doh_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) +{ + PERF_LOCK(&self->lock); + + if (!self->is_ready) { + // TODO: query will be lost here + PERF_UNLOCK(&self->lock); + errno = EINPROGRESS; + return -1; + } + + if (self->is_sending) { + perf_log_fatal("called when sending"); + } + + self->qid = qid; + + switch (doh_method) { + case doh_method_get: + _submit_dns_query_get(sock, buf, len); + break; + case doh_method_post: + _submit_dns_query_post(sock, buf, len); + break; + } + + int ret = nghttp2_session_send(self->http2.session); + if (ret < 0) { + // TODO: handle error better, reconnect when needed + perf_log_warning("nghttp2_session_send failed: %s", nghttp2_strerror(ret)); + self->do_reconnect = true; + PERF_UNLOCK(&self->lock); + errno = EINPROGRESS; + return -1; + } + + if (self->is_post_sending || nghttp2_session_get_outbound_queue_size(self->http2.session) > 0 || nghttp2_session_want_write(self->http2.session)) { + self->is_sending = true; + PERF_UNLOCK(&self->lock); + errno = EINPROGRESS; + return -1; + } + PERF_UNLOCK(&self->lock); + + return len; +} + +static int perf__doh_close(struct perf_net_socket* sock) +{ + // TODO + return close(sock->fd); +} + +static int perf__doh_sockeq(struct perf_net_socket* sock_a, struct perf_net_socket* sock_b) +{ + return sock_a->fd == sock_b->fd; +} + +static int perf__doh_sockready(struct perf_net_socket* sock, int pipe_fd, int64_t timeout) +{ + PERF_LOCK(&self->lock); + + if (self->do_reconnect) { + perf__doh_reconnect(sock); + self->do_reconnect = false; + } + + if (self->is_ready) { + // do nghttp2 I/O send to flush outstanding frames + int ret = nghttp2_session_send(self->http2.session); + if (ret != 0) { + // TODO: handle error better, reconnect when needed + perf_log_warning("nghttp2_session_send failed: %s", nghttp2_strerror(ret)); + self->do_reconnect = true; + PERF_UNLOCK(&self->lock); + return 0; + } + + bool sent = false; + if (self->is_sending) { + if (self->is_post_sending || nghttp2_session_get_outbound_queue_size(self->http2.session) > 0 || nghttp2_session_want_write(self->http2.session)) { + PERF_UNLOCK(&self->lock); + return 0; + } + self->is_sending = false; + sent = true; + } + PERF_UNLOCK(&self->lock); + if (sent && sock->sent) { + sock->sent(sock, self->qid); + } + 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; + } + // unrecoverable error, reconnect + self->do_reconnect = true; + PERF_UNLOCK(&self->lock); + return 0; + } + break; + } + default: + PERF_UNLOCK(&self->lock); + return -1; + } + self->is_conn_ready = true; + } + + if (!self->is_ssl_ready) { + int ret = SSL_connect(self->ssl); + if (!ret) { + // unrecoverable error, reconnect + self->do_reconnect = true; + PERF_UNLOCK(&self->lock); + return 0; + } + if (ret < 0) { + switch (SSL_get_error(self->ssl, ret)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + default: + // unrecoverable error, reconnect + self->do_reconnect = true; + } + PERF_UNLOCK(&self->lock); + return 0; + } + + const uint8_t* alpn = 0; + uint32_t alpn_len = 0; +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(self->ssl, &alpn, &alpn_len); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (!alpn) { + SSL_get0_alpn_selected(self->ssl, &alpn, &alpn_len); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ +#if defined(OPENSSL_NO_NEXTPROTONEG) && OPENSSL_VERSION_NUMBER < 0x10002000L +#error "OpenSSL has no support for getting alpn" +#endif + if (!alpn || alpn_len != 2 || memcmp("h2", alpn, 2) != 0) { + perf_log_warning("Unable to get ALPN or not \"h2\", reconnecting"); + self->do_reconnect = true; + PERF_UNLOCK(&self->lock); + return 0; + } + self->is_ssl_ready = true; + } + + if (!self->http2.settings_sent) { + // send settings + // TODO: does sending settings need session_send()? + nghttp2_settings_entry iv[] = { + { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, doh_max_concurr }, + { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 }, + { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 65535 } + }; + int ret = nghttp2_submit_settings(self->http2.session, NGHTTP2_FLAG_NONE, iv, + sizeof(iv) / sizeof(*iv)); + if (ret != 0) { + perf_log_warning("Could not submit https2 SETTINGS: %s", nghttp2_strerror(ret)); + self->do_reconnect = true; + PERF_UNLOCK(&self->lock); + return 0; + } + self->http2.settings_sent = true; + } + + 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_reconnected; + } + + return 1; +} + +static bool perf__doh_have_more(struct perf_net_socket* sock) +{ + // return self->have_more; TODO + return false; +} + +/* nghttp2 callbacks */ + +static ssize_t _http2_send_cb(nghttp2_session* session, + const uint8_t* data, + size_t length, + int flags, + void* sock) +{ + // TODO: remove once non-experimental + if (!PERF_TRYLOCK(&self->lock)) { + perf_log_fatal("_http2_send_cb called without lock"); + } + + if (!self->is_ready) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + ssize_t n = SSL_write(self->ssl, data, length); + 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__doh_reconnect(sock); + return NGHTTP2_ERR_CALLBACK_FAILURE; + default: + break; + } + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return NGHTTP2_ERR_WOULDBLOCK; + default: + break; + } + perf_log_warning("SSL_write(): %s", ERR_error_string(SSL_get_error(self->ssl, n), 0)); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return n; +} + +static int _http2_frame_recv_cb(nghttp2_session* session, const nghttp2_frame* frame, void* sock) +{ + // TODO: remove once non-experimental + if (!PERF_TRYLOCK(&self->lock)) { + perf_log_fatal("_http2_frame_recv_cb called without lock"); + } + + switch (frame->hd.type) { + case NGHTTP2_DATA: + // we are interested in DATA frame which will carry the DNS response + // NGHTTP2_FLAG_END_STREAM indicates that we have the data in full + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + // TODO: what's the point of below code? if dnsmsg_at > max size then it will already done a buffer overflow + // if (self->http2.dnsmsg_at > DNS_MSG_MAX_SIZE) { + // perf_log_warning("DNS response > DNS message maximum size"); + // return NGHTTP2_ERR_CALLBACK_FAILURE; + // } + + // TODO: need to be able to receive multiple responses at the same time + if (self->http2.dnsmsg_completed) { + perf_log_fatal("_http2_frame_recv_cb: frame received when already having a dns msg"); + } + + self->http2.dnsmsg_completed = true; + // self->have_more = false; TODO + } + break; + case NGHTTP2_RST_STREAM: + case NGHTTP2_GOAWAY: + perf__doh_reconnect(sock); + break; + default: + break; + } + + return 0; +} + +static int _http2_on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* sock) +{ + switch (frame->hd.type) { + case NGHTTP2_HEADERS: { + if (!value) { + return 0; + } + if (!strncasecmp(":status:", (const char*)name, namelen)) { + self->http2_code = atoi((const char*)value); + } else if (!strncasecmp("content-type:", (const char*)name, namelen)) { + if (!strncasecmp("application/dns-message", (const char*)value, valuelen)) { + self->http2_is_dns = true; + } + } + break; + } + default: + break; + } + return 0; +} + +static int _http2_data_chunk_recv_cb(nghttp2_session* session, + uint8_t flags, + int32_t stream_id, + const uint8_t* data, + size_t len, void* sock) +{ + // TODO: remove once non-experimental + if (!PERF_TRYLOCK(&self->lock)) { + perf_log_fatal("_http2_data_chunk_recv_cb called without lock"); + } + + if (self->http2.dnsmsg_completed) { + perf_log_fatal("_http2_data_chunk_recv_cb: chunk received when already having a dns msg"); + } + + // TODO: point of nghttp2_session_get_stream_user_data() code? + // if (nghttp2_session_get_stream_user_data(session, stream_id)) { + if (self->http2.dnsmsg_at + len > DNS_MSG_MAX_SIZE) { + perf_log_warning("http2 chunk data exceeds DNS message max size"); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + memcpy(self->http2.dnsmsg + self->http2.dnsmsg_at, data, len); + self->http2.dnsmsg_at += len; + // } + + return 0; +} + +#ifndef OPENSSL_NO_NEXTPROTONEG +/* NPN TLS extension check */ +static int select_next_proto_cb(SSL* ssl, unsigned char** out, + unsigned char* outlen, const unsigned char* in, + unsigned int inlen, void* arg) +{ + if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { + perf_log_warning("Server did not advertise %u", NGHTTP2_PROTO_VERSION_ID); + return SSL_TLSEXT_ERR_ALERT_WARNING; + } + + return SSL_TLSEXT_ERR_OK; +} +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +struct perf_net_socket* perf_net_doh_opensocket(const perf_sockaddr_t* server, const perf_sockaddr_t* local, size_t bufsize, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event) +{ + struct perf__doh_socket* tmp = calloc(1, sizeof(struct perf__doh_socket)); // clang scan-build + struct perf_net_socket* sock = (struct perf_net_socket*)tmp; + + if (!sock) { + perf_log_fatal("perf_net_doh_opensocket(): out of memory"); + return 0; // needed for clang scan build + } + + sock->recv = perf__doh_recv; + sock->sendto = perf__doh_sendto; + sock->close = perf__doh_close; + sock->sockeq = perf__doh_sockeq; + sock->sockready = perf__doh_sockready; + sock->have_more = perf__doh_have_more; + + sock->data = data; + sock->sent = sent; + sock->event = event; + + self->server = *server; + self->local = *local; + self->bufsize = bufsize; + if (self->bufsize > 0) { + self->bufsize *= 1024; + } + self->conning_event = perf_socket_event_connecting; + self->conn_event = perf_socket_event_connected; + PERF_MUTEX_INIT(&self->lock); + + if (!(self->http2.payload.buf = malloc(MAX_EDNS_PACKET))) { + perf_log_fatal("perf_net_doh_opensocket(): out of memory"); + } + self->http2.payload.buf_len = MAX_EDNS_PACKET; + + 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); +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char*)"\x02h2", 3); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + } + + /* setup HTTP/2 callbacks */ + if (!_nghttp2_callbacks || !_nghttp2_option) { + PERF_LOCK(&_nghttp2_lock); + if (!_nghttp2_callbacks) { + if (nghttp2_session_callbacks_new(&_nghttp2_callbacks)) { + perf_log_fatal("Unable to create nghttp2 callbacks: out of memory"); + } + nghttp2_session_callbacks_set_send_callback(_nghttp2_callbacks, _http2_send_cb); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(_nghttp2_callbacks, _http2_data_chunk_recv_cb); + nghttp2_session_callbacks_set_on_frame_recv_callback(_nghttp2_callbacks, _http2_frame_recv_cb); + nghttp2_session_callbacks_set_on_header_callback(_nghttp2_callbacks, _http2_on_header_callback); + + // nghttp2_session_callbacks_set_recv_callback(_nghttp2_callbacks, _recv_callback); + } + + /* setup HTTP/2 options */ + if (!_nghttp2_option) { + if (nghttp2_option_new(&_nghttp2_option)) { + perf_log_fatal("Unable to create nghttp2 options: out of memory"); + } + nghttp2_option_set_peer_max_concurrent_streams(_nghttp2_option, doh_max_concurr); + } + PERF_UNLOCK(&_nghttp2_lock); + } + + perf__doh_connect(sock); + + return sock; +} + +static struct _doh_stats doh_stats; + +void perf_net_doh_stats_init() +{ + memset(&doh_stats, 0, sizeof(doh_stats)); +} + +void perf_net_doh_stats_compile(struct perf_net_socket* sock) +{ + int i; + for (i = 0; i < (sizeof(doh_stats.code) / sizeof(doh_stats.code[0])); i++) { + doh_stats.code[i] += self->stats.code[i]; + } + doh_stats.unknown_code += self->stats.unknown_code; +} + +void perf_net_doh_stats_print() +{ + printf("DNS-over-HTTPS statistics:\n\n"); + + printf(" HTTP/2 return codes: "); + bool first_code = true, no_codes = true; + int i; + for (i = 0; i < (sizeof(doh_stats.code) / sizeof(doh_stats.code[0])); i++) { + if (doh_stats.code[i]) { + if (first_code) + first_code = false; + else + printf(", "); + printf("%d: %zu", i + 100, doh_stats.code[i]); + no_codes = false; + } + } + if (doh_stats.unknown_code) { + if (!first_code) + printf(", "); + printf("unknown: %zu", doh_stats.unknown_code); + no_codes = false; + } + if (no_codes) { + printf("none"); + } + printf("\n"); + + printf("\n"); +} diff --git a/src/net_dot.c b/src/net_dot.c index a662f7d..6b53e35 100644 --- a/src/net_dot.c +++ b/src/net_dot.c @@ -61,8 +61,6 @@ 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]; @@ -119,12 +117,14 @@ static void perf__dot_connect(struct perf_net_socket* sock) } if (connect(sock->fd, &self->server.sa.sa, self->server.length)) { if (errno == EINPROGRESS) { - self->is_ready = false; + return; } else { char __s[256]; perf_log_fatal("connect() failed: %s", perf_strerror_r(errno, __s, sizeof(__s))); } } + + self->is_conn_ready = true; } static void perf__dot_reconnect(struct perf_net_socket* sock) @@ -136,6 +136,7 @@ static void perf__dot_reconnect(struct perf_net_socket* sock) self->sending = 0; self->is_sending = false; } + self->is_ready = false; self->is_conn_ready = false; perf__dot_connect(sock); } @@ -416,7 +417,7 @@ 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_net_socket* perf_net_dot_opensocket(const perf_sockaddr_t* server, const perf_sockaddr_t* local, size_t bufsize, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event) { 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; @@ -433,6 +434,10 @@ struct perf_net_socket* perf_net_dot_opensocket(const perf_sockaddr_t* server, c sock->sockready = perf__dot_sockready; sock->have_more = perf__dot_have_more; + sock->data = data; + sock->sent = sent; + sock->event = event; + self->server = *server; self->local = *local; self->bufsize = bufsize; diff --git a/src/net_tcp.c b/src/net_tcp.c index c04f25d..97d1f65 100644 --- a/src/net_tcp.c +++ b/src/net_tcp.c @@ -367,7 +367,7 @@ 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_net_socket* perf_net_tcp_opensocket(const perf_sockaddr_t* server, const perf_sockaddr_t* local, size_t bufsize, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event) { 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; @@ -384,6 +384,10 @@ struct perf_net_socket* perf_net_tcp_opensocket(const perf_sockaddr_t* server, c sock->sockready = perf__tcp_sockready; sock->have_more = perf__tcp_have_more; + sock->data = data; + sock->sent = sent; + sock->event = event; + self->server = *server; self->local = *local; self->bufsize = bufsize; diff --git a/src/net_udp.c b/src/net_udp.c index 03e0ae1..685e609 100644 --- a/src/net_udp.c +++ b/src/net_udp.c @@ -61,7 +61,7 @@ static int perf__udp_sockready(struct perf_net_socket* sock, int pipe_fd, int64_ 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_net_socket* perf_net_udp_opensocket(const perf_sockaddr_t* server, const perf_sockaddr_t* local, size_t bufsize, void* data, perf_net_sent_cb_t sent, perf_net_event_cb_t event) { 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; @@ -79,6 +79,10 @@ struct perf_net_socket* perf_net_udp_opensocket(const perf_sockaddr_t* server, c sock->sockeq = perf__udp_sockeq; sock->sockready = perf__udp_sockready; + sock->data = data; + sock->sent = sent; + sock->event = event; + sock->fd = socket(server->sa.sa.sa_family, SOCK_DGRAM, 0); if (sock->fd == -1) { char __s[256]; @@ -44,7 +44,7 @@ typedef struct { const char* desc; const char* help; const char* defval; - char defvalbuf[32]; + char defvalbuf[512]; union { void* valp; char** stringp; @@ -56,7 +56,28 @@ typedef struct { } u; } opt_t; +typedef struct long_opt long_opt_t; +struct long_opt { + long_opt_t* next; + const char* name; + perf_opttype_t type; + const char* desc; + const char* help; + const char* defval; + char defvalbuf[512]; + union { + void* valp; + char** stringp; + bool* boolp; + unsigned int* uintp; + uint64_t* uint64p; + double* doublep; + in_port_t* portp; + } u; +}; + static opt_t opts[MAX_OPTS]; +static long_opt_t* longopts = 0; static unsigned int nopts; static char optstr[MAX_OPTS * 2 + 2 + 1] = { 0 }; extern const char* progname; @@ -76,13 +97,13 @@ void perf_opt_add(char c, perf_opttype_t type, const char* desc, const char* hel opt->desc = desc; opt->help = help; if (defval != NULL) { - opt->defvalbuf[sizeof(opt->defvalbuf) - 1] = 0; - strncpy(opt->defvalbuf, defval, sizeof(opt->defvalbuf)); - if (opt->defvalbuf[sizeof(opt->defvalbuf) - 1]) { + if (strlen(defval) > sizeof(opt->defvalbuf) - 1) { perf_log_fatal("perf_opt_add(): defval too large"); return; } - opt->defval = opt->defvalbuf; + strncpy(opt->defvalbuf, defval, sizeof(opt->defvalbuf)); + opt->defvalbuf[sizeof(opt->defvalbuf) - 1] = 0; + opt->defval = opt->defvalbuf; } else { opt->defval = NULL; } @@ -94,6 +115,37 @@ void perf_opt_add(char c, perf_opttype_t type, const char* desc, const char* hel optstr[sizeof(optstr) - 1] = 0; } +void perf_long_opt_add(const char* name, perf_opttype_t type, const char* desc, const char* help, const char* defval, void* valp) +{ + long_opt_t* opt = calloc(1, sizeof(long_opt_t)); + + if (!opt) { + perf_log_fatal("perf_long_opt_add(): out of memory"); + return; + } + + opt->name = name; + opt->type = type; + opt->desc = desc; + opt->help = help; + if (defval != NULL) { + if (strlen(defval) > sizeof(opt->defvalbuf) - 1) { + perf_log_fatal("perf_opt_add(): defval too large"); + free(opt); // fix clang scan-build + return; + } + strncpy(opt->defvalbuf, defval, sizeof(opt->defvalbuf)); + opt->defvalbuf[sizeof(opt->defvalbuf) - 1] = 0; + opt->defval = opt->defvalbuf; + } else { + opt->defval = NULL; + } + opt->u.valp = valp; + + opt->next = longopts; + longopts = opt; +} + void perf_opt_usage(void) { unsigned int prefix_len, position, arg_len, i, j; @@ -181,6 +233,70 @@ parse_timeval(const char* desc, const char* str) return MILLION * parse_double(desc, str); } +static int perf_opt_long_parse(char* optarg) +{ + ssize_t optlen; + char* arg; + + // TODO: Allow boolean not to have =/value + if (!(arg = strchr(optarg, '='))) { + return -1; + } + optlen = arg - optarg; + if (optlen < 1) { + return -1; + } + arg++; + + long_opt_t* opt = longopts; + while (opt) { + if (!strncmp(optarg, opt->name, optlen)) { + switch (opt->type) { + case perf_opt_string: + *opt->u.stringp = arg; + break; + case perf_opt_boolean: + *opt->u.boolp = true; + break; + case perf_opt_uint: + *opt->u.uintp = parse_uint(opt->desc, arg, 1, 0xFFFFFFFF); + break; + case perf_opt_zpint: + *opt->u.uintp = parse_uint(opt->desc, arg, 0, 0xFFFFFFFF); + break; + case perf_opt_timeval: + *opt->u.uint64p = parse_timeval(opt->desc, arg); + break; + case perf_opt_double: + *opt->u.doublep = parse_double(opt->desc, arg); + break; + case perf_opt_port: + *opt->u.portp = parse_uint(opt->desc, arg, 0, 0xFFFF); + break; + } + return 0; + } + opt = opt->next; + } + + return -1; +} + +void perf_long_opt_usage(void) +{ + fprintf(stderr, "Usage: %s ... -O <name>=<value> ...\n\nAvailable long options:\n", progname); + long_opt_t* opt = longopts; + while (opt) { + fprintf(stderr, " %s: %s", opt->name, opt->help); + if (opt->defval) { + fprintf(stderr, " (default: %s)", opt->defval); + } + fprintf(stderr, "\n"); + + opt = opt->next; + } +} + void perf_opt_parse(int argc, char** argv) { int c; @@ -188,6 +304,8 @@ void perf_opt_parse(int argc, char** argv) unsigned int i; perf_opt_add('h', perf_opt_boolean, NULL, "print this help", NULL, NULL); + perf_opt_add('H', perf_opt_boolean, NULL, "print long options help", NULL, NULL); + perf_opt_add('O', perf_opt_string, NULL, "set long options: <name>=<value>", NULL, NULL); while ((c = getopt(argc, argv, optstr)) != -1) { for (i = 0; i < nopts; i++) { @@ -202,6 +320,18 @@ void perf_opt_parse(int argc, char** argv) perf_opt_usage(); exit(0); } + if (c == 'H') { + perf_long_opt_usage(); + exit(0); + } + if (c == 'O') { + if (perf_opt_long_parse(optarg)) { + fprintf(stderr, "invalid long option: %s\n", optarg); + perf_opt_usage(); + exit(1); + } + continue; + } opt = &opts[i]; switch (opt->type) { case perf_opt_string: @@ -30,10 +30,11 @@ typedef enum { perf_opt_port, } perf_opttype_t; -void perf_opt_add(char c, perf_opttype_t type, const char* desc, const char* help, - const char* defval, void* valp); +void perf_opt_add(char c, perf_opttype_t type, const char* desc, const char* help, const char* defval, void* valp); +void perf_long_opt_add(const char* name, perf_opttype_t type, const char* desc, const char* help, const char* defval, void* valp); void perf_opt_usage(void); +void perf_long_opt_usage(void); void perf_opt_parse(int argc, char** argv); diff --git a/src/parse_uri.c b/src/parse_uri.c new file mode 100644 index 0000000..fa74c85 --- /dev/null +++ b/src/parse_uri.c @@ -0,0 +1,112 @@ +#include "parse_uri.h" + +#include <string.h> + +// From: https://github.com/nghttp2/nghttp2/blob/master/examples/client.c +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +int parse_uri(struct URI* res, const char* uri) +{ + /* We only interested in https */ + size_t len, i, offset; + int ipv6addr = 0; + memset(res, 0, sizeof(struct URI)); + len = strlen(uri); + if (len < 9 || memcmp("https://", uri, 8) != 0) { + return -1; + } + offset = 8; + res->host = res->hostport = &uri[offset]; + res->hostlen = 0; + if (uri[offset] == '[') { + /* IPv6 literal address */ + ++offset; + ++res->host; + ipv6addr = 1; + for (i = offset; i < len; ++i) { + if (uri[i] == ']') { + res->hostlen = i - offset; + offset = i + 1; + break; + } + } + } else { + const char delims[] = ":/?#"; + for (i = offset; i < len; ++i) { + if (strchr(delims, uri[i]) != NULL) { + break; + } + } + res->hostlen = i - offset; + offset = i; + } + if (res->hostlen == 0) { + return -1; + } + /* Assuming https */ + res->port = 443; + if (offset < len) { + if (uri[offset] == ':') { + /* port */ + const char delims[] = "/?#"; + int port = 0; + ++offset; + for (i = offset; i < len; ++i) { + if (strchr(delims, uri[i]) != NULL) { + break; + } + if ('0' <= uri[i] && uri[i] <= '9') { + port *= 10; + port += uri[i] - '0'; + if (port > 65535) { + return -1; + } + } else { + return -1; + } + } + if (port == 0) { + return -1; + } + offset = i; + res->port = (uint16_t)port; + } + } + res->hostportlen = (size_t)(uri + offset + ipv6addr - res->host); + for (i = offset; i < len; ++i) { + if (uri[i] == '#') { + break; + } + } + if (i - offset == 0) { + res->path = "/"; + res->pathlen = 1; + } else { + res->path = &uri[offset]; + res->pathlen = i - offset; + } + return 0; +} diff --git a/src/parse_uri.h b/src/parse_uri.h new file mode 100644 index 0000000..bb69e79 --- /dev/null +++ b/src/parse_uri.h @@ -0,0 +1,41 @@ +// From: https://github.com/nghttp2/nghttp2/blob/master/examples/client.c +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <inttypes.h> +#include <stdlib.h> + +struct URI { + const char* host; + /* In this program, path contains query component as well. */ + const char* path; + size_t pathlen; + const char* hostport; + size_t hostlen; + size_t hostportlen; + uint16_t port; +}; + +int parse_uri(struct URI* res, const char* uri); diff --git a/src/resperf.1.in b/src/resperf.1.in index ea077f2..8a37457 100644 --- a/src/resperf.1.in +++ b/src/resperf.1.in @@ -44,6 +44,7 @@ resperf \- test the resolution performance of a caching DNS server [\fB\-F\ \fIfall_behind\fR] [\fB\-v\fR] [\fB\-W\fR] +[\fB\-O\ \fIoption=value\fR] .ad .hy .hy 0 @@ -74,6 +75,7 @@ resperf \- test the resolution performance of a caching DNS server [\fB\-F\ \fIfall_behind\fR] [\fB\-v\fR] [\fB\-W\fR] +[\fB\-O\ \fIoption=value\fR] .ad .hy .SH DESCRIPTION @@ -413,6 +415,11 @@ 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. +.SS "Using DNS-over-HTTPS" +When using DNS-over-HTTPS you must set the \fB-O doh\-uri=...\fR to something +that works with the server you're sending to. +Also note that the value for maximum outstanding queries will be used to +control the maximum concurrent streams within the HTTP/2 connection. .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 @@ -437,7 +444,7 @@ This allows for long running tests on very small and simple query datafile. \fB-M \fImode\fR .br .RS -Specifies the transport mode to use, "udp", "tcp" or "dot". +Specifies the transport mode to use, "udp", "tcp", "dot" or "doh". Default is "udp". .RE @@ -452,7 +459,7 @@ The default is the loopback address, 127.0.0.1. .br .RS Sets the port on which the DNS packets are sent. -If not specified, the standard DNS port (udp/tcp 53, DoT 853) is used. +If not specified, the standard DNS port (udp/tcp 53, DoT 853, DoH 443) is used. .RE \fB-a \fIlocal_addr\fR @@ -634,6 +641,14 @@ Enables verbose mode to report about network readiness and congestion. 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-O \fIoption=value\fR +.br +.RS +Set an extended long option for various things to control different aspects +of testing or protocol modules, see EXTENDED OPTIONS in \fBdnsperf\fR(1) for +list of available options. +.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. diff --git a/src/resperf.c b/src/resperf.c index f7038e9..2a93bf1 100644 --- a/src/resperf.c +++ b/src/resperf.c @@ -53,7 +53,8 @@ #define DEFAULT_SERVER_NAME "127.0.0.1" #define DEFAULT_SERVER_PORT 53 #define DEFAULT_SERVER_DOT_PORT 853 -#define DEFAULT_SERVER_PORTS "udp/tcp 53 or DoT 853" +#define DEFAULT_SERVER_DOH_PORT 443 +#define DEFAULT_SERVER_PORTS "udp/tcp 53, DoT 853 or DoH 443" #define DEFAULT_LOCAL_PORT 0 #define DEFAULT_SOCKET_BUFFER 32 #define DEFAULT_TIMEOUT 45 @@ -218,8 +219,23 @@ stringify(double value, int precision) 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) +static ramp_bucket* init_buckets(int n) +{ + ramp_bucket* p; + int i; + + if (!(p = calloc(n, sizeof(*p)))) { + perf_log_fatal("out of memory"); + return 0; // fix clang scan-build + } + for (i = 0; i < n; i++) { + p[i].queries = p[i].responses = p[i].failures = 0; + p[i].latency_sum = 0.0; + } + return p; +} + +static void setup(int argc, char** argv) { const char* family = NULL; const char* server_name = DEFAULT_SERVER_NAME; @@ -233,6 +249,8 @@ setup(int argc, char** argv) unsigned int i; const char* _mode = 0; const char* edns_option_str = NULL; + const char* doh_uri = DEFAULT_DOH_URI; + const char* doh_method = DEFAULT_DOH_METHOD; sock_family = AF_UNSPEC; server_port = 0; @@ -251,7 +269,7 @@ setup(int argc, char** argv) 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", "udp", &_mode); + perf_opt_add('M', perf_opt_string, "mode", "set transport mode: udp, tcp, dot or doh", "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", @@ -310,6 +328,10 @@ setup(int argc, char** argv) 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_long_opt_add("doh-uri", perf_opt_string, "doh_uri", + "the URI to use for DNS-over-HTTPS", DEFAULT_DOH_URI, &doh_uri); + perf_long_opt_add("doh-method", perf_opt_string, "doh_method", + "the HTTP method to use for DNS-over-HTTPS: GET or POST", DEFAULT_DOH_METHOD, &doh_method); perf_opt_parse(argc, argv); @@ -321,8 +343,26 @@ setup(int argc, char** argv) mode = perf_net_parsemode(_mode); if (!server_port) { - server_port = mode == sock_dot ? DEFAULT_SERVER_DOT_PORT : DEFAULT_SERVER_PORT; + switch (mode) { + case sock_doh: + server_port = DEFAULT_SERVER_DOH_PORT; + break; + case sock_dot: + server_port = DEFAULT_SERVER_DOT_PORT; + break; + default: + server_port = DEFAULT_SERVER_PORT; + break; + } + } + + if (doh_uri) { + perf_net_doh_parse_uri(doh_uri); + } + if (doh_method) { + perf_net_doh_parse_method(doh_method); } + perf_net_doh_set_max_concurrent_streams(max_outstanding); if (max_outstanding > nsocks * DEFAULT_MAX_OUTSTANDING) perf_log_fatal("number of outstanding packets (%u) must not " @@ -364,17 +404,23 @@ setup(int argc, char** argv) if (edns_option_str != NULL) edns_option = perf_edns_parseoption(edns_option_str); + traffic_time = ramp_time + sustain_time; + end_time = traffic_time + wait_time; + + n_buckets = (traffic_time + bucket_interval - 1) / bucket_interval; + buckets = init_buckets(n_buckets); + + time_now = perf_get_time(); + time_of_program_start = time_now; + if (!(socks = calloc(nsocks, sizeof(*socks)))) { perf_log_fatal("out of memory"); } for (i = 0; i < nsocks; i++) { - socks[i] = perf_net_opensocket(mode, &server_addr, &local_addr, i, bufsize); + socks[i] = perf_net_opensocket(mode, &server_addr, &local_addr, i, bufsize, (void*)(intptr_t)i, perf__net_sent, perf__net_event); 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; } } @@ -384,8 +430,10 @@ cleanup(void) unsigned int i; perf_datafile_close(&input); - for (i = 0; i < nsocks; i++) + for (i = 0; i < nsocks; i++) { + perf_net_stats_compile(mode, socks[i]); (void)perf_net_close(socks[i]); + } close(dummypipe[0]); close(dummypipe[1]); @@ -499,23 +547,7 @@ print_statistics(void) } printf(" Maximum throughput: %.6lf qps\n", max_throughput); printf(" Lost at that point: %.2f%%\n", loss_at_max_throughput); -} - -static ramp_bucket* -init_buckets(int n) -{ - ramp_bucket* p; - int i; - - if (!(p = calloc(n, sizeof(*p)))) { - perf_log_fatal("out of memory"); - return 0; // fix clang scan-build - } - for (i = 0; i < n; i++) { - p[i].queries = p[i].responses = p[i].failures = 0; - p[i].latency_sum = 0.0; - } - return p; + printf("\n"); } /* @@ -776,6 +808,7 @@ int main(int argc, char** argv) switch (mode) { case sock_tcp: case sock_dot: + case sock_doh: // block SIGPIPE for TCP/DOT mode, if connection is closed it will generate a signal perf_os_blocksignal(SIGPIPE, true); break; @@ -788,15 +821,6 @@ int main(int argc, char** argv) max_packet_size = edns ? MAX_EDNS_PACKET : MAX_UDP_PACKET; perf_buffer_init(&msg, outpacket_buffer, max_packet_size); - traffic_time = ramp_time + sustain_time; - end_time = traffic_time + wait_time; - - n_buckets = (traffic_time + bucket_interval - 1) / bucket_interval; - buckets = init_buckets(n_buckets); - - time_now = perf_get_time(); - time_of_program_start = time_now; - printf("[Status] Command line: %s", progname); for (i = 1; i < argc; i++) { printf(" %s", argv[i]); @@ -896,7 +920,9 @@ end_loop: fclose(plotf); print_statistics(); + perf_net_stats_init(mode); cleanup(); + perf_net_stats_print(mode); #if OPENSSL_VERSION_NUMBER < 0x10100000L ERR_free_strings(); #endif diff --git a/src/test/Makefile.in b/src/test/Makefile.in index 4c89ac9..fa6167e 100644 --- a/src/test/Makefile.in +++ b/src/test/Makefile.in @@ -441,6 +441,8 @@ libdir = @libdir@ libexecdir = @libexecdir@ libldns_CFLAGS = @libldns_CFLAGS@ libldns_LIBS = @libldns_LIBS@ +libnghttp2_CFLAGS = @libnghttp2_CFLAGS@ +libnghttp2_LIBS = @libnghttp2_LIBS@ libssl_CFLAGS = @libssl_CFLAGS@ libssl_LIBS = @libssl_LIBS@ localedir = @localedir@ diff --git a/src/test/test2.sh b/src/test/test2.sh index e42ef49..a5e72ad 100755 --- a/src/test/test2.sh +++ b/src/test/test2.sh @@ -100,6 +100,9 @@ echo " A" | ../dnsperf -W -s 1.1.1.1 \ echo "toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolongname" \ | ../dnsperf -W -s 1.1.1.1 -u \ | grep "Unable to parse domain name" +echo "tooooooooooooooooooooooooooooo.oooooooooooooooooooooooo.ooooooooooooooooooooooooooooo.ooooooooooooooooooooooooooooooo.oooooooooooooooooooooooooooooooooooooo.oooooooooooooooooooooooooooo.ooooooooooooooooooooooooo.ooooooooooooooooooooooooooooooooo.oooooooooooooooooooooooooooo.oooooooooooooooooooooooooooooooo.ooooooooooooooooooo.longname" \ + | ../dnsperf -W -s 1.1.1.1 -u \ + | grep "Unable to parse domain name" echo -e "test\ndelete toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooolongname" \ | ../dnsperf -W -s 1.1.1.1 -u \ | grep "invalid update command, domain name too large" diff --git a/src/test/test5.sh b/src/test/test5.sh index 40c9b31..515a5ff 100755 --- a/src/test/test5.sh +++ b/src/test/test5.sh @@ -5,13 +5,17 @@ test "$TEST_DNSPERF_WITH_NETWORK" = "1" || exit 0 dumdumd=`which dumdumd` if [ -n "$dumdumd" ]; then + pkill -9 dumdumd || true + $dumdumd 127.0.0.1 5353 -r -D 100 & pid="$!" + sleep 2 ../dnsperf -s 127.0.0.1 -p 5353 -d "$srcdir/datafile" -t 2 -l 2 -Q 10 -m tcp kill "$pid" $dumdumd 127.0.0.1 5353 -r -D 10 & pid="$!" + sleep 2 ../dnsperf -s 127.0.0.1 -p 5353 -d "$srcdir/datafile" -t 2 -l 10 -Q 100 -m tcp kill "$pid" @@ -20,11 +24,13 @@ if [ -n "$dumdumd" ]; then $dumdumd 127.0.0.1 5353 -r -T -D 100 & pid="$!" + sleep 2 ../dnsperf -s 127.0.0.1 -p 5353 -d "$srcdir/datafile" -t 2 -l 2 -Q 10 -m dot kill "$pid" $dumdumd 127.0.0.1 5353 -r -T -D 10 & pid="$!" + sleep 2 ../dnsperf -s 127.0.0.1 -p 5353 -d "$srcdir/datafile" -t 2 -l 10 -Q 100 -m dot kill "$pid" @@ -28,6 +28,7 @@ #include <stdbool.h> #include <string.h> #include <sys/time.h> +#include <errno.h> #define MILLION ((uint64_t)1000000) @@ -76,6 +77,19 @@ } \ } while (0) +static inline int PERF_TRYLOCK(pthread_mutex_t* mutex) +{ + int __n = pthread_mutex_trylock(mutex); + if (__n == EBUSY) { + return 1; + } + if (__n != 0) { + char __s[256]; + perf_log_fatal("pthread_mutex_lock failed: %s", perf_strerror_r(__n, __s, sizeof(__s))); + } + return 0; +} + #define PERF_UNLOCK(mutex) \ do { \ int __n = pthread_mutex_unlock((mutex)); \ |