From c1fd2c573750fd753d4d1fa552406948cd936750 Mon Sep 17 00:00:00 2001 From: Pieter Lexis Date: Wed, 14 Jan 2026 14:43:10 +0100 Subject: [PATCH] feat(dnsdist): remove h2o support --- builder-support/helpers/h2o.json | 6 - builder-support/helpers/install_h2o.sh | 26 - pdns/dnsdistdist/Makefile.am | 10 - pdns/dnsdistdist/configure.ac | 11 +- .../dnsdistdist/dnsdist-configuration-yaml.cc | 13 +- pdns/dnsdistdist/dnsdist-doh-common.hh | 2 +- pdns/dnsdistdist/dnsdist-idstate.hh | 2 +- pdns/dnsdistdist/dnsdist-internal-queries.cc | 6 - pdns/dnsdistdist/dnsdist-lua.cc | 15 +- .../dnsdist-settings-definitions.yml | 3 +- pdns/dnsdistdist/dnsdist.cc | 19 - .../docs/guides/dns-over-https.rst | 4 +- pdns/dnsdistdist/docs/install.rst | 1 - pdns/dnsdistdist/docs/reference/config.rst | 4 +- pdns/dnsdistdist/docs/reference/selectors.rst | 3 - pdns/dnsdistdist/docs/upgrade_guide.rst | 5 + pdns/dnsdistdist/doh.cc | 1792 ----------------- pdns/dnsdistdist/doh.hh | 56 - pdns/dnsdistdist/m4/dnsdist_enable_doh.m4 | 2 +- .../m4/pdns_check_libh2o_evloop.m4 | 40 - pdns/dnsdistdist/meson.build | 8 - pdns/dnsdistdist/meson/doh2/meson.build | 8 +- pdns/dnsdistdist/meson/h2o/meson.build | 16 - pdns/dnsdistdist/meson_options.txt | 1 - regression-tests.dnsdist/dnsdisttests.py | 3 - regression-tests.dnsdist/test_Async.py | 28 +- regression-tests.dnsdist/test_DOH.py | 61 +- regression-tests.dnsdist/test_OCSP.py | 16 +- regression-tests.dnsdist/test_Protobuf.py | 17 +- .../test_ProxyProtocol.py | 9 +- .../test_TLSSessionResumption.py | 15 +- tasks.py | 23 - 32 files changed, 35 insertions(+), 2190 deletions(-) delete mode 100644 builder-support/helpers/h2o.json delete mode 100755 builder-support/helpers/install_h2o.sh delete mode 100644 pdns/dnsdistdist/doh.cc delete mode 100644 pdns/dnsdistdist/doh.hh delete mode 100644 pdns/dnsdistdist/m4/pdns_check_libh2o_evloop.m4 delete mode 100644 pdns/dnsdistdist/meson/h2o/meson.build diff --git a/builder-support/helpers/h2o.json b/builder-support/helpers/h2o.json deleted file mode 100644 index 39089d6123..0000000000 --- a/builder-support/helpers/h2o.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "version": "2.2.6-pdns3", - "license": "MIT", - "publisher": "https://github.com/h2o/h2o", - "SHA256SUM": "c37b2184169f41a2d10bacb8a2b0e90df238b7a172478ddc77e7077754e0ab17" -} diff --git a/builder-support/helpers/install_h2o.sh b/builder-support/helpers/install_h2o.sh deleted file mode 100755 index cb8366ab06..0000000000 --- a/builder-support/helpers/install_h2o.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh -set -v -set -e - -readonly H2O_VERSION=$(jq -r .version < h2o.json) -readonly H2O_TARBALL="v${H2O_VERSION}.tar.gz" -readonly H2O_TARBALL_URL="https://github.com/PowerDNS/h2o/archive/refs/tags/${H2O_TARBALL}" -readonly H2O_TARBALL_HASH=$(jq -r .SHA256SUM < h2o.json) - -cd /tmp -echo $0: Downloading $H2O_TARBALL -curl -f -L -o "${H2O_TARBALL}" "${H2O_TARBALL_URL}" - -# Line below should echo two spaces between digest and name -if echo "${H2O_TARBALL_HASH} ${H2O_TARBALL}" | sha256sum -c -; then - true -else - result=$? - echo "error: Downloaded ${H2O_TARBALL_URL} failed sha256sum validation" - exit $result -fi -tar xf "${H2O_TARBALL}" -CFLAGS='-fPIC' cmake -DWITH_PICOTLS=off -DWITH_BUNDLED_SSL=off -DWITH_MRUBY=off -DCMAKE_INSTALL_PREFIX=/opt ./h2o-${H2O_VERSION} -make -j $(nproc) -make install -rm -rf "${H2O_TARBALL}" "h2o-${H2O_VERSION}" diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index f5e03c69f7..1e6daf6163 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -111,9 +111,6 @@ if HAVE_GNUTLS AM_CPPFLAGS += $(GNUTLS_CFLAGS) endif -if HAVE_LIBH2OEVLOOP -AM_CPPFLAGS += $(LIBH2OEVLOOP_CFLAGS) -endif endif EXTRA_DIST=COPYING \ @@ -264,7 +261,6 @@ dnsdist_SOURCES = \ dnsparser.hh dnsparser.cc \ dnstap.cc dnstap.hh \ dnswriter.cc dnswriter.hh \ - doh.hh \ doh3.cc doh3.hh \ dolog.cc dolog.hh \ doq-common.hh \ @@ -522,11 +518,6 @@ if HAVE_GNUTLS dnsdist_LDADD += $(GNUTLS_LIBS) endif -if HAVE_LIBH2OEVLOOP -dnsdist_SOURCES += doh.cc -dnsdist_LDADD += $(LIBH2OEVLOOP_LIBS) -endif - if HAVE_NGHTTP2 dnsdist_SOURCES += dnsdist-nghttp2-in.cc dnsdist_SOURCES += dnsdist-nghttp2.cc @@ -637,7 +628,6 @@ fuzz_target_dnsdistcache_SOURCES = \ dnsname.cc dnsname.hh \ dnsparser.cc dnsparser.hh \ dnswriter.cc dnswriter.hh \ - doh.hh \ ednsoptions.cc ednsoptions.hh \ ednssubnet.cc ednssubnet.hh \ fuzz_dnsdistcache.cc \ diff --git a/pdns/dnsdistdist/configure.ac b/pdns/dnsdistdist/configure.ac index 2297d1a78b..cd4ac5561c 100644 --- a/pdns/dnsdistdist/configure.ac +++ b/pdns/dnsdistdist/configure.ac @@ -82,7 +82,6 @@ PDNS_CHECK_LUA_HPP AM_CONDITIONAL([HAVE_CDB], [false]) AM_CONDITIONAL([HAVE_GNUTLS], [false]) -AM_CONDITIONAL([HAVE_LIBH2OEVLOOP], [false]) AM_CONDITIONAL([HAVE_LIBSSL], [false]) AM_CONDITIONAL([HAVE_LMDB], [false]) AM_CONDITIONAL([HAVE_NGHTTP2], [false]) @@ -111,10 +110,8 @@ AS_IF([test "x$enable_dns_over_tls" != "xno"], [ AS_IF([test "x$enable_dns_over_https" != "xno"], [ PDNS_WITH_NGHTTP2 - PDNS_WITH_LIBH2OEVLOOP - - AS_IF([test "x$HAVE_LIBH2OEVLOOP" != "x1" -a "x$HAVE_NGHTTP2" != "x1" ], [ - AC_MSG_ERROR([DNS over HTTPS support requested but neither libh2o-evloop nor nghttp2 was not found]) + AS_IF([test "x$HAVE_NGHTTP2" != "x1" ], [ + AC_MSG_ERROR([DNS over HTTPS support requested but nghttp2 was not found]) ]) AS_IF([test "x$HAVE_GNUTLS" != "x1" -a "x$HAVE_LIBSSL" != "x1"], [ @@ -304,10 +301,6 @@ AS_IF([test "x$enable_dns_over_tls" != "xno" -o "x$enable_dns_over_https" != "xn [AC_MSG_NOTICE([OpenSSL: no])] )] ) -AS_IF([test "x$LIBH2OEVLOOP_LIBS" != "x"], - [AC_MSG_NOTICE([h2o-evloop: yes])], - [AC_MSG_NOTICE([h2o-evloop: no])] -) AS_IF([test "x$NGHTTP2_LIBS" != "x"], [AC_MSG_NOTICE([nghttp2: yes])], [AC_MSG_NOTICE([nghttp2: no])] diff --git a/pdns/dnsdistdist/dnsdist-configuration-yaml.cc b/pdns/dnsdistdist/dnsdist-configuration-yaml.cc index f25e41d1f4..811452ab9b 100644 --- a/pdns/dnsdistdist/dnsdist-configuration-yaml.cc +++ b/pdns/dnsdistdist/dnsdist-configuration-yaml.cc @@ -40,7 +40,6 @@ #include "dnsdist-kvs.hh" #include "dnsdist-web.hh" #include "dnsdist-xsk.hh" -#include "doh.hh" #include "fstrm_logger.hh" #include "iputils.hh" #include "remote_logger.hh" @@ -359,17 +358,7 @@ static bool handleTLSConfiguration(const dnsdist::rust::settings::BindConfigurat tlsContext->d_provider = std::string(bind.tls.provider); boost::algorithm::to_lower(tlsContext->d_provider); frontend->d_library = std::string(bind.doh.provider); - if (frontend->d_library == "h2o") { -#ifdef HAVE_LIBH2OEVLOOP - frontend = std::make_shared(); - // we _really_ need to set it again, as we just replaced the generic frontend by a new one - frontend->d_library = "h2o"; -#else /* HAVE_LIBH2OEVLOOP */ - errlog("DOH bind %s is configured to use libh2o but the library is not available", bind.listen_address); - return false; -#endif /* HAVE_LIBH2OEVLOOP */ - } - else if (frontend->d_library == "nghttp2") { + if (frontend->d_library == "nghttp2") { #ifndef HAVE_NGHTTP2 errlog("DOH bind %s is configured to use nghttp2 but the library is not available", bind.listen_address); return false; diff --git a/pdns/dnsdistdist/dnsdist-doh-common.hh b/pdns/dnsdistdist/dnsdist-doh-common.hh index 8c1c6623cd..48d29a46b9 100644 --- a/pdns/dnsdistdist/dnsdist-doh-common.hh +++ b/pdns/dnsdistdist/dnsdist-doh-common.hh @@ -99,7 +99,7 @@ struct DOHFrontend std::shared_ptr d_dsc{nullptr}; std::shared_ptr>> d_responsesMap; std::shared_ptr d_tlsContext; - std::string d_serverTokens{"h2o/dnsdist"}; + std::string d_serverTokens{"dnsdist"}; std::unordered_map d_customResponseHeaders; std::string d_library; diff --git a/pdns/dnsdistdist/dnsdist-idstate.hh b/pdns/dnsdistdist/dnsdist-idstate.hh index 0bee101659..185ad693a0 100644 --- a/pdns/dnsdistdist/dnsdist-idstate.hh +++ b/pdns/dnsdistdist/dnsdist-idstate.hh @@ -296,7 +296,7 @@ struct IDState the 'outstanding' counters, which should only be increased when we are picking an empty state, and not when reusing ; For DoH, though, we have dynamically allocated a DOHUnit object that needs to - be freed, as well as internal objects internals to libh2o. + be freed, as well as internal objects. - one of the UDP receiver threads receiving a response from a backend, picking the corresponding state and sending the response to the client ; - the 'healthcheck' thread scanning the states to actively discover timeouts, diff --git a/pdns/dnsdistdist/dnsdist-internal-queries.cc b/pdns/dnsdistdist/dnsdist-internal-queries.cc index 66b327e51b..a50bae1646 100644 --- a/pdns/dnsdistdist/dnsdist-internal-queries.cc +++ b/pdns/dnsdistdist/dnsdist-internal-queries.cc @@ -22,7 +22,6 @@ #include "dnsdist-internal-queries.hh" #include "dnsdist-nghttp2-in.hh" #include "dnsdist-tcp.hh" -#include "doh.hh" #include "doq.hh" std::unique_ptr getUDPCrossProtocolQueryFromDQ(DNSQuestion& dq); @@ -37,11 +36,6 @@ std::unique_ptr getInternalQueryFromDQ(DNSQuestion& dnsQuest } #ifdef HAVE_DNS_OVER_HTTPS else if (protocol == dnsdist::Protocol::DoH) { -#ifdef HAVE_LIBH2OEVLOOP - if (dnsQuestion.ids.cs->dohFrontend->d_library == "h2o") { - return getDoHCrossProtocolQueryFromDQ(dnsQuestion, isResponse); - } -#endif /* HAVE_LIBH2OEVLOOP */ return getTCPCrossProtocolQueryFromDQ(dnsQuestion); } #endif diff --git a/pdns/dnsdistdist/dnsdist-lua.cc b/pdns/dnsdistdist/dnsdist-lua.cc index 4f70538f1b..dc062fbb2e 100644 --- a/pdns/dnsdistdist/dnsdist-lua.cc +++ b/pdns/dnsdistdist/dnsdist-lua.cc @@ -65,7 +65,6 @@ #include "base64.hh" #include "coverage.hh" -#include "doh.hh" #include "doq-common.hh" #include "dolog.hh" #include "threadname.hh" @@ -2163,21 +2162,9 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) if (getOptionalValue(vars, "library", frontend->d_library) == 0) { #ifdef HAVE_NGHTTP2 frontend->d_library = "nghttp2"; -#else /* HAVE_NGHTTP2 */ - frontend->d_library = "h2o"; #endif /* HAVE_NGHTTP2 */ } - if (frontend->d_library == "h2o") { -#ifdef HAVE_LIBH2OEVLOOP - frontend = std::make_shared(); - // we _really_ need to set it again, as we just replaced the generic frontend by a new one - frontend->d_library = "h2o"; -#else /* HAVE_LIBH2OEVLOOP */ - errlog("DOH bind %s is configured to use libh2o but the library is not available", addr); - return; -#endif /* HAVE_LIBH2OEVLOOP */ - } - else if (frontend->d_library == "nghttp2") { + if (frontend->d_library == "nghttp2") { #ifndef HAVE_NGHTTP2 errlog("DOH bind %s is configured to use nghttp2 but the library is not available", addr); return; diff --git a/pdns/dnsdistdist/dnsdist-settings-definitions.yml b/pdns/dnsdistdist/dnsdist-settings-definitions.yml index a466bd0488..ad1318f283 100644 --- a/pdns/dnsdistdist/dnsdist-settings-definitions.yml +++ b/pdns/dnsdistdist/dnsdist-settings-definitions.yml @@ -948,7 +948,6 @@ incoming_doh: descripton: "Which underlying HTTP2 library should be used" supported-values: - "nghttp2" - - "h2o" - name: "paths" type: "Vec" default: "/dns-query" @@ -960,7 +959,7 @@ incoming_doh: - name: "server_tokens" type: "String" default: "" - description: "The content of the Server: HTTP header returned by dnsdist. The default is ``h2o/dnsdist`` when ``h2o`` is used, ``nghttp2-/dnsdist`` when ``nghttp2`` is" + description: "The content of the Server: HTTP header returned by dnsdist. The default is ``nghttp2-/dnsdist`` when ``nghttp2`` is used" - name: "send_cache_control_headers" type: "bool" default: "true" diff --git a/pdns/dnsdistdist/dnsdist.cc b/pdns/dnsdistdist/dnsdist.cc index 200f4d3d4b..d14d6876e0 100644 --- a/pdns/dnsdistdist/dnsdist.cc +++ b/pdns/dnsdistdist/dnsdist.cc @@ -80,7 +80,6 @@ #include "capabilities.hh" #include "coverage.hh" #include "delaypipe.hh" -#include "doh.hh" #include "dolog.hh" #include "dnsname.hh" #include "ednsoptions.hh" @@ -3019,12 +3018,6 @@ static void reportFeatures() #endif /* HAVE_DNS_OVER_TLS */ #ifdef HAVE_DNS_OVER_HTTPS cout << "dns-over-https("; -#ifdef HAVE_LIBH2OEVLOOP - cout << "h2o"; -#endif /* HAVE_LIBH2OEVLOOP */ -#if defined(HAVE_LIBH2OEVLOOP) && defined(HAVE_NGHTTP2) - cout << " "; -#endif /* defined(HAVE_LIBH2OEVLOOP) && defined(HAVE_NGHTTP2) */ #ifdef HAVE_NGHTTP2 cout << "nghttp2"; #endif /* HAVE_NGHTTP2 */ @@ -3350,18 +3343,6 @@ static void startFrontends() } #endif /* HAVE_XSK */ - if (clientState->dohFrontend != nullptr && clientState->dohFrontend->d_library == "h2o") { -#ifdef HAVE_DNS_OVER_HTTPS -#ifdef HAVE_LIBH2OEVLOOP - std::thread dohThreadHandle(dohThread, clientState.get()); - if (!clientState->cpus.empty()) { - mapThreadToCPUList(dohThreadHandle.native_handle(), clientState->cpus); - } - dohThreadHandle.detach(); -#endif /* HAVE_LIBH2OEVLOOP */ -#endif /* HAVE_DNS_OVER_HTTPS */ - continue; - } if (clientState->doqFrontend != nullptr) { #ifdef HAVE_DNS_OVER_QUIC std::thread doqThreadHandle(doqThread, clientState.get()); diff --git a/pdns/dnsdistdist/docs/guides/dns-over-https.rst b/pdns/dnsdistdist/docs/guides/dns-over-https.rst index ecb1a0f65f..ca26a2bb93 100644 --- a/pdns/dnsdistdist/docs/guides/dns-over-https.rst +++ b/pdns/dnsdistdist/docs/guides/dns-over-https.rst @@ -6,7 +6,7 @@ DNS-over-HTTPS (DoH) :program:`dnsdist` supports DNS-over-HTTPS (DoH, standardized in RFC 8484) for incoming queries since 1.4.0, and for outgoing queries since 1.7.0. To see if the installation supports this, run ``dnsdist --version``. -If the output shows ``dns-over-https(DOH)`` (``dns-over-https(h2o nghttp2)``, ``dns-over-https(h2o)`` or ``dns-over-https(nghttp2)`` since 1.9.0) , incoming DNS-over-HTTPS is supported. If ``outgoing-dns-over-https(nghttp2)`` shows up then outgoing DNS-over-HTTPS is supported. +If the output shows ``dns-over-https(DOH)`` (``dns-over-https(nghttp2)`` since 1.9.0) , incoming DNS-over-HTTPS is supported. If ``outgoing-dns-over-https(nghttp2)`` shows up then outgoing DNS-over-HTTPS is supported. Incoming -------- @@ -88,7 +88,7 @@ To let dnsdist listen for DoH queries over HTTP on localhost at port 8053 add on HTTP/1 support ^^^^^^^^^^^^^^ -dnsdist initially relied on the ``h2o`` library to support incoming DNS over HTTPS. Since 1.9.0, ``h2o`` has been deprecated and ``nghttp2`` is the +dnsdist initially relied on the ``h2o`` library to support incoming DNS over HTTPS. Since 2.1.0, ``h2o`` is no longer in use and ``nghttp2`` is the preferred library for incoming DoH support, because ``h2o`` has unfortunately really never been maintained in a way that is suitable for use as a library (see https://github.com/h2o/h2o/issues/3230). While we took great care to make the migration as painless as possible, ``h2o`` supported HTTP/1 while ``nghttp2`` does not. This is not an issue for actual DNS over HTTPS clients that support HTTP/2, but might be one in setups running dnsdist behind a reverse-proxy that diff --git a/pdns/dnsdistdist/docs/install.rst b/pdns/dnsdistdist/docs/install.rst index 2763cea519..9ffcfac860 100644 --- a/pdns/dnsdistdist/docs/install.rst +++ b/pdns/dnsdistdist/docs/install.rst @@ -54,7 +54,6 @@ dnsdist depends on the following libraries: (required for building with ``meson``) * `libbpf `_ and `libxdp `_ (optional, `XSK`/`AF_XDP` support) * `libcap `_ (optional, capabilities support) -* `libh2o `_ (optional, incoming DoH support, deprecated in 1.9.0 in favor of ``nghttp2``) * `libsodium `_ (optional, DNSCrypt support) * `LMDB `_ (optional, LMDB support) * `net-snmp `_ (optional, SNMP support) diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 65e82ae8fe..25a4f24326 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -164,7 +164,7 @@ Listen Sockets * ``idleTimeout=30``: int - Set the idle timeout, in seconds. * ``ciphers``: str - The TLS ciphers to use, in OpenSSL format. Ciphers for TLS 1.3 must be specified via ``ciphersTLS13``. * ``ciphersTLS13``: str - The TLS ciphers to use for TLS 1.3, in OpenSSL format. - * ``serverTokens``: str - The content of the Server: HTTP header returned by dnsdist. The default is "h2o/dnsdist" when ``h2o`` is used, "nghttp2-/dnsdist" when ``nghttp2`` is. + * ``serverTokens``: str - The content of the Server: HTTP header returned by dnsdist. The default is "nghttp2-/dnsdist" when ``nghttp2`` is used. * ``customResponseHeaders={}``: table - Set custom HTTP header(s) returned by dnsdist. * ``ocspResponses``: list - List of files containing OCSP responses, in the same order than the certificates and keys, that will be used to provide OCSP stapling responses. * ``minTLSVersion``: str - Minimum version of the TLS protocol to support. Possible values are 'tls1.0', 'tls1.1', 'tls1.2' and 'tls1.3'. Default is to require at least TLS 1.0. @@ -187,7 +187,7 @@ Listen Sockets * ``keepIncomingHeaders``: bool - Whether to retain the incoming headers in memory, to be able to use :func:`HTTPHeaderRule` or :meth:`DNSQuestion.getHTTPHeaders`. Default is false. Before 1.8.0 the headers were always kept in-memory. * ``additionalAddresses``: list - List of additional addresses (with port) to listen on. Using this option instead of creating a new frontend for each address avoids the creation of new thread and Frontend objects, reducing the memory usage. The drawback is that there will be a single set of metrics for all addresses. * ``ignoreTLSConfigurationErrors=false``: bool - Ignore TLS configuration errors (such as invalid certificate path) and just issue a warning instead of aborting the whole process - * ``library``: str - Which underlying HTTP2 library should be used, either h2o or nghttp2. Until 1.9.0 only h2o was available, but the use of this library is now deprecated as it is no longer maintained. nghttp2 is the new default since 1.9.0. + * ``library``: str - Which underlying HTTP2 library should be used, only ``nghttp2`` is supported. * ``ktls=false``: bool - Whether to enable the experimental kernel TLS support on Linux, if both the kernel and the OpenSSL library support it. Default is false. * ``tlsAsyncMode=false``: bool - Whether to enable experimental asynchronous TLS I/O operations if the ``nghttp2`` library is used, ``OpenSSL`` is used as the TLS implementation and an asynchronous capable SSL engine (or provider) is loaded. See also :func:`loadTLSEngine` or :func:`loadTLSProvider` to load the engine (or provider). * ``readAhead``: bool - When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls. Default is true. diff --git a/pdns/dnsdistdist/docs/reference/selectors.rst b/pdns/dnsdistdist/docs/reference/selectors.rst index 1aafd51f9f..e5288ae1ec 100644 --- a/pdns/dnsdistdist/docs/reference/selectors.rst +++ b/pdns/dnsdistdist/docs/reference/selectors.rst @@ -354,9 +354,6 @@ Selectors can be combined via :func:`AndRule`, :func:`OrRule` and :func:`NotRule Matches against the TLS Server Name Indication value sent by the client, if any. Only makes sense for DoT or DoH, and for that last one matching on the HTTP Host header using :func:`HTTPHeaderRule` might provide more consistent results. - As of the version 2.3.0-beta of h2o, it is unfortunately not possible to extract the SNI value from DoH - connections, and it is therefore necessary to use the HTTP Host header until version 2.3.0 is released, - or ``nghttp2`` is used for incoming DoH instead (1.9.0+). :param str name: The exact SNI name to match. diff --git a/pdns/dnsdistdist/docs/upgrade_guide.rst b/pdns/dnsdistdist/docs/upgrade_guide.rst index b8d253898e..69572f81d1 100644 --- a/pdns/dnsdistdist/docs/upgrade_guide.rst +++ b/pdns/dnsdistdist/docs/upgrade_guide.rst @@ -6,6 +6,11 @@ Upgrade Guide Custom load-balancing policies written in Lua now need to return the index in the servers array of the backend they intend to select, instead of returning a reference to the backend itself. +dnsdist no longer supports ``h2o`` for incoming DNS over HTTPS, as it is unfortunately no longer maintained in a way that is suitable for use as a library +(see https://github.com/h2o/h2o/issues/3230). This means that only ``nghttp2`` is supported from now on. +Note that ``nghttp2`` only supports HTTP/2, and not HTTP/1, while ``h2o`` supported both. This is not an issue for actual DNS over HTTPS clients that +support HTTP/2, but might be one in setups running dnsdist behind a reverse-proxy that does not support HTTP/2. See :doc:`guides/dns-over-https` for some work-around. + 1.9.x to 2.0.0 -------------- diff --git a/pdns/dnsdistdist/doh.cc b/pdns/dnsdistdist/doh.cc deleted file mode 100644 index 9f7610125a..0000000000 --- a/pdns/dnsdistdist/doh.cc +++ /dev/null @@ -1,1792 +0,0 @@ -#include "config.h" -#include "doh.hh" - -#ifdef HAVE_DNS_OVER_HTTPS -#ifdef HAVE_LIBH2OEVLOOP -#define H2O_USE_EPOLL 1 - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include "base64.hh" -#include "dnsname.hh" -#undef CERT -#include "dnsdist.hh" -#include "dnsdist-tcp.hh" -#include "misc.hh" -#include "dns.hh" -#include "dolog.hh" -#include "dnsdist-concurrent-connections.hh" -#include "dnsdist-dnsparser.hh" -#include "dnsdist-ecs.hh" -#include "dnsdist-metrics.hh" -#include "dnsdist-proxy-protocol.hh" -#include "libssl.hh" -#include "threadname.hh" - -/* So, how does this work. We use h2o for our http2 and TLS needs. - If the operator has configured multiple IP addresses to listen on, - we launch multiple h2o listener threads. We can hook in to multiple - URLs though on the same IP. There is no SNI yet (I think). - - h2o is event driven, so we get callbacks if a new DNS query arrived. - When it does, we do some minimal parsing on it, and send it on to the - dnsdist worker thread which we also launched. - - This dnsdist worker thread injects the query into the normal dnsdist flow - (over a pipe). The response also goes back over a (different) pipe, - where we pick it up and deliver it back to h2o. - - For coordination, we use the h2o socket multiplexer, which is sensitive to our - pipe too. -*/ - -/* h2o notes. - Paths and parameters etc just *happen* to be null-terminated in HTTP2. - They are not in HTTP1. So you MUST use the length field! -*/ - -/* 'Intermediate' compatibility from https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29 */ -static constexpr std::string_view DOH_DEFAULT_CIPHERS = "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; - -class DOHAcceptContext -{ -public: - DOHAcceptContext() - { - memset(&d_h2o_accept_ctx, 0, sizeof(d_h2o_accept_ctx)); - d_rotatingTicketsKey.clear(); - } - DOHAcceptContext(const DOHAcceptContext&) = delete; - DOHAcceptContext(DOHAcceptContext&&) = delete; - DOHAcceptContext& operator=(const DOHAcceptContext&) = delete; - DOHAcceptContext& operator=(DOHAcceptContext&&) = delete; - - h2o_accept_ctx_t* get() - { - return &d_h2o_accept_ctx; - } - - ~DOHAcceptContext() - { - SSL_CTX_free(d_h2o_accept_ctx.ssl_ctx); - d_h2o_accept_ctx.ssl_ctx = nullptr; - } - - void decrementConcurrentConnections() const - { - if (d_cs != nullptr) { - --d_cs->tcpCurrentConnections; - } - } - - [[nodiscard]] time_t getNextTicketsKeyRotation() const - { - return d_ticketsKeyNextRotation; - } - - [[nodiscard]] size_t getTicketsKeysCount() const - { - size_t res = 0; - if (d_ticketKeys) { - res = d_ticketKeys->getKeysCount(); - } - return res; - } - - void rotateTicketsKey(time_t now) - { - if (!d_ticketKeys) { - return; - } - - d_ticketKeys->rotateTicketsKey(now); - - if (d_ticketsKeyRotationDelay > 0) { - d_ticketsKeyNextRotation = now + d_ticketsKeyRotationDelay; - } - } - - void loadTicketsKeys(const std::string& keyFile) - { - if (!d_ticketKeys) { - return; - } - d_ticketKeys->loadTicketsKeys(keyFile); - - if (d_ticketsKeyRotationDelay > 0) { - d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay; - } - } - - void handleTicketsKeyRotation() - { - if (d_ticketsKeyRotationDelay == 0) { - return; - } - - time_t now = time(nullptr); - if (now > d_ticketsKeyNextRotation) { - if (d_rotatingTicketsKey.test_and_set()) { - /* someone is already rotating */ - return; - } - try { - rotateTicketsKey(now); - - d_rotatingTicketsKey.clear(); - } - catch(const std::runtime_error& e) { - d_rotatingTicketsKey.clear(); - throw std::runtime_error(std::string("Error generating a new tickets key for TLS context:") + e.what()); - } - catch(...) { - d_rotatingTicketsKey.clear(); - throw; - } - } - } - - std::map d_ocspResponses; - std::unique_ptr d_ticketKeys{nullptr}; - // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes) - pdns::UniqueFilePtr d_keyLogFile{nullptr}; - ClientState* d_cs{nullptr}; - time_t d_ticketsKeyRotationDelay{0}; - -private: - h2o_accept_ctx_t d_h2o_accept_ctx{}; - time_t d_ticketsKeyNextRotation{0}; - std::atomic_flag d_rotatingTicketsKey; -}; - -struct DOHUnit; - -// we create one of these per thread, and pass around a pointer to it -// through the bowels of h2o -struct DOHServerConfig -{ - DOHServerConfig(uint32_t idleTimeout, uint32_t internalPipeBufferSize): accept_ctx(std::make_shared()) - { -#ifndef USE_SINGLE_ACCEPTOR_THREAD - { - auto [sender, receiver] = pdns::channel::createObjectQueue(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverBlocking, internalPipeBufferSize); - d_querySender = std::move(sender); - d_queryReceiver = std::move(receiver); - } -#endif /* USE_SINGLE_ACCEPTOR_THREAD */ - - { - auto [sender, receiver] = pdns::channel::createObjectQueue(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, internalPipeBufferSize); - d_responseSender = std::move(sender); - d_responseReceiver = std::move(receiver); - } - - h2o_config_init(&h2o_config); - h2o_config.http2.idle_timeout = static_cast(idleTimeout) * 1000; - /* if you came here for a way to make the number of concurrent streams (concurrent requests per connection) - configurable, or even just bigger, I have bad news for you. - h2o_config.http2.max_concurrent_requests_per_connection (default of 100) is capped by - H2O_HTTP2_SETTINGS_HOST.max_concurrent_streams which is not configurable. Even if decided to change the - hard-coded value, libh2o's author warns that there might be parts of the code where the stream ID is stored - in 8 bits, making 256 a hard value: https://github.com/h2o/h2o/issues/805 - */ - } - DOHServerConfig(const DOHServerConfig&) = delete; - DOHServerConfig(DOHServerConfig&&) = delete; - DOHServerConfig& operator=(const DOHServerConfig&) = delete; - DOHServerConfig& operator=(DOHServerConfig&&) = delete; - ~DOHServerConfig() = default; - - std::set> paths; - h2o_globalconf_t h2o_config{}; - h2o_context_t h2o_ctx{}; - std::unique_ptr h2o_socket{nullptr, h2o_socket_close}; - std::shared_ptr accept_ctx{nullptr}; - ClientState* clientState{nullptr}; - std::shared_ptr dohFrontend{nullptr}; -#ifndef USE_SINGLE_ACCEPTOR_THREAD - pdns::channel::Sender d_querySender; - pdns::channel::Receiver d_queryReceiver; -#endif /* USE_SINGLE_ACCEPTOR_THREAD */ - pdns::channel::Sender d_responseSender; - pdns::channel::Receiver d_responseReceiver; -}; - -struct DOHUnit : public DOHUnitInterface -{ - DOHUnit(PacketBuffer&& query_, std::string&& path_, std::string&& host_): path(std::move(path_)), host(std::move(host_)), query(std::move(query_)) - { - ids.ednsAdded = false; - } - ~DOHUnit() override - { - if (self != nullptr) { - *self = nullptr; - } - } - - DOHUnit(const DOHUnit&) = delete; - DOHUnit(DOHUnit&&) = delete; - DOHUnit& operator=(const DOHUnit&) = delete; - DOHUnit& operator=(DOHUnit&&) = delete; - - InternalQueryState ids; - std::string sni; - std::string path; - std::string scheme; - std::string host; - std::string contentType; - PacketBuffer query; - PacketBuffer response; - std::unique_ptr> headers; - st_h2o_req_t* req{nullptr}; - DOHUnit** self{nullptr}; - DOHServerConfig* dsc{nullptr}; - pdns::channel::Sender* responseSender{nullptr}; - size_t query_at{0}; - int rsock{-1}; - /* the status_code is set from - processDOHQuery() (which is executed in - the DOH client thread) so that the correct - response can be sent in on_dnsdist(), - after the DOHUnit has been passed back to - the main DoH thread. - */ - uint16_t status_code{200}; - /* whether the query was re-sent to the backend over - TCP after receiving a truncated answer over UDP */ - bool tcp{false}; - bool truncated{false}; - - [[nodiscard]] std::string getHTTPPath() const override; - [[nodiscard]] std::string getHTTPQueryString() const override; - [[nodiscard]] const std::string& getHTTPHost() const override; - [[nodiscard]] const std::string& getHTTPScheme() const override; - [[nodiscard]] const std::unordered_map& getHTTPHeaders() const override; - [[nodiscard]] std::shared_ptr getQuerySender() const override - { - return nullptr; - } - void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType="") override; - void handleTimeout() override; - void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, [[maybe_unused]] const std::shared_ptr& downstream) override; -}; -using DOHUnitUniquePtr = std::unique_ptr; - -/* This internal function sends back the object to the main thread to send a reply. - The caller should NOT release or touch the unit after calling this function */ -static void sendDoHUnitToTheMainThread(DOHUnitUniquePtr&& dohUnit, const char* description) -{ - if (dohUnit->responseSender == nullptr) { - return; - } - try { - if (!dohUnit->responseSender->send(std::move(dohUnit))) { - ++dnsdist::metrics::g_stats.dohResponsePipeFull; - vinfolog("Unable to pass a %s to the DoH worker thread because the pipe is full", description); - } - } catch (const std::exception& e) { - vinfolog("Unable to pass a %s to the DoH worker thread because we couldn't write to the pipe: %s", description, e.what()); - } -} - -/* This function is called from other threads than the main DoH one, - instructing it to send a 502 error to the client. */ -void DOHUnit::handleTimeout() -{ - status_code = 502; - sendDoHUnitToTheMainThread(std::unique_ptr(this), "DoH timeout"); -} - -struct DOHConnection -{ - std::shared_ptr d_acceptCtx{nullptr}; - ComboAddress d_remote; - ComboAddress d_local; - struct timeval d_connectionStartTime{0, 0}; - size_t d_nbQueries{0}; - int d_desc{-1}; - uint8_t d_concurrentStreams{0}; -}; - -static thread_local std::unordered_map t_conns; - -static void on_socketclose(void *data) -{ - auto* conn = static_cast(data); - if (conn != nullptr) { - if (conn->d_acceptCtx) { - struct timeval now{}; - gettimeofday(&now, nullptr); - - auto diff = now - conn->d_connectionStartTime; - - conn->d_acceptCtx->decrementConcurrentConnections(); - conn->d_acceptCtx->d_cs->updateTCPMetrics(conn->d_nbQueries, diff.tv_sec * 1000 + diff.tv_usec / 1000, 0); - } - - dnsdist::IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(conn->d_remote); - // you can no longer touch conn, or data, after this call - t_conns.erase(conn->d_desc); - } -} - -static const std::string& getReasonFromStatusCode(uint16_t statusCode) -{ - /* no need to care too much about this, HTTP/2 has no 'reason' anyway */ - static const std::unordered_map reasons = { - { 200, "OK" }, - { 301, "Moved Permanently" }, - { 302, "Found" }, - { 303, "See Other" }, - { 304, "Not Modified" }, - { 305, "Use Proxy" }, - { 306, "Switch Proxy" }, - { 307, "Temporary Redirect" }, - { 308, "Permanent Redirect" }, - { 400, "Bad Request" }, - { 401, "Unauthorized" }, - { 402, "Payment Required" }, - { 403, "Forbidden" }, - { 404, "Not Found" }, - { 405, "Method Not Allowed" }, - { 406, "Not Acceptable" }, - { 407, "Proxy Authentication Required" }, - { 408, "Request Timeout" }, - { 409, "Conflict" }, - { 410, "Gone" }, - { 411, "Length Required" }, - { 412, "Precondition Failed" }, - { 413, "Payload Too Large" }, - { 414, "URI Too Long" }, - { 415, "Unsupported Media Type" }, - { 416, "Range Not Satisfiable" }, - { 417, "Expectation Failed" }, - { 418, "I'm a teapot" }, - { 451, "Unavailable For Legal Reasons" }, - { 500, "Internal Server Error" }, - { 501, "Not Implemented" }, - { 502, "Bad Gateway" }, - { 503, "Service Unavailable" }, - { 504, "Gateway Timeout" }, - { 505, "HTTP Version Not Supported" } - }; - static const std::string unknown = "Unknown"; - - const auto reasonIt = reasons.find(statusCode); - if (reasonIt == reasons.end()) { - return unknown; - } - return reasonIt->second; -} - -static DOHConnection* getConnectionFromQuery(const h2o_req_t* req) -{ - h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn); - const int descriptor = h2o_socket_get_fd(sock); - if (descriptor == -1) { - /* this should not happen, but let's not crash on it */ - return nullptr; - } - return &t_conns.at(descriptor); -} - -/* Always called from the main DoH thread */ -static void handleResponse(DOHFrontend& dohFrontend, st_h2o_req_t* req, uint16_t statusCode, const PacketBuffer& response, const std::unordered_map& customResponseHeaders, const std::string& contentType, bool addContentType) -{ - constexpr int overwrite_if_exists = 1; - constexpr int maybe_token = 1; - for (auto const& headerPair : customResponseHeaders) { - h2o_set_header_by_str(&req->pool, &req->res.headers, headerPair.first.c_str(), headerPair.first.size(), maybe_token, headerPair.second.c_str(), headerPair.second.size(), overwrite_if_exists); - } - - if (statusCode == 200) { - ++dohFrontend.d_validresponses; - req->res.status = 200; - - if (addContentType) { - if (contentType.empty()) { - h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, H2O_STRLIT("application/dns-message")); - } - else { - /* we need to duplicate the header content because h2o keeps a pointer and we will be deleted before the response has been sent */ - h2o_iovec_t contentTypeVect = h2o_strdup(&req->pool, contentType.c_str(), contentType.size()); - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API - h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, contentTypeVect.base, contentTypeVect.len); - } - } - - if (dohFrontend.d_sendCacheControlHeaders && response.size() > sizeof(dnsheader)) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - uint32_t minTTL = getDNSPacketMinTTL(reinterpret_cast(response.data()), response.size()); - if (minTTL != std::numeric_limits::max()) { - std::string cacheControlValue = "max-age=" + std::to_string(minTTL); - /* we need to duplicate the header content because h2o keeps a pointer and we will be deleted before the response has been sent */ - h2o_iovec_t ccv = h2o_strdup(&req->pool, cacheControlValue.c_str(), cacheControlValue.size()); - h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CACHE_CONTROL, nullptr, ccv.base, ccv.len); - } - } - - req->res.content_length = response.size(); - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API - h2o_send_inline(req, reinterpret_cast(response.data()), response.size()); - } - else if (statusCode >= 300 && statusCode < 400) { - /* in that case the response is actually a URL */ - /* we need to duplicate the URL because h2o uses it for the location header, keeping a pointer, and we will be deleted before the response has been sent */ - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API - h2o_iovec_t url = h2o_strdup(&req->pool, reinterpret_cast(response.data()), response.size()); - h2o_send_redirect(req, statusCode, getReasonFromStatusCode(statusCode).c_str(), url.base, url.len); - ++dohFrontend.d_redirectresponses; - } - else { - // we need to make sure it's null-terminated */ - if (!response.empty() && response.at(response.size() - 1) == 0) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API - h2o_send_error_generic(req, statusCode, getReasonFromStatusCode(statusCode).c_str(), reinterpret_cast(response.data()), H2O_SEND_ERROR_KEEP_HEADERS); - } - else { - switch(statusCode) { - case 400: - h2o_send_error_400(req, getReasonFromStatusCode(statusCode).c_str(), "invalid DNS query" , 0); - break; - case 403: - h2o_send_error_403(req, getReasonFromStatusCode(statusCode).c_str(), "DoH query not allowed", 0); - break; - case 502: - h2o_send_error_502(req, getReasonFromStatusCode(statusCode).c_str(), "no downstream server available", 0); - break; - case 500: - /* fall-through */ - default: - h2o_send_error_500(req, getReasonFromStatusCode(statusCode).c_str(), "Internal Server Error", 0); - break; - } - } - - ++dohFrontend.d_errorresponses; - } - - if (auto* conn = getConnectionFromQuery(req)) { - --conn->d_concurrentStreams; - } -} - -static std::unique_ptr getDUFromIDS(InternalQueryState& ids) -{ - auto dohUnit = std::unique_ptr(dynamic_cast(ids.du.release())); - return dohUnit; -} - -class DoHTCPCrossQuerySender final : public TCPQuerySender -{ -public: - DoHTCPCrossQuerySender() = default; - DoHTCPCrossQuerySender(const DoHTCPCrossQuerySender&) = delete; - DoHTCPCrossQuerySender(DoHTCPCrossQuerySender&&) = delete; - DoHTCPCrossQuerySender& operator=(const DoHTCPCrossQuerySender&) = delete; - DoHTCPCrossQuerySender& operator=(DoHTCPCrossQuerySender&&) = delete; - ~DoHTCPCrossQuerySender() final = default; - - [[nodiscard]] bool active() const override - { - return true; - } - - void handleResponse(const struct timeval& now, TCPResponse&& response) override - { - (void)now; - if (!response.d_idstate.du) { - return; - } - - auto dohUnit = getDUFromIDS(response.d_idstate); - if (dohUnit->responseSender == nullptr) { - return; - } - - dohUnit->response = std::move(response.d_buffer); - dohUnit->ids = std::move(response.d_idstate); - DNSResponse dr(dohUnit->ids, dohUnit->response, dohUnit->downstream); - - dnsheader cleartextDH{}; - memcpy(&cleartextDH, dr.getHeader().get(), sizeof(cleartextDH)); - - if (!response.isAsync()) { - dr.ids.du = std::move(dohUnit); - - if (!processResponse(dynamic_cast(dr.ids.du.get())->response, dr, false)) { - if (dr.ids.du) { - dohUnit = getDUFromIDS(dr.ids); - dohUnit->status_code = 503; - sendDoHUnitToTheMainThread(std::move(dohUnit), "Response dropped by rules"); - } - return; - } - - if (dr.isAsynchronous()) { - return; - } - - dohUnit = getDUFromIDS(dr.ids); - } - - if (!dohUnit->ids.selfGenerated) { - auto udiff = dohUnit->ids.queryRealTime.udiff(); - vinfolog("Got answer from %s, relayed to %s (https), took %d us", dohUnit->downstream->d_config.remote.toStringWithPort(), dohUnit->ids.origRemote.toStringWithPort(), udiff); - - auto backendProtocol = dohUnit->downstream->getProtocol(); - if (backendProtocol == dnsdist::Protocol::DoUDP && dohUnit->tcp) { - backendProtocol = dnsdist::Protocol::DoTCP; - } - handleResponseSent(dohUnit->ids, udiff, dohUnit->ids.origRemote, dohUnit->downstream->d_config.remote, dohUnit->response.size(), cleartextDH, backendProtocol, true); - } - - ++dnsdist::metrics::g_stats.responses; - if (dohUnit->ids.cs != nullptr) { - ++dohUnit->ids.cs->responses; - } - - sendDoHUnitToTheMainThread(std::move(dohUnit), "cross-protocol response"); - } - - void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override - { - return handleResponse(now, std::move(response)); - } - - void notifyIOError(const struct timeval& now, TCPResponse&& response) override - { - (void)now; - auto& query = response.d_idstate; - if (!query.du) { - return; - } - - auto dohUnit = getDUFromIDS(query); - if (dohUnit->responseSender == nullptr) { - return; - } - - dohUnit->ids = std::move(query); - dohUnit->status_code = 502; - sendDoHUnitToTheMainThread(std::move(dohUnit), "cross-protocol error response"); - } -}; - -class DoHCrossProtocolQuery : public CrossProtocolQuery -{ -public: - DoHCrossProtocolQuery(DOHUnitUniquePtr&& dohUnit, bool isResponse) - { - if (isResponse) { - /* happens when a response becomes async */ - query = InternalQuery(std::move(dohUnit->response), std::move(dohUnit->ids)); - } - else { - /* we need to duplicate the query here because we might need - the existing query later if we get a truncated answer */ - query = InternalQuery(PacketBuffer(dohUnit->query), std::move(dohUnit->ids)); - } - - /* it might have been moved when we moved dohUnit->ids */ - if (dohUnit) { - query.d_idstate.du = std::move(dohUnit); - } - - /* we _could_ remove it from the query buffer and put in query's d_proxyProtocolPayload, - clearing query.d_proxyProtocolPayloadAdded and dohUnit->proxyProtocolPayloadSize. - Leave it for now because we know that the onky case where the payload has been - added is when we tried over UDP, got a TC=1 answer and retried over TCP/DoT, - and we know the TCP/DoT code can handle it. */ - query.d_proxyProtocolPayloadAdded = query.d_idstate.d_proxyProtocolPayloadSize > 0; - downstream = query.d_idstate.du->downstream; - } - - void handleInternalError() - { - auto dohUnit = getDUFromIDS(query.d_idstate); - if (dohUnit == nullptr) { - return; - } - dohUnit->status_code = 502; - sendDoHUnitToTheMainThread(std::move(dohUnit), "DoH internal error"); - } - - std::shared_ptr getTCPQuerySender() override - { - auto* unit = dynamic_cast(query.d_idstate.du.get()); - if (unit != nullptr) { - unit->downstream = downstream; - } - return s_sender; - } - - DNSQuestion getDQ() override - { - auto& ids = query.d_idstate; - DNSQuestion dq(ids, query.d_buffer); - return dq; - } - - DNSResponse getDR() override - { - auto& ids = query.d_idstate; - DNSResponse dr(ids, query.d_buffer, downstream); - return dr; - } - - DOHUnitUniquePtr releaseDU() - { - return getDUFromIDS(query.d_idstate); - } - -private: - static std::shared_ptr s_sender; -}; - -std::shared_ptr DoHCrossProtocolQuery::s_sender = std::make_shared(); - -std::unique_ptr getDoHCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse) -{ - if (!dq.ids.du) { - throw std::runtime_error("Trying to create a DoH cross protocol query without a valid DoH unit"); - } - - auto dohUnit = getDUFromIDS(dq.ids); - if (&dq.ids != &dohUnit->ids) { - dohUnit->ids = std::move(dq.ids); - } - - dohUnit->ids.origID = dq.getHeader()->id; - - if (!isResponse) { - if (dohUnit->query.data() != dq.getMutableData().data()) { - dohUnit->query = std::move(dq.getMutableData()); - } - } - else { - if (dohUnit->response.data() != dq.getMutableData().data()) { - dohUnit->response = std::move(dq.getMutableData()); - } - } - - return std::make_unique(std::move(dohUnit), isResponse); -} - -/* - We are not in the main DoH thread but in the DoH 'client' thread. -*/ -static void processDOHQuery(DOHUnitUniquePtr&& unit, bool inMainThread = false) -{ - const auto handleImmediateResponse = [inMainThread](DOHUnitUniquePtr&& dohUnit, const char* reason) { - if (inMainThread) { - handleResponse(*dohUnit->dsc->dohFrontend, dohUnit->req, dohUnit->status_code, dohUnit->response, dohUnit->dsc->dohFrontend->d_customResponseHeaders, dohUnit->contentType, true); - /* so the unique pointer is stored in the InternalState which itself is stored in the unique pointer itself. We likely need - a better design, but for now let's just reset the internal one since we know it is no longer needed. */ - dohUnit->ids.du.reset(); - } - else { - sendDoHUnitToTheMainThread(std::move(dohUnit), reason); - } - }; - - dnsdist::configuration::refreshLocalRuntimeConfiguration(); - - auto& ids = unit->ids; - uint16_t queryId = 0; - ComboAddress remote; - - try { - if (unit->req == nullptr) { - // we got closed meanwhile. XXX small race condition here - // but we should be fine as long as we don't touch dohUnit->req - // outside of the main DoH thread - unit->status_code = 500; - handleImmediateResponse(std::move(unit), "DoH killed in flight"); - return; - } - - remote = ids.origRemote; - DOHServerConfig* dsc = unit->dsc; - ClientState& clientState = *dsc->clientState; - - if (unit->query.size() < sizeof(dnsheader) || unit->query.size() > std::numeric_limits::max()) { - ++dnsdist::metrics::g_stats.nonCompliantQueries; - ++clientState.nonCompliantQueries; - unit->status_code = 400; - handleImmediateResponse(std::move(unit), "DoH non-compliant query"); - return; - } - - ++clientState.queries; - ++dnsdist::metrics::g_stats.queries; - ids.queryRealTime.start(); - - { - /* don't keep that pointer around, it will be invalidated if the buffer is ever resized */ - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - const dnsheader_aligned dnsHeader(unit->query.data()); - - if (!checkQueryHeaders(*dnsHeader, clientState)) { - unit->status_code = 400; - handleImmediateResponse(std::move(unit), "DoH invalid headers"); - return; - } - - if (dnsHeader->qdcount == 0U) { - dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) { - header.rcode = RCode::NotImp; - header.qr = true; - return true; - }); - unit->response = std::move(unit->query); - - handleImmediateResponse(std::move(unit), "DoH empty query"); - return; - } - - queryId = ntohs(dnsHeader->id); - } - - { - // if there was no EDNS, we add it with a large buffer size - // so we can use UDP to talk to the backend. - dnsheader_aligned dnsHeader(unit->query.data()); - if (dnsHeader.get()->arcount == 0U) { - if (addEDNS(unit->query, 4096, false, 4096, 0)) { - ids.ednsAdded = true; - } - } - } - - auto downstream = unit->downstream; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - ids.qname = DNSName(reinterpret_cast(unit->query.data()), static_cast(unit->query.size()), static_cast(sizeof(dnsheader)), false, &ids.qtype, &ids.qclass); - DNSQuestion dnsQuestion(ids, unit->query); - const uint16_t* flags = getFlagsFromDNSHeader(dnsQuestion.getHeader().get()); - ids.origFlags = *flags; - ids.cs = &clientState; - dnsQuestion.sni = std::move(unit->sni); - ids.du = std::move(unit); - auto result = processQuery(dnsQuestion, downstream); - - if (result == ProcessQueryResult::Drop) { - unit = getDUFromIDS(ids); - unit->status_code = 403; - handleImmediateResponse(std::move(unit), "DoH dropped query"); - return; - } - if (result == ProcessQueryResult::Asynchronous) { - return; - } - if (result == ProcessQueryResult::SendAnswer) { - unit = getDUFromIDS(ids); - if (unit->response.empty()) { - unit->response = std::move(unit->query); - } - if (unit->response.size() >= sizeof(dnsheader) && unit->contentType.empty()) { - dnsheader_aligned dnsHeader(unit->response.data()); - handleResponseSent(unit->ids.qname, QType(unit->ids.qtype), 0, unit->ids.origDest, ComboAddress(), unit->response.size(), *(dnsHeader.get()), dnsdist::Protocol::DoH, dnsdist::Protocol::DoH, false); - } - handleImmediateResponse(std::move(unit), "DoH self-answered response"); - return; - } - - unit = getDUFromIDS(ids); - if (result != ProcessQueryResult::PassToBackend) { - unit->status_code = 500; - handleImmediateResponse(std::move(unit), "DoH no backend available"); - return; - } - - if (downstream == nullptr) { - unit->status_code = 502; - handleImmediateResponse(std::move(unit), "DoH no backend available"); - return; - } - - unit->downstream = downstream; - - if (downstream->isTCPOnly()) { - std::string proxyProtocolPayload; - /* we need to do this _before_ creating the cross protocol query because - after that the buffer will have been moved */ - if (downstream->d_config.useProxyProtocol) { - proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion); - } - - unit->ids.origID = htons(queryId); - unit->tcp = true; - - /* this moves du->ids, careful! */ - auto cpq = std::make_unique(std::move(unit), false); - if (!cpq) { - // make linters happy - return; - } - cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload); - - if (downstream->passCrossProtocolQuery(std::move(cpq))) { - return; - } - - if (inMainThread) { - // cpq is not altered if the call fails but linters are not smart enough to notice that - if (cpq) { - // NOLINTNEXTLINE(bugprone-use-after-move): cpq is not altered if the call fails - unit = cpq->releaseDU(); - } - unit->status_code = 502; - handleImmediateResponse(std::move(unit), "DoH internal error"); - } - else { - // cpq is not altered if the call fails but linters are not smart enough to notice that - if (cpq) { - // NOLINTNEXTLINE(bugprone-use-after-move): cpq is not altered if the call fails - cpq->handleInternalError(); - } - } - return; - } - - auto& query = unit->query; - ids.du = std::move(unit); - if (!assignOutgoingUDPQueryToBackend(downstream, htons(queryId), dnsQuestion, query)) { - unit = getDUFromIDS(ids); - unit->status_code = 502; - handleImmediateResponse(std::move(unit), "DoH internal error"); - return; - } - } - catch (const std::exception& e) { - vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what()); - unit->status_code = 500; - handleImmediateResponse(std::move(unit), "DoH internal error"); - return; - } -} - -/* called when a HTTP response is about to be sent, from the main DoH thread */ -static void on_response_ready_cb(struct st_h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot) -{ - (void)self; - if (req == nullptr) { - return; - } - - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API - auto* dsc = static_cast(req->conn->ctx->storage.entries[0].data); - - DOHFrontend::HTTPVersionStats* stats = nullptr; - if (req->version < 0x200) { - /* HTTP 1.x */ - stats = &dsc->dohFrontend->d_http1Stats; - } - else { - /* HTTP 2.0 */ - stats = &dsc->dohFrontend->d_http2Stats; - } - - switch (req->res.status) { - case 200: - ++stats->d_nb200Responses; - break; - case 400: - ++stats->d_nb400Responses; - break; - case 403: - ++stats->d_nb403Responses; - break; - case 500: - ++stats->d_nb500Responses; - break; - case 502: - ++stats->d_nb502Responses; - break; - default: - ++stats->d_nbOtherResponses; - break; - } - - h2o_setup_next_ostream(req, slot); -} - -/* this is called by h2o when our request dies. - We use this to signal to the 'du' that this req is no longer alive */ -static void on_generator_dispose(void *_self) -{ - auto* dohUnit = static_cast(_self); - if (*dohUnit != nullptr) { // if nullptr, on_dnsdist cleaned up dohUnit already - (*dohUnit)->self = nullptr; - (*dohUnit)->req = nullptr; - } -} - -/* This executes in the main DoH thread. - We allocate a DOHUnit and send it to dnsdistclient() function in the doh client thread - via a pipe */ -static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_req_t* req, PacketBuffer&& query, const ComboAddress& local, const ComboAddress& remote, std::string&& path) -{ - auto* conn = getConnectionFromQuery(req); - - try { - /* we only parse it there as a sanity check, we will parse it again later */ - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - DNSPacketMangler mangler(reinterpret_cast(query.data()), query.size()); - mangler.skipDomainName(); - mangler.skipBytes(4); - - /* we are doing quite some copies here, sorry about that, - but we can't keep accessing the req object once we are in a different thread - because the request might get killed by h2o at pretty much any time */ - auto dohUnit = std::make_unique(std::move(query), std::move(path), std::string(req->authority.base, req->authority.len)); - dohUnit->dsc = dsc; - dohUnit->req = req; - dohUnit->ids.origDest = local; - dohUnit->ids.origRemote = remote; - dohUnit->ids.protocol = dnsdist::Protocol::DoH; - dohUnit->responseSender = &dsc->d_responseSender; - if (req->scheme != nullptr) { - dohUnit->scheme = std::string(req->scheme->name.base, req->scheme->name.len); - } - dohUnit->query_at = req->query_at; - - if (dsc->dohFrontend->d_keepIncomingHeaders) { - dohUnit->headers = std::make_unique>(); - dohUnit->headers->reserve(req->headers.size); - for (size_t i = 0; i < req->headers.size; ++i) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API - (*dohUnit->headers)[std::string(req->headers.entries[i].name->base, req->headers.entries[i].name->len)] = std::string(req->headers.entries[i].value.base, req->headers.entries[i].value.len); - } - } - - if (conn != nullptr) { - ++conn->d_concurrentStreams; - } -#ifdef HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME - h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn); - const char * sni = h2o_socket_get_ssl_server_name(sock); - if (sni != nullptr) { - dohUnit->sni = sni; - } -#endif /* HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME */ - dohUnit->self = static_cast(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose)); - *(dohUnit->self) = dohUnit.get(); - -#ifdef USE_SINGLE_ACCEPTOR_THREAD - processDOHQuery(std::move(dohUnit), true); -#else /* USE_SINGLE_ACCEPTOR_THREAD */ - try { - if (!dsc->d_querySender.send(std::move(dohUnit))) { - ++dnsdist::metrics::g_stats.dohQueryPipeFull; - vinfolog("Unable to pass a DoH query to the DoH worker thread because the pipe is full"); - if (conn != nullptr) { - --conn->d_concurrentStreams; - } - h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0); - } - } - catch (...) { - vinfolog("Unable to pass a DoH query to the DoH worker thread because we couldn't write to the pipe: %s", stringerror()); - if (conn != nullptr) { - --conn->d_concurrentStreams; - } - h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0); - } -#endif /* USE_SINGLE_ACCEPTOR_THREAD */ - } - catch (const std::exception& e) { - vinfolog("Had error parsing DoH DNS packet from %s: %s", remote.toStringWithPort(), e.what()); - if (conn != nullptr) { - --conn->d_concurrentStreams; - } - h2o_send_error_400(req, "Bad Request", "The DNS query could not be parsed", 0); - } -} - -/* can only be called from the main DoH thread */ -static bool getHTTPHeaderValue(const h2o_req_t* req, const std::string& headerName, std::string_view& value) -{ - bool found = false; - /* early versions of boost::string_ref didn't have the ability to compare to string */ - std::string_view headerNameView(headerName); - - for (size_t i = 0; i < req->headers.size; ++i) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API - if (std::string_view(req->headers.entries[i].name->base, req->headers.entries[i].name->len) == headerNameView) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API - value = std::string_view(req->headers.entries[i].value.base, req->headers.entries[i].value.len); - /* don't stop there, we might have more than one header with the same name, and we want the last one */ - found = true; - } - } - - return found; -} - -/* can only be called from the main DoH thread */ -static std::optional processForwardedForHeader(const h2o_req_t* req, const ComboAddress& remote) -{ - static const std::string headerName = "x-forwarded-for"; - std::string_view value; - - if (getHTTPHeaderValue(req, headerName, value)) { - try { - auto pos = value.rfind(','); - if (pos != std::string_view::npos) { - ++pos; - for (; pos < value.size() && value[pos] == ' '; ++pos) - { - } - - if (pos < value.size()) { - value = value.substr(pos); - } - } - return ComboAddress(std::string(value)); - } - catch (const std::exception& e) { - vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.what()); - } - catch (const PDNSException& e) { - vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.reason); - } - } - - return std::nullopt; -} - -/* - A query has been parsed by h2o, this executes in the main DoH thread. - For GET, the base64url-encoded payload is in the 'dns' parameter, which might be the first parameter, or not. - For POST, the payload is the payload. - */ -static int doh_handler(h2o_handler_t *self, h2o_req_t *req) -{ - dnsdist::configuration::refreshLocalRuntimeConfiguration(); - - try { - if (req->conn->ctx->storage.size == 0) { - return 0; // although we might was well crash on this - } - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API - auto* dsc = static_cast(req->conn->ctx->storage.entries[0].data); - auto* connPtr = getConnectionFromQuery(req); - if (connPtr == nullptr) { - return 0; - } - auto& conn = *connPtr; - if (conn.d_concurrentStreams >= dnsdist::doh::MAX_INCOMING_CONCURRENT_STREAMS) { - vinfolog("Too many concurrent streams on connection from %d", conn.d_remote.toStringWithPort()); - return 0; - } - - ++conn.d_nbQueries; - - h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn); - if (conn.d_nbQueries == 1) { - if (h2o_socket_get_ssl_session_reused(sock) == 0) { - ++dsc->clientState->tlsNewSessions; - } - else { - ++dsc->clientState->tlsResumptions; - } - - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API - h2o_socket_getsockname(sock, reinterpret_cast(&conn.d_local)); - } - - auto remote = conn.d_remote; - if (dsc->dohFrontend->d_trustForwardedForHeader) { - auto newRemote = processForwardedForHeader(req, remote); - if (newRemote) { - remote = *newRemote; - } - } - - if (!dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL.match(remote)) { - ++dnsdist::metrics::g_stats.aclDrops; - vinfolog("Query from %s (DoH) dropped because of ACL", remote.toStringWithPort()); - h2o_send_error_403(req, "Forbidden", "DoH query not allowed because of ACL", 0); - return 0; - } - - if (const auto* tlsversion = h2o_socket_get_ssl_protocol_version(sock)) { - if (strcmp(tlsversion, "TLSv1.0") == 0) { - ++dsc->clientState->tls10queries; - } - else if (strcmp(tlsversion, "TLSv1.1") == 0) { - ++dsc->clientState->tls11queries; - } - else if (strcmp(tlsversion, "TLSv1.2") == 0) { - ++dsc->clientState->tls12queries; - } - else if (strcmp(tlsversion, "TLSv1.3") == 0) { - ++dsc->clientState->tls13queries; - } - else { - ++dsc->clientState->tlsUnknownqueries; - } - } - - if (dsc->dohFrontend->d_exactPathMatching) { - const std::string_view pathOnly(req->path_normalized.base, req->path_normalized.len); - if (dsc->paths.count(pathOnly) == 0) { - h2o_send_error_404(req, "Not Found", "there is no endpoint configured for this path", 0); - return 0; - } - } - - // would be nice to be able to use a std::string_view there, - // but regex (called by matches() internally) requires a null-terminated string - string path(req->path.base, req->path.len); - /* the responses map can be updated at runtime, so we need to take a copy of - the shared pointer, increasing the reference counter */ - auto responsesMap = dsc->dohFrontend->d_responsesMap; - /* 1 byte for the root label, 2 type, 2 class, 4 TTL (fake), 2 record length, 2 option length, 2 option code, 2 family, 1 source, 1 scope, 16 max for a full v6 */ - const size_t maxAdditionalSizeForEDNS = 35U; - if (responsesMap) { - for (const auto& entry : *responsesMap) { - if (entry->matches(path)) { - const auto& customHeaders = entry->getHeaders(); - ++conn.d_concurrentStreams; - handleResponse(*dsc->dohFrontend, req, entry->getStatusCode(), entry->getContent(), customHeaders ? *customHeaders : dsc->dohFrontend->d_customResponseHeaders, std::string(), false); - return 0; - } - } - } - - if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST")) != 0) { - ++dsc->dohFrontend->d_postqueries; - if (req->version >= 0x0200) { - ++dsc->dohFrontend->d_http2Stats.d_nbQueries; - } - else { - ++dsc->dohFrontend->d_http1Stats.d_nbQueries; - } - - PacketBuffer query; - /* We reserve a few additional bytes to be able to add EDNS later */ - query.reserve(req->entity.len + maxAdditionalSizeForEDNS); - query.resize(req->entity.len); - memcpy(query.data(), req->entity.base, req->entity.len); - doh_dispatch_query(dsc, self, req, std::move(query), conn.d_local, remote, std::move(path)); - } - else if(req->query_at != SIZE_MAX && (req->path.len - req->query_at > 5)) { - auto pos = path.find("?dns="); - if (pos == string::npos) { - pos = path.find("&dns="); - } - if (pos != string::npos) { - // need to base64url decode this - string sdns(path.substr(pos+5)); - std::replace(sdns.begin(), sdns.end(), '-', '+'); - std::replace(sdns.begin(), sdns.end(), '_', '/'); - // re-add padding that may have been missing - switch (sdns.size() % 4) { - case 2: - sdns.append(2, '='); - break; - case 3: - sdns.append(1, '='); - break; - } - - PacketBuffer decoded; - - /* rough estimate so we hopefully don't need a new allocation later */ - /* We reserve at few additional bytes to be able to add EDNS later */ - const size_t estimate = ((sdns.size() * 3) / 4); - decoded.reserve(estimate + maxAdditionalSizeForEDNS); - if(B64Decode(sdns, decoded) < 0) { - h2o_send_error_400(req, "Bad Request", "Unable to decode BASE64-URL", 0); - ++dsc->dohFrontend->d_badrequests; - return 0; - } - - ++dsc->dohFrontend->d_getqueries; - if (req->version >= 0x0200) { - ++dsc->dohFrontend->d_http2Stats.d_nbQueries; - } - else { - ++dsc->dohFrontend->d_http1Stats.d_nbQueries; - } - - doh_dispatch_query(dsc, self, req, std::move(decoded), conn.d_local, remote, std::move(path)); - } - else - { - vinfolog("HTTP request without DNS parameter: %s", req->path.base); - h2o_send_error_400(req, "Bad Request", "Unable to find the DNS parameter", 0); - ++dsc->dohFrontend->d_badrequests; - return 0; - } - } - else { - h2o_send_error_400(req, "Bad Request", "Unable to parse the request", 0); - ++dsc->dohFrontend->d_badrequests; - } - return 0; - } - catch (const std::exception& e) { - vinfolog("DOH Handler function failed with error: '%s'", e.what()); - return 0; - } -} - -const std::unordered_map& DOHUnit::getHTTPHeaders() const -{ - if (!headers) { - static const HeadersMap empty{}; - return empty; - } - return *headers; -} - -std::string DOHUnit::getHTTPPath() const -{ - if (query_at == SIZE_MAX) { - return path; - } - return {path, 0, query_at}; -} - -const std::string& DOHUnit::getHTTPHost() const -{ - return host; -} - -const std::string& DOHUnit::getHTTPScheme() const -{ - return scheme; -} - -std::string DOHUnit::getHTTPQueryString() const -{ - if (query_at == SIZE_MAX) { - return {}; - } - return path.substr(query_at); -} - -void DOHUnit::setHTTPResponse(uint16_t statusCode, PacketBuffer&& body_, const std::string& contentType_) -{ - status_code = statusCode; - response = std::move(body_); - if (!response.empty() && statusCode >= 400) { - // we need to make sure it's null-terminated */ - if (response.at(response.size() - 1) != 0) { - response.push_back(0); - } - } - - contentType = contentType_; -} - -#ifndef USE_SINGLE_ACCEPTOR_THREAD -/* query has been parsed by h2o, which called doh_handler() in the main DoH thread. - In order not to block for long, doh_handler() called doh_dispatch_query() which allocated - a DOHUnit object and passed it to us */ -static void dnsdistclient(pdns::channel::Receiver&& receiver) -{ - setThreadName("dnsdist/doh-cli"); - - for(;;) { - try { - auto tmp = receiver.receive(); - if (!tmp) { - continue; - } - auto dohUnit = std::move(*tmp); - /* we are not in the main DoH thread anymore, so there is a real risk of - a race condition where h2o kills the query while we are processing it, - so we can't touch the content of dohUnit->req until we are back into the - main DoH thread */ - if (dohUnit->req == nullptr) { - // it got killed in flight already - dohUnit->self = nullptr; - continue; - } - - processDOHQuery(std::move(dohUnit), false); - } - catch (const std::exception& e) { - vinfolog("Error while processing query received over DoH: %s", e.what()); - } - catch (...) { - vinfolog("Unspecified error while processing query received over DoH"); - } - } -} -#endif /* USE_SINGLE_ACCEPTOR_THREAD */ - -/* Called in the main DoH thread if h2o finds that dnsdist gave us an answer by writing into - the response channel so from: - - handleDOHTimeout() when we did not get a response fast enough (called - either from the health check thread (active) or from the frontend ones (reused)) - - dnsdistclient (error 500 because processDOHQuery() returned a negative value) - - processDOHQuery (self-answered queries) - */ -static void on_dnsdist(h2o_socket_t *listener, const char *err) -{ - (void)err; - /* we want to read as many responses from the pipe as possible before - giving up. Even if we are overloaded and fighting with the DoH connections - for the CPU, the first thing we need to do is to send responses to free slots - anyway, otherwise queries and responses are piling up in our pipes, consuming - memory and likely coming up too late after the client has gone away */ - auto* dsc = static_cast(listener->data); - while (true) { - DOHUnitUniquePtr dohUnit{nullptr}; - try { - auto tmp = dsc->d_responseReceiver.receive(); - if (!tmp) { - return; - } - dohUnit = std::move(*tmp); - } - catch (const std::exception& e) { - warnlog("Error reading a DOH internal response: %s", e.what()); - return; - } - - if (dohUnit->req == nullptr) { // it got killed in flight - dohUnit->self = nullptr; - continue; - } - - if (!dohUnit->tcp && - dohUnit->truncated && - dohUnit->query.size() > dohUnit->ids.d_proxyProtocolPayloadSize && - (dohUnit->query.size() - dohUnit->ids.d_proxyProtocolPayloadSize) > sizeof(dnsheader)) { - /* restoring the original ID */ - dnsdist::PacketMangling::editDNSHeaderFromRawPacket(&dohUnit->query.at(dohUnit->ids.d_proxyProtocolPayloadSize), [oldID=dohUnit->ids.origID](dnsheader& header) { - header.id = oldID; - return true; - }); - dohUnit->ids.forwardedOverUDP = false; - dohUnit->tcp = true; - dohUnit->truncated = false; - dohUnit->response.clear(); - - auto cpq = std::make_unique(std::move(dohUnit), false); - - if (g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq))) { - continue; - } - vinfolog("Unable to pass DoH query to a TCP worker thread after getting a TC response over UDP"); - continue; - } - - if (dohUnit->self != nullptr) { - // we are back in the h2o main thread now, so we don't risk - // a race (h2o killing the query) when accessing dohUnit->req anymore - *dohUnit->self = nullptr; // so we don't clean up again in on_generator_dispose - dohUnit->self = nullptr; - } - - handleResponse(*dsc->dohFrontend, dohUnit->req, dohUnit->status_code, dohUnit->response, dsc->dohFrontend->d_customResponseHeaders, dohUnit->contentType, true); - } -} - -/* called when a TCP connection has been accepted, the TLS session has not been established */ -static void on_accept(h2o_socket_t *listener, const char *err) -{ - auto* dsc = static_cast(listener->data); - - if (err != nullptr) { - return; - } - - h2o_socket_t* sock = h2o_evloop_socket_accept(listener); - if (sock == nullptr) { - return; - } - - const int descriptor = h2o_socket_get_fd(sock); - if (descriptor == -1) { - h2o_socket_close(sock); - return; - } - - ComboAddress remote; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): h2o API - if (h2o_socket_getpeername(sock, reinterpret_cast(&remote)) == 0) { - vinfolog("Dropping DoH connection because we could not retrieve the remote host"); - h2o_socket_close(sock); - return; - } - - if (dsc->dohFrontend->d_earlyACLDrop && !dsc->dohFrontend->d_trustForwardedForHeader && !dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL.match(remote)) { - ++dnsdist::metrics::g_stats.aclDrops; - vinfolog("Dropping DoH connection from %s because of ACL", remote.toStringWithPort()); - h2o_socket_close(sock); - return; - } - - auto connectionResult = dnsdist::IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(remote, false); - if (connectionResult == dnsdist::IncomingConcurrentTCPConnectionsManager::NewConnectionResult::Denied) { - h2o_socket_close(sock); - return; - } - - auto concurrentConnections = ++dsc->clientState->tcpCurrentConnections; - if (dsc->clientState->d_tcpConcurrentConnectionsLimit > 0 && concurrentConnections > dsc->clientState->d_tcpConcurrentConnectionsLimit) { - --dsc->clientState->tcpCurrentConnections; - h2o_socket_close(sock); - return; - } - - if (concurrentConnections > dsc->clientState->tcpMaxConcurrentConnections.load()) { - dsc->clientState->tcpMaxConcurrentConnections.store(concurrentConnections); - } - - auto& conn = t_conns[descriptor]; - - gettimeofday(&conn.d_connectionStartTime, nullptr); - conn.d_nbQueries = 0; - conn.d_acceptCtx = std::atomic_load_explicit(&dsc->accept_ctx, std::memory_order_acquire); - conn.d_desc = descriptor; - conn.d_remote = remote; - - sock->on_close.cb = on_socketclose; - sock->on_close.data = &conn; - sock->data = dsc; - - ++dsc->dohFrontend->d_httpconnects; - - h2o_accept(conn.d_acceptCtx->get(), sock); -} - -static int create_listener(std::shared_ptr& dsc, int descriptor) -{ - dsc->h2o_socket = std::unique_ptr{h2o_evloop_socket_create(dsc->h2o_ctx.loop, descriptor, H2O_SOCKET_FLAG_DONT_READ), &h2o_socket_close}; - dsc->h2o_socket->data = dsc.get(); - h2o_socket_read_start(dsc->h2o_socket.get(), on_accept); - - return 0; -} - -#ifndef DISABLE_OCSP_STAPLING -static int ocsp_stapling_callback(SSL* ssl, void* arg) -{ - if (ssl == nullptr || arg == nullptr) { - return SSL_TLSEXT_ERR_NOACK; - } - const auto* ocspMap = static_cast*>(arg); - return libssl_ocsp_stapling_callback(ssl, *ocspMap); -} -#endif /* DISABLE_OCSP_STAPLING */ - -#if OPENSSL_VERSION_MAJOR >= 3 -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays): OpenSSL API -static int ticket_key_callback(SSL* sslContext, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* ivector, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx, int enc) -#else -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays): OpenSSL API -static int ticket_key_callback(SSL *sslContext, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* ivector, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx, int enc) -#endif -{ - auto* ctx = static_cast(libssl_get_ticket_key_callback_data(sslContext)); - if (ctx == nullptr || !ctx->d_ticketKeys) { - return -1; - } - - ctx->handleTicketsKeyRotation(); - - auto ret = libssl_ticket_key_callback(sslContext, *ctx->d_ticketKeys, keyName, ivector, ectx, hctx, enc); - if (enc == 0) { - if (ret == 0) { - ++ctx->d_cs->tlsUnknownTicketKey; - } - else if (ret == 2) { - ++ctx->d_cs->tlsInactiveTicketKey; - } - } - - return ret; -} - -static void setupTLSContext(DOHAcceptContext& acceptCtx, - TLSConfig& tlsConfig, - TLSErrorCounters& counters) -{ - if (tlsConfig.d_ciphers.empty()) { - tlsConfig.d_ciphers = DOH_DEFAULT_CIPHERS.data(); - } - - auto [ctx, warnings] = libssl_init_server_context_no_sni(tlsConfig, acceptCtx.d_ocspResponses); - for (const auto& warning : warnings) { - warnlog("%s", warning); - } - - if (tlsConfig.d_enableTickets && tlsConfig.d_numberOfTicketsKeys > 0) { - acceptCtx.d_ticketKeys = std::make_unique(tlsConfig.d_numberOfTicketsKeys); -#if OPENSSL_VERSION_MAJOR >= 3 - SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx.get(), &ticket_key_callback); -#else - SSL_CTX_set_tlsext_ticket_key_cb(ctx.get(), &ticket_key_callback); -#endif - libssl_set_ticket_key_callback_data(ctx.get(), &acceptCtx); - } - -#ifndef DISABLE_OCSP_STAPLING - if (!acceptCtx.d_ocspResponses.empty()) { - SSL_CTX_set_tlsext_status_cb(ctx.get(), &ocsp_stapling_callback); - SSL_CTX_set_tlsext_status_arg(ctx.get(), &acceptCtx.d_ocspResponses); - } -#endif /* DISABLE_OCSP_STAPLING */ - - libssl_set_error_counters_callback(*ctx.get(), &counters); - - if (!tlsConfig.d_keyLogFile.empty()) { - acceptCtx.d_keyLogFile = libssl_set_key_log_file(ctx.get(), tlsConfig.d_keyLogFile); - } - - h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols); - - acceptCtx.d_ticketsKeyRotationDelay = tlsConfig.d_ticketsKeyRotationDelay; - if (tlsConfig.d_ticketKeyFile.empty()) { - acceptCtx.handleTicketsKeyRotation(); - } - else { - acceptCtx.loadTicketsKeys(tlsConfig.d_ticketKeyFile); - } - - auto* nativeCtx = acceptCtx.get(); - nativeCtx->ssl_ctx = ctx.release(); -} - -static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool setupTLS) -{ - auto* nativeCtx = ctx.get(); - nativeCtx->ctx = &dsc.h2o_ctx; - nativeCtx->hosts = dsc.h2o_config.hosts; - auto dohFrontend = std::atomic_load_explicit(&dsc.dohFrontend, std::memory_order_acquire); - ctx.d_ticketsKeyRotationDelay = dohFrontend->d_tlsContext->d_tlsConfig.d_ticketsKeyRotationDelay; - - if (setupTLS && dohFrontend->isHTTPS()) { - try { - setupTLSContext(ctx, - dohFrontend->d_tlsContext->d_tlsConfig, - dohFrontend->d_tlsContext->d_tlsCounters); - } - catch (const std::runtime_error& e) { - throw std::runtime_error("Error setting up TLS context for DoH listener on '" + dohFrontend->d_tlsContext->d_addr.toStringWithPort() + "': " + e.what()); - } - } - ctx.d_cs = dsc.clientState; -} - -static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *)) -{ - h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0); - if (pathconf == nullptr) { - return pathconf; - } - h2o_filter_t *filter = h2o_create_filter(pathconf, sizeof(*filter)); - if (filter != nullptr) { - filter->on_setup_ostream = on_response_ready_cb; - } - - h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler)); - if (handler != nullptr) { - handler->on_req = on_req; - } - - return pathconf; -} - -// this is the entrypoint from dnsdist.cc -void dohThread(ClientState* clientState) -{ - try { - std::shared_ptr& dohFrontend = clientState->dohFrontend; - auto& dsc = dohFrontend->d_dsc; - dsc->clientState = clientState; - std::atomic_store_explicit(&dsc->dohFrontend, clientState->dohFrontend, std::memory_order_release); - dsc->h2o_config.server_name = h2o_iovec_init(dohFrontend->d_serverTokens.c_str(), dohFrontend->d_serverTokens.size()); - -#ifndef USE_SINGLE_ACCEPTOR_THREAD - std::thread dnsdistThread(dnsdistclient, std::move(dsc->d_queryReceiver)); - dnsdistThread.detach(); // gets us better error reporting -#endif - - setThreadName("dnsdist/doh"); - // I wonder if this registers an IP address.. I think it does - // this may mean we need to actually register a site "name" here and not the IP address - h2o_hostconf_t *hostconf = h2o_config_register_host(&dsc->h2o_config, h2o_iovec_init(dohFrontend->d_tlsContext->d_addr.toString().c_str(), dohFrontend->d_tlsContext->d_addr.toString().size()), 65535); - - dsc->paths = dohFrontend->d_urls; - for (const auto& url : dsc->paths) { - register_handler(hostconf, url.c_str(), doh_handler); - } - - h2o_context_init(&dsc->h2o_ctx, h2o_evloop_create(), &dsc->h2o_config); - - // in this complicated way we insert the DOHServerConfig pointer in there - h2o_vector_reserve(nullptr, &dsc->h2o_ctx.storage, 1); - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): h2o API - dsc->h2o_ctx.storage.entries[0].data = dsc.get(); - ++dsc->h2o_ctx.storage.size; - - auto sock = std::unique_ptr{h2o_evloop_socket_create(dsc->h2o_ctx.loop, dsc->d_responseReceiver.getDescriptor(), H2O_SOCKET_FLAG_DONT_READ), &h2o_socket_close}; - sock->data = dsc.get(); - - // this listens to responses from dnsdist to turn into http responses - h2o_socket_read_start(sock.get(), on_dnsdist); - - setupAcceptContext(*dsc->accept_ctx, *dsc, false); - - if (create_listener(dsc, clientState->tcpFD) != 0) { - throw std::runtime_error("DOH server failed to listen on " + dohFrontend->d_tlsContext->d_addr.toStringWithPort() + ": " + stringerror(errno)); - } - for (const auto& [addr, descriptor] : clientState->d_additionalAddresses) { - if (create_listener(dsc, descriptor) != 0) { - throw std::runtime_error("DOH server failed to listen on additional address " + addr.toStringWithPort() + " for DOH local" + dohFrontend->d_tlsContext->d_addr.toStringWithPort() + ": " + stringerror(errno)); - } - } - - bool stop = false; - do { - int result = h2o_evloop_run(dsc->h2o_ctx.loop, INT32_MAX); - if (result == -1) { - if (errno != EINTR) { - errlog("Error in the DoH event loop: %s", stringerror(errno)); - stop = true; - } - } - } - while (!stop); - - h2o_evloop_destroy(dsc->h2o_ctx.loop); - } - catch (const std::exception& e) { - throw runtime_error("DOH thread failed to launch: " + std::string(e.what())); - } - catch (...) { - throw runtime_error("DOH thread failed to launch"); - } -} - -void DOHUnit::handleUDPResponse(PacketBuffer&& udpResponse, InternalQueryState&& state, [[maybe_unused]] const std::shared_ptr& downstream_) -{ - auto dohUnit = std::unique_ptr(this); - dohUnit->ids = std::move(state); - - { - dnsheader_aligned dnsHeader(udpResponse.data()); - if (dnsHeader.get()->tc) { - dohUnit->truncated = true; - } - } - if (!dohUnit->truncated) { - DNSResponse dnsResponse(dohUnit->ids, udpResponse, dohUnit->downstream); - dnsheader cleartextDH{}; - memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH)); - - dnsResponse.ids.du = std::move(dohUnit); - if (!processResponse(udpResponse, dnsResponse, false)) { - if (dnsResponse.ids.du) { - dohUnit = getDUFromIDS(dnsResponse.ids); - dohUnit->status_code = 503; - sendDoHUnitToTheMainThread(std::move(dohUnit), "Response dropped by rules"); - } - return; - } - - if (dnsResponse.isAsynchronous()) { - return; - } - - dohUnit = getDUFromIDS(dnsResponse.ids); - dohUnit->response = std::move(udpResponse); - auto udiff = dohUnit->ids.queryRealTime.udiff(); - vinfolog("Got answer from %s, relayed to %s (https), took %d us", dohUnit->downstream->d_config.remote.toStringWithPort(), dohUnit->ids.origRemote.toStringWithPort(), udiff); - - handleResponseSent(dohUnit->ids, udiff, dnsResponse.ids.origRemote, dohUnit->downstream->d_config.remote, dohUnit->response.size(), cleartextDH, dohUnit->downstream->getProtocol(), true); - - ++dnsdist::metrics::g_stats.responses; - if (dohUnit->ids.cs != nullptr) { - ++dohUnit->ids.cs->responses; - } - } - - sendDoHUnitToTheMainThread(std::move(dohUnit), "DoH response"); -} - -void H2ODOHFrontend::rotateTicketsKey(time_t now) -{ - if (d_dsc && d_dsc->accept_ctx) { - d_dsc->accept_ctx->rotateTicketsKey(now); - } -} - -void H2ODOHFrontend::loadTicketsKeys(const std::string& keyFile) -{ - if (d_dsc && d_dsc->accept_ctx) { - d_dsc->accept_ctx->loadTicketsKeys(keyFile); - } -} - -void H2ODOHFrontend::handleTicketsKeyRotation() -{ - if (d_dsc && d_dsc->accept_ctx) { - d_dsc->accept_ctx->handleTicketsKeyRotation(); - } -} - -std::string H2ODOHFrontend::getNextTicketsKeyRotation() const -{ - if (d_dsc && d_dsc->accept_ctx) { - return std::to_string(d_dsc->accept_ctx->getNextTicketsKeyRotation()); - } - return {}; -} - -size_t H2ODOHFrontend::getTicketsKeysCount() -{ - size_t res = 0; - if (d_dsc && d_dsc->accept_ctx) { - res = d_dsc->accept_ctx->getTicketsKeysCount(); - } - return res; -} - -void H2ODOHFrontend::reloadCertificates() -{ - auto newAcceptContext = std::make_shared(); - setupAcceptContext(*newAcceptContext, *d_dsc, true); - std::atomic_store_explicit(&d_dsc->accept_ctx, std::move(newAcceptContext), std::memory_order_release); -} - -void H2ODOHFrontend::setup() -{ - registerOpenSSLUser(); - - d_dsc = std::make_shared(d_idleTimeout, d_internalPipeBufferSize); - - if (isHTTPS()) { - try { - setupTLSContext(*d_dsc->accept_ctx, - d_tlsContext->d_tlsConfig, - d_tlsContext->d_tlsCounters); - } - catch (const std::runtime_error& e) { - throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_tlsContext->d_addr.toStringWithPort() + "': " + e.what()); - } - } -} - -#endif /* HAVE_LIBH2OEVLOOP */ -#endif /* HAVE_DNS_OVER_HTTPS */ diff --git a/pdns/dnsdistdist/doh.hh b/pdns/dnsdistdist/doh.hh deleted file mode 100644 index 04fe358ec5..0000000000 --- a/pdns/dnsdistdist/doh.hh +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of PowerDNS or dnsdist. - * Copyright -- PowerDNS.COM B.V. and its contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * In addition, for the avoidance of any doubt, permission is granted to - * link this program with OpenSSL and to (re)distribute the binaries - * produced as the result of such linking. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ -#pragma once - -#include "config.h" - -#ifdef HAVE_DNS_OVER_HTTPS -#ifdef HAVE_LIBH2OEVLOOP - -#include -#include -#include - -struct CrossProtocolQuery; -struct DNSQuestion; - -std::unique_ptr getDoHCrossProtocolQueryFromDQ(DNSQuestion& dq, bool isResponse); - -#include "dnsdist-doh-common.hh" - -struct H2ODOHFrontend : public DOHFrontend -{ -public: - void setup() override; - void reloadCertificates() override; - - void rotateTicketsKey(time_t now) override; - void loadTicketsKeys(const std::string& keyFile) override; - void handleTicketsKeyRotation() override; - std::string getNextTicketsKeyRotation() const override; - size_t getTicketsKeysCount() override; -}; - -void dohThread(ClientState* clientState); - -#endif /* HAVE_LIBH2OEVLOOP */ -#endif /* HAVE_DNS_OVER_HTTPS */ diff --git a/pdns/dnsdistdist/m4/dnsdist_enable_doh.m4 b/pdns/dnsdistdist/m4/dnsdist_enable_doh.m4 index baf9118e67..064154595c 100644 --- a/pdns/dnsdistdist/m4/dnsdist_enable_doh.m4 +++ b/pdns/dnsdistdist/m4/dnsdist_enable_doh.m4 @@ -1,7 +1,7 @@ AC_DEFUN([DNSDIST_ENABLE_DNS_OVER_HTTPS], [ AC_MSG_CHECKING([whether to enable incoming DNS over HTTPS (DoH) support]) AC_ARG_ENABLE([dns-over-https], - AS_HELP_STRING([--enable-dns-over-https], [enable incoming DNS over HTTPS (DoH) support (requires libh2o or nghttp2) @<:@default=no@:>@]), + AS_HELP_STRING([--enable-dns-over-https], [enable incoming DNS over HTTPS (DoH) support (requires nghttp2) @<:@default=no@:>@]), [enable_dns_over_https=$enableval], [enable_dns_over_https=no] ) diff --git a/pdns/dnsdistdist/m4/pdns_check_libh2o_evloop.m4 b/pdns/dnsdistdist/m4/pdns_check_libh2o_evloop.m4 deleted file mode 100644 index 43c112249f..0000000000 --- a/pdns/dnsdistdist/m4/pdns_check_libh2o_evloop.m4 +++ /dev/null @@ -1,40 +0,0 @@ -AC_DEFUN([PDNS_WITH_LIBH2OEVLOOP], [ - AC_MSG_CHECKING([whether we will be linking in libh2o-evloop]) - HAVE_LIBH2OEVLOOP=0 - AC_ARG_WITH([h2o], - AS_HELP_STRING([--with-h2o],[use libh2o-evloop @<:@default=no@:>@]), - [with_h2o=$withval], - [with_h2o=no], - ) - AC_MSG_RESULT([$with_h2o]) - - AS_IF([test "x$with_h2o" = "xyes" -o "x$with_h2o" = "xauto"], [ - PKG_CHECK_MODULES([LIBH2OEVLOOP], [libh2o-evloop], [ - [HAVE_LIBH2OEVLOOP=1] - AC_DEFINE([HAVE_LIBH2OEVLOOP], [1], [Define to 1 if you have libh2o-evloop]) - save_CFLAGS=$CFLAGS - save_LIBS=$LIBS - CFLAGS="$LIBH2OEVLOOP_CFLAGS $CFLAGS" - LIBS="$LIBH2OEVLOOP_LIBS $LIBS" - AC_CHECK_DECLS([h2o_socket_get_ssl_server_name], [ - AC_DEFINE([HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME], [1], [define to 1 if h2o_socket_get_ssl_server_name is available.]) - ], - [ : ], - [AC_INCLUDES_DEFAULT - #include - ]) - CFLAGS=$save_CFLAGS - LIBS=$save_LIBS - ], [ : ]) - ]) - AM_CONDITIONAL([HAVE_LIBH2OEVLOOP], [test "x$LIBH2OEVLOOP_LIBS" != "x"]) - AM_COND_IF([HAVE_LIBH2OEVLOOP], [ - AC_DEFINE([HAVE_LIBH2OEVLOOP], [1], [Define to 1 if you enable h2o-evloop support]) - ]) - - AS_IF([test "x$with_h2o" = "xyes"], [ - AS_IF([test x"LIBH2OEVLOOP_LIBS" = "x"], [ - AC_MSG_ERROR([h2o-evloop requested but libraries were not found]) - ]) - ]) -]) diff --git a/pdns/dnsdistdist/meson.build b/pdns/dnsdistdist/meson.build index faa30ffe3c..84c08c7750 100644 --- a/pdns/dnsdistdist/meson.build +++ b/pdns/dnsdistdist/meson.build @@ -74,7 +74,6 @@ subdir('meson' / 'ipcipher') # IPCipher (requires libcrypto) subdir('meson' / 'cdb') # CDB subdir('meson' / 'ebpf') # eBPF subdir('meson' / 'gnutls') # GNUTLS -subdir('meson' / 'h2o') # H2O subdir('meson' / 'lmdb') # LMDB subdir('meson' / 'nghttp2') # NGHTTP2 subdir('meson' / 'quiche') # Quiche @@ -225,12 +224,6 @@ conditional_sources = { ], 'condition': dep_cdb.found(), }, - 'doh': { - 'sources': [ - src_dir / 'doh.cc', - ], - 'condition': dep_libh2o_evloop.found(), - }, 'doq': { 'sources': [ src_dir / 'doq.cc', @@ -391,7 +384,6 @@ deps = [ dep_libcrypto, dep_gnutls, dep_libedit, - dep_libh2o_evloop, dep_json11, dep_libnghttp2, dep_libquiche, diff --git a/pdns/dnsdistdist/meson/doh2/meson.build b/pdns/dnsdistdist/meson/doh2/meson.build index 8c8aec50f1..54b1433d7e 100644 --- a/pdns/dnsdistdist/meson/doh2/meson.build +++ b/pdns/dnsdistdist/meson/doh2/meson.build @@ -4,10 +4,10 @@ if opt_doh2.enabled() if not dep_libssl.found() and not dep_gnutls.found() error('DNS over HTTP/2 support was requested but neither OpenSSL libssl nor GnuTLS support is enabled') endif - if not dep_libnghttp2.found() and not dep_libh2o_evloop.found() - error('DNS over HTTP/2 support was requested but neither nghttp2 not libh2o-evloop support is enabled') + if not dep_libnghttp2.found() + error('DNS over HTTP/2 support was requested but nghttp2 support is enabled') endif endif -conf.set('HAVE_DNS_OVER_HTTPS', opt_doh2.allowed() and (dep_libssl.found() or dep_gnutls.found()) and (dep_libnghttp2.found() or dep_libh2o_evloop.found()), description: 'DNS over HTTP/2 (DoH)') -summary('DNS over HTTP/2', opt_doh2.allowed() and (dep_libssl.found() or dep_gnutls.found()) and (dep_libnghttp2.found() or dep_libh2o_evloop.found()), bool_yn: true, section: 'Configuration') +conf.set('HAVE_DNS_OVER_HTTPS', opt_doh2.allowed() and (dep_libssl.found() or dep_gnutls.found()) and (dep_libnghttp2.found()), description: 'DNS over HTTP/2 (DoH)') +summary('DNS over HTTP/2', opt_doh2.allowed() and (dep_libssl.found() or dep_gnutls.found()) and (dep_libnghttp2.found()), bool_yn: true, section: 'Configuration') diff --git a/pdns/dnsdistdist/meson/h2o/meson.build b/pdns/dnsdistdist/meson/h2o/meson.build deleted file mode 100644 index 4c758f04c8..0000000000 --- a/pdns/dnsdistdist/meson/h2o/meson.build +++ /dev/null @@ -1,16 +0,0 @@ -opt_h2o = get_option('h2o') -dep_libh2o_evloop = dependency('libh2o-evloop', required: opt_h2o) - -if dep_libh2o_evloop.found() - funcs = [ - 'h2o_socket_get_ssl_server_name', - ] - - foreach func: funcs - has = cxx.has_function(func, dependencies: dep_libh2o_evloop) - conf.set('HAVE_' + func.to_upper(), has, description: 'Have h2o ' + func) - endforeach -endif - -conf.set('HAVE_LIBH2OEVLOOP', dep_libh2o_evloop.found(), description: 'H2O library with event loop support for DNS over HTTP/2') -summary('H2O library with event loop support for DNS over HTTP/2', dep_libh2o_evloop.found(), bool_yn: true, section: 'DNS over HTTP/2') diff --git a/pdns/dnsdistdist/meson_options.txt b/pdns/dnsdistdist/meson_options.txt index e66d1d4091..2dab4f385f 100644 --- a/pdns/dnsdistdist/meson_options.txt +++ b/pdns/dnsdistdist/meson_options.txt @@ -28,7 +28,6 @@ option('systemd-service-group', type: 'string', value: 'dnsdist', description: ' option('auto-var-init', type: 'combo', value: 'disabled', choices: ['zero', 'pattern', 'disabled'], description: 'Enable initialization of automatic variables') option('snmp', type: 'feature', value: 'disabled', description: 'Enable SNMP') option('dnstap', type: 'feature', value: 'auto', description: 'Enable DNSTAP support through libfstrm') -option('h2o', type: 'feature', value: 'disabled', description: 'Enable H2O library with event loop support for DNS over HTTP/2') option('nghttp2', type: 'feature', value: 'auto', description: 'Enable nghttp2 library support for DNS over HTTP/2') option('cdb', type: 'feature', value: 'auto', description: 'CDB key-value store support') option('lmdb', type: 'feature', value: 'auto', description: 'LMDB key-value store support') diff --git a/regression-tests.dnsdist/dnsdisttests.py b/regression-tests.dnsdist/dnsdisttests.py index 9cf6662f50..a00368c5f5 100644 --- a/regression-tests.dnsdist/dnsdisttests.py +++ b/regression-tests.dnsdist/dnsdisttests.py @@ -1148,9 +1148,6 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase): def sendDOHWithNGHTTP2QueryWrapper(self, query, response, useQueue=True, timeout=2, serverName=None): return self.sendDOHQuery(self._dohWithNGHTTP2ServerPort, self._serverName if not serverName else serverName, self._dohWithNGHTTP2BaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, timeout=timeout) - def sendDOHWithH2OQueryWrapper(self, query, response, useQueue=True, timeout=2, serverName=None): - return self.sendDOHQuery(self._dohWithH2OServerPort, self._serverName if not serverName else serverName, self._dohWithH2OBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, timeout=timeout) - def sendDOTQueryWrapper(self, query, response, useQueue=True, timeout=2, serverName=None): return self.sendDOTQuery(self._tlsServerPort, self._serverName if not serverName else serverName, query, response, self._caCert, useQueue=useQueue, timeout=timeout) diff --git a/regression-tests.dnsdist/test_Async.py b/regression-tests.dnsdist/test_Async.py index 6bea2cb7d4..96292f249c 100644 --- a/regression-tests.dnsdist/test_Async.py +++ b/regression-tests.dnsdist/test_Async.py @@ -101,9 +101,7 @@ class AsyncTests(object): _caCert = "ca.pem" _tlsServerPort = pickAvailablePort() _dohWithNGHTTP2ServerPort = pickAvailablePort() - _dohWithH2OServerPort = pickAvailablePort() _dohWithNGHTTP2BaseURL = "https://%s:%d/" % (_serverName, _dohWithNGHTTP2ServerPort) - _dohWithH2OBaseURL = "https://%s:%d/" % (_serverName, _dohWithH2OServerPort) _doqServerPort = pickAvailablePort() def testPass(self): @@ -127,7 +125,6 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) @@ -157,15 +154,10 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) - if ( - method != "sendDOTQueryWrapper" - and method != "sendDOHWithH2OQueryWrapper" - and method != "sendDOQQueryWrapper" - ): + if method != "sendDOTQueryWrapper" and method != "sendDOQQueryWrapper": # first time to fill the cache # disabled for DoT since it was already filled via TCP (receivedQuery, receivedResponse) = sender(query, response) @@ -202,7 +194,6 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) @@ -235,7 +226,6 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) @@ -272,7 +262,6 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) @@ -311,7 +300,6 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) @@ -344,7 +332,6 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) @@ -377,7 +364,6 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) @@ -400,7 +386,6 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) @@ -426,7 +411,6 @@ class AsyncTests(object): "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", - "sendDOHWithH2OQueryWrapper", "sendDOQQueryWrapper", ): sender = getattr(self, method) @@ -444,7 +428,7 @@ class AsyncTests(object): # the query is first forwarded over UDP, leading to a TC=1 answer from the # backend, then over TCP - for method in ("sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper"): + for method in ["sendDOHWithNGHTTP2QueryWrapper"]: sender = getattr(self, method) name = "timeout-then-accept." + method + ".tc.async.tests.powerdns.com." query = dns.message.make_query(name, "A", "IN") @@ -490,7 +474,6 @@ class TestAsyncFFI(DNSDistTest, AsyncTests): newServer{address="127.0.0.1:%d", pool="tcp-only", tcpOnly=true } addTLSLocal("127.0.0.1:%d", "%s", "%s", { provider="openssl" }) - addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="h2o"}) addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="nghttp2"}) addDOQLocal("127.0.0.1:%d", "%s", "%s") @@ -620,9 +603,6 @@ class TestAsyncFFI(DNSDistTest, AsyncTests): "_tlsServerPort", "_serverCert", "_serverKey", - "_dohWithH2OServerPort", - "_serverCert", - "_serverKey", "_dohWithNGHTTP2ServerPort", "_serverCert", "_serverKey", @@ -642,7 +622,6 @@ class TestAsyncLua(DNSDistTest, AsyncTests): newServer{address="127.0.0.1:%d", pool="tcp-only", tcpOnly=true } addTLSLocal("127.0.0.1:%d", "%s", "%s", { provider="openssl" }) - addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="h2o"}) addDOHLocal("127.0.0.1:%d", "%s", "%s", {"/"}, {library="nghttp2"}) addDOQLocal("127.0.0.1:%d", "%s", "%s") @@ -753,9 +732,6 @@ class TestAsyncLua(DNSDistTest, AsyncTests): "_tlsServerPort", "_serverCert", "_serverKey", - "_dohWithH2OServerPort", - "_serverCert", - "_serverKey", "_dohWithNGHTTP2ServerPort", "_serverCert", "_serverKey", diff --git a/regression-tests.dnsdist/test_DOH.py b/regression-tests.dnsdist/test_DOH.py index da02f03605..be16466bfa 100644 --- a/regression-tests.dnsdist/test_DOH.py +++ b/regression-tests.dnsdist/test_DOH.py @@ -263,8 +263,6 @@ class DOHTests(object): """ DOH: qdcount == 0 """ - if self._dohLibrary == 'h2o': - raise unittest.SkipTest('h2o tries to parse the qname early, so this check will fail') query = dns.message.Message() query.id = 0 query.flags &= ~dns.flags.RD @@ -349,8 +347,6 @@ class DOHTests(object): """ DOH: Invalid method """ - if self._dohLibrary == 'h2o': - raise unittest.SkipTest('h2o does not check the HTTP method') name = 'invalid-method.doh.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN', use_edns=False) wire = query.to_wire() @@ -397,8 +393,6 @@ class DOHTests(object): """ DOH: HTTP/1.1 """ - if self._dohLibrary == 'h2o': - raise unittest.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2') httpConnections = self.getHTTPCounter('connects') http1 = self.getHTTPCounter('http/1.1') http2 = self.getHTTPCounter('http/2') @@ -440,8 +434,6 @@ class DOHTests(object): """ DOH: Check that HTTP/1.1 is not selected over H2 when offered in the wrong order by the client """ - if self._dohLibrary == 'h2o': - raise unittest.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2') alpn = ['http/1.1', 'h2'] conn = self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn) if not hasattr(conn, 'selected_alpn_protocol'): @@ -510,8 +502,6 @@ class DOHTests(object): """ DOH: No backend """ - if self._dohLibrary == 'h2o': - raise unittest.SkipTest('h2o does not check the HTTP method') name = 'no-backend.doh.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN', use_edns=False) wire = query.to_wire() @@ -789,9 +779,6 @@ class DOHTests(object): class TestDoHNGHTTP2(DOHTests, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDoHH2O(DOHTests, DNSDistDOHTest): - _dohLibrary = 'h2o' - class TestDoHNGHTTP2Yaml(DOHTests, DNSDistDOHTest): _dohLibrary = 'nghttp2' _yaml_config_template = """--- @@ -995,9 +982,6 @@ class DOHSubPathsTests(object): class TestDoHSubPathsNGHTTP2(DOHSubPathsTests, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDoHSubPathsH2O(DOHSubPathsTests, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHAddingECSTests(object): _serverKey = 'server.key' @@ -1097,9 +1081,6 @@ class DOHAddingECSTests(object): class TestDoHAddingECSNGHTTP2(DOHAddingECSTests, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDoHAddingECSH2O(DOHAddingECSTests, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHOverHTTP(object): _dohServerPort = pickAvailablePort() _serverName = 'tls.tests.dnsdist.org' @@ -1167,12 +1148,6 @@ class TestDOHOverHTTPNGHTTP2(DOHOverHTTP, DNSDistDOHTest): Configuration 'configs/dnsdist_TestDOHOverHTTPNGHTTP2.conf' OK! """ % (DOHOverHTTP._dohServerPort) -class TestDOHOverHTTPH2O(DOHOverHTTP, DNSDistDOHTest): - _dohLibrary = 'h2o' - _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS -Configuration 'configs/dnsdist_TestDOHOverHTTPH2O.conf' OK! -""" % (DOHOverHTTP._dohServerPort) - class DOHWithCache(object): _serverKey = 'server.key' @@ -1386,9 +1361,6 @@ class TestDOHWithCacheNGHTTP2(DOHWithCache, DNSDistDOHTest): _dohLibrary = 'nghttp2' _verboseMode = True -class TestDOHWithCacheH2O(DOHWithCache, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHWithoutCacheControl(object): _serverKey = 'server.key' @@ -1433,9 +1405,6 @@ class DOHWithoutCacheControl(object): class TestDOHWithoutCacheControlNGHTTP2(DOHWithoutCacheControl, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDOHWithoutCacheControlH2O(DOHWithoutCacheControl, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHFFI(object): _serverKey = 'server.key' _serverCert = 'server.chain' @@ -1500,9 +1469,6 @@ class DOHFFI(object): class TestDOHFFINGHTTP2(DOHFFI, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDOHFFIH2O(DOHFFI, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHForwardedFor(object): _serverKey = 'server.key' _serverCert = 'server.chain' @@ -1571,9 +1537,6 @@ class DOHForwardedFor(object): class TestDOHForwardedForNGHTTP2(DOHForwardedFor, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDOHForwardedForH2O(DOHForwardedFor, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHForwardedForNoTrusted(object): _serverKey = 'server.key' @@ -1620,9 +1583,6 @@ class DOHForwardedForNoTrusted(object): class TestDOHForwardedForNoTrustedNGHTTP2(DOHForwardedForNoTrusted, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDOHForwardedForNoTrustedH2O(DOHForwardedForNoTrusted, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHFrontendLimits(object): # this test suite uses a different responder port @@ -1655,9 +1615,7 @@ class DOHFrontendLimits(object): for idx in range(self._maxTCPConnsPerDOHFrontend + 1): try: - alpn = [] - if self._dohLibrary != 'h2o': - alpn.append('h2') + alpn = ['h2'] conns.append(self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn)) except Exception: conns.append(None) @@ -1694,9 +1652,6 @@ class DOHFrontendLimits(object): class TestDOHFrontendLimitsNGHTTP2(DOHFrontendLimits, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDOHFrontendLimitsH2O(DOHFrontendLimits, DNSDistDOHTest): - _dohLibrary = 'h2o' - class Protocols(object): _serverKey = 'server.key' _serverCert = 'server.chain' @@ -1741,9 +1696,6 @@ class Protocols(object): class TestProtocolsNGHTTP2(Protocols, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestProtocolsH2O(Protocols, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHWithPKCS12Cert(object): _serverCert = 'server.p12' _pkcs12Password = 'passw0rd' @@ -1784,9 +1736,6 @@ class DOHWithPKCS12Cert(object): class TestDOHWithPKCS12CertNGHTTP2(DOHWithPKCS12Cert, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDOHWithPKCS12CertH2O(DOHWithPKCS12Cert, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHForwardedToTCPOnly(object): _serverKey = 'server.key' _serverCert = 'server.chain' @@ -1825,9 +1774,6 @@ class DOHForwardedToTCPOnly(object): class TestDOHForwardedToTCPOnlyNGHTTP2(DOHForwardedToTCPOnly, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDOHForwardedToTCPOnlyH2O(DOHForwardedToTCPOnly, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHLimits(object): _serverName = 'tls.tests.dnsdist.org' _caCert = 'ca.pem' @@ -1885,9 +1831,6 @@ class DOHLimits(object): class TestDOHLimitsNGHTTP2(DOHLimits, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDOHLimitsH2O(DOHLimits, DNSDistDOHTest): - _dohLibrary = 'h2o' - class DOHXFR(object): _serverName = 'tls.tests.dnsdist.org' _caCert = 'ca.pem' @@ -1920,5 +1863,3 @@ class DOHXFR(object): class TestDOHXFRNGHTTP2(DOHXFR, DNSDistDOHTest): _dohLibrary = 'nghttp2' -class TestDOHXFRH2O(DOHXFR, DNSDistDOHTest): - _dohLibrary = 'h2o' diff --git a/regression-tests.dnsdist/test_OCSP.py b/regression-tests.dnsdist/test_OCSP.py index 99a9d6b0f9..39474ed294 100644 --- a/regression-tests.dnsdist/test_OCSP.py +++ b/regression-tests.dnsdist/test_OCSP.py @@ -73,7 +73,6 @@ class TestOCSPStaplingDOH(DNSDistOCSPStaplingTest): _caCert = "ca.pem" _caKey = "ca.key" _dohWithNGHTTP2ServerPort = pickAvailablePort() - _dohWithH2OServerPort = pickAvailablePort() _config_template = """ newServer{address="127.0.0.1:%d"} setKey("%s") @@ -82,7 +81,6 @@ class TestOCSPStaplingDOH(DNSDistOCSPStaplingTest): -- generate an OCSP response file for our certificate, valid one day generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0) addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='nghttp2'}) - addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='h2o'}) """ _config_params = [ "_testServerPort", @@ -96,10 +94,6 @@ class TestOCSPStaplingDOH(DNSDistOCSPStaplingTest): "_serverCert", "_serverKey", "_ocspFile", - "_dohWithH2OServerPort", - "_serverCert", - "_serverKey", - "_ocspFile", ] @classmethod @@ -120,7 +114,7 @@ class TestOCSPStaplingDOH(DNSDistOCSPStaplingTest): """ OCSP Stapling: DOH """ - for port in [self._dohWithNGHTTP2ServerPort, self._dohWithH2OServerPort]: + for port in [self._dohWithNGHTTP2ServerPort]: output = self.checkOCSPStaplingStatus( "127.0.0.1", port, self._serverName, self._caCert ) @@ -156,14 +150,12 @@ class TestBrokenOCSPStaplingDoH(DNSDistOCSPStaplingTest): # invalid OCSP file! _ocspFile = "/dev/null" _dohWithNGHTTP2ServerPort = pickAvailablePort() - _dohWithH2OServerPort = pickAvailablePort() _config_template = """ newServer{address="127.0.0.1:%d"} setKey("%s") controlSocket("127.0.0.1:%d") addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='nghttp2'}) - addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { ocspResponses={"%s"}, library='h2o'}) """ _config_params = [ @@ -174,17 +166,13 @@ class TestBrokenOCSPStaplingDoH(DNSDistOCSPStaplingTest): "_serverCert", "_serverKey", "_ocspFile", - "_dohWithH2OServerPort", - "_serverCert", - "_serverKey", - "_ocspFile", ] def testBrokenOCSPStapling(self): """ OCSP Stapling: Broken (DoH) """ - for port in [self._dohWithNGHTTP2ServerPort, self._dohWithH2OServerPort]: + for port in [self._dohWithNGHTTP2ServerPort]: output = self.checkOCSPStaplingStatus( "127.0.0.1", port, self._serverName, self._caCert ) diff --git a/regression-tests.dnsdist/test_Protobuf.py b/regression-tests.dnsdist/test_Protobuf.py index e158cc7a7a..06d0e86344 100644 --- a/regression-tests.dnsdist/test_Protobuf.py +++ b/regression-tests.dnsdist/test_Protobuf.py @@ -692,21 +692,18 @@ class TestProtobufMetaDOH(DNSDistProtobufTest): _tlsServerPort = pickAvailablePort() _dohWithNGHTTP2ServerPort = pickAvailablePort() _dohWithNGHTTP2BaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithNGHTTP2ServerPort)) - _dohWithH2OServerPort = pickAvailablePort() - _dohWithH2OBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithH2OServerPort)) _config_template = """ newServer{address="127.0.0.1:%d"} rl = newRemoteLogger('127.0.0.1:%d') addTLSLocal("127.0.0.1:%d", "%s", "%s", { provider="openssl" }) addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true, library='nghttp2' }) - addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { keepIncomingHeaders=true, library='h2o' }) local mytags = {path='doh-path', host='doh-host', ['query-string']='doh-query-string', scheme='doh-scheme', agent='doh-header:user-agent'} addAction(AllRule(), RemoteLogAction(rl, nil, {serverID='dnsdist-server-1'}, mytags)) addResponseAction(AllRule(), RemoteLogResponseAction(rl, nil, false, {serverID='dnsdist-server-1'}, mytags)) """ - _config_params = ['_testServerPort', '_protobufServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey'] + _config_params = ['_testServerPort', '_protobufServerPort', '_tlsServerPort', '_serverCert', '_serverKey', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey'] def testProtobufMetaDoH(self): """ @@ -722,7 +719,7 @@ class TestProtobufMetaDOH(DNSDistProtobufTest): '127.0.0.1') response.answer.append(rrset) - for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper", "sendDOHWithH2OQueryWrapper"): + for method in ("sendUDPQuery", "sendTCPQuery", "sendDOTQueryWrapper", "sendDOHWithNGHTTP2QueryWrapper"): sender = getattr(self, method) (receivedQuery, receivedResponse) = sender(query, response) @@ -745,7 +742,7 @@ class TestProtobufMetaDOH(DNSDistProtobufTest): pbMessageType = dnsmessage_pb2.PBDNSMessage.TCP elif method == "sendDOTQueryWrapper": pbMessageType = dnsmessage_pb2.PBDNSMessage.DOT - elif method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper": + elif method == "sendDOHWithNGHTTP2QueryWrapper": pbMessageType = dnsmessage_pb2.PBDNSMessage.DOH self.checkProtobufQuery(msg, pbMessageType, query, dns.rdataclass.IN, dns.rdatatype.A, name) @@ -756,13 +753,11 @@ class TestProtobufMetaDOH(DNSDistProtobufTest): tags[entry.key] = entry.value.stringVal[0] self.assertIn('agent', tags) - if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper": + if method == "sendDOHWithNGHTTP2QueryWrapper": self.assertIn('PycURL', tags['agent']) self.assertIn('host', tags) if method == "sendDOHWithNGHTTP2QueryWrapper": self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithNGHTTP2ServerPort)) - elif method == "sendDOHWithH2OQueryWrapper": - self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithH2OServerPort)) self.assertIn('path', tags) self.assertEqual(tags['path'], '/dns-query') self.assertIn('query-string', tags) @@ -781,13 +776,11 @@ class TestProtobufMetaDOH(DNSDistProtobufTest): tags[entry.key] = entry.value.stringVal[0] self.assertIn('agent', tags) - if method == "sendDOHWithNGHTTP2QueryWrapper" or method == "sendDOHWithH2OQueryWrapper": + if method == "sendDOHWithNGHTTP2QueryWrapper": self.assertIn('PycURL', tags['agent']) self.assertIn('host', tags) if method == "sendDOHWithNGHTTP2QueryWrapper": self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithNGHTTP2ServerPort)) - elif method == "sendDOHWithH2OQueryWrapper": - self.assertEqual(tags['host'], self._serverName + ':' + str(self._dohWithH2OServerPort)) self.assertIn('path', tags) self.assertEqual(tags['path'], '/dns-query') self.assertIn('query-string', tags) diff --git a/regression-tests.dnsdist/test_ProxyProtocol.py b/regression-tests.dnsdist/test_ProxyProtocol.py index 578e1dccdb..2dd9cf3580 100644 --- a/regression-tests.dnsdist/test_ProxyProtocol.py +++ b/regression-tests.dnsdist/test_ProxyProtocol.py @@ -1220,16 +1220,13 @@ class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest): _caCert = 'ca.pem' _dohWithNGHTTP2ServerPort = pickAvailablePort() _dohWithNGHTTP2BaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithNGHTTP2ServerPort)) - _dohWithH2OServerPort = pickAvailablePort() - _dohWithH2OBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohWithH2OServerPort)) _proxyResponderPort = proxyResponderPort _config_template = """ newServer{address="127.0.0.1:%d", useProxyProtocol=true} addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='nghttp2' }) - addDOHLocal("127.0.0.1:%d", "%s", "%s", { '/dns-query' }, { trustForwardedForHeader=true, library='h2o' }) setACL( { "::1/128", "127.0.0.0/8" } ) """ - _config_params = ['_proxyResponderPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey', '_dohWithH2OServerPort', '_serverCert', '_serverKey'] + _config_params = ['_proxyResponderPort', '_dohWithNGHTTP2ServerPort', '_serverCert', '_serverKey'] def testTruncation(self): """ @@ -1250,7 +1247,7 @@ class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest): '127.0.0.1') response.answer.append(rrset) - for (port,url) in [(self._dohWithNGHTTP2ServerPort, self._dohWithNGHTTP2BaseURL), (self._dohWithH2OServerPort, self._dohWithH2OBaseURL)]: + for (port,url) in [(self._dohWithNGHTTP2ServerPort, self._dohWithNGHTTP2BaseURL)]: # first response is a TC=1 tcResponse = dns.message.make_response(query) tcResponse.flags |= dns.flags.TC @@ -1302,7 +1299,7 @@ class TestDOHWithOutgoingProxyProtocol(DNSDistDOHTest): '127.0.0.1') response.answer.append(rrset) - for (port,url) in [(self._dohWithNGHTTP2ServerPort, self._dohWithNGHTTP2BaseURL), (self._dohWithH2OServerPort, self._dohWithH2OBaseURL)]: + for (port,url) in [(self._dohWithNGHTTP2ServerPort, self._dohWithNGHTTP2BaseURL)]: # the query should be dropped (receivedQuery, receivedResponse) = self.sendDOHQuery(port, self._serverName, url, query, caFile=self._caCert, customHeaders=['x-forwarded-for: [::1]:8080'], useQueue=False) self.assertFalse(receivedQuery) diff --git a/regression-tests.dnsdist/test_TLSSessionResumption.py b/regression-tests.dnsdist/test_TLSSessionResumption.py index db8ceecdc4..4c25018b94 100644 --- a/regression-tests.dnsdist/test_TLSSessionResumption.py +++ b/regression-tests.dnsdist/test_TLSSessionResumption.py @@ -105,13 +105,11 @@ class TestNoTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): _serverName = "tls.tests.dnsdist.org" _caCert = "ca.pem" _dohWithNGHTTP2ServerPort = pickAvailablePort() - _dohWithH2OServerPort = pickAvailablePort() _numberOfKeys = 0 _config_template = """ newServer{address="127.0.0.1:%d"} addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false, library='nghttp2' }) - addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, numberOfStoredSessions=0, sessionTickets=false, library='h2o' }) """ _config_params = [ "_testServerPort", @@ -119,17 +117,13 @@ class TestNoTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): "_serverCert", "_serverKey", "_numberOfKeys", - "_dohWithH2OServerPort", - "_serverCert", - "_serverKey", - "_numberOfKeys", ] def testNoSessionResumption(self): """ Session Resumption: DoH (disabled) """ - for port in [self._dohWithNGHTTP2ServerPort, self._dohWithH2OServerPort]: + for port in [self._dohWithNGHTTP2ServerPort]: self.assertFalse( self.checkSessionResumed( "127.0.0.1", @@ -162,7 +156,6 @@ class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): _serverName = "tls.tests.dnsdist.org" _caCert = "ca.pem" _dohWithNGHTTP2ServerPort = pickAvailablePort() - _dohWithH2OServerPort = pickAvailablePort() _numberOfKeys = 5 _config_template = """ setKey("%s") @@ -170,7 +163,6 @@ class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): newServer{address="127.0.0.1:%d"} addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, library='nghttp2' }) - addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, { numberOfTicketsKeys=%d, library='h2o' }) """ _config_params = [ "_consoleKeyB64", @@ -180,10 +172,6 @@ class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): "_serverCert", "_serverKey", "_numberOfKeys", - "_dohWithH2OServerPort", - "_serverCert", - "_serverKey", - "_numberOfKeys", ] def testSessionResumption(self): @@ -192,7 +180,6 @@ class TestTLSSessionResumptionDOH(DNSDistTLSSessionResumptionTest): """ for port, bindIdx in [ (self._dohWithNGHTTP2ServerPort, 0), - (self._dohWithH2OServerPort, 1), ]: self.assertFalse( self.checkSessionResumed( diff --git a/tasks.py b/tasks.py index 8564b5e2f9..a267dda594 100644 --- a/tasks.py +++ b/tasks.py @@ -340,7 +340,6 @@ def install_dnsdist_test_deps(c, skipXDP=False): # FIXME: rename this, we do way libxdp1' c.sudo(f'apt-get install -y {deps}') - ci_install_libh2o(c) c.run('sed "s/agentxperms 0700 0755 dnsdist/agentxperms 0777 0755/g" regression-tests.dnsdist/snmpd.conf | sudo tee /etc/snmp/snmpd.conf') c.sudo('/etc/init.d/snmpd restart') time.sleep(5) @@ -353,7 +352,6 @@ def install_rec_build_deps(c): @task(optional=['skipXDP']) def install_dnsdist_build_deps(c, skipXDP=False): c.sudo('apt-get install -y --no-install-recommends ' + ' '.join(all_build_deps + git_build_deps + dnsdist_build_deps + (dnsdist_xdp_build_deps if not skipXDP else []))) - ci_install_libh2o(c) @task def ci_autoconf(c, meson=False): @@ -762,7 +760,6 @@ def ci_dnsdist_configure_autotools(features, additional_flags, additional_ld_fla --enable-yaml \ --prefix=/opt/dnsdist \ --with-gnutls \ - --with-h2o \ --with-libsodium \ --with-lua=luajit \ --with-libcap \ @@ -777,7 +774,6 @@ def ci_dnsdist_configure_autotools(features, additional_flags, additional_ld_fla --without-cdb \ --without-ebpf \ --without-gnutls \ - --without-h2o \ --without-libedit \ --without-libsodium \ --without-lmdb \ @@ -804,7 +800,6 @@ def ci_dnsdist_configure_meson(c, features, additional_flags, additional_ld_flag -D dnscrypt=enabled \ -D dnstap=enabled \ -D ebpf=enabled \ - -D h2o=enabled \ -D ipcipher=enabled \ -D ipcrypt2=enabled \ -D libedit=enabled \ @@ -826,7 +821,6 @@ def ci_dnsdist_configure_meson(c, features, additional_flags, additional_ld_flag -D dnscrypt=disabled \ -D dnstap=disabled \ -D ebpf=disabled \ - -D h2o=disabled \ -D ipcipher=disabled \ -D ipcrypt2=disabled \ -D libedit=disabled \ @@ -1276,23 +1270,6 @@ def coverity_upload(c, email, project, tarball): --form description="master build" \ https://scan.coverity.com/builds?project={project}', hide=True) -def build_and_install_libh2o(c): - with c.cd(f'{repo_home}/builder-support/helpers/'): - c.run('sudo sh install_h2o.sh') - - c.run("sudo mkdir -p /usr/lib/pkgconfig") - c.run("sudo cp /opt/lib/pkgconfig/libh2o-evloop.pc /usr/lib/pkgconfig/libh2o-evloop.pc") - -@task -def ci_install_libh2o(c): - libh2o_package_name = "libh2o-evloop-dev" # also installs libh2o-evloop0.13 on Debian 11 & 12 - res = c.run(f'apt-cache policy {libh2o_package_name} | grep -qq Candidate && echo "True" || echo ""') - - if bool(res.stdout.strip()): - c.run(f'sudo apt-get install -y {libh2o_package_name}') - else: - build_and_install_libh2o(c) - @task def ci_build_and_install_quiche(c, repo): with c.cd(f'{repo}/builder-support/helpers/'): -- 2.47.3