]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Initial support for DNS-over-HTTP(S)
authorArtem Boldariev <artem@boldariev.com>
Mon, 7 Dec 2020 12:19:10 +0000 (14:19 +0200)
committerOndřej Surý <ondrej@sury.org>
Wed, 3 Feb 2021 11:06:17 +0000 (12:06 +0100)
This commit completes the support for DNS-over-HTTP(S) built on top of
nghttp2 and plugs it into the BIND. Support for both GET and POST
requests is present, as required by RFC8484.

Both encrypted (via TLS) and unencrypted HTTP/2 connections are
supported. The latter are mostly there for debugging/troubleshooting
purposes and for the means of encryption offloading to third-party
software (as might be desirable in some environments to simplify TLS
certificates management).

25 files changed:
CHANGES
bin/named/include/named/globals.h
bin/named/named.rst
bin/named/server.c
bin/tests/system/conf.sh.common
bin/tests/system/get_ports.sh
bin/tests/system/run.sh.in
lib/isc/include/isc/netmgr.h
lib/isc/netmgr/http.c
lib/isc/netmgr/netmgr-int.h
lib/isc/netmgr/netmgr.c
lib/isc/netmgr/tcp.c
lib/isc/netmgr/tlsstream.c
lib/isc/tests/Makefile.am
lib/isc/tests/doh_test.c [new file with mode: 0644]
lib/isc/tests/tls_test_cert_key.h [new file with mode: 0644]
lib/isc/tls.c
lib/isc/url.c
lib/isc/win32/libisc.def.in
lib/ns/include/ns/interfacemgr.h
lib/ns/include/ns/listenlist.h
lib/ns/interfacemgr.c
lib/ns/listenlist.c
lib/ns/win32/libns.def
util/copyrights

diff --git a/CHANGES b/CHANGES
index 437266581202b9e9bb5f12fc74cd7aee7fa4349b..cd946939a120ff4fe347e170217fe36abc89fd3c 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+5576.  [func]          Initial support for DNS-over-HTTP(S). BIND now
+                       includes DNS-over-HTTP(S) layer built on top of nghttp2.
+                       Both encrypted and unencrypted HTTP/2 connections
+                       are supported. [GL !4566]
+
 5575.  [bug]           When migrating to dnssec-policy, BIND considered keys
                        with the "Inactive" and/or "Delete" timing metadata as
                        possible active keys. This has been fixed. [GL #2406]
index 302eb10cd39011ac3037493932a122fc84be66d4..b8ea5946a411ab10d5fb0eb615e5859fe92fbcac 100644 (file)
@@ -73,8 +73,8 @@ EXTERN const char *named_g_configargs  INIT(PACKAGE_CONFIGARGS);
 EXTERN const char *named_g_builder     INIT(PACKAGE_BUILDER);
 EXTERN in_port_t named_g_port         INIT(0);
 EXTERN in_port_t named_g_tlsport       INIT(0);
-EXTERN in_port_t named_g_httpport      INIT(0);
 EXTERN in_port_t named_g_httpsport     INIT(0);
+EXTERN in_port_t named_g_httpport      INIT(0);
 EXTERN isc_dscp_t named_g_dscp        INIT(-1);
 
 EXTERN named_server_t *named_g_server INIT(NULL);
index c2b19f6dd60d13302bedbc12d57647a93ca93424..3fa96e038cf0027240fcfca86a0c7fa2d67dd592 100644 (file)
@@ -117,6 +117,8 @@ Options
    listen for TLS queries on ``portnum``; the default is 853.
    If ``value`` is of the form ``https=<portnum>``, the server will
    listen for HTTPS queries on ``portnum``; the default is 443.
+   If ``value`` is of the form ``http=<portnum>``, the server will
+   listen for HTTP queries on ``portnum``; the default is 80.
    
 ``-s``
    This option writes memory usage statistics to ``stdout`` on exit.
index 3d39d8f65764226cab653194c26f7d6e3c6c8821..f46202633ae6d29c76af9dd76077e235bb6dddde 100644 (file)
 #include <dst/result.h>
 
 #include <isccfg/grammar.h>
+#include <isccfg/httpconf.h>
 #include <isccfg/kaspconf.h>
 #include <isccfg/namedconf.h>
+#include <isccfg/tlsconf.h>
 
 #include <ns/client.h>
 #include <ns/hooks.h>
@@ -397,14 +399,24 @@ fatal(named_server_t *server, const char *msg, isc_result_t result);
 static void
 named_server_reload(isc_task_t *task, isc_event_t *event);
 
+static isc_result_t
+ns_listenelt_from_http(isc_cfg_http_obj_t *http, isc_cfg_tls_obj_t *tls,
+                      in_port_t port, isc_mem_t *mctx,
+                      ns_listenelt_t **target);
+
 static isc_result_t
 ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
                        cfg_aclconfctx_t *actx, isc_mem_t *mctx,
-                       uint16_t family, ns_listenelt_t **target);
+                       uint16_t family, isc_cfg_http_storage_t *http_servers,
+                       isc_cfg_tls_data_storage_t *tls_storage,
+                       ns_listenelt_t **target);
+
 static isc_result_t
 ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config,
                         cfg_aclconfctx_t *actx, isc_mem_t *mctx,
-                        uint16_t family, ns_listenlist_t **target);
+                        uint16_t family, isc_cfg_http_storage_t *http_servers,
+                        isc_cfg_tls_data_storage_t *tls_storage,
+                        ns_listenlist_t **target);
 
 static isc_result_t
 configure_forward(const cfg_obj_t *config, dns_view_t *view,
@@ -8505,6 +8517,8 @@ load_configuration(const char *filename, named_server_t *server,
        unsigned int initial, idle, keepalive, advertised;
        dns_aclenv_t *env =
                ns_interfacemgr_getaclenv(named_g_server->interfacemgr);
+       isc_cfg_tls_data_storage_t tls_storage;
+       isc_cfg_http_storage_t http_storage;
 
        ISC_LIST_INIT(kasplist);
        ISC_LIST_INIT(viewlist);
@@ -8512,6 +8526,9 @@ load_configuration(const char *filename, named_server_t *server,
        ISC_LIST_INIT(cachelist);
        ISC_LIST_INIT(altsecrets);
 
+       cfg_tls_storage_init(named_g_mctx, &tls_storage);
+       cfg_http_storage_init(named_g_mctx, &http_storage);
+
        /* Create the ACL configuration context */
        if (named_g_aclconfctx != NULL) {
                cfg_aclconfctx_detach(&named_g_aclconfctx);
@@ -8573,6 +8590,19 @@ load_configuration(const char *filename, named_server_t *server,
        maps[i++] = named_g_defaults;
        maps[i] = NULL;
 
+       obj = NULL;
+       result = named_config_get(maps, "http-port", &obj);
+       INSIST(result == ISC_R_SUCCESS);
+       named_g_httpport = (in_port_t)cfg_obj_asuint32(obj);
+
+       obj = NULL;
+       result = named_config_get(maps, "https-port", &obj);
+       INSIST(result == ISC_R_SUCCESS);
+       named_g_httpsport = (in_port_t)cfg_obj_asuint32(obj);
+
+       CHECK(cfg_tls_storage_load(config, &tls_storage));
+       CHECK(cfg_http_storage_load(config, &http_storage));
+
        /*
         * If bind.keys exists, load it.  If "dnssec-validation auto"
         * is turned on, the root key found there will be used as a
@@ -8991,7 +9021,8 @@ load_configuration(const char *filename, named_server_t *server,
                        /* check return code? */
                        (void)ns_listenlist_fromconfig(
                                clistenon, config, named_g_aclconfctx,
-                               named_g_mctx, AF_INET, &listenon);
+                               named_g_mctx, AF_INET, &http_storage,
+                               &tls_storage, &listenon);
                } else {
                        /*
                         * Not specified, use default.
@@ -9019,7 +9050,8 @@ load_configuration(const char *filename, named_server_t *server,
                        /* check return code? */
                        (void)ns_listenlist_fromconfig(
                                clistenon, config, named_g_aclconfctx,
-                               named_g_mctx, AF_INET6, &listenon);
+                               named_g_mctx, AF_INET6, &http_storage,
+                               &tls_storage, &listenon);
                } else {
                        /*
                         * Not specified, use default.
@@ -9780,6 +9812,9 @@ cleanup:
                isc_task_endexclusive(server->task);
        }
 
+       cfg_http_storage_uninit(&http_storage);
+       cfg_tls_storage_uninit(&tls_storage);
+
        isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL,
                      NAMED_LOGMODULE_SERVER, ISC_LOG_DEBUG(1),
                      "load_configuration: %s", isc_result_totext(result));
@@ -10987,7 +11022,9 @@ named_server_togglequerylog(named_server_t *server, isc_lex_t *lex) {
 static isc_result_t
 ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config,
                         cfg_aclconfctx_t *actx, isc_mem_t *mctx,
-                        uint16_t family, ns_listenlist_t **target) {
+                        uint16_t family, isc_cfg_http_storage_t *http_servers,
+                        isc_cfg_tls_data_storage_t *tls_storage,
+                        ns_listenlist_t **target) {
        isc_result_t result;
        const cfg_listelt_t *element;
        ns_listenlist_t *dlist = NULL;
@@ -11005,7 +11042,8 @@ ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config,
                ns_listenelt_t *delt = NULL;
                const cfg_obj_t *listener = cfg_listelt_value(element);
                result = ns_listenelt_fromconfig(listener, config, actx, mctx,
-                                                family, &delt);
+                                                family, http_servers,
+                                                tls_storage, &delt);
                if (result != ISC_R_SUCCESS) {
                        goto cleanup;
                }
@@ -11026,14 +11064,18 @@ cleanup:
 static isc_result_t
 ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
                        cfg_aclconfctx_t *actx, isc_mem_t *mctx,
-                       uint16_t family, ns_listenelt_t **target) {
+                       uint16_t family, isc_cfg_http_storage_t *http_servers,
+                       isc_cfg_tls_data_storage_t *tls_storage,
+                       ns_listenelt_t **target) {
        isc_result_t result;
-       const cfg_obj_t *tlsobj, *portobj, *dscpobj;
-       in_port_t port;
+       const cfg_obj_t *tlsobj, *portobj, *dscpobj, *httpobj;
+       in_port_t port = 0;
        isc_dscp_t dscp = -1;
        const char *key = NULL, *cert = NULL;
-       bool tls = false;
+       bool tls = false, http = false;
        ns_listenelt_t *delt = NULL;
+       isc_cfg_http_obj_t *http_server = NULL;
+       isc_cfg_tls_obj_t *tls_cert = NULL;
        REQUIRE(target != NULL && *target == NULL);
 
        /* XXXWPK TODO be more verbose on failures. */
@@ -11042,43 +11084,60 @@ ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
                if (!strcmp(cfg_obj_asstring(tlsobj), "ephemeral")) {
                        tls = true;
                } else {
-                       const cfg_obj_t *tlsconfigs = NULL;
-                       const cfg_listelt_t *element;
-                       (void)cfg_map_get(config, "tls", &tlsconfigs);
-                       for (element = cfg_list_first(tlsconfigs);
-                            element != NULL; element = cfg_list_next(element))
-                       {
-                               cfg_obj_t *tconfig = cfg_listelt_value(element);
-                               const cfg_obj_t *name =
-                                       cfg_map_getname(tconfig);
-                               if (!strcmp(cfg_obj_asstring(name),
-                                           cfg_obj_asstring(tlsobj))) {
-                                       tls = true;
-                                       const cfg_obj_t *keyo = NULL,
-                                                       *certo = NULL;
-                                       (void)cfg_map_get(tconfig, "key-file",
-                                                         &keyo);
-                                       if (keyo == NULL) {
-                                               return (ISC_R_FAILURE);
-                                       }
-                                       (void)cfg_map_get(tconfig, "cert-file",
-                                                         &certo);
-                                       if (certo == NULL) {
-                                               return (ISC_R_FAILURE);
-                                       }
-                                       key = cfg_obj_asstring(keyo);
-                                       cert = cfg_obj_asstring(certo);
-                                       break;
-                               }
+                       tls_cert = cfg_tls_storage_find(
+                               cfg_obj_asstring(tlsobj), tls_storage);
+                       if (tls_cert != NULL) {
+                               tls = true;
+                               key = tls_cert->key_file;
+                               cert = tls_cert->cert_file;
+                               INSIST(key != NULL);
+                               INSIST(cert != NULL);
                        }
                }
                if (!tls) {
                        return (ISC_R_FAILURE);
                }
        }
+       httpobj = cfg_tuple_get(listener, "http");
+       if (httpobj != NULL && cfg_obj_isstring(httpobj)) {
+               if (tls && tls_cert == NULL) {
+                       return (ISC_R_FAILURE);
+               }
+               http = true;
+               http_server = cfg_http_find(cfg_obj_asstring(httpobj),
+                                           http_servers);
+               if (http_server == NULL) {
+                       isc_log_write(
+                               named_g_lctx, NAMED_LOGCATEGORY_GENERAL,
+                               NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING,
+                               "HTTP(S) server \"%s\" is nowhere to be found",
+                               cfg_obj_asstring(httpobj));
+                       return (ISC_R_FAILURE);
+               }
+       }
        portobj = cfg_tuple_get(listener, "port");
        if (!cfg_obj_isuint32(portobj)) {
-               if (tls) {
+               if (http && tls) {
+                       if (named_g_httpsport != 0) {
+                               port = named_g_httpsport;
+                       } else {
+                               result = named_config_getport(
+                                       config, "https-port", &port);
+                               if (result != ISC_R_SUCCESS) {
+                                       return (result);
+                               }
+                       }
+               } else if (http && !tls) {
+                       if (named_g_httpport != 0) {
+                               port = named_g_port;
+                       } else {
+                               result = named_config_getport(
+                                       config, "http-port", &port);
+                               if (result != ISC_R_SUCCESS) {
+                                       return (result);
+                               }
+                       }
+               } else if (tls) {
                        if (named_g_tlsport != 0) {
                                port = named_g_tlsport;
                        } else {
@@ -11122,8 +11181,14 @@ ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
                dscp = (isc_dscp_t)cfg_obj_asuint32(dscpobj);
        }
 
-       result = ns_listenelt_create(mctx, port, dscp, NULL, tls, key, cert,
-                                    &delt);
+       if (http) {
+               INSIST(http_server != NULL);
+               result = ns_listenelt_from_http(http_server, tls_cert, port,
+                                               mctx, &delt);
+       } else {
+               result = ns_listenelt_create(mctx, port, dscp, NULL, tls, key,
+                                            cert, &delt);
+       }
        if (result != ISC_R_SUCCESS) {
                return (result);
        }
@@ -11139,6 +11204,65 @@ ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
        return (ISC_R_SUCCESS);
 }
 
+/*
+ * Create a listen list for HTTP/HTTPS
+ */
+static isc_result_t
+ns_listenelt_from_http(isc_cfg_http_obj_t *http, isc_cfg_tls_obj_t *tls,
+                      in_port_t port, isc_mem_t *mctx,
+                      ns_listenelt_t **target) {
+       isc_result_t result = ISC_R_SUCCESS;
+       ns_listenelt_t *delt = NULL;
+       const char *key = NULL, *cert = NULL;
+       char **http_endpoints = NULL;
+       size_t http_endpoints_number;
+       isc_cfg_http_endpoint_t *ep;
+       size_t i = 0;
+       REQUIRE(target != NULL && *target == NULL);
+
+       if (tls) {
+               INSIST(tls->key_file != NULL);
+               INSIST(tls->cert_file != NULL);
+               key = tls->key_file;
+               cert = tls->cert_file;
+       }
+
+       if (port == 0) {
+               port = tls != NULL ? named_g_httpsport : named_g_httpport;
+       }
+
+       for (ep = ISC_LIST_HEAD(http->endpoints), i = 0; ep != NULL;
+            ep = ISC_LIST_NEXT(ep, link), i++)
+               ;
+
+       INSIST(i > 0);
+
+       http_endpoints_number = i;
+       http_endpoints = isc_mem_allocate(mctx, sizeof(http_endpoints[0]) *
+                                                       http_endpoints_number);
+       for (ep = ISC_LIST_HEAD(http->endpoints), i = 0; ep != NULL;
+            ep = ISC_LIST_NEXT(ep, link), i++)
+       {
+               http_endpoints[i] = isc_mem_strdup(mctx, ep->path);
+       }
+
+       INSIST(i == http_endpoints_number);
+
+       result = ns_listenelt_create_http(mctx, port, named_g_dscp, NULL, key,
+                                         cert, http_endpoints,
+                                         http_endpoints_number, &delt);
+
+       if (result != ISC_R_SUCCESS) {
+               if (delt != NULL) {
+                       ns_listenelt_destroy(delt);
+               }
+               return result;
+       }
+
+       *target = delt;
+       return (result);
+}
+
 isc_result_t
 named_server_dumpstats(named_server_t *server) {
        isc_result_t result;
index df88dd1d1065aa688d3c1524fc4ae630c743adf1..76f8c7cdf4431b20fa7a7a5e33f70d62bfa54bee 100644 (file)
@@ -668,6 +668,7 @@ copy_setports() {
     atsign="@"
     sed -e "s/${atsign}PORT${atsign}/${PORT}/g" \
         -e "s/${atsign}TLSPORT${atsign}/${TLSPORT}/g" \
+               -e "s/${atsign}HTTPPORT${atsign}/${HTTPSPORT}/g" \
         -e "s/${atsign}HTTPSPORT${atsign}/${HTTPSPORT}/g" \
         -e "s/${atsign}EXTRAPORT1${atsign}/${EXTRAPORT1}/g" \
         -e "s/${atsign}EXTRAPORT2${atsign}/${EXTRAPORT2}/g" \
index f63ba472535a98c63b41f31ff4d8ddbdfe1b7874..a8bb7ae6a2b4b3a1642d59ad4fac25b69a0fc93c 100755 (executable)
@@ -82,6 +82,7 @@ done
 
 echo "export PORT=$(get_port "$baseport")"
 echo "export TLSPORT=$(get_port)"
+echo "export HTTPPORT=$(get_port)"
 echo "export HTTPSPORT=$(get_port)"
 echo "export EXTRAPORT1=$(get_port)"
 echo "export EXTRAPORT2=$(get_port)"
index 38021817622d5ac33faa923f94dfa11da935c6b6..7027ab21924857b1ce0476bf4fff1a4ad999b55c 100644 (file)
@@ -149,7 +149,7 @@ stop_servers() {
 echostart "S:$systest:$(date_with_args)"
 echoinfo  "T:$systest:1:A"
 echoinfo  "A:$systest:System test $systest"
-echoinfo  "I:$systest:PORTS:${PORT},${TLSPORT},${HTTPSPORT},${EXTRAPORT1},${EXTRAPORT2},${EXTRAPORT3},${EXTRAPORT4},${EXTRAPORT5},${EXTRAPORT6},${EXTRAPORT7},${EXTRAPORT8},${CONTROLPORT}"
+echoinfo  "I:$systest:PORTS:${PORT},${TLSPORT},${HTTPPORT},${HTTPSPORT},${EXTRAPORT1},${EXTRAPORT2},${EXTRAPORT3},${EXTRAPORT4},${EXTRAPORT5},${EXTRAPORT6},${EXTRAPORT7},${EXTRAPORT8},${CONTROLPORT}"
 
 $PERL ${srcdir}/testsock.pl -p "$PORT"  || {
     echowarn "I:$systest:Network interface aliases not set up.  Skipping test."
index d2e4ef4c0ad48c5b2c3389e999868a8e28c855d0..cb505f92378492bb7a961a35fa85312c62f9a964 100644 (file)
@@ -507,33 +507,37 @@ isc_nm_tlsdnsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
  */
 
 typedef void (*isc_nm_http_cb_t)(isc_nmhandle_t *handle, isc_result_t eresult,
-                                isc_region_t *postdata, isc_region_t *getdata,
-                                void *cbarg);
+                                isc_region_t *data, void *cbarg);
 /*%<
  * Callback function to be used when receiving an HTTP request.
  *
  * 'handle' the handle that can be used to send back the answer.
  * 'eresult' the result of the event.
- * 'postdata' contains the received POST data, if any. It will be freed
+ * 'data' contains the received data, if any. It will be freed
  *          after return by caller.
- * 'getdata' contains the received GET data (past '?'), if any. It will be
- *          freed after return by caller.
  * 'cbarg'  the callback argument passed to listen function.
  */
 
 isc_result_t
-isc_nm_doh_request(isc_nm_t *mgr, const char *uri, isc_region_t *message,
-                  isc_nm_recv_cb_t cb, void *cbarg, isc_ssl_ctx_t *ctx);
+isc_nm_http_connect_send_request(isc_nm_t *mgr, const char *uri, bool POST,
+                                isc_region_t *message, isc_nm_recv_cb_t cb,
+                                void *cbarg, isc_tlsctx_t *ctx,
+                                unsigned int timeout);
 
 isc_result_t
-isc_nm_httpsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
-                   const char *uri, isc_nm_cb_t cb, void *cbarg,
-                   unsigned int timeout, size_t extrahandlesize);
+isc_nm_httpconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
+                  const char *uri, bool POST, isc_nm_cb_t cb, void *cbarg,
+                  isc_tlsctx_t *ctx, unsigned int timeout,
+                  size_t extrahandlesize);
 
 isc_result_t
-isc_nm_listenhttps(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog,
-                  isc_quota_t *quota, isc_ssl_ctx_t *ctx,
-                  isc_nmsocket_t **sockp);
+isc_nm_httprequest(isc_nmhandle_t *handle, isc_region_t *region,
+                  isc_nm_recv_cb_t reply_cb, void *cbarg);
+
+isc_result_t
+isc_nm_listenhttp(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog,
+                 isc_quota_t *quota, isc_tlsctx_t *ctx,
+                 isc_nmsocket_t **sockp);
 
 isc_result_t
 isc_nm_http_add_endpoint(isc_nmsocket_t *sock, const char *uri,
index f11f4dfea980936bad23255e5a6db1bb6a01cb22..f6b5a778d4b603dcd235491b27026f1e7cfecd5a 100644 (file)
@@ -9,6 +9,9 @@
  * information regarding copyright ownership.
  */
 
+#include <ctype.h>
+#include <inttypes.h>
+#include <limits.h>
 #include <nghttp2/nghttp2.h>
 #include <signal.h>
 #include <string.h>
 #include <isc/base64.h>
 #include <isc/netmgr.h>
 #include <isc/print.h>
+#include <isc/tls.h>
 #include <isc/url.h>
 
 #include "netmgr-int.h"
 
 #define AUTHEXTRA 7
 
+#define MAX_DNS_MESSAGE_SIZE (UINT16_MAX)
+
+#define HEADER_MATCH(header, name, namelen)   \
+       (((namelen) == sizeof(header) - 1) && \
+        (strncasecmp((header), (const char *)(name), (namelen)) == 0))
+
+typedef struct isc_nm_http2_response_status {
+       size_t code;
+       size_t content_length;
+       bool content_type_valid;
+} isc_nm_http2_response_status_t;
+
 typedef struct http2_client_stream {
-       isc_nm_recv_cb_t cb;
-       void *cbarg;
+       isc_nm_recv_cb_t read_cb;
+       void *read_cbarg;
+
+       isc_nm_cb_t connect_cb;
+       void *connect_cbarg;
 
        char *uri;
        isc_url_parser_t up;
@@ -37,13 +56,21 @@ typedef struct http2_client_stream {
        size_t authoritylen;
        char *path;
 
-       uint8_t rbuf[65535];
+       uint8_t rbuf[MAX_DNS_MESSAGE_SIZE];
        size_t rbufsize;
 
        size_t pathlen;
        int32_t stream_id;
-       isc_region_t *postdata;
+
+       bool post; /* POST or GET */
+       isc_region_t postdata;
        size_t postdata_pos;
+       char *GET_path;
+       size_t GET_path_len;
+
+       isc_nm_http2_response_status_t response_status;
+
+       LINK(struct http2_client_stream) link;
 } http2_client_stream_t;
 
 #define HTTP2_SESSION_MAGIC    ISC_MAGIC('H', '2', 'S', 'S')
@@ -57,42 +84,94 @@ struct isc_nm_http2_session {
        bool reading;
 
        nghttp2_session *ngsession;
-       http2_client_stream_t *cstream;
+       bool client;
+
+       ISC_LIST(http2_client_stream_t) cstreams;
        ISC_LIST(isc_nmsocket_h2_t) sstreams;
+#ifdef NETMGR_TRACE
+       size_t sstreams_count;
+#endif /* NETMGR_TRACE */
 
        isc_nmhandle_t *handle;
        isc_nmsocket_t *serversocket;
 
        isc_region_t r;
-       uint8_t buf[65535];
+       uint8_t buf[MAX_DNS_MESSAGE_SIZE];
        size_t bufsize;
 
-       SSL_CTX *ctx;
+       bool ssl_ctx_created;
+       SSL_CTX *ssl_ctx;
 };
 
-static bool
+typedef enum isc_http2_error_responses {
+       ISC_HTTP_ERROR_SUCCESS,                /* 200 */
+       ISC_HTTP_ERROR_NOT_FOUND,              /* 404 */
+       ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE,      /* 413 */
+       ISC_HTTP_ERROR_URI_TOO_LONG,           /* 414 */
+       ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE, /* 415 */
+       ISC_HTTP_ERROR_BAD_REQUEST,            /* 400 */
+       ISC_HTTP_ERROR_NOT_IMPLEMENTED,        /* 501 */
+       ISC_HTTP_ERROR_GENERIC,                /* 500 Internal Server Error */
+       ISC_HTTP_ERROR_MAX
+} isc_http2_error_responses_t;
+
+static void
 http2_do_bio(isc_nm_http2_session_t *session);
 
+static void
+failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result,
+                         isc_nm_http2_session_t *session);
+
+static void
+failed_read_cb(isc_result_t result, isc_nm_http2_session_t *session);
+
+static int
+server_send_error_response(const isc_http2_error_responses_t error,
+                          nghttp2_session *ngsession, isc_nmsocket_t *socket);
+
+static bool
+inactive(isc_nmsocket_t *sock) {
+       return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
+               atomic_load(&sock->mgr->closing) ||
+               (sock->server != NULL && !isc__nmsocket_active(sock->server)));
+}
+
+static http2_client_stream_t *
+find_http2_client_stream(int32_t stream_id, isc_nm_http2_session_t *session) {
+       http2_client_stream_t *cstream = NULL;
+       REQUIRE(VALID_HTTP2_SESSION(session));
+
+       for (cstream = ISC_LIST_HEAD(session->cstreams); cstream != NULL;
+            cstream = ISC_LIST_NEXT(cstream, link))
+       {
+               if (cstream->stream_id == stream_id) {
+                       break;
+               }
+       }
+
+       return (cstream);
+}
+
 static isc_result_t
 get_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t **streamp,
-                       const char *uri, uint16_t *port) {
+                       const char *uri) {
        http2_client_stream_t *stream = NULL;
-       int rv;
+       isc_result_t result;
 
        REQUIRE(streamp != NULL && *streamp == NULL);
        REQUIRE(uri != NULL);
-       REQUIRE(port != NULL);
 
        stream = isc_mem_get(mctx, sizeof(http2_client_stream_t));
-       *stream = (http2_client_stream_t){ .stream_id = -1 };
+       *stream = (http2_client_stream_t){ .stream_id = -1, .rbufsize = 0 };
 
        stream->uri = isc_mem_strdup(mctx, uri);
 
-       rv = isc_url_parse(stream->uri, strlen(stream->uri), 0, &stream->up);
-       if (rv != 0) {
-               isc_mem_put(mctx, stream, sizeof(http2_client_stream_t));
+       result = isc_url_parse(stream->uri, strlen(stream->uri), 0,
+                              &stream->up);
+       if (result != ISC_R_SUCCESS) {
                isc_mem_free(mctx, stream->uri);
-               return (ISC_R_FAILURE);
+               isc_mem_put(mctx, stream, sizeof(http2_client_stream_t));
+               return (result);
        }
 
        stream->authoritylen = stream->up.field_data[ISC_UF_HOST].len;
@@ -136,10 +215,10 @@ get_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t **streamp,
                        stream->up.field_data[ISC_UF_QUERY].len);
        }
 
-       *port = 443;
-       if ((stream->up.field_set & (1 << ISC_UF_PORT)) != 0) {
-               *port = stream->up.port;
-       }
+       stream->GET_path = NULL;
+       stream->GET_path_len = 0;
+       stream->postdata = (isc_region_t){ .base = NULL, .length = 0 };
+       memset(&stream->response_status, 0, sizeof(stream->response_status));
 
        *streamp = stream;
 
@@ -149,12 +228,36 @@ get_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t **streamp,
 static void
 put_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t *stream) {
        isc_mem_put(mctx, stream->path, stream->pathlen);
-       isc_mem_put(mctx, stream->authority, stream->authoritylen + AUTHEXTRA);
+       isc_mem_put(mctx, stream->authority,
+                   stream->up.field_data[ISC_UF_HOST].len + AUTHEXTRA);
+       isc_mem_free(mctx, stream->uri);
+       if (stream->GET_path != NULL) {
+               isc_mem_free(mctx, stream->GET_path);
+               stream->GET_path = NULL;
+               stream->GET_path_len = 0;
+       }
+       if (stream->postdata.base != NULL) {
+               isc_mem_put(mctx, stream->postdata.base,
+                           stream->postdata.length);
+       }
        isc_mem_put(mctx, stream, sizeof(http2_client_stream_t));
 }
 
 static void
 delete_http2_session(isc_nm_http2_session_t *session) {
+       REQUIRE(ISC_LIST_EMPTY(session->sstreams));
+       REQUIRE(ISC_LIST_EMPTY(session->cstreams));
+
+       session->magic = 0;
+       if (session->ssl_ctx_created) {
+               SSL_CTX_free(session->ssl_ctx);
+       }
+       isc_mem_putanddetach(&session->mctx, session,
+                            sizeof(isc_nm_http2_session_t));
+}
+
+static void
+finish_http2_session(isc_nm_http2_session_t *session) {
        if (session->handle != NULL) {
                isc_nm_pauseread(session->handle);
                isc_nmhandle_detach(&session->handle);
@@ -163,9 +266,23 @@ delete_http2_session(isc_nm_http2_session_t *session) {
                nghttp2_session_del(session->ngsession);
                session->ngsession = NULL;
        }
-       if (session->cstream != NULL) {
-               put_http2_client_stream(session->mctx, session->cstream);
-               session->cstream = NULL;
+       if (!ISC_LIST_EMPTY(session->cstreams)) {
+               http2_client_stream_t *cstream =
+                       ISC_LIST_HEAD(session->cstreams);
+               while (cstream != NULL) {
+                       http2_client_stream_t *next = ISC_LIST_NEXT(cstream,
+                                                                   link);
+                       ISC_LIST_DEQUEUE(session->cstreams, cstream, link);
+                       put_http2_client_stream(session->mctx, cstream);
+
+                       cstream = next;
+               }
+       }
+       INSIST(ISC_LIST_EMPTY(session->cstreams));
+
+       /* detach from server socket */
+       if (session->serversocket != NULL) {
+               isc__nmsocket_detach(&session->serversocket);
        }
 
        /*
@@ -173,10 +290,6 @@ delete_http2_session(isc_nm_http2_session_t *session) {
         */
        if (session->sending) {
                session->closed = true;
-       } else if (!session->reading) {
-               session->magic = 0;
-               isc_mem_putanddetach(&session->mctx, session,
-                                    sizeof(isc_nm_http2_session_t));
        }
 }
 
@@ -189,25 +302,45 @@ on_data_chunk_recv_callback(nghttp2_session *ngsession, uint8_t flags,
        UNUSED(ngsession);
        UNUSED(flags);
 
-       if (session->cstream != NULL) {
-               if (session->cstream->stream_id == stream_id) {
-                       /* TODO buffer overrun! */
-                       memmove(session->cstream->rbuf +
-                                       session->cstream->rbufsize,
-                               data, len);
-                       session->cstream->rbufsize += len;
+       if (session->client) {
+               http2_client_stream_t *cstream =
+                       find_http2_client_stream(stream_id, session);
+               if (cstream) {
+                       size_t new_rbufsize = cstream->rbufsize + len;
+                       if (new_rbufsize <= MAX_DNS_MESSAGE_SIZE &&
+                           new_rbufsize <=
+                                   cstream->response_status.content_length)
+                       {
+                               memmove(cstream->rbuf + cstream->rbufsize, data,
+                                       len);
+                               cstream->rbufsize = new_rbufsize;
+                       } else {
+                               return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+                       }
+               } else {
+                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
                }
        } else {
                isc_nmsocket_h2_t *sock_h2 = ISC_LIST_HEAD(session->sstreams);
                while (sock_h2 != NULL) {
                        if (stream_id == sock_h2->stream_id) {
-                               memmove(sock_h2->buf + sock_h2->bufsize, data,
-                                       len);
-                               sock_h2->bufsize += len;
+                               size_t new_bufsize = sock_h2->bufsize + len;
+                               if (new_bufsize <= MAX_DNS_MESSAGE_SIZE &&
+                                   new_bufsize <= sock_h2->content_length)
+                               {
+                                       memmove(sock_h2->buf + sock_h2->bufsize,
+                                               data, len);
+                                       sock_h2->bufsize = new_bufsize;
+                               } else {
+                                       return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+                               }
                                break;
                        }
                        sock_h2 = ISC_LIST_NEXT(sock_h2, link);
                }
+               if (sock_h2 == NULL) {
+                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
+               }
        }
 
        return (0);
@@ -217,40 +350,68 @@ static int
 on_stream_close_callback(nghttp2_session *ngsession, int32_t stream_id,
                         uint32_t error_code, void *user_data) {
        isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data;
+       int rv = 0;
 
        REQUIRE(VALID_HTTP2_SESSION(session));
 
        UNUSED(error_code);
 
-       if (session->cstream != NULL) {
-               if (session->cstream->stream_id == stream_id) {
-                       int rv;
-
-                       session->cstream->cb(
-                               NULL, ISC_R_SUCCESS,
-                               &(isc_region_t){ session->cstream->rbuf,
-                                                session->cstream->rbufsize },
-                               session->cstream->cbarg);
-                       rv = nghttp2_session_terminate_session(
-                               ngsession, NGHTTP2_NO_ERROR);
-                       if (rv != 0) {
-                               return (NGHTTP2_ERR_CALLBACK_FAILURE);
+       /* NOTE: calling isc_nm_cancelread() or
+        * isc__nmsocket_prep_destroy() on a socket will lead to an
+        * indirect call to the delete_http2_session() which will, in
+        * turn, perform required stream session cleanup.*/
+       if (session->client) {
+               http2_client_stream_t *cstream =
+                       find_http2_client_stream(stream_id, session);
+               if (cstream) {
+                       isc_result_t result =
+                               cstream->response_status.code >= 200 &&
+                                               cstream->response_status.code <
+                                                       300
+                                       ? ISC_R_SUCCESS
+                                       : ISC_R_FAILURE;
+                       cstream->read_cb(session->handle, result,
+                                        &(isc_region_t){ cstream->rbuf,
+                                                         cstream->rbufsize },
+                                        cstream->read_cbarg);
+                       ISC_LIST_UNLINK(session->cstreams, cstream, link);
+                       put_http2_client_stream(session->mctx, cstream);
+                       if (ISC_LIST_EMPTY(session->cstreams)) {
+                               rv = nghttp2_session_terminate_session(
+                                       ngsession, NGHTTP2_NO_ERROR);
+                               if (rv != 0) {
+                                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
+                               }
+                               if (session->handle->sock->h2.session->reading)
+                               {
+                                       isc_nm_cancelread(
+                                               session->handle->sock->h2
+                                                       .session->handle);
+                               }
                        }
+               } else {
+                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
                }
        } else {
-               /* XXX */
+               isc_nmsocket_t *sock = nghttp2_session_get_stream_user_data(
+                       ngsession, stream_id);
+               if (ISC_LIST_EMPTY(session->sstreams)) {
+                       rv = nghttp2_session_terminate_session(
+                               ngsession, NGHTTP2_NO_ERROR);
+               }
+               isc__nmsocket_prep_destroy(sock);
+               if (rv != 0) {
+                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
+               }
        }
 
-       /* XXXWPK TODO we need to close the session */
-
        return (0);
 }
 
 #ifndef OPENSSL_NO_NEXTPROTONEG
 /*
  * NPN TLS extension client callback. We check that server advertised
- * the HTTP/2 protocol the nghttp2 library supports. If not, exit the
- * program.
+ * the HTTP/2 protocol the nghttp2 library supports.
  */
 static int
 select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
@@ -259,7 +420,7 @@ select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
        UNUSED(arg);
 
        if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
-               /* TODO */
+               return (SSL_TLSEXT_ERR_NOACK);
        }
        return (SSL_TLSEXT_ERR_OK);
 }
@@ -267,32 +428,109 @@ select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
 
 /* Create SSL_CTX. */
 static SSL_CTX *
-create_ssl_ctx(void) {
+create_client_ssl_ctx(void) {
        SSL_CTX *ssl_ctx = NULL;
 
-       ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+       RUNTIME_CHECK(isc_tlsctx_createclient(&ssl_ctx) == ISC_R_SUCCESS);
        RUNTIME_CHECK(ssl_ctx != NULL);
 
-       SSL_CTX_set_options(
-               ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
-                                SSL_OP_NO_COMPRESSION |
-                                SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
 #ifndef OPENSSL_NO_NEXTPROTONEG
        SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
 #endif /* !OPENSSL_NO_NEXTPROTONEG */
 
 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
-       SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3);
+       SSL_CTX_set_alpn_protos(ssl_ctx,
+                               (const unsigned char *)NGHTTP2_PROTO_ALPN,
+                               NGHTTP2_PROTO_ALPN_LEN);
 #endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
 
+       ENSURE(ssl_ctx != NULL);
        return (ssl_ctx);
 }
 
+static void
+client_handle_status_header(http2_client_stream_t *cstream,
+                           const uint8_t *value, const size_t valuelen) {
+       char tmp[32] = { 0 };
+       const size_t tmplen = sizeof(tmp) - 1;
+
+       strncpy(tmp, (const char *)value,
+               valuelen > tmplen ? tmplen : valuelen);
+       cstream->response_status.code = strtoul(tmp, NULL, 10);
+}
+
+static void
+client_handle_content_length_header(http2_client_stream_t *cstream,
+                                   const uint8_t *value,
+                                   const size_t valuelen) {
+       char tmp[32] = { 0 };
+       const size_t tmplen = sizeof(tmp) - 1;
+
+       strncpy(tmp, (const char *)value,
+               valuelen > tmplen ? tmplen : valuelen);
+       cstream->response_status.content_length = strtoul(tmp, NULL, 10);
+}
+
+static void
+client_handle_content_type_header(http2_client_stream_t *cstream,
+                                 const uint8_t *value, const size_t valuelen) {
+       const char type_dns_message[] = "application/dns-message";
+
+       UNUSED(valuelen);
+
+       if (strncasecmp((const char *)value, type_dns_message,
+                       sizeof(type_dns_message) - 1) == 0)
+       {
+               cstream->response_status.content_type_valid = true;
+       }
+}
+
+static int
+client_on_header_callback(nghttp2_session *ngsession,
+                         const nghttp2_frame *frame, const uint8_t *name,
+                         size_t namelen, const uint8_t *value, size_t valuelen,
+                         uint8_t flags, void *user_data) {
+       isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data;
+       const char status[] = ":status";
+       const char content_length[] = "Content-Length";
+       const char content_type[] = "Content-Type";
+
+       REQUIRE(VALID_HTTP2_SESSION(session));
+       REQUIRE(session->client);
+       REQUIRE(!ISC_LIST_EMPTY(session->cstreams));
+
+       UNUSED(flags);
+       UNUSED(ngsession);
+
+       http2_client_stream_t *cstream =
+               find_http2_client_stream(frame->hd.stream_id, session);
+
+       switch (frame->hd.type) {
+       case NGHTTP2_HEADERS:
+               if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
+                       break;
+               }
+
+               if (HEADER_MATCH(status, name, namelen)) {
+                       client_handle_status_header(cstream, value, valuelen);
+               } else if (HEADER_MATCH(content_length, name, namelen)) {
+                       client_handle_content_length_header(cstream, value,
+                                                           valuelen);
+               } else if (HEADER_MATCH(content_type, name, namelen)) {
+                       client_handle_content_type_header(cstream, value,
+                                                         valuelen);
+               }
+               break;
+       }
+
+       return (0);
+}
+
 static void
 initialize_nghttp2_client_session(isc_nm_http2_session_t *session) {
        nghttp2_session_callbacks *callbacks = NULL;
 
-       nghttp2_session_callbacks_new(&callbacks);
+       RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0);
 
        nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
                callbacks, on_data_chunk_recv_callback);
@@ -300,25 +538,26 @@ initialize_nghttp2_client_session(isc_nm_http2_session_t *session) {
        nghttp2_session_callbacks_set_on_stream_close_callback(
                callbacks, on_stream_close_callback);
 
+       nghttp2_session_callbacks_set_on_header_callback(
+               callbacks, client_on_header_callback);
+
        nghttp2_session_client_new(&session->ngsession, callbacks, session);
 
        nghttp2_session_callbacks_del(callbacks);
 }
 
-static void
+static bool
 send_client_connection_header(isc_nm_http2_session_t *session) {
-       nghttp2_settings_entry iv[1] = {
-               { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 }
-       };
+       nghttp2_settings_entry iv[] = { { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 } };
        int rv;
 
        rv = nghttp2_submit_settings(session->ngsession, NGHTTP2_FLAG_NONE, iv,
-                                    1);
+                                    sizeof(iv) / sizeof(iv[0]));
        if (rv != 0) {
-               /* TODO */
+               return (false);
        }
 
-       http2_do_bio(session);
+       return (true);
 }
 
 #define MAKE_NV(NAME, VALUE, VALUELEN)                                       \
@@ -335,36 +574,44 @@ send_client_connection_header(isc_nm_http2_session_t *session) {
        }
 
 static ssize_t
-client_post_read_callback(nghttp2_session *ngsession, int32_t stream_id,
-                         uint8_t *buf, size_t length, uint32_t *data_flags,
-                         nghttp2_data_source *source, void *user_data) {
+client_read_callback(nghttp2_session *ngsession, int32_t stream_id,
+                    uint8_t *buf, size_t length, uint32_t *data_flags,
+                    nghttp2_data_source *source, void *user_data) {
        isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data;
+       http2_client_stream_t *cstream;
 
-       REQUIRE(session->cstream != NULL);
+       REQUIRE(session->client);
+       REQUIRE(!ISC_LIST_EMPTY(session->cstreams));
 
        UNUSED(ngsession);
        UNUSED(source);
 
-       if (session->cstream->stream_id == stream_id) {
-               size_t len = session->cstream->postdata->length -
-                            session->cstream->postdata_pos;
+       cstream = find_http2_client_stream(stream_id, session);
+       if (!cstream || cstream->stream_id != stream_id) {
+               /* We haven't found the stream, so we are not reading anything
+                */
+               return (NGHTTP2_ERR_CALLBACK_FAILURE);
+       }
+
+       if (cstream->post) {
+               size_t len = cstream->postdata.length - cstream->postdata_pos;
 
                if (len > length) {
                        len = length;
                }
 
-               memmove(buf,
-                       session->cstream->postdata->base +
-                               session->cstream->postdata_pos,
+               memmove(buf, cstream->postdata.base + cstream->postdata_pos,
                        len);
-               session->cstream->postdata_pos += len;
+               cstream->postdata_pos += len;
 
-               if (session->cstream->postdata_pos ==
-                   session->cstream->postdata->length) {
+               if (cstream->postdata_pos == cstream->postdata.length) {
                        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
                }
 
                return (len);
+       } else {
+               *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+               return (0);
        }
 
        return (0);
@@ -372,31 +619,55 @@ client_post_read_callback(nghttp2_session *ngsession, int32_t stream_id,
 
 /* Send HTTP request to the remote peer */
 static isc_result_t
-client_submit_request(isc_nm_http2_session_t *session) {
+client_submit_request(isc_nm_http2_session_t *session,
+                     http2_client_stream_t *stream) {
        int32_t stream_id;
-       http2_client_stream_t *stream = session->cstream;
        char *uri = stream->uri;
        isc_url_parser_t *up = &stream->up;
        nghttp2_data_provider dp;
-       char p[64];
-
-       snprintf(p, 64, "%u", stream->postdata->length);
-
-       nghttp2_nv hdrs[] = {
-               MAKE_NV2(":method", "POST"),
-               MAKE_NV(":scheme", &uri[up->field_data[ISC_UF_SCHEMA].off],
-                       up->field_data[ISC_UF_SCHEMA].len),
-               MAKE_NV(":authority", stream->authority, stream->authoritylen),
-               MAKE_NV(":path", stream->path, stream->pathlen),
-               MAKE_NV2("content-type", "application/dns-message"),
-               MAKE_NV2("accept", "application/dns-message"),
-               MAKE_NV("content-length", p, strlen(p)),
-       };
 
-       dp = (nghttp2_data_provider){ .read_callback =
-                                             client_post_read_callback };
-       stream_id = nghttp2_submit_request(session->ngsession, NULL, hdrs, 7,
-                                          &dp, stream);
+       if (stream->post) {
+               char p[64];
+               snprintf(p, sizeof(p), "%u", stream->postdata.length);
+               nghttp2_nv hdrs[] = {
+                       MAKE_NV2(":method", "POST"),
+                       MAKE_NV(":scheme",
+                               &uri[up->field_data[ISC_UF_SCHEMA].off],
+                               up->field_data[ISC_UF_SCHEMA].len),
+                       MAKE_NV(":authority", stream->authority,
+                               stream->authoritylen),
+                       MAKE_NV(":path", stream->path, stream->pathlen),
+                       MAKE_NV2("content-type", "application/dns-message"),
+                       MAKE_NV2("accept", "application/dns-message"),
+                       MAKE_NV("content-length", p, strlen(p)),
+               };
+
+               dp = (nghttp2_data_provider){ .read_callback =
+                                                     client_read_callback };
+               stream_id = nghttp2_submit_request(
+                       session->ngsession, NULL, hdrs,
+                       sizeof(hdrs) / sizeof(hdrs[0]), &dp, stream);
+       } else {
+               INSIST(stream->GET_path != NULL);
+               INSIST(stream->GET_path_len != 0);
+               nghttp2_nv hdrs[] = {
+                       MAKE_NV2(":method", "GET"),
+                       MAKE_NV(":scheme",
+                               &uri[up->field_data[ISC_UF_SCHEMA].off],
+                               up->field_data[ISC_UF_SCHEMA].len),
+                       MAKE_NV(":authority", stream->authority,
+                               stream->authoritylen),
+                       MAKE_NV(":path", stream->GET_path,
+                               stream->GET_path_len),
+                       MAKE_NV2("accept", "application/dns-message")
+               };
+
+               dp = (nghttp2_data_provider){ .read_callback =
+                                                     client_read_callback };
+               stream_id = nghttp2_submit_request(
+                       session->ngsession, NULL, hdrs,
+                       sizeof(hdrs) / sizeof(hdrs[0]), &dp, stream);
+       }
        if (stream_id < 0) {
                return (ISC_R_FAILURE);
        }
@@ -419,26 +690,23 @@ https_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
        REQUIRE(VALID_HTTP2_SESSION(session));
 
        UNUSED(handle);
-       UNUSED(result);
 
        if (result != ISC_R_SUCCESS) {
                session->reading = false;
-               delete_http2_session(session);
-               /* TODO callback! */
+               failed_read_cb(result, session);
                return;
        }
 
        readlen = nghttp2_session_mem_recv(session->ngsession, region->base,
                                           region->length);
        if (readlen < 0) {
-               delete_http2_session(session);
-               /* TODO callback! */
+               failed_read_cb(ISC_R_CANCELED, session);
                return;
        }
 
        if (readlen < region->length) {
                INSIST(session->bufsize == 0);
-               INSIST(region->length - readlen < 65535);
+               INSIST(region->length - readlen < MAX_DNS_MESSAGE_SIZE);
                memmove(session->buf, region->base, region->length - readlen);
                session->bufsize = region->length - readlen;
                isc_nm_pauseread(session->handle);
@@ -455,15 +723,16 @@ https_writecb(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
        REQUIRE(VALID_HTTP2_SESSION(session));
 
        UNUSED(handle);
-       UNUSED(result);
 
        session->sending = false;
        isc_mem_put(session->mctx, session->r.base, session->r.length);
        session->r.base = NULL;
-       http2_do_bio(session);
+       if (result == ISC_R_SUCCESS) {
+               http2_do_bio(session);
+       }
 }
 
-static bool
+static void
 http2_do_bio(isc_nm_http2_session_t *session) {
        REQUIRE(VALID_HTTP2_SESSION(session));
 
@@ -471,8 +740,8 @@ http2_do_bio(isc_nm_http2_session_t *session) {
            (nghttp2_session_want_read(session->ngsession) == 0 &&
             nghttp2_session_want_write(session->ngsession) == 0))
        {
-               delete_http2_session(session);
-               return (false);
+               finish_http2_session(session);
+               return;
        }
 
        if (nghttp2_session_want_read(session->ngsession) != 0) {
@@ -495,7 +764,7 @@ http2_do_bio(isc_nm_http2_session_t *session) {
                        }
 
                        http2_do_bio(session);
-                       return (false);
+                       return;
                } else {
                        /* Resume reading, it's idempotent, wait for more */
                        isc_nm_resumeread(session->handle);
@@ -528,128 +797,404 @@ http2_do_bio(isc_nm_http2_session_t *session) {
                session->sending = true;
                isc_nm_send(session->handle, &session->r, https_writecb,
                            session);
-               return (true);
+               return;
        }
 
-       return (false);
+       return;
 }
 
+typedef struct http_connect_data {
+       char *uri;
+       isc_nm_cb_t connect_cb;
+       void *connect_cbarg;
+       bool post;
+       bool ssl_ctx_created;
+       SSL_CTX *ssl_ctx;
+} http_connect_data_t;
+
 static void
-https_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
-       isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)arg;
+transport_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+       char *uri = NULL;
+       http_connect_data_t *pconn_data = (http_connect_data_t *)cbarg;
+       http_connect_data_t conn_data;
+       isc_nm_http2_session_t *session = NULL;
+       isc_mem_t *mctx;
+
+       REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(cbarg != NULL);
+
+       handle->sock->h2.session = NULL;
+       mctx = handle->sock->mgr->mctx;
+
+       conn_data = *pconn_data;
+       uri = conn_data.uri;
+       isc_mem_put(mctx, pconn_data, sizeof(*pconn_data));
+
+       INSIST(conn_data.connect_cb != NULL);
+       INSIST(conn_data.uri != NULL);
 
        if (result != ISC_R_SUCCESS) {
-               delete_http2_session(session);
-               return;
+               goto error;
        }
 
-       isc_nmhandle_attach(handle, &session->handle);
-
-#if 0
-/* TODO H2 */
+       session = isc_mem_get(mctx, sizeof(isc_nm_http2_session_t));
+       *session = (isc_nm_http2_session_t){ .magic = HTTP2_SESSION_MAGIC,
+#ifdef NETMGR_TRACE
+                                            .sstreams_count = 0,
+#endif /* NETMGR_TRACE */
+                                            .ssl_ctx_created =
+                                                    conn_data.ssl_ctx_created,
+                                            .ssl_ctx = conn_data.ssl_ctx,
+                                            .handle = NULL,
+                                            .client = true };
+       isc_mem_attach(mctx, &session->mctx);
+
+       handle->sock->h2.connect.uri = uri;
+       handle->sock->h2.connect.post = conn_data.post;
+
+       session->ssl_ctx = conn_data.ssl_ctx;
+       conn_data.ssl_ctx = NULL;
+       session->ssl_ctx_created = conn_data.ssl_ctx_created;
+       conn_data.ssl_ctx_created = false;
+
+       if (session->ssl_ctx != NULL) {
+               const unsigned char *alpn = NULL;
+               unsigned int alpnlen = 0;
+               SSL *ssl = NULL;
+
+               REQUIRE(VALID_NMHANDLE(handle));
+               REQUIRE(VALID_NMSOCK(handle->sock));
+               REQUIRE(handle->sock->type == isc_nm_tlssocket);
+
+               ssl = handle->sock->tlsstream.ssl;
+               INSIST(ssl != NULL);
 #ifndef OPENSSL_NO_NEXTPROTONEG
-       SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen);
+               SSL_get0_next_proto_negotiated(handle->sock->tlsstream.ssl,
+                                              &alpn, &alpnlen);
 #endif
 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
-       if (alpn == NULL) {
-               SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
-       }
+               if (alpn == NULL) {
+                       SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
+               }
 #endif
 
-       if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) {
-               delete_http2_session(session);
-               return;
+               if (alpn == NULL || alpnlen != 2 ||
+                   memcmp(NGHTTP2_PROTO_VERSION_ID, alpn,
+                          NGHTTP2_PROTO_VERSION_ID_LEN) != 0)
+               {
+                       result = ISC_R_CANCELED;
+                       goto error;
+               }
        }
-#endif
+
+       isc_nmhandle_attach(handle, &session->handle);
+       handle->sock->h2.session = session;
 
        initialize_nghttp2_client_session(session);
-       send_client_connection_header(session);
-       client_submit_request(session);
-       http2_do_bio(session);
+       if (!send_client_connection_header(session)) {
+               handle->sock->h2.session = NULL;
+               goto error;
+       }
+       conn_data.connect_cb(handle, ISC_R_SUCCESS, conn_data.connect_cbarg);
+       if (ISC_LIST_EMPTY(session->cstreams)) {
+               delete_http2_session(session);
+               isc__nmsocket_prep_destroy(handle->sock);
+       } else {
+               http2_do_bio(session);
+       }
+
+       return;
+error:
+       conn_data.connect_cb(handle, result, conn_data.connect_cbarg);
+       if (conn_data.ssl_ctx_created && conn_data.ssl_ctx) {
+               SSL_CTX_free(conn_data.ssl_ctx);
+       }
+
+       if (session != NULL) {
+               delete_http2_session(session);
+       }
+
+       if (uri != NULL) {
+               isc_mem_free(mctx, uri);
+       }
 }
 
 isc_result_t
-isc_nm_httpsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
-                   const char *uri, isc_nm_cb_t cb, void *cbarg,
-                   unsigned int timeout, size_t extrahandlesize) {
+isc_nm_httpconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
+                  const char *uri, bool post, isc_nm_cb_t cb, void *cbarg,
+                  SSL_CTX *ctx, unsigned int timeout, size_t extrahandlesize) {
+       isc_nmiface_t resolved_peer, resolved_local;
+       http_connect_data_t *pconn_data;
+       isc_result_t result;
+       uint16_t port = 80;
+       isc_url_parser_t url_parser;
+       bool create_ssl_ctx;
+       const char http[] = "http";
+       const char http_secured[] = "https";
+       size_t schema_len;
+       const char *schema;
+
        REQUIRE(VALID_NM(mgr));
+       REQUIRE(cb != NULL);
+       REQUIRE(uri != NULL);
+       REQUIRE(*uri != '\0');
+
+       result = isc_url_parse(uri, strlen(uri), 0, &url_parser);
+       if (result != ISC_R_SUCCESS) {
+               return (result);
+       }
+
+       schema_len = url_parser.field_data[ISC_UF_SCHEMA].len;
+       INSIST(schema_len > 0);
+       schema = &uri[url_parser.field_data[ISC_UF_SCHEMA].off];
+
+       if (schema_len == sizeof(http_secured) - 1 &&
+           strncasecmp(http_secured, schema, sizeof(http_secured) - 1) == 0)
+       {
+               create_ssl_ctx = true;
+       } else if (schema_len == sizeof(http) - 1 &&
+                  strncasecmp(http, schema, sizeof(http) - 1) == 0)
+       {
+               create_ssl_ctx = false;
+       } else {
+               INSIST(0);
+               ISC_UNREACHABLE();
+       }
+
+       if (ctx == NULL && create_ssl_ctx) {
+               port = 443;
+               ctx = create_client_ssl_ctx();
+       }
+
+       if (peer == NULL || local == NULL) {
+               size_t hostlen = 0;
+               char *host = NULL;
+               struct addrinfo hints;
+               struct addrinfo *res = NULL;
+
+#ifndef WIN32 /* FIXME */
+               /* extract host name */
+               hostlen = url_parser.field_data[ISC_UF_HOST].len + 1;
+               host = isc_mem_get(mgr->mctx, hostlen);
+               memmove(host, &uri[url_parser.field_data[ISC_UF_HOST].off],
+                       hostlen - 1);
+               host[hostlen - 1] = '\0';
+
+               memset(&hints, 0, sizeof(hints));
+               hints.ai_family = AF_UNSPEC;
+               hints.ai_socktype = SOCK_STREAM;
+               hints.ai_protocol = IPPROTO_TCP;
+               hints.ai_flags = AI_CANONNAME;
+
+               result = getaddrinfo(host, NULL, &hints, &res);
+               isc_mem_put(mgr->mctx, host, hostlen);
+               if (result != 0) {
+                       return (ISC_R_FAILURE);
+               }
+#endif /* WIN32 */
+
+               if ((url_parser.field_set & (1 << ISC_UF_PORT)) != 0) {
+                       port = url_parser.port;
+               }
+
+               if (peer == NULL) {
+                       (void)isc_sockaddr_fromsockaddr(&resolved_peer.addr,
+                                                       res->ai_addr);
+                       isc_sockaddr_setport(&resolved_peer.addr, port);
+                       peer = &resolved_peer;
+               }
+               if (local == NULL) {
+                       isc_sockaddr_anyofpf(&resolved_local.addr,
+                                            res->ai_family);
+                       local = &resolved_local;
+               }
+
+               freeaddrinfo(res);
+       }
+
+       pconn_data = isc_mem_get(mgr->mctx, sizeof(*pconn_data));
+       *pconn_data =
+               (http_connect_data_t){ .uri = isc_mem_strdup(mgr->mctx, uri),
+                                      .post = post,
+                                      .connect_cb = cb,
+                                      .connect_cbarg = cbarg,
+                                      .ssl_ctx_created = create_ssl_ctx,
+                                      .ssl_ctx = ctx };
 
-       UNUSED(local);
-       UNUSED(peer);
-       UNUSED(uri);
-       UNUSED(cb);
-       UNUSED(cbarg);
-       UNUSED(timeout);
-       UNUSED(extrahandlesize);
+       if (ctx != NULL) {
+               result = isc_nm_tlsconnect(mgr, local, peer,
+                                          transport_connect_cb, pconn_data,
+                                          ctx, timeout, extrahandlesize);
+       } else {
+               result = isc_nm_tcpconnect(mgr, local, peer,
+                                          transport_connect_cb, pconn_data,
+                                          timeout, extrahandlesize);
+       }
 
-       return (ISC_R_NOTIMPLEMENTED);
+       return (result);
 }
 
 isc_result_t
-isc_nm_doh_request(isc_nm_t *mgr, const char *uri, isc_region_t *region,
-                  isc_nm_recv_cb_t cb, void *cbarg, SSL_CTX *ctx) {
-       uint16_t port;
-       char *host = NULL;
-       isc_nm_http2_session_t *session = NULL;
+isc_nm_httprequest(isc_nmhandle_t *handle, isc_region_t *region,
+                  isc_nm_recv_cb_t reply_cb, void *cbarg) {
        http2_client_stream_t *cstream = NULL;
-       struct addrinfo hints;
-       struct addrinfo *res = NULL;
-       isc_sockaddr_t local, peer;
-       isc_result_t result;
-       int s;
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nm_http2_session_t *session = NULL;
+       isc_mem_t *mctx;
 
-       if (ctx == NULL) {
-               ctx = create_ssl_ctx();
+       REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(VALID_NMSOCK(handle->sock));
+       REQUIRE(handle->sock->tid == isc_nm_tid());
+       REQUIRE(VALID_HTTP2_SESSION(handle->sock->h2.session));
+       REQUIRE(region != NULL);
+       REQUIRE(region->base != NULL);
+       REQUIRE(region->length != 0);
+       REQUIRE(region->length <= MAX_DNS_MESSAGE_SIZE);
+       REQUIRE(reply_cb != NULL);
+
+       session = handle->sock->h2.session;
+       mctx = handle->sock->mgr->mctx;
+
+       result = get_http2_client_stream(mctx, &cstream,
+                                        handle->sock->h2.connect.uri);
+       if (result != ISC_R_SUCCESS) {
+               goto error;
        }
 
-       session = isc_mem_get(mgr->mctx, sizeof(isc_nm_http2_session_t));
-       *session = (isc_nm_http2_session_t){ .magic = HTTP2_SESSION_MAGIC,
-                                            .ctx = ctx };
-       isc_mem_attach(mgr->mctx, &session->mctx);
+       cstream->read_cb = reply_cb;
+       cstream->read_cbarg = cbarg;
+       cstream->post = handle->sock->h2.connect.post;
+
+       if (cstream->post) { /* POST */
+               cstream->postdata = (isc_region_t){
+                       .base = isc_mem_get(mctx, region->length),
+                       .length = region->length
+               };
+               memmove(cstream->postdata.base, region->base, region->length);
+               cstream->postdata_pos = 0;
+       } else { /* GET */
+               size_t path_size = 0;
+               char *base64url_data = NULL;
+               size_t base64url_data_len = 0;
+               isc_buffer_t *buf = NULL;
+               isc_region_t data = *region;
+               isc_region_t base64_region;
+               size_t base64_len = ((4 * data.length / 3) + 3) & ~3;
+
+               isc_buffer_allocate(mctx, &buf, base64_len);
+
+               if ((result = isc_base64_totext(&data, -1, "", buf)) !=
+                   ISC_R_SUCCESS) {
+                       isc_buffer_free(&buf);
+                       goto error;
+               }
 
-       result = get_http2_client_stream(mgr->mctx, &cstream, uri, &port);
-       if (result != ISC_R_SUCCESS) {
-               delete_http2_session(session);
-               return (result);
-       }
+               isc__buffer_usedregion(buf, &base64_region);
+               INSIST(base64_region.length == base64_len);
 
-       cstream->postdata = region;
-       cstream->postdata_pos = 0;
-       cstream->cb = cb;
-       cstream->cbarg = cbarg;
+               base64url_data = isc__nm_base64_to_base64url(
+                       mctx, (const char *)base64_region.base,
+                       base64_region.length, &base64url_data_len);
+               isc_buffer_free(&buf);
+               if (base64url_data == NULL) {
+                       goto error;
+               }
 
-       session->cstream = cstream;
+               /* len("?dns=") + len(path) + len(base64url) + len("\0") */
+               path_size = cstream->pathlen + base64url_data_len + 5 + 1;
+               cstream->GET_path = isc_mem_allocate(mctx, path_size);
+               cstream->GET_path_len = (size_t)snprintf(
+                       cstream->GET_path, path_size, "%.*s?dns=%s",
+                       (int)cstream->pathlen, cstream->path, base64url_data);
 
-#ifndef WIN32 /* FIXME */
-       hints = (struct addrinfo){ .ai_family = PF_UNSPEC,
-                                  .ai_socktype = SOCK_STREAM,
-                                  .ai_flags = AI_CANONNAME };
-       host = isc_mem_strndup(mgr->mctx, cstream->authority,
-                              cstream->authoritylen + 1);
-
-       s = getaddrinfo(host, NULL, &hints, &res);
-       isc_mem_free(mgr->mctx, host);
-       if (s != 0) {
-               delete_http2_session(session);
-               return (ISC_R_FAILURE);
+               INSIST(cstream->GET_path_len == (path_size - 1));
+               isc_mem_free(mctx, base64url_data);
        }
-#endif /* WIN32 */
 
-       isc_sockaddr_fromsockaddr(&peer, res->ai_addr);
-       isc_sockaddr_setport(&peer, port);
-       isc_sockaddr_anyofpf(&local, res->ai_family);
+       ISC_LINK_INIT(cstream, link);
+       ISC_LIST_APPEND(session->cstreams, cstream, link);
 
-       freeaddrinfo(res);
+       INSIST(cstream != NULL);
 
-       result = isc_nm_tlsconnect(mgr, (isc_nmiface_t *)&local,
-                                  (isc_nmiface_t *)&peer, https_connect_cb,
-                                  session, ctx, 30000, 0);
-       /* XXX: timeout is hard-coded to 30 seconds - make it a parameter */
+       result = client_submit_request(session, cstream);
        if (result != ISC_R_SUCCESS) {
-               return (result);
+               ISC_LIST_UNLINK(session->cstreams, cstream, link);
+               goto error;
        }
+       http2_do_bio(session);
 
        return (ISC_R_SUCCESS);
+error:
+       reply_cb(handle, result, NULL, cbarg);
+       if (cstream) {
+               put_http2_client_stream(mctx, cstream);
+       }
+       return (result);
+}
+
+typedef struct isc_nm_connect_send_data {
+       isc_nm_recv_cb_t reply_cb;
+       void *cb_arg;
+       isc_region_t region;
+} isc_nm_connect_send_data_t;
+
+static void
+https_connect_send_cb(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
+       isc_nm_connect_send_data_t data;
+
+       REQUIRE(VALID_NMHANDLE(handle));
+
+       memmove(&data, arg, sizeof(data));
+       isc_mem_put(handle->sock->mgr->mctx, arg, sizeof(data));
+       if (result != ISC_R_SUCCESS) {
+               goto error;
+       }
+
+       result = isc_nm_httprequest(handle, &data.region, data.reply_cb,
+                                   data.cb_arg);
+       if (result != ISC_R_SUCCESS) {
+               goto error;
+       }
+
+       isc_mem_put(handle->sock->mgr->mctx, data.region.base,
+                   data.region.length);
+       return;
+error:
+       data.reply_cb(handle, result, NULL, data.cb_arg);
+       isc_mem_put(handle->sock->mgr->mctx, data.region.base,
+                   data.region.length);
+}
+
+isc_result_t
+isc_nm_http_connect_send_request(isc_nm_t *mgr, const char *uri, bool post,
+                                isc_region_t *region, isc_nm_recv_cb_t cb,
+                                void *cbarg, SSL_CTX *ctx,
+                                unsigned int timeout) {
+       isc_region_t copy;
+       isc_result_t result;
+       isc_nm_connect_send_data_t *data;
+
+       REQUIRE(VALID_NM(mgr));
+       REQUIRE(uri != NULL);
+       REQUIRE(*uri != '\0');
+       REQUIRE(region != NULL);
+       REQUIRE(region->base != NULL);
+       REQUIRE(region->length != 0);
+       REQUIRE(region->length <= MAX_DNS_MESSAGE_SIZE);
+       REQUIRE(cb != NULL);
+
+       copy = (isc_region_t){ .base = isc_mem_get(mgr->mctx, region->length),
+                              .length = region->length };
+       memmove(copy.base, region->base, region->length);
+       data = isc_mem_get(mgr->mctx, sizeof(*data));
+       *data = (isc_nm_connect_send_data_t){ .reply_cb = cb,
+                                             .cb_arg = cbarg,
+                                             .region = copy };
+       result = isc_nm_httpconnect(mgr, NULL, NULL, uri, post,
+                                   https_connect_send_cb, data, ctx, timeout,
+                                   0);
+
+       return (result);
 }
 
 static int
@@ -669,29 +1214,214 @@ server_on_begin_headers_callback(nghttp2_session *ngsession,
        iface = isc_nmhandle_localaddr(session->handle);
        isc__nmsocket_init(socket, session->serversocket->mgr,
                           isc_nm_httpstream, (isc_nmiface_t *)&iface);
-       socket->h2 = (isc_nmsocket_h2_t){ .bufpos = 0,
-                                         .bufsize = 0,
-                                         .psock = socket,
-                                         .handler = NULL,
-                                         .request_path = NULL,
-                                         .query_data = NULL,
-                                         .stream_id = frame->hd.stream_id,
-                                         .session = session };
-
+       socket->h2 = (isc_nmsocket_h2_t){
+               .bufpos = 0,
+               .bufsize = 0,
+               .buf = isc_mem_allocate(session->mctx, MAX_DNS_MESSAGE_SIZE),
+               .psock = socket,
+               .handler = NULL,
+               .request_path = NULL,
+               .query_data = NULL,
+               .stream_id = frame->hd.stream_id,
+               .session = session
+       };
+       socket->tid = session->handle->sock->tid;
        ISC_LINK_INIT(&socket->h2, link);
        ISC_LIST_APPEND(session->sstreams, &socket->h2, link);
+
+#ifdef NETMGR_TRACE
+       session->sstreams_count++;
+       if (session->sstreams_count > 1) {
+               fprintf(stderr, "HTTP/2 session %p (active streams: %lu)\n",
+                       session, session->sstreams_count);
+       }
+#endif /* NETMGR_TRACE */
+
        nghttp2_session_set_stream_user_data(ngsession, frame->hd.stream_id,
                                             socket);
        return (0);
 }
 
-static int
-server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+static isc_nm_http2_server_handler_t *
+find_server_request_handler(const char *request_path,
+                           isc_nmsocket_t *serversocket) {
+       isc_nm_http2_server_handler_t *handler = NULL;
+
+       REQUIRE(VALID_NMSOCK(serversocket));
+
+       if (request_path == NULL || *request_path == '\0') {
+               return (NULL);
+       }
+
+       RWLOCK(&serversocket->h2.handlers_lock, isc_rwlocktype_read);
+       if (atomic_load(&serversocket->listening)) {
+               for (handler = ISC_LIST_HEAD(serversocket->h2.handlers);
+                    handler != NULL; handler = ISC_LIST_NEXT(handler, link))
+               {
+                       if (!strcmp(request_path, handler->path)) {
+                               break;
+                       }
+               }
+       }
+       RWUNLOCK(&serversocket->h2.handlers_lock, isc_rwlocktype_read);
+
+       return (handler);
+}
+
+static isc_http2_error_responses_t
+server_handle_path_header(isc_nmsocket_t *socket, const uint8_t *value,
+                         const size_t valuelen) {
+       isc_nm_http2_server_handler_t *handler = NULL;
+       const uint8_t *qstr = NULL;
+       size_t vlen = valuelen;
+
+       qstr = memchr(value, '?', valuelen);
+       if (qstr != NULL) {
+               vlen = qstr - value;
+       }
+
+       if (socket->h2.request_path != NULL) {
+               isc_mem_free(socket->mgr->mctx, socket->h2.request_path);
+       }
+       socket->h2.request_path = isc_mem_strndup(
+               socket->mgr->mctx, (const char *)value, vlen + 1);
+       handler = find_server_request_handler(socket->h2.request_path,
+                                             socket->h2.session->serversocket);
+       if (handler != NULL) {
+               socket->h2.handler_cb = handler->cb;
+               socket->h2.handler_cbarg = handler->cbarg;
+               socket->extrahandlesize = handler->extrahandlesize;
+       } else {
+               isc_mem_free(socket->mgr->mctx, socket->h2.request_path);
+               socket->h2.request_path = NULL;
+               return (ISC_HTTP_ERROR_NOT_FOUND);
+       }
+       if (qstr != NULL) {
+               const char *dns_value = NULL;
+               size_t dns_value_len = 0;
+
+               if (socket->h2.request_type != ISC_HTTP_REQ_GET) {
+                       return (ISC_HTTP_ERROR_BAD_REQUEST);
+               }
+
+               if (isc__nm_parse_doh_query_string((const char *)qstr,
+                                                  &dns_value, &dns_value_len))
+               {
+                       const size_t decoded_size = dns_value_len / 4 * 3;
+                       if (decoded_size <= MAX_DNS_MESSAGE_SIZE) {
+                               if (socket->h2.query_data != NULL) {
+                                       isc_mem_free(socket->mgr->mctx,
+                                                    socket->h2.query_data);
+                               }
+                               socket->h2.query_data =
+                                       isc__nm_base64url_to_base64(
+                                               socket->mgr->mctx, dns_value,
+                                               dns_value_len,
+                                               &socket->h2.query_data_len);
+                       } else {
+                               socket->h2.query_too_large = true;
+                               return (ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE);
+                       }
+               } else {
+                       return (ISC_HTTP_ERROR_BAD_REQUEST);
+               }
+       }
+       return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static isc_http2_error_responses_t
+server_handle_method_header(isc_nmsocket_t *socket, const uint8_t *value,
+                           const size_t valuelen) {
+       const char get[] = "GET";
+       const char post[] = "POST";
+
+       if (HEADER_MATCH(get, value, valuelen)) {
+               socket->h2.request_type = ISC_HTTP_REQ_GET;
+       } else if (HEADER_MATCH(post, value, valuelen)) {
+               socket->h2.request_type = ISC_HTTP_REQ_POST;
+       } else {
+               return (ISC_HTTP_ERROR_NOT_IMPLEMENTED);
+       }
+       return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static isc_http2_error_responses_t
+server_handle_scheme_header(isc_nmsocket_t *socket, const uint8_t *value,
+                           const size_t valuelen) {
+       const char http[] = "http";
+       const char http_secure[] = "https";
+
+       if (HEADER_MATCH(http_secure, value, valuelen)) {
+               socket->h2.request_scheme = ISC_HTTP_SCHEME_HTTP_SECURE;
+       } else if (HEADER_MATCH(http, value, valuelen)) {
+               socket->h2.request_scheme = ISC_HTTP_SCHEME_HTTP;
+       } else {
+               return (ISC_HTTP_ERROR_BAD_REQUEST);
+       }
+       return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static isc_http2_error_responses_t
+server_handle_content_length_header(isc_nmsocket_t *socket,
+                                   const uint8_t *value,
+                                   const size_t valuelen) {
+       char tmp[32] = { 0 };
+       const size_t tmplen = sizeof(tmp) - 1;
+
+       if (socket->h2.request_type != ISC_HTTP_REQ_POST) {
+               return (ISC_HTTP_ERROR_BAD_REQUEST);
+       }
+       strncpy(tmp, (const char *)value,
+               valuelen > tmplen ? tmplen : valuelen);
+       socket->h2.content_length = strtoul(tmp, NULL, 10);
+       if (socket->h2.content_length > MAX_DNS_MESSAGE_SIZE) {
+               return (ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE);
+       }
+       return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static isc_http2_error_responses_t
+server_handle_content_type_header(isc_nmsocket_t *socket, const uint8_t *value,
+                                 const size_t valuelen) {
+       const char type_dns_message[] = "application/dns-message";
+
+       if (HEADER_MATCH(type_dns_message, value, valuelen)) {
+               socket->h2.content_type_verified = true;
+       } else {
+               return (ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE);
+       }
+       return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static isc_http2_error_responses_t
+server_handle_accept_header(isc_nmsocket_t *socket, const uint8_t *value,
+                           const size_t valuelen) {
+       const char type_accept_all[] = "*/*";
+       const char type_dns_message[] = "application/dns-message";
+
+       if (HEADER_MATCH(type_dns_message, value, valuelen) ||
+           HEADER_MATCH(type_accept_all, value, valuelen))
+       {
+               socket->h2.accept_type_verified = true;
+       } else {
+               return (ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE);
+       }
+       return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static int
+server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
                          const uint8_t *name, size_t namelen,
                          const uint8_t *value, size_t valuelen, uint8_t flags,
                          void *user_data) {
        isc_nmsocket_t *socket = NULL;
+       isc_http2_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
        const char path[] = ":path";
+       const char method[] = ":method";
+       const char scheme[] = ":scheme";
+       const char accept[] = "accept";
+       const char content_length[] = "Content-Length";
+       const char content_type[] = "Content-Type";
 
        UNUSED(flags);
        UNUSED(user_data);
@@ -704,26 +1434,41 @@ server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
 
                socket = nghttp2_session_get_stream_user_data(
                        session, frame->hd.stream_id);
-               if (socket == NULL || socket->h2.request_path != NULL) {
+               if (socket == NULL) {
                        break;
                }
 
-               if (namelen == sizeof(path) - 1 &&
-                   memcmp(path, name, namelen) == 0) {
-                       size_t j;
-                       for (j = 0; j < valuelen && value[j] != '?'; ++j)
-                               ;
-                       socket->h2.request_path = isc_mem_strndup(
-                               socket->mgr->mctx, (const char *)value, j + 1);
-                       if (j < valuelen) {
-                               socket->h2.query_data = isc_mem_strndup(
-                                       socket->mgr->mctx, (char *)value + j,
-                                       valuelen - j);
-                       }
+               if (HEADER_MATCH(path, name, namelen)) {
+                       code = server_handle_path_header(socket, value,
+                                                        valuelen);
+               } else if (HEADER_MATCH(method, name, namelen)) {
+                       code = server_handle_method_header(socket, value,
+                                                          valuelen);
+               } else if (HEADER_MATCH(scheme, name, namelen)) {
+                       code = server_handle_scheme_header(socket, value,
+                                                          valuelen);
+               } else if (HEADER_MATCH(content_length, name, namelen)) {
+                       code = server_handle_content_length_header(
+                               socket, value, valuelen);
+               } else if (HEADER_MATCH(content_type, name, namelen)) {
+                       code = server_handle_content_type_header(socket, value,
+                                                                valuelen);
+               } else if (HEADER_MATCH(accept, (const char *)name, namelen)) {
+                       code = server_handle_accept_header(socket, value,
+                                                          valuelen);
                }
                break;
        }
 
+       if (code == ISC_HTTP_ERROR_SUCCESS) {
+               return (0);
+       }
+
+       INSIST(socket != NULL);
+       if (server_send_error_response(code, session, socket) != 0) {
+               return (NGHTTP2_ERR_CALLBACK_FAILURE);
+       };
+       failed_httpstream_read_cb(socket, ISC_R_CANCELED, socket->h2.session);
        return (0);
 }
 
@@ -758,8 +1503,9 @@ static int
 server_send_response(nghttp2_session *ngsession, int32_t stream_id,
                     const nghttp2_nv *nva, size_t nvlen,
                     isc_nmsocket_t *socket) {
-       int rv;
        nghttp2_data_provider data_prd;
+       int rv;
+
        data_prd.source.ptr = socket;
        data_prd.read_callback = server_read_callback;
 
@@ -771,80 +1517,226 @@ server_send_response(nghttp2_session *ngsession, int32_t stream_id,
        return (0);
 }
 
-static const char ERROR_HTML[] = "<html><head><title>404</title></head>"
-                                "<body><h1>404 Not Found</h1></body></html>";
+#define MAKE_ERROR_REPLY(tag, code)             \
+       {                                       \
+               tag, MAKE_NV2(":status", #code) \
+       }
+
+static struct http2_error_responses {
+       const isc_http2_error_responses_t type;
+       const nghttp2_nv header;
+} error_responses[] = {
+       MAKE_ERROR_REPLY(ISC_HTTP_ERROR_SUCCESS, 200),
+       MAKE_ERROR_REPLY(ISC_HTTP_ERROR_NOT_FOUND, 404),
+       MAKE_ERROR_REPLY(ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE, 413),
+       MAKE_ERROR_REPLY(ISC_HTTP_ERROR_URI_TOO_LONG, 414),
+       MAKE_ERROR_REPLY(ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE, 415),
+       MAKE_ERROR_REPLY(ISC_HTTP_ERROR_BAD_REQUEST, 400),
+       MAKE_ERROR_REPLY(ISC_HTTP_ERROR_NOT_IMPLEMENTED, 501),
+       MAKE_ERROR_REPLY(ISC_HTTP_ERROR_GENERIC, 500),
+};
 
 static int
-error_reply(nghttp2_session *ngsession, isc_nmsocket_t *socket) {
-       const nghttp2_nv hdrs[] = { MAKE_NV2(":status", "404") };
+server_send_error_response(const isc_http2_error_responses_t error,
+                          nghttp2_session *ngsession, isc_nmsocket_t *socket) {
+       int rv;
 
-       memmove(socket->h2.buf, ERROR_HTML, sizeof(ERROR_HTML));
-       socket->h2.bufsize = sizeof(ERROR_HTML);
+       socket->h2.bufsize = 0;
        socket->h2.bufpos = 0;
 
-       server_send_response(ngsession, socket->h2.stream_id, hdrs,
-                            sizeof(hdrs) / sizeof(nghttp2_nv), socket);
-       return (0);
+       for (size_t i = 0;
+            i < sizeof(error_responses) / sizeof(error_responses[0]); i++)
+       {
+               if (error_responses[i].type == error) {
+                       rv = server_send_response(
+                               ngsession, socket->h2.stream_id,
+                               &error_responses[i].header,
+                               sizeof(error_responses[i].header), socket);
+                       return (rv);
+               }
+       }
+
+       rv = server_send_error_response(ISC_HTTP_ERROR_GENERIC, ngsession,
+                                       socket);
+       return (rv);
 }
 
 static int
 server_on_request_recv(nghttp2_session *ngsession,
                       isc_nm_http2_session_t *session,
                       isc_nmsocket_t *socket) {
-       isc_nm_http2_server_handler_t *handler = NULL;
        isc_nmhandle_t *handle = NULL;
        isc_sockaddr_t addr;
+       isc_http2_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
+       isc_region_t data;
 
-       if (!socket->h2.request_path) {
-               if (error_reply(ngsession, socket) != 0) {
-                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
-               }
-               return (0);
+       /*
+        * Sanity checks. Here we use the same error codes that
+        * Unbound uses.
+        * (https://blog.nlnetlabs.nl/dns-over-https-in-unbound/)
+        */
+       if (!socket->h2.request_path || !socket->h2.handler_cb) {
+               code = ISC_HTTP_ERROR_NOT_FOUND;
+       } else if ((socket->h2.request_type == ISC_HTTP_REQ_POST &&
+                   !socket->h2.content_type_verified) ||
+                  !socket->h2.accept_type_verified)
+       {
+               code = ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE;
+       } else if (socket->h2.request_type == ISC_HTTP_REQ_UNSUPPORTED) {
+               code = ISC_HTTP_ERROR_NOT_IMPLEMENTED;
+       } else if (socket->h2.request_scheme == ISC_HTTP_SCHEME_UNSUPPORTED) {
+               /*
+                * TODO: additional checks if we have enabled encryption
+                * on the socket or not
+                */
+               code = ISC_HTTP_ERROR_BAD_REQUEST;
+       } else if (socket->h2.content_length > MAX_DNS_MESSAGE_SIZE ||
+                  socket->h2.query_too_large)
+       {
+               code = ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE;
+       } else if (socket->h2.request_type == ISC_HTTP_REQ_GET &&
+                  (socket->h2.content_length > 0 ||
+                   socket->h2.query_data_len == 0))
+       {
+               code = ISC_HTTP_ERROR_BAD_REQUEST;
+       } else if (socket->h2.request_type == ISC_HTTP_REQ_POST &&
+                  socket->h2.content_length == 0)
+       {
+               code = ISC_HTTP_ERROR_BAD_REQUEST;
+       } else if (socket->h2.request_type == ISC_HTTP_REQ_POST &&
+                  socket->h2.bufsize > socket->h2.content_length)
+       {
+               code = ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE;
+       } else if (socket->h2.request_type == ISC_HTTP_REQ_POST &&
+                  socket->h2.bufsize != socket->h2.content_length)
+       {
+               code = ISC_HTTP_ERROR_BAD_REQUEST;
        }
 
-       for (handler = ISC_LIST_HEAD(session->serversocket->handlers);
-            handler != NULL; handler = ISC_LIST_NEXT(handler, link))
-       {
-               if (!strcmp(socket->h2.request_path, handler->path)) {
-                       break;
-               }
+       if (code != ISC_HTTP_ERROR_SUCCESS) {
+               goto error;
        }
 
-       if (handler == NULL) {
-               if (error_reply(ngsession, socket) != 0) {
-                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
+       if (socket->h2.request_type == ISC_HTTP_REQ_GET) {
+               isc_buffer_t decoded_buf;
+               isc__buffer_init(&decoded_buf, socket->h2.buf,
+                                MAX_DNS_MESSAGE_SIZE);
+               if (isc_base64_decodestring(socket->h2.query_data,
+                                           &decoded_buf) != ISC_R_SUCCESS)
+               {
+                       code = ISC_HTTP_ERROR_GENERIC;
+                       goto error;
                }
-               return (0);
+               isc__buffer_usedregion(&decoded_buf, &data);
+       } else if (socket->h2.request_type == ISC_HTTP_REQ_POST) {
+               INSIST(socket->h2.content_length > 0);
+               data = (isc_region_t){ socket->h2.buf, socket->h2.bufsize };
+       } else {
+               INSIST(0);
+               ISC_UNREACHABLE();
        }
 
-       socket->extrahandlesize = handler->extrahandlesize;
        addr = isc_nmhandle_peeraddr(session->handle);
        handle = isc__nmhandle_get(socket, &addr, NULL);
-       handler->cb(handle, ISC_R_SUCCESS,
-                   &(isc_region_t){ socket->h2.buf, socket->h2.bufsize },
-                   &(isc_region_t){ (unsigned char *)socket->h2.query_data,
-                                    strlen(socket->h2.query_data) + 1 },
-                   handler->cbarg);
+       socket->h2.handler_cb(handle, ISC_R_SUCCESS, &data,
+                             socket->h2.handler_cbarg);
+       isc_nmhandle_detach(&handle);
+       return (0);
+
+error:
+       if (server_send_error_response(code, ngsession, socket) != 0) {
+               return (NGHTTP2_ERR_CALLBACK_FAILURE);
+       }
        return (0);
 }
 
 void
 isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region,
                  isc_nm_cb_t cb, void *cbarg) {
-       const nghttp2_nv hdrs[] = { MAKE_NV2(":status", "200") };
        isc_nmsocket_t *sock = handle->sock;
+       isc__nm_uvreq_t *uvreq = NULL;
+       isc__netievent_httpsend_t *ievent = NULL;
+
+       REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(handle->httpsession != NULL);
+       REQUIRE(VALID_NMSOCK(sock));
+
+       /* TODO: should be called from within the context of an NM thread? */
+       if (inactive(sock) || handle->httpsession->closed) {
+               cb(handle, ISC_R_CANCELED, cbarg);
+               return;
+       }
+
+       INSIST(VALID_NMHANDLE(handle->httpsession->handle));
+       INSIST(VALID_NMSOCK(handle->httpsession->handle->sock));
+       INSIST(sock->tid == handle->httpsession->handle->sock->tid);
+
+       uvreq = isc__nm_uvreq_get(sock->mgr, sock);
+       isc_nmhandle_attach(handle, &uvreq->handle);
+       uvreq->cb.send = cb;
+       uvreq->cbarg = cbarg;
+
+       uvreq->uvbuf.base = (char *)region->base;
+       uvreq->uvbuf.len = region->length;
 
-       /* TODO FIXME do it asynchronously!!! */
-       memcpy(sock->h2.buf, region->base, region->length);
-       sock->h2.bufsize = region->length;
+       ievent = isc__nm_get_netievent_httpsend(sock->mgr, sock, uvreq);
+       isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+                              (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0) {
+       isc__netievent_httpsend_t *ievent = (isc__netievent_httpsend_t *)ev0;
+       isc_nmsocket_t *sock = ievent->sock;
+       isc__nm_uvreq_t *req = ievent->req;
+       isc_nmhandle_t *handle = NULL;
+       isc_nm_cb_t cb = NULL;
+       void *cbarg = NULL;
+       isc_result_t res;
+       size_t content_length_str_len;
+
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(VALID_UVREQ(req));
+
+       ievent->req = NULL;
+       handle = req->handle;
+       cb = req->cb.send;
+       cbarg = req->cbarg;
+
+       REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(VALID_NMHANDLE(handle->httpsession->handle));
+       REQUIRE(VALID_NMSOCK(handle->httpsession->handle->sock));
+       REQUIRE(handle->httpsession->handle->sock->tid == isc_nm_tid());
+
+       UNUSED(worker);
+
+       memmove(sock->h2.buf, req->uvbuf.base, req->uvbuf.len);
+       sock->h2.bufsize = req->uvbuf.len;
+
+       content_length_str_len =
+               snprintf(sock->h2.response_content_length_str,
+                        sizeof(sock->h2.response_content_length_str), "%lu",
+                        (unsigned long)req->uvbuf.len);
+       const nghttp2_nv hdrs[] = {
+               MAKE_NV2(":status", "200"),
+               MAKE_NV2("Content-Type", "application/dns-message"),
+               MAKE_NV("Content-Length", sock->h2.response_content_length_str,
+                       content_length_str_len)
+       };
        if (server_send_response(handle->httpsession->ngsession,
                                 sock->h2.stream_id, hdrs,
                                 sizeof(hdrs) / sizeof(nghttp2_nv), sock) != 0)
        {
-               cb(handle, ISC_R_FAILURE, cbarg);
+               res = ISC_R_FAILURE;
        } else {
-               cb(handle, ISC_R_SUCCESS, cbarg);
+               res = ISC_R_SUCCESS;
        }
+
+       http2_do_bio(handle->httpsession); /*TODO: Should we call it only
+                                           * on success? */
+       cb(handle, res, cbarg);
+
+       isc__nm_uvreq_put(&req, sock);
 }
 
 static int
@@ -884,7 +1776,7 @@ static void
 initialize_nghttp2_server_session(isc_nm_http2_session_t *session) {
        nghttp2_session_callbacks *callbacks = NULL;
 
-       nghttp2_session_callbacks_new(&callbacks);
+       RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0);
 
        nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
                callbacks, on_data_chunk_recv_callback);
@@ -925,20 +1817,55 @@ static isc_result_t
 httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
        isc_nmsocket_t *httplistensock = (isc_nmsocket_t *)cbarg;
        isc_nm_http2_session_t *session = NULL;
+       isc_nmsocket_t *listener = NULL, *httpserver = NULL;
+
+       REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(VALID_NMSOCK(handle->sock));
+       if (handle->sock->type == isc_nm_tlssocket) {
+               REQUIRE(VALID_NMSOCK(handle->sock->listener));
+               listener = handle->sock->listener;
+               httpserver = listener->h2.httpserver;
+       } else {
+               REQUIRE(VALID_NMSOCK(handle->sock->server));
+               listener = handle->sock->server;
+               REQUIRE(VALID_NMSOCK(listener->parent));
+               httpserver = listener->parent->h2.httpserver;
+       }
+
+       /*
+        * NOTE: HTTP listener socket might be destroyed by the time this
+        * function gets invoked, so we need to do extra sanity checks to
+        * detect this case.
+        */
+       if (inactive(handle->sock) || httpserver == NULL) {
+               return (ISC_R_CANCELED);
+       }
 
        if (result != ISC_R_SUCCESS) {
                /* XXXWPK do nothing? */
                return (result);
        }
 
+       REQUIRE(VALID_NMSOCK(httplistensock));
+       INSIST(httplistensock == httpserver);
+
+       if (inactive(httplistensock) ||
+           !atomic_load(&httplistensock->listening)) {
+               return (ISC_R_CANCELED);
+       }
+
        session = isc_mem_get(httplistensock->mgr->mctx,
                              sizeof(isc_nm_http2_session_t));
-       *session = (isc_nm_http2_session_t){ .magic = HTTP2_SESSION_MAGIC };
+       *session = (isc_nm_http2_session_t){ .magic = HTTP2_SESSION_MAGIC,
+                                            .ssl_ctx_created = false,
+                                            .client = false };
        initialize_nghttp2_server_session(session);
+       handle->sock->h2.session = session;
 
        isc_mem_attach(httplistensock->mgr->mctx, &session->mctx);
        isc_nmhandle_attach(handle, &session->handle);
        isc__nmsocket_attach(httplistensock, &session->serversocket);
+       session->serversocket = httplistensock;
        server_send_connection_header(session);
 
        /* TODO H2 */
@@ -946,16 +1873,55 @@ httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
        return (ISC_R_SUCCESS);
 }
 
+#ifndef OPENSSL_NO_NEXTPROTONEG
+static int
+next_proto_cb(SSL *ssl, const unsigned char **data, unsigned int *len,
+             void *arg) {
+       UNUSED(ssl);
+       UNUSED(arg);
+
+       *data = (const unsigned char *)NGHTTP2_PROTO_ALPN;
+       *len = (unsigned int)NGHTTP2_PROTO_ALPN_LEN;
+       return (SSL_TLSEXT_ERR_OK);
+}
+#endif /* !OPENSSL_NO_NEXTPROTONEG */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+static int
+alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
+                    const unsigned char *in, unsigned int inlen, void *arg) {
+       int ret;
+
+       UNUSED(ssl);
+       UNUSED(arg);
+
+       ret = nghttp2_select_next_protocol((unsigned char **)(uintptr_t)out,
+                                          outlen, in, inlen);
+
+       if (ret != 1) {
+               return (SSL_TLSEXT_ERR_NOACK);
+       }
+
+       return (SSL_TLSEXT_ERR_OK);
+}
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
+
 isc_result_t
-isc_nm_listenhttps(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog,
-                  isc_quota_t *quota, SSL_CTX *ctx, isc_nmsocket_t **sockp) {
+isc_nm_listenhttp(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog,
+                 isc_quota_t *quota, SSL_CTX *ctx, isc_nmsocket_t **sockp) {
        isc_nmsocket_t *sock = NULL;
        isc_result_t result;
 
-       isc_mem_get(mgr->mctx, sizeof(*sock));
+       sock = isc_mem_get(mgr->mctx, sizeof(*sock));
        isc__nmsocket_init(sock, mgr, isc_nm_httplistener, iface);
 
        if (ctx != NULL) {
+#ifndef OPENSSL_NO_NEXTPROTONEG
+               SSL_CTX_set_next_protos_advertised_cb(ctx, next_proto_cb, NULL);
+#endif // OPENSSL_NO_NEXTPROTONEG
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+               SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
                result = isc_nm_listentls(mgr, iface, httplisten_acceptcb, sock,
                                          sizeof(isc_nm_http2_session_t),
                                          backlog, quota, ctx, &sock->outer);
@@ -971,6 +1937,13 @@ isc_nm_listenhttps(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog,
                return (result);
        }
 
+       sock->outer->h2.httpserver = sock;
+
+       sock->nchildren = sock->outer->nchildren;
+       sock->result = ISC_R_DEFAULT;
+       sock->tid = isc_random_uniform(sock->nchildren);
+       sock->fd = (uv_os_sock_t)-1;
+
        atomic_store(&sock->listening, true);
        *sockp = sock;
        return (ISC_R_SUCCESS);
@@ -985,72 +1958,42 @@ isc_nm_http_add_endpoint(isc_nmsocket_t *sock, const char *uri,
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(sock->type == isc_nm_httplistener);
 
-       handler = isc_mem_get(sock->mgr->mctx, sizeof(*handler));
-       *handler = (isc_nm_http2_server_handler_t){
-               .cb = cb,
-               .cbarg = cbarg,
-               .extrahandlesize = extrahandlesize,
-               .path = isc_mem_strdup(sock->mgr->mctx, uri)
-       };
-
-       ISC_LINK_INIT(handler, link);
-       ISC_LIST_APPEND(sock->handlers, handler, link);
+       if (find_server_request_handler(uri, sock) == NULL) {
+               handler = isc_mem_get(sock->mgr->mctx, sizeof(*handler));
+               *handler = (isc_nm_http2_server_handler_t){
+                       .cb = cb,
+                       .cbarg = cbarg,
+                       .extrahandlesize = extrahandlesize,
+                       .path = isc_mem_strdup(sock->mgr->mctx, uri)
+               };
+               ISC_LINK_INIT(handler, link);
+
+               RWLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
+               ISC_LIST_APPEND(sock->h2.handlers, handler, link);
+               RWUNLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
+       }
 
        return (ISC_R_SUCCESS);
 }
 
-typedef struct {
-       isc_nm_recv_cb_t cb;
-       void *cbarg;
-} cbarg_t;
-
-static unsigned char doh_error[] =
-       "<html><head><title>No request</title></head>"
-       "<body><h1>No request</h1></body></html>";
-
-static const isc_region_t doh_error_r = { doh_error, sizeof(doh_error) };
-
-static void
-https_sendcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
-       UNUSED(handle);
-       UNUSED(result);
-       UNUSED(cbarg);
-}
-
 /*
  * In DoH we just need to intercept the request - the response can be sent
  * to the client code via the nmhandle directly as it's always just the
  * http * content.
  */
 static void
-doh_callback(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *post,
-            isc_region_t *get, void *arg) {
-       cbarg_t *dohcbarg = arg;
-       isc_region_t *data = NULL;
+doh_callback(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *data,
+            void *arg) {
+       isc_nm_http_doh_cbarg_t *dohcbarg = arg;
 
        REQUIRE(VALID_NMHANDLE(handle));
 
-       UNUSED(result);
-       UNUSED(get);
-
        if (result != ISC_R_SUCCESS) {
                /* Shut down the client, then ourselves */
-               dohcbarg->cb(NULL, result, NULL, dohcbarg->cbarg);
+               dohcbarg->cb(handle, result, NULL, dohcbarg->cbarg);
                /* XXXWPK FREE */
                return;
        }
-
-       if (post != NULL) {
-               data = post;
-       } else if (get != NULL) {
-               /* XXXWPK PARSE */
-               data = NULL; /* FIXME */
-       } else {
-               /* Invalid request, just send the error response */
-               isc_nm_send(handle, &doh_error_r, https_sendcb, dohcbarg);
-               return;
-       }
-
        dohcbarg->cb(handle, result, data, dohcbarg->cbarg);
 }
 
@@ -1059,19 +2002,586 @@ isc_nm_http_add_doh_endpoint(isc_nmsocket_t *sock, const char *uri,
                             isc_nm_recv_cb_t cb, void *cbarg,
                             size_t extrahandlesize) {
        isc_result_t result;
-       cbarg_t *dohcbarg = NULL;
+       isc_nm_http_doh_cbarg_t *dohcbarg = NULL;
 
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(sock->type == isc_nm_httplistener);
 
-       dohcbarg = isc_mem_get(sock->mgr->mctx, sizeof(cbarg_t));
-       *dohcbarg = (cbarg_t){ cb, cbarg };
+       dohcbarg = isc_mem_get(sock->mgr->mctx,
+                              sizeof(isc_nm_http_doh_cbarg_t));
+       *dohcbarg = (isc_nm_http_doh_cbarg_t){ .cb = cb, .cbarg = cbarg };
+       ISC_LINK_INIT(dohcbarg, link);
 
        result = isc_nm_http_add_endpoint(sock, uri, doh_callback, dohcbarg,
                                          extrahandlesize);
        if (result != ISC_R_SUCCESS) {
-               isc_mem_put(sock->mgr->mctx, dohcbarg, sizeof(cbarg_t));
+               isc_mem_put(sock->mgr->mctx, dohcbarg,
+                           sizeof(isc_nm_http_doh_cbarg_t));
        }
 
+       RWLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
+       ISC_LIST_APPEND(sock->h2.handlers_cbargs, dohcbarg, link);
+       RWUNLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
+
        return (result);
 }
+
+void
+isc__nm_http_stoplistening(isc_nmsocket_t *sock) {
+       isc__netievent_httpstop_t *ievent = NULL;
+
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(sock->type == isc_nm_httplistener);
+
+       if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+                                           true)) {
+               INSIST(0);
+               ISC_UNREACHABLE();
+       }
+
+       ievent = isc__nm_get_netievent_httpstop(sock->mgr, sock);
+       isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+                              (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_http_clear_handlers(isc_nmsocket_t *sock) {
+       isc_nm_http2_server_handler_t *handler = NULL;
+       isc_nm_http_doh_cbarg_t *dohcbarg = NULL;
+
+       /* delete all handlers */
+       RWLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
+       handler = ISC_LIST_HEAD(sock->h2.handlers);
+       while (handler != NULL) {
+               isc_nm_http2_server_handler_t *next;
+
+               next = ISC_LIST_NEXT(handler, link);
+               ISC_LIST_DEQUEUE(sock->h2.handlers, handler, link);
+               isc_mem_free(sock->mgr->mctx, handler->path);
+               isc_mem_put(sock->mgr->mctx, handler, sizeof(*handler));
+               handler = next;
+       }
+
+       dohcbarg = ISC_LIST_HEAD(sock->h2.handlers_cbargs);
+       while (dohcbarg != NULL) {
+               isc_nm_http_doh_cbarg_t *next;
+
+               next = ISC_LIST_NEXT(dohcbarg, link);
+               ISC_LIST_DEQUEUE(sock->h2.handlers_cbargs, dohcbarg, link);
+               isc_mem_put(sock->mgr->mctx, dohcbarg,
+                           sizeof(isc_nm_http_doh_cbarg_t));
+               dohcbarg = next;
+       }
+       RWUNLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
+}
+
+void
+isc__nm_http_clear_session(isc_nmsocket_t *sock) {
+       if (sock->type != isc_nm_httpstream && sock->h2.session != NULL) {
+               isc_nm_http2_session_t *session = sock->h2.session;
+               INSIST(ISC_LIST_EMPTY(session->sstreams));
+               session->magic = 0;
+               if (session->ssl_ctx_created) {
+                       SSL_CTX_free(session->ssl_ctx);
+               }
+               isc_mem_putanddetach(&sock->h2.session->mctx, session,
+                                    sizeof(isc_nm_http2_session_t));
+               sock->h2.session = NULL;
+       }
+}
+
+void
+isc__nm_async_httpstop(isc__networker_t *worker, isc__netievent_t *ev0) {
+       isc__netievent_httpstop_t *ievent = (isc__netievent_httpstop_t *)ev0;
+       isc_nmsocket_t *sock = ievent->sock;
+
+       UNUSED(worker);
+
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(sock->tid == isc_nm_tid());
+
+       atomic_store(&sock->listening, false);
+       atomic_store(&sock->closing, false);
+       atomic_store(&sock->closed, true);
+       if (sock->outer != NULL) {
+               sock->outer->h2.httpserver = NULL;
+               isc_nm_stoplistening(sock->outer);
+               isc_nmsocket_close(&sock->outer);
+       }
+}
+
+static void
+http_close_direct(isc_nmsocket_t *sock) {
+       bool sessions_empty;
+       isc_nm_http2_session_t *session;
+
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(VALID_HTTP2_SESSION(sock->h2.session));
+
+       atomic_store(&sock->closed, true);
+       session = sock->h2.session;
+
+       if (ISC_LINK_LINKED(&sock->h2, link)) {
+               ISC_LIST_UNLINK(session->sstreams, &sock->h2, link);
+#ifdef NETMGR_TRACE
+               session->sstreams_count--;
+#endif /* NETMGR_TRACE */
+       }
+
+       sessions_empty = ISC_LIST_EMPTY(session->sstreams);
+       if (!sessions_empty) {
+               http2_do_bio(session);
+       } else if (session->reading) {
+               session->reading = false;
+               if (session->handle != NULL) {
+                       isc_nm_cancelread(session->handle);
+               }
+       }
+       /* If session is closed then the only reference to the socket is
+        * the one, created when handling the netievent. */
+       if (!session->closed) {
+               INSIST(session->handle != NULL);
+               isc__nmsocket_detach(&sock);
+       } else {
+               INSIST(isc_refcount_current(&sock->references) == 1);
+       }
+}
+
+void
+isc__nm_http_close(isc_nmsocket_t *sock) {
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(sock->type == isc_nm_httpstream);
+       REQUIRE(!isc__nmsocket_active(sock));
+
+       if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+                                           true)) {
+               return;
+       }
+
+       isc__netievent_httpclose_t *ievent =
+               isc__nm_get_netievent_httpclose(sock->mgr, sock);
+
+       isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+                              (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0) {
+       isc__netievent_httpclose_t *ievent = (isc__netievent_httpclose_t *)ev0;
+       isc_nmsocket_t *sock = ievent->sock;
+
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(sock->tid == isc_nm_tid());
+
+       UNUSED(worker);
+
+       http_close_direct(sock);
+}
+
+static void
+failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result,
+                         isc_nm_http2_session_t *session) {
+       isc_nmhandle_t *handle = NULL;
+       isc_sockaddr_t addr;
+
+       REQUIRE(VALID_NMSOCK(sock));
+       INSIST(sock->type == isc_nm_httpstream);
+
+       if (!sock->h2.request_path) {
+               return;
+       }
+
+       INSIST(sock->h2.handler_cbarg != NULL);
+
+       (void)nghttp2_submit_rst_stream(
+               session->ngsession, NGHTTP2_FLAG_END_STREAM, sock->h2.stream_id,
+               NGHTTP2_REFUSED_STREAM);
+       addr = isc_nmhandle_peeraddr(session->handle);
+       handle = isc__nmhandle_get(sock, &addr, NULL);
+       sock->h2.handler_cb(handle, result,
+                           &(isc_region_t){ sock->h2.buf, sock->h2.bufsize },
+                           sock->h2.handler_cbarg);
+       isc_nmhandle_detach(&handle);
+}
+
+static void
+failed_read_cb(isc_result_t result, isc_nm_http2_session_t *session) {
+       REQUIRE(VALID_HTTP2_SESSION(session));
+
+       if (session->client) {
+               http2_client_stream_t *cstream = NULL;
+               cstream = ISC_LIST_HEAD(session->cstreams);
+               while (cstream != NULL) {
+                       http2_client_stream_t *next = ISC_LIST_NEXT(cstream,
+                                                                   link);
+                       ISC_LIST_DEQUEUE(session->cstreams, cstream, link);
+
+                       cstream->read_cb(session->handle, result,
+                                        &(isc_region_t){ cstream->rbuf,
+                                                         cstream->rbufsize },
+                                        cstream->read_cbarg);
+
+                       put_http2_client_stream(session->mctx, cstream);
+                       cstream = next;
+               }
+       } else {
+               isc_nmsocket_h2_t *h2data = NULL; /* stream socket */
+               session->closed = true;
+               for (h2data = ISC_LIST_HEAD(session->sstreams); h2data != NULL;
+                    h2data = ISC_LIST_NEXT(h2data, link))
+               {
+                       failed_httpstream_read_cb(h2data->psock, result,
+                                                 session);
+               }
+
+               h2data = ISC_LIST_HEAD(session->sstreams);
+               while (h2data != NULL) {
+                       isc_nmsocket_h2_t *next = ISC_LIST_NEXT(h2data, link);
+                       ISC_LIST_DEQUEUE(session->sstreams, h2data, link);
+                       /* Cleanup socket in place */
+                       atomic_store(&h2data->psock->active, false);
+                       atomic_store(&h2data->psock->closed, true);
+                       isc__nmsocket_detach(&h2data->psock);
+
+                       h2data = next;
+               }
+       }
+       finish_http2_session(session);
+}
+
+static const bool base64url_validation_table[256] = {
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, true,  false, false, true,  true,
+       true,  true,  true,  true,  true,  true,  true,  true,  false, false,
+       false, false, false, false, false, true,  true,  true,  true,  true,
+       true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
+       true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
+       true,  false, false, false, false, true,  false, true,  true,  true,
+       true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
+       true,  true,  true,  true,  true,  true,  true,  true,  true,  true,
+       true,  true,  true,  false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false, false, false, false, false,
+       false, false, false, false, false, false
+};
+
+char *
+isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url,
+                           const size_t base64url_len, size_t *res_len) {
+       char *res = NULL;
+       size_t i, k, len;
+
+       if (mem == NULL || base64url == NULL || base64url_len == 0) {
+               return (NULL);
+       }
+
+       len = base64url_len % 4 ? base64url_len + (4 - base64url_len % 4)
+                               : base64url_len;
+       res = isc_mem_allocate(mem, len + 1); /* '\0' */
+
+       for (i = 0; i < base64url_len; i++) {
+               switch (base64url[i]) {
+               case '-':
+                       res[i] = '+';
+                       break;
+               case '_':
+                       res[i] = '/';
+                       break;
+               default:
+                       if (base64url_validation_table[(size_t)base64url[i]]) {
+                               res[i] = base64url[i];
+                       } else {
+                               isc_mem_free(mem, res);
+                               return (NULL);
+                       }
+                       break;
+               }
+       }
+
+       if (base64url_len % 4 != 0) {
+               for (k = 0; k < (4 - base64url_len % 4); k++, i++) {
+                       res[i] = '=';
+               }
+       }
+
+       INSIST(i == len);
+
+       if (res_len) {
+               *res_len = len;
+       }
+
+       res[len] = '\0';
+
+       return (res);
+}
+
+char *
+isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64,
+                           const size_t base64_len, size_t *res_len) {
+       char *res = NULL;
+       size_t i;
+
+       if (mem == NULL || base64 == NULL || base64_len == 0) {
+               return (NULL);
+       }
+
+       res = isc_mem_allocate(mem, base64_len + 1); /* '\0' */
+
+       for (i = 0; i < base64_len; i++) {
+               switch (base64[i]) {
+               case '+':
+                       res[i] = '-';
+                       break;
+               case '/':
+                       res[i] = '_';
+                       break;
+               case '=':
+                       goto end;
+                       break;
+               default:
+                       /* All other characters from the alphabet are the same
+                        * for both base64 and base64url, so we can reuse the
+                        * validation table for the rest of the characters. */
+                       if (base64[i] != '-' && base64[i] != '_' &&
+                           base64url_validation_table[(size_t)base64[i]])
+                       {
+                               res[i] = base64[i];
+                       } else {
+                               isc_mem_free(mem, res);
+                               return (NULL);
+                       }
+                       break;
+               }
+       }
+end:
+       if (res_len) {
+               *res_len = i;
+       }
+
+       res[i] = '\0';
+
+       return (res);
+}
+
+/* DoH GET Query String Scanner-less Recursive Descent Parser/Verifier
+
+It is based on the following grammar (using WSN/EBNF):
+
+S                = query-string.
+query-string     = ['?'] { key-value-pair } EOF.
+key-value-pair   = key '=' value [ '&' ].
+key              = ('_' | alpha) { '_' | alnum}.
+value            = value-char {value-char}.
+value-char       = unreserved-char | percent-charcode.
+unreserved-char  = alnum |'_' | '.' | '-' | '~'. (* RFC3986, Section 2.3 *)
+percent-charcode = '%' hexdigit hexdigit.
+...
+
+Should be good enough.
+*/
+
+typedef struct isc_doh_query_parser_state {
+       const char *str;
+
+       const char *last_key;
+       size_t last_key_len;
+
+       const char *last_value;
+       size_t last_value_len;
+
+       bool doh_query_found;
+       const char *doh_query;
+       size_t doh_query_length;
+} isc_doh_query_parser_state_t;
+
+#define MATCH(ch)      (st->str[0] == (ch))
+#define MATCH_ALPHA()  isalpha(st->str[0])
+#define MATCH_ALNUM()  isalnum(st->str[0])
+#define MATCH_XDIGIT() isxdigit(st->str[0])
+#define ADVANCE()      st->str++
+#define GETP()        (st->str)
+
+static bool
+rule_query_string(isc_doh_query_parser_state_t *st);
+
+bool
+isc__nm_parse_doh_query_string(const char *query_string, const char **start,
+                              size_t *len) {
+       isc_doh_query_parser_state_t state;
+
+       REQUIRE(start != NULL);
+       REQUIRE(len != 0);
+
+       if (query_string == NULL || *query_string == '\0' || start == NULL ||
+           len == 0) {
+               return (false);
+       }
+
+       memset(&state, 0, sizeof(state));
+       state.str = query_string;
+       if (!rule_query_string(&state)) {
+               return (false);
+       }
+
+       if (!state.doh_query_found) {
+               return (false);
+       }
+
+       *start = state.doh_query;
+       *len = state.doh_query_length;
+
+       return (true);
+}
+
+static bool
+rule_key_value_pair(isc_doh_query_parser_state_t *st);
+
+static bool
+rule_key(isc_doh_query_parser_state_t *st);
+
+static bool
+rule_value(isc_doh_query_parser_state_t *st);
+
+static bool
+rule_value_char(isc_doh_query_parser_state_t *st);
+
+static bool
+rule_percent_charcode(isc_doh_query_parser_state_t *st);
+
+static bool
+rule_unreserved_char(isc_doh_query_parser_state_t *st);
+
+static bool
+rule_query_string(isc_doh_query_parser_state_t *st) {
+       if (MATCH('?')) {
+               ADVANCE();
+       }
+
+       while (rule_key_value_pair(st)) {
+               /* skip */;
+       }
+
+       if (!MATCH('\0')) {
+               return (false);
+       }
+
+       ADVANCE();
+       return (true);
+}
+
+static bool
+rule_key_value_pair(isc_doh_query_parser_state_t *st) {
+       if (!rule_key(st)) {
+               return (false);
+       }
+
+       if (MATCH('=')) {
+               ADVANCE();
+       } else {
+               return (false);
+       }
+
+       if (rule_value(st)) {
+               const char dns[] = "dns";
+               if (st->last_key_len == sizeof(dns) - 1 &&
+                   memcmp(st->last_key, dns, sizeof(dns) - 1) == 0)
+               {
+                       st->doh_query_found = true;
+                       st->doh_query = st->last_value;
+                       st->doh_query_length = st->last_value_len;
+               }
+       } else {
+               return (false);
+       }
+
+       if (MATCH('&')) {
+               ADVANCE();
+       }
+
+       return (true);
+}
+
+static bool
+rule_key(isc_doh_query_parser_state_t *st) {
+       if (MATCH('_') || MATCH_ALPHA()) {
+               st->last_key = GETP();
+               ADVANCE();
+       } else {
+               return (false);
+       }
+
+       while (MATCH('_') || MATCH_ALNUM()) {
+               ADVANCE();
+       }
+
+       st->last_key_len = GETP() - st->last_key;
+       return (true);
+}
+
+static bool
+rule_value(isc_doh_query_parser_state_t *st) {
+       const char *s = GETP();
+       if (!rule_value_char(st)) {
+               return (false);
+       }
+
+       st->last_value = s;
+       while (rule_value_char(st)) {
+               /* skip */;
+       }
+       st->last_value_len = GETP() - st->last_value;
+       return (true);
+}
+
+static bool
+rule_value_char(isc_doh_query_parser_state_t *st) {
+       if (rule_unreserved_char(st)) {
+               return (true);
+       }
+
+       return (rule_percent_charcode(st));
+}
+
+static bool
+rule_unreserved_char(isc_doh_query_parser_state_t *st) {
+       if (MATCH_ALNUM() || MATCH('_') || MATCH('.') || MATCH('-') ||
+           MATCH('~')) {
+               ADVANCE();
+               return (true);
+       }
+       return (false);
+}
+
+static bool
+rule_percent_charcode(isc_doh_query_parser_state_t *st) {
+       if (MATCH('%')) {
+               ADVANCE();
+       } else {
+               return (false);
+       }
+
+       if (!MATCH_XDIGIT()) {
+               return (false);
+       }
+       ADVANCE();
+
+       if (!MATCH_XDIGIT()) {
+               return (false);
+       }
+       ADVANCE();
+
+       return (true);
+}
index 3ad09823bf9a3356842814d1eeedcb46c25a3253..105ea4d0bbb3e2ae043d559bf108fd76ebce9ba1 100644 (file)
@@ -30,6 +30,7 @@
 #include <isc/refcount.h>
 #include <isc/region.h>
 #include <isc/result.h>
+#include <isc/rwlock.h>
 #include <isc/sockaddr.h>
 #include <isc/stats.h>
 #include <isc/thread.h>
@@ -273,6 +274,10 @@ typedef enum isc__netievent_type {
        netievent_tlsdnscycle,
        netievent_tlsdnsshutdown,
 
+       netievent_httpstop,
+       netievent_httpsend,
+       netievent_httpclose,
+
        netievent_close,
        netievent_shutdown,
        netievent_stop,
@@ -701,18 +706,61 @@ typedef struct isc_nmsocket_tls_send_req {
        isc_region_t data;
 } isc_nmsocket_tls_send_req_t;
 
+typedef enum isc_doh_request_type {
+       ISC_HTTP_REQ_GET,
+       ISC_HTTP_REQ_POST,
+       ISC_HTTP_REQ_UNSUPPORTED
+} isc_http2_request_type_t;
+
+typedef enum isc_http2_scheme_type {
+       ISC_HTTP_SCHEME_HTTP,
+       ISC_HTTP_SCHEME_HTTP_SECURE,
+       ISC_HTTP_SCHEME_UNSUPPORTED
+} isc_http2_scheme_type_t;
+
+typedef struct isc_nm_http_doh_cbarg {
+       isc_nm_recv_cb_t cb;
+       void *cbarg;
+       LINK(struct isc_nm_http_doh_cbarg) link;
+} isc_nm_http_doh_cbarg_t;
+
 typedef struct isc_nmsocket_h2 {
        isc_nmsocket_t *psock; /* owner of the structure */
        char *request_path;
        char *query_data;
+       size_t query_data_len;
+       bool query_too_large;
        isc_nm_http2_server_handler_t *handler;
 
-       uint8_t buf[65535];
+       uint8_t *buf;
        size_t bufsize;
        size_t bufpos;
 
        int32_t stream_id;
+       isc_nm_http2_session_t *session;
+
+       isc_nmsocket_t *httpserver;
+
+       isc_http2_request_type_t request_type;
+       isc_http2_scheme_type_t request_scheme;
+       size_t content_length;
+       bool content_type_verified;
+       bool accept_type_verified;
+
+       isc_nm_http_cb_t handler_cb;
+       void *handler_cbarg;
        LINK(struct isc_nmsocket_h2) link;
+
+       ISC_LIST(isc_nm_http2_server_handler_t) handlers;
+       ISC_LIST(isc_nm_http_doh_cbarg_t) handlers_cbargs;
+       isc_rwlock_t handlers_lock;
+
+       char response_content_length_str[128];
+
+       struct isc_nmsocket_h2_connect_data {
+               char *uri;
+               bool post;
+       } connect;
 } isc_nmsocket_h2_t;
 struct isc_nmsocket {
        /*% Unlocked, RO */
@@ -974,8 +1022,6 @@ struct isc_nmsocket {
 
        atomic_int_fast32_t active_child_connections;
 
-       ISC_LIST(isc_nm_http2_server_handler_t) handlers;
-
 #ifdef NETMGR_TRACE
        void *backtrace[TRACE_SIZE];
        int backtrace_size;
@@ -1472,10 +1518,43 @@ isc__nm_tls_cleanup_data(isc_nmsocket_t *sock);
 void
 isc__nm_tls_stoplistening(isc_nmsocket_t *sock);
 
+void
+isc__nm_http_stoplistening(isc_nmsocket_t *sock);
+
+void
+isc__nm_http_clear_handlers(isc_nmsocket_t *sock);
+
+void
+isc__nm_http_clear_session(isc_nmsocket_t *sock);
+
 void
 isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region,
                  isc_nm_cb_t cb, void *cbarg);
 
+void
+isc__nm_http_close(isc_nmsocket_t *sock);
+
+void
+isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0);
+
+void
+isc__nm_async_httpstop(isc__networker_t *worker, isc__netievent_t *ev0);
+
+void
+isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0);
+
+bool
+isc__nm_parse_doh_query_string(const char *query_string, const char **start,
+                              size_t *len);
+
+char *
+isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url,
+                           const size_t base64url_len, size_t *res_len);
+
+char *
+isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64,
+                           const size_t base64_len, size_t *res_len);
+
 #define isc__nm_uverr2result(x) \
        isc___nm_uverr2result(x, true, __FILE__, __LINE__, __func__)
 isc_result_t
@@ -1608,6 +1687,10 @@ NETIEVENT_SOCKET_HANDLE_TYPE(tlsdnscancel);
 NETIEVENT_SOCKET_QUOTA_TYPE(tlsdnsaccept);
 NETIEVENT_SOCKET_TYPE(tlsdnscycle);
 
+NETIEVENT_SOCKET_TYPE(httpstop);
+NETIEVENT_SOCKET_REQ_TYPE(httpsend);
+NETIEVENT_SOCKET_TYPE(httpclose);
+
 NETIEVENT_SOCKET_REQ_TYPE(tcpconnect);
 NETIEVENT_SOCKET_REQ_TYPE(tcpsend);
 NETIEVENT_SOCKET_TYPE(tcpstartread);
@@ -1668,6 +1751,10 @@ NETIEVENT_SOCKET_HANDLE_DECL(tlsdnscancel);
 NETIEVENT_SOCKET_QUOTA_DECL(tlsdnsaccept);
 NETIEVENT_SOCKET_DECL(tlsdnscycle);
 
+NETIEVENT_SOCKET_DECL(httpstop);
+NETIEVENT_SOCKET_REQ_DECL(httpsend);
+NETIEVENT_SOCKET_DECL(httpclose);
+
 NETIEVENT_SOCKET_REQ_DECL(tcpconnect);
 NETIEVENT_SOCKET_REQ_DECL(tcpsend);
 NETIEVENT_SOCKET_REQ_DECL(tlssend);
index d1331cea2b1bc923b58f282aff6a5f13567fb50f..fb8b1fa701837261454817250867962e6bad3d93 100644 (file)
@@ -734,6 +734,10 @@ process_netievent(isc__networker_t *worker, isc__netievent_t *ievent) {
                NETIEVENT_CASE(tlsdnsstop);
                NETIEVENT_CASE(tlsdnsshutdown);
 
+               NETIEVENT_CASE(httpstop);
+               NETIEVENT_CASE(httpsend);
+               NETIEVENT_CASE(httpclose);
+
                NETIEVENT_CASE(connectcb);
                NETIEVENT_CASE(readcb);
                NETIEVENT_CASE(sendcb);
@@ -814,6 +818,10 @@ NETIEVENT_SOCKET_QUOTA_DEF(tlsdnsaccept);
 NETIEVENT_SOCKET_DEF(tlsdnscycle);
 NETIEVENT_SOCKET_DEF(tlsdnsshutdown);
 
+NETIEVENT_SOCKET_DEF(httpstop);
+NETIEVENT_SOCKET_REQ_DEF(httpsend);
+NETIEVENT_SOCKET_DEF(httpclose);
+
 NETIEVENT_SOCKET_REQ_DEF(tcpconnect);
 NETIEVENT_SOCKET_REQ_DEF(tcpsend);
 NETIEVENT_SOCKET_REQ_DEF(tlssend);
@@ -1001,6 +1009,32 @@ nmsocket_cleanup(isc_nmsocket_t *sock, bool dofree FLARG) {
        isc_condition_destroy(&sock->scond);
        isc__nm_tls_cleanup_data(sock);
 
+       if (sock->type == isc_nm_httplistener) {
+               isc__nm_http_clear_handlers(sock);
+               isc_rwlock_destroy(&sock->h2.handlers_lock);
+       }
+
+       if (sock->h2.request_path != NULL) {
+               isc_mem_free(sock->mgr->mctx, sock->h2.request_path);
+               sock->h2.request_path = NULL;
+       }
+
+       if (sock->h2.query_data != NULL) {
+               isc_mem_free(sock->mgr->mctx, sock->h2.query_data);
+               sock->h2.query_data = NULL;
+       }
+
+       if (sock->h2.connect.uri != NULL) {
+               isc_mem_free(sock->mgr->mctx, sock->h2.connect.uri);
+               sock->h2.query_data = NULL;
+       }
+
+       if (sock->h2.buf != NULL) {
+               isc_mem_free(sock->mgr->mctx, sock->h2.buf);
+               sock->h2.buf = NULL;
+       }
+
+       isc__nm_http_clear_session(sock);
 #ifdef NETMGR_TRACE
        LOCK(&sock->mgr->lock);
        ISC_LIST_UNLINK(sock->mgr->active_sockets, sock, active_link);
@@ -1115,6 +1149,9 @@ isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG) {
                case isc_nm_tlsdnssocket:
                        isc__nm_tlsdns_close(sock);
                        return;
+               case isc_nm_httpstream:
+                       isc__nm_http_close(sock);
+                       return;
                default:
                        break;
                }
@@ -1158,7 +1195,8 @@ isc_nmsocket_close(isc_nmsocket_t **sockp) {
                (*sockp)->type == isc_nm_tcplistener ||
                (*sockp)->type == isc_nm_tcpdnslistener ||
                (*sockp)->type == isc_nm_tlsdnslistener ||
-               (*sockp)->type == isc_nm_tlslistener);
+               (*sockp)->type == isc_nm_tlslistener ||
+               (*sockp)->type == isc_nm_httplistener);
 
        isc__nmsocket_detach(sockp);
 }
@@ -1221,6 +1259,8 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type,
        case isc_nm_tcpdnslistener:
        case isc_nm_tlsdnssocket:
        case isc_nm_tlsdnslistener:
+       case isc_nm_httpstream:
+       case isc_nm_httplistener:
                if (family == AF_INET) {
                        sock->statsindex = tcp4statsindex;
                } else {
@@ -1250,6 +1290,28 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type,
 
        atomic_store(&sock->active_child_connections, 0);
 
+       if (type == isc_nm_httplistener) {
+               ISC_LIST_INIT(sock->h2.handlers);
+               ISC_LIST_INIT(sock->h2.handlers_cbargs);
+               isc_rwlock_init(&sock->h2.handlers_lock, 0, 1);
+       }
+
+       sock->h2.session = NULL;
+       sock->h2.httpserver = NULL;
+       sock->h2.query_data = NULL;
+       sock->h2.query_data_len = 0;
+       sock->h2.query_too_large = false;
+       sock->h2.request_path = NULL;
+       sock->h2.request_type = ISC_HTTP_REQ_UNSUPPORTED;
+       sock->h2.request_scheme = ISC_HTTP_SCHEME_UNSUPPORTED;
+       sock->h2.content_length = 0;
+       sock->h2.content_type_verified = false;
+       sock->h2.accept_type_verified = false;
+       sock->h2.handler_cb = NULL;
+       sock->h2.handler_cbarg = NULL;
+       sock->h2.connect.uri = NULL;
+       sock->h2.buf = NULL;
+
        sock->magic = NMSOCK_MAGIC;
 }
 
@@ -1391,6 +1453,10 @@ isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t *peer,
                sock->statichandle = handle;
        }
 
+       if (sock->type == isc_nm_httpstream) {
+               handle->httpsession = sock->h2.session;
+       }
+
        return (handle);
 }
 
@@ -1822,6 +1888,9 @@ isc_nm_stoplistening(isc_nmsocket_t *sock) {
        case isc_nm_tlsdnslistener:
                isc__nm_tlsdns_stoplistening(sock);
                break;
+       case isc_nm_httplistener:
+               isc__nm_http_stoplistening(sock);
+               break;
        default:
                INSIST(0);
                ISC_UNREACHABLE();
@@ -2374,6 +2443,10 @@ nmsocket_type_totext(isc_nmsocket_type type) {
                return ("isc_nm_tlsdnslistener");
        case isc_nm_tlsdnssocket:
                return ("isc_nm_tlsdnssocket");
+       case isc_nm_httplistener:
+               return ("isc_nm_httplistener");
+       case isc_nm_httpstream:
+               return ("isc_nm_httpstream");
        default:
                INSIST(0);
                ISC_UNREACHABLE();
index 6c52aed62c6c52a527633332da4dbaa06c5fbab8..e7ad6cd634c04d79dbb1599f2b12fc79318db4e4 100644 (file)
@@ -167,6 +167,27 @@ tcp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
        REQUIRE(isc__nm_in_netthread());
        REQUIRE(sock->tid == isc_nm_tid());
 
+       result = isc__nm_socket(req->peer.type.sa.sa_family, SOCK_STREAM, 0,
+                               &sock->fd);
+       /*
+        * The socket() call can fail spuriously on FreeBSD 12, so we need to
+        * handle the failure early and gracefully.
+        */
+       if (result != ISC_R_SUCCESS) {
+               atomic_store(&sock->closed, true);
+               isc__nm_uvreq_t *cbreq = NULL;
+               cbreq = isc__nm_uvreq_get(sock->mgr, sock);
+               cbreq->cb.connect = req->cb.connect;
+               cbreq->cbarg = req->cbarg;
+               isc_nmhandle_attach(req->handle, &cbreq->handle);
+               isc__nmsocket_clearcb(sock);
+               isc__nm_connectcb(sock, cbreq, result);
+               goto error;
+       }
+       result = isc__nm_socket_connectiontimeout(sock->fd,
+                                                 sock->connect_timeout);
+       RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
        worker = &sock->mgr->workers[sock->tid];
 
        atomic_store(&sock->connecting, true);
@@ -210,7 +231,7 @@ tcp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
 
 done:
        result = isc__nm_uverr2result(r);
-
+error:
        LOCK(&sock->lock);
        sock->result = result;
        SIGNAL(&sock->cond);
@@ -239,10 +260,13 @@ isc__nm_async_tcpconnect(isc__networker_t *worker, isc__netievent_t *ev0) {
        REQUIRE(sock->parent == NULL);
        REQUIRE(sock->tid == isc_nm_tid());
 
+       sock->fd = (uv_os_sock_t)(-1);
        result = tcp_connect_direct(sock, req);
        if (result != ISC_R_SUCCESS) {
                atomic_store(&sock->active, false);
-               isc__nm_tcp_close(sock);
+               if (sock->fd != (uv_os_sock_t)(-1)) {
+                       isc__nm_tcp_close(sock);
+               }
                isc__nm_uvreq_put(&req, sock);
        }
 
@@ -309,36 +333,19 @@ isc_nm_tcpconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
        isc_nmsocket_t *sock = NULL;
        isc__netievent_tcpconnect_t *ievent = NULL;
        isc__nm_uvreq_t *req = NULL;
-       sa_family_t sa_family;
-       uv_os_sock_t fd;
 
        REQUIRE(VALID_NM(mgr));
        REQUIRE(local != NULL);
        REQUIRE(peer != NULL);
 
-       sa_family = peer->addr.type.sa.sa_family;
-
-       /*
-        * The socket() call can fail spuriously on FreeBSD 12, so we need to
-        * handle the failure early and gracefully.
-        */
-       result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &fd);
-       if (result != ISC_R_SUCCESS) {
-               return (result);
-       }
-
        sock = isc_mem_get(mgr->mctx, sizeof(*sock));
        isc__nmsocket_init(sock, mgr, isc_nm_tcpsocket, local);
 
        sock->extrahandlesize = extrahandlesize;
        sock->connect_timeout = timeout;
        sock->result = ISC_R_DEFAULT;
-       sock->fd = fd;
        atomic_init(&sock->client, true);
 
-       result = isc__nm_socket_connectiontimeout(fd, timeout);
-       RUNTIME_CHECK(result == ISC_R_SUCCESS);
-
        req = isc__nm_uvreq_get(mgr, sock);
        req->cb.connect = cb;
        req->cbarg = cbarg;
index a2d4f80d6235fd4735819f7289353ddac54c24d4..87242e0a0bae21014c63fc274dded22d67c7d064 100644 (file)
@@ -510,7 +510,7 @@ isc_nm_listentls(isc_nm_t *mgr, isc_nmiface_t *iface,
                *sockp = tlssock;
        }
 
-       return result;
+       return (result);
 }
 
 void
index c8d07aa28819acb9b036c5a69fc0d016a7a4b747..58b43607a2eafc898af60b21e39a132f3872cf72 100644 (file)
@@ -48,6 +48,7 @@ TESTS =                       \
        tcp_quota_test  \
        tcpdns_test     \
        tlsdns_test     \
+       doh_test        \
        time_test       \
        timer_test      \
        udp_test
@@ -55,6 +56,16 @@ TESTS =                      \
 check_PROGRAMS =       \
        $(TESTS)
 
+doh_test_CPPFLAGS =    \
+       $(AM_CPPFLAGS)  \
+       $(LIBUV_CFLAGS) \
+       $(OPENSSL_CFLAGS)
+
+doh_test_LDADD =       \
+       $(LDADD)        \
+       $(LIBUV_LIBS)   \
+       $(OPENSSL_LIBS)
+
 hmac_test_CPPFLAGS =   \
        $(AM_CPPFLAGS)  \
        $(OPENSSL_CFLAGS)
@@ -77,8 +88,8 @@ random_test_LDADD =   \
 
 tcp_test_CPPFLAGS =    \
        $(AM_CPPFLAGS)  \
-       $(OPENSSL_CFLAGS)       \
-       $(LIBUV_CFLAGS)
+       $(LIBUV_CFLAGS) \
+       $(OPENSSL_CFLAGS)
 
 tcp_test_LDADD =       \
        $(LDADD)        \
@@ -86,8 +97,8 @@ tcp_test_LDADD =      \
 
 tcp_quota_test_CPPFLAGS =      \
        $(AM_CPPFLAGS)          \
-       $(OPENSSL_CFLAGS)       \
-       $(LIBUV_CFLAGS)
+       $(LIBUV_CFLAGS)         \
+       $(OPENSSL_CFLAGS)
 
 tcp_quota_test_LDADD = \
        $(LDADD)        \
@@ -95,8 +106,8 @@ tcp_quota_test_LDADD =       \
 
 tcpdns_test_CPPFLAGS = \
        $(AM_CPPFLAGS)  \
-       $(OPENSSL_CFLAGS)       \
-       $(LIBUV_CFLAGS)
+       $(LIBUV_CFLAGS) \
+       $(OPENSSL_CFLAGS)
 
 tcpdns_test_LDADD =    \
        $(LDADD)        \
@@ -104,8 +115,8 @@ tcpdns_test_LDADD = \
 
 tlsdns_test_CPPFLAGS = \
        $(AM_CPPFLAGS)  \
-       $(OPENSSL_CFLAGS)       \
-       $(LIBUV_CFLAGS)
+       $(LIBUV_CFLAGS) \
+       $(OPENSSL_CFLAGS)
 
 tlsdns_test_LDADD =    \
        $(LDADD)        \
@@ -113,8 +124,8 @@ tlsdns_test_LDADD = \
 
 udp_test_CPPFLAGS =    \
        $(AM_CPPFLAGS)  \
-       $(OPENSSL_CFLAGS)       \
-       $(LIBUV_CFLAGS)
+       $(LIBUV_CFLAGS) \
+       $(OPENSSL_CFLAGS)
 
 udp_test_LDADD =       \
        $(LDADD)        \
diff --git a/lib/isc/tests/doh_test.c b/lib/isc/tests/doh_test.c
new file mode 100644 (file)
index 0000000..9e47d10
--- /dev/null
@@ -0,0 +1,1875 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#if HAVE_CMOCKA
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <uv.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/atomic.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/mutex.h>
+#include <isc/netmgr.h>
+#include <isc/nonce.h>
+#include <isc/os.h>
+#include <isc/refcount.h>
+#include <isc/sockaddr.h>
+#include <isc/thread.h>
+
+#include "uv_wrap.h"
+#define KEEP_BEFORE
+
+#include "../netmgr/http.c"
+#include "../netmgr/netmgr-int.h"
+#include "../netmgr/uv-compat.c"
+#include "../netmgr/uv-compat.h"
+#include "isctest.h"
+#include "tls_test_cert_key.h"
+
+#define MAX_NM 2
+
+static isc_sockaddr_t tcp_listen_addr;
+
+static uint64_t send_magic = 0;
+static uint64_t stop_magic = 0;
+
+static uv_buf_t send_msg = { .base = (char *)&send_magic,
+                            .len = sizeof(send_magic) };
+
+static atomic_uint_fast64_t nsends;
+
+static atomic_uint_fast64_t ssends;
+static atomic_uint_fast64_t sreads;
+
+static atomic_uint_fast64_t csends;
+static atomic_uint_fast64_t creads;
+
+static atomic_bool was_error;
+
+static unsigned int workers = 1;
+
+static bool reuse_supported = true;
+
+static atomic_bool POST = true;
+
+static atomic_bool use_TLS = false;
+static SSL_CTX *server_ssl_ctx = NULL;
+
+static SSL_CTX *
+create_server_ssl_ctx(const char *key, const size_t key_size,
+                     const char *key_pass, const char *cert,
+                     const size_t cert_size);
+
+#define NSENDS 100
+#define NWRITES 10
+
+#define DOH_PATH "/dns-query"
+
+#define CHECK_RANGE_FULL(v)                                       \
+       {                                                         \
+               int __v = atomic_load(&v);                        \
+               assert_true(__v > NSENDS * NWRITES * 10 / 100);   \
+               assert_true(__v <= NSENDS * NWRITES * 110 / 100); \
+       }
+
+#define CHECK_RANGE_HALF(v)                                       \
+       {                                                         \
+               int __v = atomic_load(&v);                        \
+               assert_true(__v > NSENDS * NWRITES * 5 / 100);    \
+               assert_true(__v <= NSENDS * NWRITES * 110 / 100); \
+       }
+
+/* Enable this to print values while running tests */
+#undef PRINT_DEBUG
+#ifdef PRINT_DEBUG
+#define X(v) fprintf(stderr, #v " = %" PRIu64 "\n", atomic_load(&v))
+#else
+#define X(v)
+#endif
+
+static int
+setup_ephemeral_port(isc_sockaddr_t *addr, sa_family_t family) {
+       isc_result_t result;
+       socklen_t addrlen = sizeof(*addr);
+       int fd;
+       int r;
+
+       isc_sockaddr_fromin6(addr, &in6addr_loopback, 0);
+
+       fd = socket(AF_INET6, family, 0);
+       if (fd < 0) {
+               perror("setup_ephemeral_port: socket()");
+               return (-1);
+       }
+
+       r = bind(fd, (const struct sockaddr *)&addr->type.sa,
+                sizeof(addr->type.sin6));
+       if (r != 0) {
+               perror("setup_ephemeral_port: bind()");
+               isc__nm_closesocket(fd);
+               return (r);
+       }
+
+       r = getsockname(fd, (struct sockaddr *)&addr->type.sa, &addrlen);
+       if (r != 0) {
+               perror("setup_ephemeral_port: getsockname()");
+               isc__nm_closesocket(fd);
+               return (r);
+       }
+
+       result = isc__nm_socket_reuse(fd);
+       if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) {
+               fprintf(stderr,
+                       "setup_ephemeral_port: isc__nm_socket_reuse(): %s",
+                       isc_result_totext(result));
+               close(fd);
+               return (-1);
+       }
+
+       result = isc__nm_socket_reuse_lb(fd);
+       if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) {
+               fprintf(stderr,
+                       "setup_ephemeral_port: isc__nm_socket_reuse_lb(): %s",
+                       isc_result_totext(result));
+               close(fd);
+               return (-1);
+       }
+       if (result == ISC_R_NOTIMPLEMENTED) {
+               reuse_supported = false;
+       }
+
+#if IPV6_RECVERR
+#define setsockopt_on(socket, level, name) \
+       setsockopt(socket, level, name, &(int){ 1 }, sizeof(int))
+
+       r = setsockopt_on(fd, IPPROTO_IPV6, IPV6_RECVERR);
+       if (r != 0) {
+               perror("setup_ephemeral_port");
+               close(fd);
+               return (r);
+       }
+#endif
+
+       return (fd);
+}
+
+static int
+_setup(void **state) {
+       UNUSED(state);
+
+       /*workers = isc_os_ncpus();*/
+
+       if (isc_test_begin(NULL, false, workers) != ISC_R_SUCCESS) {
+               return (-1);
+       }
+
+       signal(SIGPIPE, SIG_IGN);
+
+       server_ssl_ctx = create_server_ssl_ctx(
+               (const char *)TLS_test_key, sizeof(TLS_test_key), NULL,
+               (const char *)TLS_test_cert, sizeof(TLS_test_cert));
+
+       return (0);
+}
+
+static int
+_teardown(void **state) {
+       UNUSED(state);
+
+       if (server_ssl_ctx) {
+               SSL_CTX_free(server_ssl_ctx);
+               server_ssl_ctx = NULL;
+       }
+
+       isc_test_end();
+
+       return (0);
+}
+
+/* Generic */
+
+static void
+noop_read_cb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
+            void *cbarg) {
+       UNUSED(handle);
+       UNUSED(result);
+       UNUSED(region);
+       UNUSED(cbarg);
+}
+
+thread_local uint8_t tcp_buffer_storage[4096];
+thread_local size_t tcp_buffer_length = 0;
+
+static int
+nm_setup(void **state) {
+       size_t nworkers = ISC_MAX(ISC_MIN(workers, 32), 1);
+       int tcp_listen_sock = -1;
+       isc_nm_t **nm = NULL;
+
+       tcp_listen_addr = (isc_sockaddr_t){ .length = 0 };
+       tcp_listen_sock = setup_ephemeral_port(&tcp_listen_addr, SOCK_STREAM);
+       if (tcp_listen_sock < 0) {
+               return (-1);
+       }
+       close(tcp_listen_sock);
+       tcp_listen_sock = -1;
+
+       atomic_store(&nsends, NSENDS * NWRITES);
+
+       atomic_store(&csends, 0);
+       atomic_store(&creads, 0);
+       atomic_store(&sreads, 0);
+       atomic_store(&ssends, 0);
+
+       atomic_store(&was_error, false);
+
+       atomic_store(&POST, false);
+       atomic_store(&use_TLS, false);
+
+       isc_nonce_buf(&send_magic, sizeof(send_magic));
+       isc_nonce_buf(&stop_magic, sizeof(stop_magic));
+       if (send_magic == stop_magic) {
+               return (-1);
+       }
+
+       nm = isc_mem_get(test_mctx, MAX_NM * sizeof(nm[0]));
+       for (size_t i = 0; i < MAX_NM; i++) {
+               nm[i] = isc_nm_start(test_mctx, nworkers);
+               assert_non_null(nm[i]);
+       }
+
+       *state = nm;
+
+       return (0);
+}
+
+static int
+nm_teardown(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+
+       for (size_t i = 0; i < MAX_NM; i++) {
+               isc_nm_destroy(&nm[i]);
+               assert_null(nm[i]);
+       }
+       isc_mem_put(test_mctx, nm, MAX_NM * sizeof(nm[0]));
+
+       return (0);
+}
+
+thread_local size_t nwrites = NWRITES;
+
+static void
+sockaddr_to_url(isc_sockaddr_t *sa, const bool https, char *outbuf,
+               size_t outbuf_len, const char *append) {
+       uint16_t port;
+       char saddr[INET6_ADDRSTRLEN] = { 0 };
+       int family;
+       if (sa == NULL || outbuf == NULL || outbuf_len == 0) {
+               return;
+       }
+
+       family = ((struct sockaddr *)&sa->type.sa)->sa_family;
+
+       port = ntohs(family == AF_INET ? sa->type.sin.sin_port
+                                      : sa->type.sin6.sin6_port);
+       inet_ntop(family,
+                 family == AF_INET
+                         ? (struct sockaddr *)&sa->type.sin.sin_addr
+                         : (struct sockaddr *)&sa->type.sin6.sin6_addr,
+                 saddr, sizeof(saddr));
+
+       snprintf(outbuf, outbuf_len, "%s://%s%s%s:%u%s",
+                https ? "https" : "http", family == AF_INET ? "" : "[", saddr,
+                family == AF_INET ? "" : "]", port, append ? append : "");
+}
+
+/* SSL utils */
+static bool
+ssl_ctx_add_privatekey(SSL_CTX *ctx, const void *key,
+                      const unsigned int key_size, const char *pass) {
+       BIO *key_bio = BIO_new_mem_buf(key, key_size);
+       bool res = false;
+       if (key_bio) {
+               RSA *rsa = PEM_read_bio_RSAPrivateKey(
+                       key_bio, 0, 0, (void *)((uintptr_t)pass));
+               if (rsa) {
+                       res = (1 == SSL_CTX_use_RSAPrivateKey(ctx, rsa))
+                                     ? true
+                                     : false;
+               }
+               RSA_free(rsa);
+               BIO_free_all(key_bio);
+       } else {
+               return false;
+       }
+
+       res = SSL_CTX_check_private_key(ctx) == 1 ? true : false;
+
+       return res;
+}
+
+static bool
+ssl_ctx_add_cert_chain(SSL_CTX *ctx, const void *cert,
+                      const unsigned int size) {
+       BIO *chain_bio = NULL;
+       STACK_OF(X509_INFO) *chain_stack = NULL;
+       size_t count = 0;
+       X509_INFO *ci = NULL;
+       bool res = true;
+
+       chain_bio = BIO_new_mem_buf(cert, size);
+       if (chain_bio == NULL) {
+               res = false;
+               goto exit;
+       }
+
+       /* read info into BIO */
+       chain_stack = PEM_X509_INFO_read_bio(chain_bio, NULL, NULL, NULL);
+       if (chain_stack == NULL) {
+               res = false;
+               goto exit;
+       }
+
+       count = sk_X509_INFO_num(chain_stack);
+       /* add certs */
+       for (size_t i = count; i > 0; i--) {
+               /* get the cert */
+               ci = sk_X509_INFO_value(chain_stack, i - 1);
+               if (ci == NULL) {
+                       res = false;
+                       goto exit;
+               }
+
+               /* add the cert */
+               if (SSL_CTX_add_extra_chain_cert(ctx, ci->x509) != 1) {
+                       res = false;
+                       goto exit;
+               }
+
+               /* use the first cert in chain by default */
+               if (i == 1) {
+                       if (SSL_CTX_use_certificate(ctx, ci->x509) != 1) {
+                               res = false;
+                               goto exit;
+                       }
+               }
+       }
+exit:
+       if (chain_stack) {
+               while ((ci = sk_X509_INFO_pop(chain_stack)) != NULL) {
+                       X509_INFO_free(ci);
+               }
+               sk_X509_INFO_free(chain_stack);
+       }
+       if (chain_bio) {
+               BIO_free_all(chain_bio);
+       }
+       return res;
+}
+
+static SSL_CTX *
+create_server_ssl_ctx(const char *key, const size_t key_size,
+                     const char *key_pass, const char *cert,
+                     const size_t cert_size) {
+       SSL_CTX *ssl_ctx;
+       EC_KEY *ecdh;
+
+       ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+       if (!ssl_ctx) {
+               fprintf(stderr, "Could not create SSL/TLS context: %s",
+                       ERR_error_string(ERR_get_error(), NULL));
+               SSL_CTX_free(ssl_ctx);
+               return NULL;
+       }
+       /* >= TLSv1.2 */
+       SSL_CTX_set_options(
+               ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                                SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 |
+                                SSL_OP_NO_COMPRESSION |
+                                SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+       ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+       if (!ecdh) {
+               fprintf(stderr, "EC_KEY_new_by_curv_name failed: %s",
+                       ERR_error_string(ERR_get_error(), NULL));
+               SSL_CTX_free(ssl_ctx);
+               return NULL;
+       }
+       SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
+       EC_KEY_free(ecdh);
+
+       if (ssl_ctx_add_cert_chain(ssl_ctx, cert, cert_size) != true) {
+               fprintf(stderr, "Could not read certificate file\n");
+               SSL_CTX_free(ssl_ctx);
+               return NULL;
+       }
+
+       if (ssl_ctx_add_privatekey(ssl_ctx, key, key_size, key_pass) != true) {
+               fprintf(stderr, "Could not read private key\n");
+               SSL_CTX_free(ssl_ctx);
+               return NULL;
+       }
+
+       return ssl_ctx;
+}
+
+static void
+doh_receive_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult,
+                    isc_region_t *region, void *cbarg) {
+       uint_fast64_t sends = atomic_load(&nsends);
+       assert_non_null(handle);
+       UNUSED(cbarg);
+       UNUSED(region);
+
+       if (eresult == ISC_R_SUCCESS) {
+               atomic_fetch_add(&csends, 1);
+               atomic_fetch_add(&creads, 1);
+               if (sends > 0) {
+                       atomic_fetch_sub(&nsends, 1);
+               }
+               isc_nm_resumeread(handle);
+       } else {
+               /* We failed to connect; try again */
+               while (sends > 0) {
+                       /* Continue until we subtract or we are done */
+                       if (atomic_compare_exchange_weak(&nsends, &sends,
+                                                        sends - 1)) {
+                               sends--;
+                               break;
+                       }
+               }
+               atomic_store(&was_error, true);
+               /* Send failed, we need to stop reading too */
+               isc_nm_cancelread(handle);
+       }
+}
+
+static void
+doh_reply_sent_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
+       UNUSED(eresult);
+       UNUSED(cbarg);
+
+       assert_non_null(handle);
+
+       if (eresult == ISC_R_SUCCESS) {
+               atomic_fetch_add(&ssends, 1);
+       }
+}
+
+static void
+doh_receive_request_cb(isc_nmhandle_t *handle, isc_result_t eresult,
+                      isc_region_t *region, void *cbarg) {
+       uint64_t magic = 0;
+
+       UNUSED(cbarg);
+       assert_non_null(handle);
+
+       if (eresult != ISC_R_SUCCESS) {
+               atomic_store(&was_error, true);
+               return;
+       }
+
+       atomic_fetch_add(&sreads, 1);
+
+       memmove(tcp_buffer_storage + tcp_buffer_length, region->base,
+               region->length);
+       tcp_buffer_length += region->length;
+
+       while (tcp_buffer_length >= sizeof(magic)) {
+               magic = *(uint64_t *)tcp_buffer_storage;
+               assert_true(magic == stop_magic || magic == send_magic);
+
+               tcp_buffer_length -= sizeof(magic);
+               memmove(tcp_buffer_storage, tcp_buffer_storage + sizeof(magic),
+                       tcp_buffer_length);
+
+               if (magic == send_magic) {
+                       isc_nm_send(handle, region, doh_reply_sent_cb, NULL);
+                       return;
+               } else if (magic == stop_magic) {
+                       /* We are done, so we don't send anything back */
+                       /* There should be no more packets in the buffer */
+                       assert_int_equal(tcp_buffer_length, 0);
+               }
+       }
+}
+
+static void
+doh_noop(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+       isc_nm_t *listen_nm = nm[0];
+       isc_nm_t *connect_nm = nm[1];
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *listen_sock = NULL;
+       isc_sockaddr_t tcp_connect_addr;
+       char req_url[256];
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = isc_nm_listenhttp(listen_nm, (isc_nmiface_t *)&tcp_listen_addr,
+                                  0, NULL, NULL, &listen_sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+       result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH,
+                                             noop_read_cb, NULL, 0);
+
+       isc_nm_stoplistening(listen_sock);
+       isc_nmsocket_close(&listen_sock);
+       assert_null(listen_sock);
+
+       sockaddr_to_url(&tcp_listen_addr, false, req_url, sizeof(req_url),
+                       DOH_PATH);
+       (void)isc_nm_http_connect_send_request(
+               connect_nm, req_url, atomic_load(&POST),
+               &(isc_region_t){ .base = (uint8_t *)send_msg.base,
+                                .length = send_msg.len },
+               noop_read_cb, NULL, NULL, 30000);
+
+       isc_nm_closedown(connect_nm);
+
+       assert_int_equal(0, atomic_load(&csends));
+       assert_int_equal(0, atomic_load(&creads));
+       assert_int_equal(0, atomic_load(&sreads));
+       assert_int_equal(0, atomic_load(&ssends));
+}
+
+static void
+doh_noop_POST(void **state) {
+       atomic_store(&POST, true);
+       doh_noop(state);
+}
+
+static void
+doh_noop_GET(void **state) {
+       atomic_store(&POST, false);
+       doh_noop(state);
+}
+
+static void
+doh_noresponse(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+       isc_nm_t *listen_nm = nm[0];
+       isc_nm_t *connect_nm = nm[1];
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *listen_sock = NULL;
+       isc_sockaddr_t tcp_connect_addr;
+       char req_url[256];
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = isc_nm_listenhttp(listen_nm, (isc_nmiface_t *)&tcp_listen_addr,
+                                  0, NULL, NULL, &listen_sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH,
+                                             noop_read_cb, NULL, 0);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       sockaddr_to_url(&tcp_listen_addr, false, req_url, sizeof(req_url),
+                       DOH_PATH);
+       (void)isc_nm_http_connect_send_request(
+               connect_nm, req_url, atomic_load(&POST),
+               &(isc_region_t){ .base = (uint8_t *)send_msg.base,
+                                .length = send_msg.len },
+               noop_read_cb, NULL, NULL, 30000);
+
+       isc_nm_stoplistening(listen_sock);
+       isc_nmsocket_close(&listen_sock);
+       assert_null(listen_sock);
+       isc_nm_closedown(connect_nm);
+}
+
+static void
+doh_noresponse_POST(void **state) {
+       atomic_store(&POST, true);
+       doh_noresponse(state);
+}
+
+static void
+doh_noresponse_GET(void **state) {
+       atomic_store(&POST, false);
+       doh_noresponse(state);
+}
+
+static void
+doh_receive_send_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult,
+                         isc_region_t *region, void *cbarg) {
+       uint_fast64_t sends = atomic_load(&nsends);
+       assert_non_null(handle);
+       UNUSED(region);
+
+       if (eresult == ISC_R_SUCCESS) {
+               atomic_fetch_add(&csends, 1);
+               atomic_fetch_add(&creads, 1);
+               if (sends > 0) {
+                       size_t i;
+                       atomic_fetch_sub(&nsends, 1);
+                       for (i = 0; i < NWRITES / 2; i++) {
+                               eresult = isc_nm_httprequest(
+                                       handle,
+                                       &(isc_region_t){
+                                               .base = (uint8_t *)send_msg.base,
+                                               .length = send_msg.len },
+                                       doh_receive_send_reply_cb, cbarg);
+                               assert_true(eresult == ISC_R_SUCCESS);
+                       }
+               }
+       } else {
+               /* We failed to connect; try again */
+               while (sends > 0) {
+                       /* Continue until we subtract or we are done */
+                       if (atomic_compare_exchange_weak(&nsends, &sends,
+                                                        sends - 1)) {
+                               sends--;
+                               break;
+                       }
+               }
+               atomic_store(&was_error, true);
+       }
+}
+
+static isc_threadresult_t
+doh_connect_thread(isc_threadarg_t arg) {
+       isc_nm_t *connect_nm = (isc_nm_t *)arg;
+       isc_sockaddr_t tcp_connect_addr;
+       char req_url[256];
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+       sockaddr_to_url(&tcp_listen_addr, atomic_load(&use_TLS), req_url,
+                       sizeof(req_url), DOH_PATH);
+
+       while (atomic_load(&nsends) > 0) {
+               (void)isc_nm_http_connect_send_request(
+                       connect_nm, req_url, atomic_load(&POST),
+                       &(isc_region_t){ .base = (uint8_t *)send_msg.base,
+                                        .length = send_msg.len },
+                       doh_receive_send_reply_cb, NULL, NULL, 5000);
+       }
+
+       return ((isc_threadresult_t)0);
+}
+
+static void
+doh_recv_one(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+       isc_nm_t *listen_nm = nm[0];
+       isc_nm_t *connect_nm = nm[1];
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *listen_sock = NULL;
+       isc_sockaddr_t tcp_connect_addr;
+       char req_url[256];
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       atomic_store(&nsends, 1);
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = isc_nm_listenhttp(
+               listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL,
+               atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH,
+                                             doh_receive_request_cb, NULL, 0);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       sockaddr_to_url(&tcp_listen_addr, atomic_load(&use_TLS), req_url,
+                       sizeof(req_url), DOH_PATH);
+       result = isc_nm_http_connect_send_request(
+               connect_nm, req_url, atomic_load(&POST),
+               &(isc_region_t){ .base = (uint8_t *)send_msg.base,
+                                .length = send_msg.len },
+               doh_receive_reply_cb, NULL, NULL, 5000);
+
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       while (atomic_load(&nsends) > 0) {
+               if (atomic_load(&was_error)) {
+                       break;
+               }
+               isc_thread_yield();
+       }
+
+       while (atomic_load(&ssends) != 1 || atomic_load(&sreads) != 1 ||
+              atomic_load(&csends) != 1)
+       {
+               if (atomic_load(&was_error)) {
+                       break;
+               }
+               isc_thread_yield();
+       }
+
+       isc_nm_stoplistening(listen_sock);
+       isc_nmsocket_close(&listen_sock);
+       assert_null(listen_sock);
+       isc_nm_closedown(connect_nm);
+
+       X(csends);
+       X(creads);
+       X(sreads);
+       X(ssends);
+
+       assert_int_equal(atomic_load(&csends), 1);
+       assert_int_equal(atomic_load(&creads), 1);
+       assert_int_equal(atomic_load(&sreads), 1);
+       assert_int_equal(atomic_load(&ssends), 1);
+}
+
+static void
+doh_recv_one_POST(void **state) {
+       atomic_store(&POST, true);
+       doh_recv_one(state);
+}
+
+static void
+doh_recv_one_GET(void **state) {
+       atomic_store(&POST, false);
+       doh_recv_one(state);
+}
+
+static void
+doh_recv_one_POST_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, true);
+       doh_recv_one(state);
+}
+
+static void
+doh_recv_one_GET_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, false);
+       doh_recv_one(state);
+}
+
+static void
+doh_connect_send_two_requests_cb(isc_nmhandle_t *handle, isc_result_t result,
+                                void *arg) {
+       REQUIRE(VALID_NMHANDLE(handle));
+       if (result != ISC_R_SUCCESS) {
+               goto error;
+       }
+
+       result = isc_nm_httprequest(
+               handle,
+               &(isc_region_t){ .base = (uint8_t *)send_msg.base,
+                                .length = send_msg.len },
+               doh_receive_reply_cb, arg);
+       if (result != ISC_R_SUCCESS) {
+               goto error;
+       }
+
+       result = isc_nm_httprequest(
+               handle,
+               &(isc_region_t){ .base = (uint8_t *)send_msg.base,
+                                .length = send_msg.len },
+               doh_receive_reply_cb, arg);
+       if (result != ISC_R_SUCCESS) {
+               goto error;
+       }
+
+       isc_nm_resumeread(handle);
+       return;
+error:
+       atomic_store(&was_error, true);
+}
+
+static void
+doh_recv_two(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+       isc_nm_t *listen_nm = nm[0];
+       isc_nm_t *connect_nm = nm[1];
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *listen_sock = NULL;
+       isc_sockaddr_t tcp_connect_addr;
+       char req_url[256];
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       atomic_store(&nsends, 2);
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = isc_nm_listenhttp(
+               listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL,
+               atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH,
+                                             doh_receive_request_cb, NULL, 0);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       sockaddr_to_url(&tcp_listen_addr, atomic_load(&use_TLS), req_url,
+                       sizeof(req_url), DOH_PATH);
+       result = isc_nm_httpconnect(
+               connect_nm, NULL, NULL, req_url, atomic_load(&POST),
+               doh_connect_send_two_requests_cb, NULL, NULL, 5000, 0);
+
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       while (atomic_load(&nsends) > 0) {
+               if (atomic_load(&was_error)) {
+                       break;
+               }
+               isc_thread_yield();
+       }
+
+       while (atomic_load(&ssends) != 2 || atomic_load(&sreads) != 2 ||
+              atomic_load(&csends) != 2)
+       {
+               if (atomic_load(&was_error)) {
+                       break;
+               }
+               isc_thread_yield();
+       }
+
+       isc_nm_stoplistening(listen_sock);
+       isc_nmsocket_close(&listen_sock);
+       assert_null(listen_sock);
+       isc_nm_closedown(connect_nm);
+
+       X(csends);
+       X(creads);
+       X(sreads);
+       X(ssends);
+
+       assert_int_equal(atomic_load(&csends), 2);
+       assert_int_equal(atomic_load(&creads), 2);
+       assert_int_equal(atomic_load(&sreads), 2);
+       assert_int_equal(atomic_load(&ssends), 2);
+}
+
+static void
+doh_recv_two_POST(void **state) {
+       atomic_store(&POST, true);
+       doh_recv_two(state);
+}
+
+static void
+doh_recv_two_GET(void **state) {
+       atomic_store(&POST, false);
+       doh_recv_two(state);
+}
+
+static void
+doh_recv_two_POST_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, true);
+       doh_recv_two(state);
+}
+
+static void
+doh_recv_two_GET_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, false);
+       doh_recv_two(state);
+}
+
+static void
+doh_recv_send(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+       isc_nm_t *listen_nm = nm[0];
+       isc_nm_t *connect_nm = nm[1];
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *listen_sock = NULL;
+       size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1);
+       isc_thread_t threads[32] = { 0 };
+       isc_sockaddr_t tcp_connect_addr;
+
+       if (!reuse_supported) {
+               skip();
+               return;
+       }
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = isc_nm_listenhttp(
+               listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL,
+               atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH,
+                                             doh_receive_request_cb, NULL, 0);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       for (size_t i = 0; i < nthreads; i++) {
+               isc_thread_create(doh_connect_thread, connect_nm, &threads[i]);
+       }
+
+       for (size_t i = 0; i < nthreads; i++) {
+               isc_thread_join(threads[i], NULL);
+       }
+
+       isc_nm_closedown(connect_nm);
+       isc_nm_stoplistening(listen_sock);
+       isc_nmsocket_close(&listen_sock);
+       assert_null(listen_sock);
+
+       X(csends);
+       X(creads);
+       X(sreads);
+       X(ssends);
+
+       CHECK_RANGE_FULL(csends);
+       CHECK_RANGE_FULL(creads);
+       CHECK_RANGE_FULL(sreads);
+       CHECK_RANGE_FULL(ssends);
+}
+
+static void
+doh_recv_send_POST(void **state) {
+       atomic_store(&POST, true);
+       doh_recv_send(state);
+}
+
+static void
+doh_recv_send_GET(void **state) {
+       atomic_store(&POST, false);
+       doh_recv_send(state);
+}
+
+static void
+doh_recv_send_POST_TLS(void **state) {
+       atomic_store(&POST, true);
+       atomic_store(&use_TLS, true);
+       doh_recv_send(state);
+}
+
+static void
+doh_recv_send_GET_TLS(void **state) {
+       atomic_store(&POST, false);
+       atomic_store(&use_TLS, true);
+       doh_recv_send(state);
+}
+
+static void
+doh_recv_half_send(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+       isc_nm_t *listen_nm = nm[0];
+       isc_nm_t *connect_nm = nm[1];
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *listen_sock = NULL;
+       size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1);
+       isc_thread_t threads[32] = { 0 };
+       isc_sockaddr_t tcp_connect_addr;
+
+       if (!reuse_supported) {
+               skip();
+               return;
+       }
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = isc_nm_listenhttp(
+               listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL,
+               atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH,
+                                             doh_receive_request_cb, NULL, 0);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       for (size_t i = 0; i < nthreads; i++) {
+               isc_thread_create(doh_connect_thread, connect_nm, &threads[i]);
+       }
+
+       while (atomic_load(&nsends) >= (NSENDS * NWRITES) / 2) {
+               isc_thread_yield();
+       }
+
+       isc_nm_closedown(connect_nm);
+
+       for (size_t i = 0; i < nthreads; i++) {
+               isc_thread_join(threads[i], NULL);
+       }
+
+       isc_nm_stoplistening(listen_sock);
+       isc_nmsocket_close(&listen_sock);
+       assert_null(listen_sock);
+
+       X(csends);
+       X(creads);
+       X(sreads);
+       X(ssends);
+
+       CHECK_RANGE_HALF(csends);
+       CHECK_RANGE_HALF(creads);
+       CHECK_RANGE_HALF(sreads);
+       CHECK_RANGE_HALF(ssends);
+}
+
+static void
+doh_recv_half_send_POST(void **state) {
+       atomic_store(&POST, true);
+       doh_recv_half_send(state);
+}
+
+static void
+doh_recv_half_send_GET(void **state) {
+       atomic_store(&POST, false);
+       doh_recv_half_send(state);
+}
+
+static void
+doh_recv_half_send_POST_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, true);
+       doh_recv_half_send(state);
+}
+
+static void
+doh_recv_half_send_GET_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, false);
+       doh_recv_half_send(state);
+}
+
+static void
+doh_half_recv_send(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+       isc_nm_t *listen_nm = nm[0];
+       isc_nm_t *connect_nm = nm[1];
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *listen_sock = NULL;
+       size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1);
+       isc_thread_t threads[32] = { 0 };
+       isc_sockaddr_t tcp_connect_addr;
+
+       if (!reuse_supported) {
+               skip();
+               return;
+       }
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = isc_nm_listenhttp(
+               listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL,
+               atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH,
+                                             doh_receive_request_cb, NULL, 0);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       for (size_t i = 0; i < nthreads; i++) {
+               isc_thread_create(doh_connect_thread, connect_nm, &threads[i]);
+       }
+
+       while (atomic_load(&nsends) >= (NSENDS * NWRITES) / 2) {
+               isc_thread_yield();
+       }
+
+       isc_nm_stoplistening(listen_sock);
+       isc_nmsocket_close(&listen_sock);
+       assert_null(listen_sock);
+
+       for (size_t i = 0; i < nthreads; i++) {
+               isc_thread_join(threads[i], NULL);
+       }
+
+       isc_nm_closedown(connect_nm);
+
+       X(csends);
+       X(creads);
+       X(sreads);
+       X(ssends);
+
+       CHECK_RANGE_HALF(csends);
+       CHECK_RANGE_HALF(creads);
+       CHECK_RANGE_HALF(sreads);
+       CHECK_RANGE_HALF(ssends);
+}
+
+static void
+doh_half_recv_send_POST(void **state) {
+       atomic_store(&POST, true);
+       doh_half_recv_send(state);
+}
+
+static void
+doh_half_recv_send_GET(void **state) {
+       atomic_store(&POST, false);
+       doh_half_recv_send(state);
+}
+
+static void
+doh_half_recv_send_POST_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, true);
+       doh_half_recv_send(state);
+}
+
+static void
+doh_half_recv_send_GET_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, false);
+       doh_half_recv_send(state);
+}
+
+static void
+doh_half_recv_half_send(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+       isc_nm_t *listen_nm = nm[0];
+       isc_nm_t *connect_nm = nm[1];
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *listen_sock = NULL;
+       size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1);
+       isc_thread_t threads[32] = { 0 };
+       isc_sockaddr_t tcp_connect_addr;
+
+       if (!reuse_supported) {
+               skip();
+               return;
+       }
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
+       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
+
+       result = isc_nm_listenhttp(
+               listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL,
+               atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH,
+                                             doh_receive_request_cb, NULL, 0);
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       for (size_t i = 0; i < nthreads; i++) {
+               isc_thread_create(doh_connect_thread, connect_nm, &threads[i]);
+       }
+
+       while (atomic_load(&nsends) >= (NSENDS * NWRITES) / 2) {
+               isc_thread_yield();
+       }
+
+       isc_nm_closedown(connect_nm);
+       isc_nm_stoplistening(listen_sock);
+       isc_nmsocket_close(&listen_sock);
+       assert_null(listen_sock);
+
+       for (size_t i = 0; i < nthreads; i++) {
+               isc_thread_join(threads[i], NULL);
+       }
+
+       X(csends);
+       X(creads);
+       X(sreads);
+       X(ssends);
+
+       CHECK_RANGE_HALF(csends);
+       CHECK_RANGE_HALF(creads);
+       CHECK_RANGE_HALF(sreads);
+       CHECK_RANGE_HALF(ssends);
+}
+
+static void
+doh_half_recv_half_send_POST(void **state) {
+       atomic_store(&POST, true);
+       doh_half_recv_half_send(state);
+}
+
+static void
+doh_half_recv_half_send_GET(void **state) {
+       atomic_store(&POST, false);
+       doh_half_recv_half_send(state);
+}
+
+static void
+doh_half_recv_half_send_POST_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, true);
+       doh_half_recv_half_send(state);
+}
+
+static void
+doh_half_recv_half_send_GET_TLS(void **state) {
+       atomic_store(&use_TLS, true);
+       atomic_store(&POST, false);
+       doh_half_recv_half_send(state);
+}
+
+static void
+doh_parse_GET_query_string(void **state) {
+       UNUSED(state);
+       /* valid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] =
+                       "dns=AAABAAABAAAAAAAAAWE-"
+                       "NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3"
+                       "QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_true(ret);
+               assert_non_null(queryp);
+               assert_true(len > 0);
+               assert_true(len == strlen(str) - 4);
+               assert_true(memcmp(queryp, str + 4, len) == 0);
+       }
+       /* valid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] =
+                       "?dns=AAABAAABAAAAAAAAAWE-"
+                       "NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3"
+                       "QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_true(ret);
+               assert_non_null(queryp);
+               assert_true(len > 0);
+               assert_true(len == strlen(str) - 6);
+               assert_true(memcmp(queryp, str + 5, len) == 0);
+       }
+       /* valid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?dns=123&dns=567";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_true(ret);
+               assert_non_null(queryp);
+               assert_true(len > 0);
+               assert_true(len == 3);
+               assert_true(memcmp(queryp, "567", 3) == 0);
+       }
+       /* valid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?name1=123&dns=567&name2=123&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_true(ret);
+               assert_non_null(queryp);
+               assert_true(len > 0);
+               assert_true(len == 3);
+               assert_true(memcmp(queryp, "567", 3) == 0);
+       }
+       /* complex, but still valid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] =
+                       "?title=%D0%92%D1%96%D0%B4%D1%81%D0%BE%D1%82%D0%BA%D0%"
+                       "BE%D0%B2%D0%B5_%D0%BA%D0%BE%D0%B4%D1%83%D0%B2%D0%B0%"
+                       "D0%BD%D0%BD%D1%8F&dns=123&veaction=edit&section=0";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_true(ret);
+               assert_non_null(queryp);
+               assert_true(len > 0);
+               assert_true(len == 3);
+               assert_true(memcmp(queryp, "123", 3) == 0);
+       }
+       /* invalid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] =
+                       "?title=%D0%92%D1%96%D0%B4%D1%81%D0%BE%D1%82%D0%BA%D0%"
+                       "BE%D0%B2%D0%B5_%D0%BA%D0%BE%D0%B4%D1%83%D0%B2%D0%B0%"
+                       "D0%BD%D0%BD%D1%8F&veaction=edit&section=0";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_false(ret);
+               assert_null(queryp);
+               assert_true(len == 0);
+       }
+       /* invalid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_false(ret);
+               assert_null(queryp);
+               assert_true(len == 0);
+       }
+       /* invalid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_false(ret);
+               assert_null(queryp);
+               assert_true(len == 0);
+       }
+       /* invalid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?dns&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_false(ret);
+               assert_null(queryp);
+               assert_true(len == 0);
+       }
+       /* invalid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?dns=&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_false(ret);
+               assert_null(queryp);
+               assert_true(len == 0);
+       }
+       /* invalid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?dns=123&&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_false(ret);
+               assert_null(queryp);
+               assert_true(len == 0);
+       }
+       /* valid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?dns=123%12&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_true(ret);
+               assert_non_null(queryp);
+               assert_true(len > 0);
+               assert_true(len == 6);
+               assert_true(memcmp(queryp, "123%12", 6) == 0);
+       }
+       /* invalid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?dns=123%ZZ&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_false(ret);
+               assert_null(queryp);
+               assert_true(len == 0);
+       }
+       /* invalid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?dns=123%%&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_false(ret);
+               assert_null(queryp);
+               assert_true(len == 0);
+       }
+       /* invalid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?dns=123%AZ&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_false(ret);
+               assert_null(queryp);
+               assert_true(len == 0);
+       }
+       /* valid */
+       {
+               bool ret;
+               const char *queryp = NULL;
+               size_t len = 0;
+               char str[] = "?dns=123%0AZ&";
+
+               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               assert_true(ret);
+               assert_non_null(queryp);
+               assert_true(len > 0);
+               assert_true(len == 7);
+               assert_true(memcmp(queryp, "123%0AZ", 7) == 0);
+       }
+}
+
+static void
+doh_base64url_to_base64(void **state) {
+       UNUSED(state);
+       char *res;
+       size_t res_len = 0;
+       /* valid */
+       {
+               char test[] = "YW55IGNhcm5hbCBwbGVhc3VyZS4";
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhc3VyZS4=";
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char test[] = "YW55IGNhcm5hbCBwbGVhcw";
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhcw==";
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char test[] = "YW55IGNhcm5hbCBwbGVhc3Vy";
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhc3Vy";
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char test[] = "YW55IGNhcm5hbCBwbGVhc3U";
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhc3U=";
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char test[] = "YW55IGNhcm5hbCBwbGVhcw";
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhcw==";
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char test[] = "PDw_Pz8-Pg";
+               char res_test[] = "PDw/Pz8+Pg==";
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char test[] = "PDw_Pz8-Pg";
+               char res_test[] = "PDw/Pz8+Pg==";
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 NULL);
+               assert_non_null(res);
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* invalid */
+       {
+               char test[] = "YW55IGNhcm5hbCBwbGVhcw";
+               res_len = 0;
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, 0, &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+       /* invalid */
+       {
+               char test[] = "";
+               res_len = 0;
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+       /* invalid */
+       {
+               char test[] = "PDw_Pz8-Pg==";
+               res_len = 0;
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+       /* invalid */
+       {
+               char test[] = "PDw_Pz8-Pg%3D%3D"; /* percent encoded "==" at the
+                                                    end */
+               res_len = 0;
+
+               res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+       /* invalid */
+       {
+               res_len = 0;
+
+               res = isc__nm_base64url_to_base64(test_mctx, NULL, 31231,
+                                                 &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+}
+
+static void
+doh_base64_to_base64url(void **state) {
+       char *res;
+       size_t res_len = 0;
+       UNUSED(state);
+       /* valid */
+       {
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhc3VyZS4";
+               char test[] = "YW55IGNhcm5hbCBwbGVhc3VyZS4=";
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhcw";
+               char test[] = "YW55IGNhcm5hbCBwbGVhcw==";
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhc3Vy";
+               char test[] = "YW55IGNhcm5hbCBwbGVhc3Vy";
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhc3U";
+               char test[] = "YW55IGNhcm5hbCBwbGVhc3U=";
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char res_test[] = "YW55IGNhcm5hbCBwbGVhcw";
+               char test[] = "YW55IGNhcm5hbCBwbGVhcw==";
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char res_test[] = "PDw_Pz8-Pg";
+               char test[] = "PDw/Pz8+Pg==";
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_non_null(res);
+               assert_true(res_len == strlen(res_test));
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* valid */
+       {
+               char res_test[] = "PDw_Pz8-Pg";
+               char test[] = "PDw/Pz8+Pg==";
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 NULL);
+               assert_non_null(res);
+               assert_true(strcmp(res, res_test) == 0);
+               isc_mem_free(test_mctx, res);
+       }
+       /* invalid */
+       {
+               char test[] = "YW55IGNhcm5hbCBwbGVhcw";
+               res_len = 0;
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, 0, &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+       /* invalid */
+       {
+               char test[] = "";
+               res_len = 0;
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+       /* invalid */
+       {
+               char test[] = "PDw_Pz8-Pg==";
+               res_len = 0;
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+       /* invalid */
+       {
+               char test[] = "PDw_Pz8-Pg%3D%3D"; /* percent encoded "==" at the
+                                                    end */
+               res_len = 0;
+
+               res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test),
+                                                 &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+       /* invalid */
+       {
+               res_len = 0;
+
+               res = isc__nm_base64_to_base64url(test_mctx, NULL, 31231,
+                                                 &res_len);
+               assert_null(res);
+               assert_true(res_len == 0);
+       }
+}
+/*
+static char wikipedia_org_A[] = { 0xae, 0x35, 0x01, 0x00, 0x00, 0x01, 0x00,
+                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x77,
+                                 0x69, 0x6b, 0x69, 0x70, 0x65, 0x64, 0x69,
+                                 0x61, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00,
+                                 0x01, 0x00, 0x01 };
+
+static void
+doh_print_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult,
+                  isc_region_t *region, void *cbarg) {
+       assert_non_null(handle);
+       UNUSED(cbarg);
+       UNUSED(region);
+
+       puts("Cloudflare DNS query result:");
+       if (eresult == ISC_R_SUCCESS) {
+               puts("success!");
+               printf("Response size: %lu\n", region->length);
+               atomic_fetch_add(&creads, 1);
+               isc_nm_resumeread(handle);
+       } else {
+               puts("failure!");
+               atomic_store(&was_error, true);
+               isc_nm_cancelread(handle);
+       }
+}
+
+static void
+doh_cloudflare(void **state) {
+       isc_nm_t **nm = (isc_nm_t **)*state;
+       isc_result_t result = ISC_R_SUCCESS;
+
+       result = isc_nm_http_connect_send_request(
+               nm[0], "https://cloudflare-dns.com/dns-query",
+               atomic_load(&POST),
+               &(isc_region_t){ .base = (uint8_t *)wikipedia_org_A,
+                                .length = sizeof(wikipedia_org_A) },
+               doh_print_reply_cb, NULL, NULL, 5000);
+
+       assert_int_equal(result, ISC_R_SUCCESS);
+
+       while (atomic_load(&creads) != 1 && atomic_load(&was_error) == false) {
+               isc_thread_yield();
+       }
+
+       isc_nm_closedown(nm[0]);
+}
+
+static void
+doh_cloudflare_POST(void **state) {
+       atomic_store(&POST, true);
+       doh_cloudflare(state);
+}
+
+static void
+doh_cloudflare_GET(void **state) {
+       atomic_store(&POST, false);
+       doh_cloudflare(state);
+}
+*/
+int
+main(void) {
+       const struct CMUnitTest tests[] = {
+               cmocka_unit_test_setup_teardown(doh_parse_GET_query_string,
+                                               NULL, NULL),
+               cmocka_unit_test_setup_teardown(doh_base64url_to_base64, NULL,
+                                               NULL),
+               cmocka_unit_test_setup_teardown(doh_base64_to_base64url, NULL,
+                                               NULL),
+               cmocka_unit_test_setup_teardown(doh_noop_POST, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_noop_GET, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_noresponse_POST, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_noresponse_GET, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_one_POST, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_one_GET, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_one_POST_TLS, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_one_GET_TLS, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_two_POST, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_two_GET, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_two_POST_TLS, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_two_GET_TLS, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_send_GET, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_send_POST, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_send_GET_TLS, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_send_POST_TLS,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_half_send_GET,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_half_send_POST,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_half_send_GET_TLS,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_recv_half_send_POST_TLS,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_half_recv_send_GET,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_half_recv_send_POST,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_half_recv_send_GET_TLS,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_half_recv_send_POST_TLS,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_half_recv_half_send_GET,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_half_recv_half_send_POST,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_half_recv_half_send_GET_TLS,
+                                               nm_setup, nm_teardown),
+               cmocka_unit_test_setup_teardown(
+                       doh_half_recv_half_send_POST_TLS, nm_setup,
+                       nm_teardown),
+               /*cmocka_unit_test_setup_teardown(doh_cloudflare_GET, nm_setup,
+                                               nm_teardown),
+               cmocka_unit_test_setup_teardown(doh_cloudflare_POST, nm_setup,
+               nm_teardown)*/
+       };
+
+       return (cmocka_run_group_tests(tests, _setup, _teardown));
+}
+
+#else /* HAVE_CMOCKA */
+
+#include <stdio.h>
+
+int
+main(void) {
+       printf("1..0 # Skipped: cmocka not available\n");
+       return (0);
+}
+
+#endif /* if HAVE_CMOCKA */
diff --git a/lib/isc/tests/tls_test_cert_key.h b/lib/isc/tests/tls_test_cert_key.h
new file mode 100644 (file)
index 0000000..898b77b
--- /dev/null
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/* THIS FILE IS AUTOMATICALLY GENERATED!!! DO NOT EDIT!!! */
+/* Generated on: [ Tue Dec 29 17:58:04 2020 ], from file: test_cert.pem */
+
+#ifndef _TLS_TEST_CERT_H
+#define _TLS_TEST_CERT_H
+
+static const unsigned char TLS_test_cert[] = {
+       0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43,
+       0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d,
+       0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x46, 0x69, 0x54, 0x43, 0x43,
+       0x41, 0x33, 0x47, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x55,
+       0x66, 0x67, 0x2f, 0x48, 0x6f, 0x43, 0x6c, 0x42, 0x43, 0x67, 0x6e, 0x64,
+       0x50, 0x32, 0x30, 0x56, 0x47, 0x2b, 0x30, 0x4c, 0x63, 0x4b, 0x58, 0x56,
+       0x69, 0x66, 0x55, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49,
+       0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x4c, 0x0a, 0x42, 0x51, 0x41,
+       0x77, 0x55, 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55,
+       0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x55, 0x45, 0x78, 0x45, 0x44, 0x41,
+       0x4f, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, 0x4d, 0x42, 0x31, 0x56,
+       0x72, 0x63, 0x6d, 0x46, 0x70, 0x62, 0x6d, 0x55, 0x78, 0x45, 0x44, 0x41,
+       0x4f, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x4d, 0x42, 0x30, 0x74,
+       0x6f, 0x0a, 0x59, 0x58, 0x4a, 0x72, 0x61, 0x58, 0x59, 0x78, 0x44, 0x44,
+       0x41, 0x4b, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x4d, 0x41, 0x30,
+       0x6c, 0x54, 0x51, 0x7a, 0x45, 0x53, 0x4d, 0x42, 0x41, 0x47, 0x41, 0x31,
+       0x55, 0x45, 0x41, 0x77, 0x77, 0x4a, 0x62, 0x47, 0x39, 0x6a, 0x59, 0x57,
+       0x78, 0x6f, 0x62, 0x33, 0x4e, 0x30, 0x4d, 0x43, 0x41, 0x58, 0x44, 0x54,
+       0x49, 0x77, 0x4d, 0x54, 0x49, 0x79, 0x0a, 0x4f, 0x54, 0x45, 0x31, 0x4e,
+       0x54, 0x51, 0x79, 0x4d, 0x31, 0x6f, 0x59, 0x44, 0x7a, 0x49, 0x78, 0x4d,
+       0x6a, 0x41, 0x78, 0x4d, 0x6a, 0x41, 0x31, 0x4d, 0x54, 0x55, 0x31, 0x4e,
+       0x44, 0x49, 0x7a, 0x57, 0x6a, 0x42, 0x54, 0x4d, 0x51, 0x73, 0x77, 0x43,
+       0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x51,
+       0x54, 0x45, 0x51, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 0x45, 0x0a,
+       0x43, 0x41, 0x77, 0x48, 0x56, 0x57, 0x74, 0x79, 0x59, 0x57, 0x6c, 0x75,
+       0x5a, 0x54, 0x45, 0x51, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 0x45,
+       0x42, 0x77, 0x77, 0x48, 0x53, 0x32, 0x68, 0x68, 0x63, 0x6d, 0x74, 0x70,
+       0x64, 0x6a, 0x45, 0x4d, 0x4d, 0x41, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45,
+       0x43, 0x67, 0x77, 0x44, 0x53, 0x56, 0x4e, 0x44, 0x4d, 0x52, 0x49, 0x77,
+       0x45, 0x41, 0x59, 0x44, 0x0a, 0x56, 0x51, 0x51, 0x44, 0x44, 0x41, 0x6c,
+       0x73, 0x62, 0x32, 0x4e, 0x68, 0x62, 0x47, 0x68, 0x76, 0x63, 0x33, 0x51,
+       0x77, 0x67, 0x67, 0x49, 0x69, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71,
+       0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55,
+       0x41, 0x41, 0x34, 0x49, 0x43, 0x44, 0x77, 0x41, 0x77, 0x67, 0x67, 0x49,
+       0x4b, 0x41, 0x6f, 0x49, 0x43, 0x41, 0x51, 0x43, 0x71, 0x0a, 0x74, 0x49,
+       0x42, 0x33, 0x43, 0x4c, 0x75, 0x71, 0x52, 0x54, 0x6d, 0x67, 0x39, 0x4d,
+       0x45, 0x56, 0x69, 0x70, 0x30, 0x63, 0x43, 0x45, 0x2f, 0x33, 0x68, 0x47,
+       0x64, 0x63, 0x53, 0x61, 0x51, 0x4f, 0x64, 0x6b, 0x39, 0x42, 0x5a, 0x41,
+       0x53, 0x79, 0x4f, 0x4a, 0x35, 0x4c, 0x42, 0x38, 0x2f, 0x63, 0x2f, 0x48,
+       0x30, 0x38, 0x4c, 0x30, 0x63, 0x4c, 0x79, 0x43, 0x4c, 0x6b, 0x31, 0x70,
+       0x33, 0x78, 0x0a, 0x67, 0x56, 0x2b, 0x53, 0x57, 0x2f, 0x6d, 0x79, 0x38,
+       0x65, 0x6b, 0x6f, 0x4d, 0x4a, 0x6a, 0x70, 0x4e, 0x54, 0x37, 0x70, 0x68,
+       0x74, 0x78, 0x4a, 0x4e, 0x4e, 0x6c, 0x4e, 0x36, 0x39, 0x56, 0x63, 0x6e,
+       0x75, 0x71, 0x33, 0x41, 0x45, 0x44, 0x4a, 0x63, 0x41, 0x41, 0x4c, 0x6f,
+       0x38, 0x2b, 0x63, 0x65, 0x33, 0x6d, 0x4b, 0x35, 0x77, 0x41, 0x4d, 0x73,
+       0x7a, 0x61, 0x52, 0x7a, 0x33, 0x44, 0x34, 0x0a, 0x5a, 0x4e, 0x66, 0x65,
+       0x54, 0x33, 0x61, 0x6b, 0x39, 0x45, 0x78, 0x74, 0x47, 0x62, 0x57, 0x42,
+       0x4f, 0x6d, 0x32, 0x4f, 0x66, 0x68, 0x49, 0x2b, 0x45, 0x53, 0x6d, 0x32,
+       0x55, 0x41, 0x32, 0x34, 0x61, 0x49, 0x6a, 0x46, 0x74, 0x4a, 0x52, 0x72,
+       0x6d, 0x56, 0x4d, 0x65, 0x37, 0x68, 0x44, 0x4b, 0x54, 0x78, 0x68, 0x49,
+       0x69, 0x54, 0x6a, 0x4c, 0x54, 0x76, 0x56, 0x4e, 0x50, 0x2b, 0x77, 0x53,
+       0x0a, 0x43, 0x62, 0x32, 0x47, 0x59, 0x34, 0x74, 0x7a, 0x7a, 0x30, 0x66,
+       0x66, 0x79, 0x62, 0x50, 0x6d, 0x65, 0x5a, 0x50, 0x58, 0x61, 0x32, 0x54,
+       0x39, 0x77, 0x65, 0x30, 0x68, 0x6a, 0x41, 0x56, 0x53, 0x4b, 0x47, 0x76,
+       0x76, 0x30, 0x48, 0x41, 0x48, 0x65, 0x4a, 0x72, 0x4f, 0x75, 0x31, 0x75,
+       0x38, 0x79, 0x4a, 0x4c, 0x6e, 0x58, 0x72, 0x64, 0x47, 0x2f, 0x4f, 0x7a,
+       0x4b, 0x71, 0x65, 0x4a, 0x38, 0x0a, 0x7a, 0x74, 0x4a, 0x34, 0x47, 0x59,
+       0x58, 0x7a, 0x70, 0x4b, 0x39, 0x42, 0x68, 0x74, 0x57, 0x6b, 0x6d, 0x7a,
+       0x38, 0x74, 0x41, 0x78, 0x7a, 0x77, 0x6c, 0x5a, 0x47, 0x6d, 0x54, 0x41,
+       0x79, 0x58, 0x45, 0x55, 0x50, 0x79, 0x33, 0x42, 0x71, 0x61, 0x4a, 0x66,
+       0x2b, 0x6c, 0x46, 0x4e, 0x67, 0x61, 0x68, 0x74, 0x57, 0x78, 0x6c, 0x77,
+       0x58, 0x2f, 0x31, 0x59, 0x4c, 0x35, 0x75, 0x69, 0x69, 0x49, 0x0a, 0x77,
+       0x4c, 0x69, 0x41, 0x36, 0x67, 0x2f, 0x39, 0x39, 0x79, 0x4e, 0x4c, 0x41,
+       0x69, 0x34, 0x38, 0x54, 0x6b, 0x41, 0x63, 0x44, 0x58, 0x42, 0x35, 0x38,
+       0x61, 0x5a, 0x78, 0x36, 0x32, 0x43, 0x63, 0x6b, 0x58, 0x75, 0x4d, 0x6d,
+       0x32, 0x68, 0x61, 0x37, 0x6e, 0x43, 0x64, 0x77, 0x66, 0x76, 0x31, 0x71,
+       0x71, 0x6a, 0x72, 0x79, 0x44, 0x56, 0x34, 0x32, 0x2b, 0x56, 0x37, 0x48,
+       0x75, 0x64, 0x4f, 0x0a, 0x57, 0x36, 0x33, 0x39, 0x6f, 0x6b, 0x6a, 0x69,
+       0x56, 0x32, 0x33, 0x69, 0x48, 0x79, 0x2f, 0x33, 0x2f, 0x2f, 0x72, 0x56,
+       0x31, 0x44, 0x59, 0x6a, 0x76, 0x35, 0x49, 0x4c, 0x48, 0x51, 0x79, 0x6d,
+       0x7a, 0x4a, 0x2b, 0x76, 0x6e, 0x4a, 0x38, 0x4a, 0x69, 0x33, 0x59, 0x74,
+       0x32, 0x66, 0x44, 0x44, 0x55, 0x58, 0x66, 0x33, 0x41, 0x45, 0x6e, 0x49,
+       0x32, 0x74, 0x54, 0x75, 0x4c, 0x34, 0x54, 0x42, 0x0a, 0x4b, 0x54, 0x69,
+       0x74, 0x33, 0x79, 0x79, 0x69, 0x54, 0x56, 0x52, 0x4e, 0x45, 0x59, 0x39,
+       0x63, 0x4b, 0x47, 0x54, 0x6a, 0x73, 0x41, 0x7a, 0x48, 0x44, 0x7a, 0x35,
+       0x33, 0x6b, 0x74, 0x6b, 0x38, 0x2b, 0x77, 0x44, 0x73, 0x49, 0x71, 0x76,
+       0x4d, 0x54, 0x6d, 0x79, 0x33, 0x72, 0x65, 0x6d, 0x36, 0x68, 0x4b, 0x30,
+       0x71, 0x51, 0x30, 0x48, 0x38, 0x50, 0x4f, 0x77, 0x57, 0x66, 0x31, 0x75,
+       0x6c, 0x0a, 0x55, 0x71, 0x46, 0x67, 0x37, 0x34, 0x77, 0x67, 0x62, 0x61,
+       0x32, 0x73, 0x71, 0x37, 0x56, 0x78, 0x61, 0x62, 0x63, 0x32, 0x71, 0x65,
+       0x4b, 0x71, 0x74, 0x59, 0x4c, 0x59, 0x30, 0x4e, 0x6b, 0x51, 0x71, 0x44,
+       0x67, 0x73, 0x42, 0x30, 0x55, 0x56, 0x7a, 0x50, 0x70, 0x68, 0x53, 0x56,
+       0x5a, 0x51, 0x65, 0x49, 0x49, 0x41, 0x4b, 0x6c, 0x6f, 0x7a, 0x6a, 0x49,
+       0x43, 0x7a, 0x39, 0x75, 0x48, 0x47, 0x0a, 0x39, 0x48, 0x31, 0x36, 0x51,
+       0x52, 0x45, 0x74, 0x43, 0x4d, 0x78, 0x37, 0x42, 0x46, 0x77, 0x48, 0x74,
+       0x36, 0x49, 0x31, 0x2b, 0x70, 0x4a, 0x69, 0x50, 0x50, 0x47, 0x69, 0x78,
+       0x66, 0x42, 0x47, 0x74, 0x72, 0x64, 0x4a, 0x51, 0x78, 0x39, 0x34, 0x62,
+       0x77, 0x76, 0x43, 0x6f, 0x38, 0x59, 0x4a, 0x71, 0x57, 0x77, 0x67, 0x66,
+       0x41, 0x77, 0x50, 0x30, 0x57, 0x45, 0x6c, 0x6f, 0x6b, 0x69, 0x6b, 0x0a,
+       0x53, 0x6a, 0x6a, 0x6b, 0x79, 0x62, 0x33, 0x69, 0x53, 0x48, 0x6c, 0x65,
+       0x54, 0x52, 0x36, 0x62, 0x7a, 0x48, 0x47, 0x73, 0x69, 0x78, 0x49, 0x38,
+       0x72, 0x76, 0x44, 0x65, 0x33, 0x74, 0x76, 0x63, 0x79, 0x44, 0x4e, 0x46,
+       0x2f, 0x78, 0x34, 0x68, 0x70, 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42,
+       0x6f, 0x31, 0x4d, 0x77, 0x55, 0x54, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56,
+       0x48, 0x51, 0x34, 0x45, 0x0a, 0x46, 0x67, 0x51, 0x55, 0x68, 0x6c, 0x51,
+       0x77, 0x37, 0x49, 0x54, 0x2b, 0x38, 0x59, 0x6e, 0x5a, 0x68, 0x6e, 0x62,
+       0x6d, 0x38, 0x62, 0x32, 0x64, 0x6e, 0x4d, 0x48, 0x6e, 0x69, 0x76, 0x6b,
+       0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x6a, 0x42, 0x42, 0x67,
+       0x77, 0x46, 0x6f, 0x41, 0x55, 0x68, 0x6c, 0x51, 0x77, 0x37, 0x49, 0x54,
+       0x2b, 0x38, 0x59, 0x6e, 0x5a, 0x68, 0x6e, 0x62, 0x6d, 0x0a, 0x38, 0x62,
+       0x32, 0x64, 0x6e, 0x4d, 0x48, 0x6e, 0x69, 0x76, 0x6b, 0x77, 0x44, 0x77,
+       0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41,
+       0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x4e, 0x42, 0x67,
+       0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51,
+       0x73, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, 0x45, 0x41, 0x49, 0x59,
+       0x36, 0x6e, 0x0a, 0x74, 0x54, 0x36, 0x54, 0x5a, 0x59, 0x2b, 0x4b, 0x6b,
+       0x56, 0x7a, 0x34, 0x67, 0x31, 0x56, 0x4c, 0x50, 0x6d, 0x44, 0x72, 0x4e,
+       0x6e, 0x6a, 0x65, 0x58, 0x59, 0x61, 0x35, 0x59, 0x42, 0x4d, 0x58, 0x4a,
+       0x4f, 0x77, 0x61, 0x56, 0x56, 0x44, 0x2b, 0x6e, 0x6d, 0x42, 0x64, 0x51,
+       0x51, 0x53, 0x67, 0x41, 0x2f, 0x30, 0x37, 0x41, 0x61, 0x63, 0x68, 0x71,
+       0x61, 0x2f, 0x52, 0x77, 0x54, 0x31, 0x61, 0x0a, 0x2b, 0x7a, 0x37, 0x45,
+       0x2f, 0x4b, 0x56, 0x34, 0x78, 0x76, 0x43, 0x55, 0x4d, 0x6a, 0x64, 0x64,
+       0x4f, 0x64, 0x36, 0x48, 0x77, 0x51, 0x35, 0x58, 0x32, 0x2b, 0x7a, 0x6e,
+       0x46, 0x41, 0x71, 0x73, 0x48, 0x75, 0x5a, 0x41, 0x46, 0x63, 0x36, 0x37,
+       0x36, 0x47, 0x4d, 0x77, 0x75, 0x73, 0x37, 0x45, 0x69, 0x30, 0x53, 0x4f,
+       0x41, 0x41, 0x36, 0x44, 0x30, 0x6f, 0x63, 0x52, 0x72, 0x2f, 0x68, 0x4c,
+       0x0a, 0x62, 0x6c, 0x4f, 0x4b, 0x70, 0x48, 0x79, 0x66, 0x72, 0x46, 0x76,
+       0x42, 0x70, 0x5a, 0x6a, 0x6e, 0x4f, 0x49, 0x33, 0x47, 0x68, 0x37, 0x45,
+       0x32, 0x49, 0x57, 0x71, 0x6f, 0x62, 0x7a, 0x73, 0x35, 0x62, 0x58, 0x5a,
+       0x45, 0x6a, 0x34, 0x47, 0x47, 0x58, 0x33, 0x30, 0x39, 0x45, 0x2f, 0x39,
+       0x38, 0x6e, 0x33, 0x47, 0x35, 0x46, 0x68, 0x2b, 0x78, 0x31, 0x7a, 0x61,
+       0x59, 0x38, 0x79, 0x78, 0x4f, 0x0a, 0x56, 0x62, 0x4a, 0x31, 0x50, 0x61,
+       0x4b, 0x64, 0x30, 0x4f, 0x36, 0x4a, 0x45, 0x6b, 0x72, 0x36, 0x6f, 0x36,
+       0x6e, 0x61, 0x48, 0x33, 0x44, 0x45, 0x33, 0x53, 0x68, 0x53, 0x68, 0x54,
+       0x30, 0x6b, 0x36, 0x63, 0x53, 0x68, 0x5a, 0x38, 0x2b, 0x74, 0x6f, 0x35,
+       0x64, 0x47, 0x38, 0x6b, 0x2b, 0x6f, 0x72, 0x69, 0x39, 0x41, 0x44, 0x52,
+       0x52, 0x48, 0x4f, 0x62, 0x71, 0x51, 0x54, 0x42, 0x4a, 0x46, 0x0a, 0x6e,
+       0x67, 0x35, 0x4f, 0x72, 0x6f, 0x46, 0x2f, 0x31, 0x34, 0x75, 0x73, 0x65,
+       0x2b, 0x49, 0x61, 0x42, 0x32, 0x79, 0x41, 0x57, 0x30, 0x68, 0x44, 0x37,
+       0x43, 0x2f, 0x79, 0x31, 0x2f, 0x56, 0x38, 0x41, 0x78, 0x35, 0x77, 0x31,
+       0x38, 0x59, 0x51, 0x36, 0x6e, 0x4d, 0x44, 0x7a, 0x51, 0x56, 0x64, 0x77,
+       0x63, 0x59, 0x69, 0x4c, 0x77, 0x54, 0x42, 0x31, 0x66, 0x4b, 0x30, 0x33,
+       0x4f, 0x55, 0x36, 0x0a, 0x31, 0x72, 0x32, 0x59, 0x62, 0x47, 0x4b, 0x49,
+       0x62, 0x43, 0x59, 0x63, 0x4e, 0x76, 0x59, 0x72, 0x4a, 0x2b, 0x61, 0x38,
+       0x74, 0x59, 0x56, 0x48, 0x58, 0x51, 0x68, 0x2f, 0x78, 0x52, 0x61, 0x74,
+       0x79, 0x61, 0x4e, 0x69, 0x41, 0x59, 0x34, 0x77, 0x6b, 0x58, 0x36, 0x49,
+       0x32, 0x79, 0x43, 0x66, 0x69, 0x35, 0x61, 0x6d, 0x32, 0x69, 0x4f, 0x42,
+       0x50, 0x51, 0x72, 0x36, 0x6f, 0x38, 0x58, 0x58, 0x0a, 0x2b, 0x44, 0x69,
+       0x35, 0x4a, 0x4e, 0x58, 0x59, 0x55, 0x2f, 0x76, 0x76, 0x59, 0x53, 0x4d,
+       0x35, 0x51, 0x61, 0x79, 0x6f, 0x35, 0x78, 0x7a, 0x4c, 0x63, 0x4d, 0x7a,
+       0x76, 0x57, 0x77, 0x4b, 0x70, 0x4d, 0x6c, 0x4c, 0x33, 0x6c, 0x6f, 0x30,
+       0x48, 0x34, 0x53, 0x70, 0x54, 0x6e, 0x75, 0x66, 0x55, 0x63, 0x78, 0x39,
+       0x6f, 0x72, 0x2f, 0x75, 0x5a, 0x6e, 0x44, 0x51, 0x45, 0x30, 0x43, 0x2f,
+       0x2f, 0x0a, 0x6d, 0x6c, 0x44, 0x52, 0x67, 0x2f, 0x73, 0x48, 0x6d, 0x65,
+       0x62, 0x37, 0x6f, 0x56, 0x79, 0x65, 0x72, 0x39, 0x30, 0x33, 0x64, 0x45,
+       0x74, 0x6b, 0x73, 0x79, 0x32, 0x7a, 0x74, 0x31, 0x6b, 0x45, 0x4b, 0x45,
+       0x78, 0x77, 0x41, 0x51, 0x75, 0x73, 0x57, 0x74, 0x63, 0x77, 0x4c, 0x7a,
+       0x66, 0x71, 0x45, 0x30, 0x32, 0x6f, 0x51, 0x78, 0x56, 0x74, 0x51, 0x71,
+       0x73, 0x66, 0x47, 0x78, 0x30, 0x4c, 0x0a, 0x43, 0x75, 0x6a, 0x51, 0x65,
+       0x5a, 0x56, 0x76, 0x42, 0x30, 0x77, 0x6c, 0x63, 0x33, 0x33, 0x33, 0x4d,
+       0x31, 0x32, 0x4d, 0x41, 0x69, 0x44, 0x75, 0x6c, 0x71, 0x2b, 0x73, 0x58,
+       0x4d, 0x70, 0x4c, 0x53, 0x2b, 0x47, 0x79, 0x39, 0x57, 0x34, 0x34, 0x42,
+       0x55, 0x45, 0x74, 0x46, 0x4f, 0x77, 0x51, 0x2f, 0x58, 0x35, 0x57, 0x32,
+       0x58, 0x77, 0x4d, 0x53, 0x45, 0x42, 0x57, 0x39, 0x49, 0x66, 0x6f, 0x0a,
+       0x58, 0x4d, 0x6d, 0x4f, 0x37, 0x51, 0x69, 0x35, 0x59, 0x65, 0x79, 0x7a,
+       0x46, 0x47, 0x78, 0x31, 0x36, 0x6e, 0x59, 0x6f, 0x35, 0x61, 0x45, 0x6e,
+       0x4c, 0x2b, 0x7a, 0x6d, 0x72, 0x64, 0x39, 0x43, 0x36, 0x36, 0x61, 0x51,
+       0x76, 0x6b, 0x42, 0x66, 0x5a, 0x2f, 0x6e, 0x50, 0x70, 0x76, 0x55, 0x63,
+       0x53, 0x57, 0x69, 0x34, 0x73, 0x45, 0x47, 0x44, 0x69, 0x6a, 0x43, 0x4c,
+       0x65, 0x4b, 0x43, 0x59, 0x0a, 0x36, 0x4c, 0x34, 0x6e, 0x74, 0x4e, 0x72,
+       0x6e, 0x7a, 0x62, 0x79, 0x38, 0x70, 0x70, 0x71, 0x50, 0x64, 0x66, 0x79,
+       0x58, 0x4e, 0x67, 0x4e, 0x35, 0x34, 0x6e, 0x4d, 0x4e, 0x77, 0x55, 0x78,
+       0x66, 0x47, 0x33, 0x4a, 0x41, 0x69, 0x55, 0x55, 0x3d, 0x0a, 0x2d, 0x2d,
+       0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49,
+       0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
+};
+
+/* THIS FILE IS AUTOMATICALLY GENERATED!!! DO NOT EDIT!!! */
+/* Generated on: [ Tue Dec 29 17:58:52 2020 ], from file: test_key.pem */
+
+static const unsigned char TLS_test_key[] = {
+       0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
+       0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d,
+       0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x4a, 0x51, 0x67, 0x49, 0x42,
+       0x41, 0x44, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47,
+       0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x53, 0x43,
+       0x43, 0x53, 0x77, 0x77, 0x67, 0x67, 0x6b, 0x6f, 0x41, 0x67, 0x45, 0x41,
+       0x41, 0x6f, 0x49, 0x43, 0x41, 0x51, 0x43, 0x71, 0x74, 0x49, 0x42, 0x33,
+       0x43, 0x4c, 0x75, 0x71, 0x52, 0x54, 0x6d, 0x67, 0x0a, 0x39, 0x4d, 0x45,
+       0x56, 0x69, 0x70, 0x30, 0x63, 0x43, 0x45, 0x2f, 0x33, 0x68, 0x47, 0x64,
+       0x63, 0x53, 0x61, 0x51, 0x4f, 0x64, 0x6b, 0x39, 0x42, 0x5a, 0x41, 0x53,
+       0x79, 0x4f, 0x4a, 0x35, 0x4c, 0x42, 0x38, 0x2f, 0x63, 0x2f, 0x48, 0x30,
+       0x38, 0x4c, 0x30, 0x63, 0x4c, 0x79, 0x43, 0x4c, 0x6b, 0x31, 0x70, 0x33,
+       0x78, 0x67, 0x56, 0x2b, 0x53, 0x57, 0x2f, 0x6d, 0x79, 0x38, 0x65, 0x6b,
+       0x6f, 0x0a, 0x4d, 0x4a, 0x6a, 0x70, 0x4e, 0x54, 0x37, 0x70, 0x68, 0x74,
+       0x78, 0x4a, 0x4e, 0x4e, 0x6c, 0x4e, 0x36, 0x39, 0x56, 0x63, 0x6e, 0x75,
+       0x71, 0x33, 0x41, 0x45, 0x44, 0x4a, 0x63, 0x41, 0x41, 0x4c, 0x6f, 0x38,
+       0x2b, 0x63, 0x65, 0x33, 0x6d, 0x4b, 0x35, 0x77, 0x41, 0x4d, 0x73, 0x7a,
+       0x61, 0x52, 0x7a, 0x33, 0x44, 0x34, 0x5a, 0x4e, 0x66, 0x65, 0x54, 0x33,
+       0x61, 0x6b, 0x39, 0x45, 0x78, 0x74, 0x0a, 0x47, 0x62, 0x57, 0x42, 0x4f,
+       0x6d, 0x32, 0x4f, 0x66, 0x68, 0x49, 0x2b, 0x45, 0x53, 0x6d, 0x32, 0x55,
+       0x41, 0x32, 0x34, 0x61, 0x49, 0x6a, 0x46, 0x74, 0x4a, 0x52, 0x72, 0x6d,
+       0x56, 0x4d, 0x65, 0x37, 0x68, 0x44, 0x4b, 0x54, 0x78, 0x68, 0x49, 0x69,
+       0x54, 0x6a, 0x4c, 0x54, 0x76, 0x56, 0x4e, 0x50, 0x2b, 0x77, 0x53, 0x43,
+       0x62, 0x32, 0x47, 0x59, 0x34, 0x74, 0x7a, 0x7a, 0x30, 0x66, 0x66, 0x0a,
+       0x79, 0x62, 0x50, 0x6d, 0x65, 0x5a, 0x50, 0x58, 0x61, 0x32, 0x54, 0x39,
+       0x77, 0x65, 0x30, 0x68, 0x6a, 0x41, 0x56, 0x53, 0x4b, 0x47, 0x76, 0x76,
+       0x30, 0x48, 0x41, 0x48, 0x65, 0x4a, 0x72, 0x4f, 0x75, 0x31, 0x75, 0x38,
+       0x79, 0x4a, 0x4c, 0x6e, 0x58, 0x72, 0x64, 0x47, 0x2f, 0x4f, 0x7a, 0x4b,
+       0x71, 0x65, 0x4a, 0x38, 0x7a, 0x74, 0x4a, 0x34, 0x47, 0x59, 0x58, 0x7a,
+       0x70, 0x4b, 0x39, 0x42, 0x0a, 0x68, 0x74, 0x57, 0x6b, 0x6d, 0x7a, 0x38,
+       0x74, 0x41, 0x78, 0x7a, 0x77, 0x6c, 0x5a, 0x47, 0x6d, 0x54, 0x41, 0x79,
+       0x58, 0x45, 0x55, 0x50, 0x79, 0x33, 0x42, 0x71, 0x61, 0x4a, 0x66, 0x2b,
+       0x6c, 0x46, 0x4e, 0x67, 0x61, 0x68, 0x74, 0x57, 0x78, 0x6c, 0x77, 0x58,
+       0x2f, 0x31, 0x59, 0x4c, 0x35, 0x75, 0x69, 0x69, 0x49, 0x77, 0x4c, 0x69,
+       0x41, 0x36, 0x67, 0x2f, 0x39, 0x39, 0x79, 0x4e, 0x4c, 0x0a, 0x41, 0x69,
+       0x34, 0x38, 0x54, 0x6b, 0x41, 0x63, 0x44, 0x58, 0x42, 0x35, 0x38, 0x61,
+       0x5a, 0x78, 0x36, 0x32, 0x43, 0x63, 0x6b, 0x58, 0x75, 0x4d, 0x6d, 0x32,
+       0x68, 0x61, 0x37, 0x6e, 0x43, 0x64, 0x77, 0x66, 0x76, 0x31, 0x71, 0x71,
+       0x6a, 0x72, 0x79, 0x44, 0x56, 0x34, 0x32, 0x2b, 0x56, 0x37, 0x48, 0x75,
+       0x64, 0x4f, 0x57, 0x36, 0x33, 0x39, 0x6f, 0x6b, 0x6a, 0x69, 0x56, 0x32,
+       0x33, 0x69, 0x0a, 0x48, 0x79, 0x2f, 0x33, 0x2f, 0x2f, 0x72, 0x56, 0x31,
+       0x44, 0x59, 0x6a, 0x76, 0x35, 0x49, 0x4c, 0x48, 0x51, 0x79, 0x6d, 0x7a,
+       0x4a, 0x2b, 0x76, 0x6e, 0x4a, 0x38, 0x4a, 0x69, 0x33, 0x59, 0x74, 0x32,
+       0x66, 0x44, 0x44, 0x55, 0x58, 0x66, 0x33, 0x41, 0x45, 0x6e, 0x49, 0x32,
+       0x74, 0x54, 0x75, 0x4c, 0x34, 0x54, 0x42, 0x4b, 0x54, 0x69, 0x74, 0x33,
+       0x79, 0x79, 0x69, 0x54, 0x56, 0x52, 0x4e, 0x0a, 0x45, 0x59, 0x39, 0x63,
+       0x4b, 0x47, 0x54, 0x6a, 0x73, 0x41, 0x7a, 0x48, 0x44, 0x7a, 0x35, 0x33,
+       0x6b, 0x74, 0x6b, 0x38, 0x2b, 0x77, 0x44, 0x73, 0x49, 0x71, 0x76, 0x4d,
+       0x54, 0x6d, 0x79, 0x33, 0x72, 0x65, 0x6d, 0x36, 0x68, 0x4b, 0x30, 0x71,
+       0x51, 0x30, 0x48, 0x38, 0x50, 0x4f, 0x77, 0x57, 0x66, 0x31, 0x75, 0x6c,
+       0x55, 0x71, 0x46, 0x67, 0x37, 0x34, 0x77, 0x67, 0x62, 0x61, 0x32, 0x73,
+       0x0a, 0x71, 0x37, 0x56, 0x78, 0x61, 0x62, 0x63, 0x32, 0x71, 0x65, 0x4b,
+       0x71, 0x74, 0x59, 0x4c, 0x59, 0x30, 0x4e, 0x6b, 0x51, 0x71, 0x44, 0x67,
+       0x73, 0x42, 0x30, 0x55, 0x56, 0x7a, 0x50, 0x70, 0x68, 0x53, 0x56, 0x5a,
+       0x51, 0x65, 0x49, 0x49, 0x41, 0x4b, 0x6c, 0x6f, 0x7a, 0x6a, 0x49, 0x43,
+       0x7a, 0x39, 0x75, 0x48, 0x47, 0x39, 0x48, 0x31, 0x36, 0x51, 0x52, 0x45,
+       0x74, 0x43, 0x4d, 0x78, 0x37, 0x0a, 0x42, 0x46, 0x77, 0x48, 0x74, 0x36,
+       0x49, 0x31, 0x2b, 0x70, 0x4a, 0x69, 0x50, 0x50, 0x47, 0x69, 0x78, 0x66,
+       0x42, 0x47, 0x74, 0x72, 0x64, 0x4a, 0x51, 0x78, 0x39, 0x34, 0x62, 0x77,
+       0x76, 0x43, 0x6f, 0x38, 0x59, 0x4a, 0x71, 0x57, 0x77, 0x67, 0x66, 0x41,
+       0x77, 0x50, 0x30, 0x57, 0x45, 0x6c, 0x6f, 0x6b, 0x69, 0x6b, 0x53, 0x6a,
+       0x6a, 0x6b, 0x79, 0x62, 0x33, 0x69, 0x53, 0x48, 0x6c, 0x65, 0x0a, 0x54,
+       0x52, 0x36, 0x62, 0x7a, 0x48, 0x47, 0x73, 0x69, 0x78, 0x49, 0x38, 0x72,
+       0x76, 0x44, 0x65, 0x33, 0x74, 0x76, 0x63, 0x79, 0x44, 0x4e, 0x46, 0x2f,
+       0x78, 0x34, 0x68, 0x70, 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x41,
+       0x6f, 0x49, 0x43, 0x41, 0x47, 0x2b, 0x31, 0x56, 0x55, 0x67, 0x51, 0x4c,
+       0x2f, 0x62, 0x5a, 0x2f, 0x44, 0x39, 0x6e, 0x53, 0x35, 0x2b, 0x55, 0x4b,
+       0x51, 0x48, 0x36, 0x0a, 0x4d, 0x70, 0x4a, 0x77, 0x55, 0x38, 0x39, 0x68,
+       0x35, 0x58, 0x6b, 0x4e, 0x56, 0x51, 0x6f, 0x65, 0x73, 0x4c, 0x41, 0x4d,
+       0x4f, 0x78, 0x49, 0x77, 0x6c, 0x34, 0x63, 0x75, 0x74, 0x36, 0x6d, 0x56,
+       0x36, 0x72, 0x45, 0x38, 0x46, 0x42, 0x47, 0x61, 0x6e, 0x4a, 0x73, 0x35,
+       0x4a, 0x56, 0x69, 0x36, 0x31, 0x61, 0x6d, 0x54, 0x67, 0x78, 0x65, 0x6f,
+       0x7a, 0x62, 0x66, 0x32, 0x2f, 0x79, 0x65, 0x45, 0x0a, 0x2b, 0x45, 0x7a,
+       0x7a, 0x78, 0x36, 0x79, 0x6c, 0x51, 0x75, 0x65, 0x73, 0x6d, 0x7a, 0x36,
+       0x4d, 0x62, 0x4e, 0x6b, 0x6c, 0x63, 0x50, 0x49, 0x74, 0x44, 0x61, 0x53,
+       0x43, 0x62, 0x4f, 0x52, 0x49, 0x44, 0x4a, 0x46, 0x44, 0x43, 0x64, 0x66,
+       0x62, 0x58, 0x7a, 0x73, 0x39, 0x69, 0x73, 0x4a, 0x52, 0x54, 0x2f, 0x76,
+       0x63, 0x58, 0x74, 0x4d, 0x61, 0x65, 0x74, 0x75, 0x4a, 0x5a, 0x37, 0x35,
+       0x53, 0x0a, 0x70, 0x41, 0x39, 0x33, 0x33, 0x63, 0x73, 0x50, 0x6b, 0x68,
+       0x72, 0x32, 0x56, 0x57, 0x4c, 0x44, 0x72, 0x45, 0x6a, 0x4a, 0x65, 0x6b,
+       0x71, 0x49, 0x55, 0x66, 0x61, 0x43, 0x55, 0x67, 0x72, 0x4e, 0x75, 0x5a,
+       0x76, 0x61, 0x48, 0x4d, 0x36, 0x6a, 0x6f, 0x52, 0x47, 0x67, 0x7a, 0x43,
+       0x54, 0x51, 0x71, 0x61, 0x5a, 0x73, 0x6f, 0x38, 0x55, 0x2f, 0x30, 0x6e,
+       0x6c, 0x6c, 0x59, 0x43, 0x6b, 0x64, 0x0a, 0x66, 0x32, 0x5a, 0x4c, 0x37,
+       0x4b, 0x6b, 0x43, 0x58, 0x30, 0x30, 0x48, 0x5a, 0x4c, 0x4c, 0x33, 0x76,
+       0x51, 0x67, 0x32, 0x6c, 0x56, 0x2f, 0x70, 0x33, 0x62, 0x75, 0x70, 0x71,
+       0x66, 0x43, 0x38, 0x32, 0x38, 0x55, 0x5a, 0x71, 0x4c, 0x39, 0x71, 0x38,
+       0x75, 0x72, 0x6e, 0x30, 0x58, 0x57, 0x45, 0x68, 0x6c, 0x4d, 0x4e, 0x6c,
+       0x78, 0x36, 0x54, 0x5a, 0x4f, 0x57, 0x6d, 0x4c, 0x6d, 0x35, 0x2f, 0x0a,
+       0x56, 0x67, 0x58, 0x65, 0x61, 0x77, 0x54, 0x66, 0x53, 0x58, 0x48, 0x6d,
+       0x65, 0x34, 0x66, 0x48, 0x48, 0x36, 0x56, 0x32, 0x62, 0x6c, 0x67, 0x53,
+       0x54, 0x75, 0x54, 0x31, 0x44, 0x59, 0x78, 0x38, 0x6e, 0x78, 0x46, 0x76,
+       0x49, 0x4a, 0x6e, 0x5a, 0x68, 0x38, 0x4d, 0x4f, 0x45, 0x75, 0x39, 0x52,
+       0x76, 0x49, 0x74, 0x65, 0x6d, 0x66, 0x72, 0x77, 0x2f, 0x31, 0x36, 0x35,
+       0x65, 0x6c, 0x75, 0x50, 0x0a, 0x56, 0x74, 0x62, 0x6b, 0x2b, 0x79, 0x54,
+       0x74, 0x44, 0x69, 0x6c, 0x38, 0x32, 0x6f, 0x4e, 0x69, 0x75, 0x68, 0x4b,
+       0x44, 0x39, 0x38, 0x4a, 0x65, 0x51, 0x69, 0x6c, 0x43, 0x75, 0x42, 0x53,
+       0x73, 0x4a, 0x4f, 0x78, 0x52, 0x75, 0x64, 0x69, 0x6c, 0x49, 0x4e, 0x53,
+       0x76, 0x63, 0x74, 0x72, 0x33, 0x65, 0x53, 0x37, 0x63, 0x63, 0x43, 0x6e,
+       0x6e, 0x75, 0x37, 0x5a, 0x61, 0x32, 0x49, 0x56, 0x45, 0x0a, 0x35, 0x31,
+       0x41, 0x4c, 0x6b, 0x42, 0x42, 0x61, 0x33, 0x4e, 0x4c, 0x76, 0x49, 0x37,
+       0x6c, 0x70, 0x6d, 0x78, 0x47, 0x41, 0x65, 0x6d, 0x33, 0x67, 0x52, 0x6b,
+       0x51, 0x30, 0x74, 0x4e, 0x63, 0x76, 0x65, 0x39, 0x65, 0x51, 0x4b, 0x6e,
+       0x42, 0x44, 0x71, 0x37, 0x75, 0x45, 0x48, 0x78, 0x35, 0x56, 0x45, 0x79,
+       0x54, 0x4d, 0x4c, 0x43, 0x49, 0x6d, 0x6d, 0x6d, 0x7a, 0x32, 0x4d, 0x58,
+       0x62, 0x61, 0x0a, 0x73, 0x71, 0x64, 0x52, 0x44, 0x33, 0x35, 0x4d, 0x57,
+       0x54, 0x72, 0x77, 0x66, 0x64, 0x36, 0x32, 0x36, 0x72, 0x6b, 0x72, 0x44,
+       0x6f, 0x6c, 0x39, 0x61, 0x56, 0x46, 0x50, 0x6d, 0x37, 0x6e, 0x2b, 0x6e,
+       0x43, 0x71, 0x34, 0x6c, 0x73, 0x50, 0x63, 0x72, 0x76, 0x7a, 0x4c, 0x52,
+       0x4e, 0x63, 0x32, 0x78, 0x31, 0x2b, 0x6a, 0x52, 0x4f, 0x48, 0x64, 0x70,
+       0x31, 0x58, 0x48, 0x65, 0x56, 0x75, 0x6e, 0x0a, 0x61, 0x67, 0x78, 0x6d,
+       0x44, 0x31, 0x44, 0x75, 0x50, 0x2f, 0x6c, 0x30, 0x42, 0x68, 0x4f, 0x36,
+       0x78, 0x55, 0x33, 0x73, 0x74, 0x50, 0x6e, 0x71, 0x33, 0x35, 0x44, 0x4d,
+       0x4a, 0x76, 0x79, 0x65, 0x30, 0x46, 0x34, 0x75, 0x2f, 0x6a, 0x6c, 0x46,
+       0x4f, 0x4a, 0x36, 0x45, 0x36, 0x2b, 0x34, 0x7a, 0x53, 0x69, 0x71, 0x45,
+       0x46, 0x6b, 0x41, 0x4e, 0x57, 0x59, 0x74, 0x67, 0x6c, 0x36, 0x42, 0x47,
+       0x0a, 0x32, 0x63, 0x36, 0x58, 0x44, 0x72, 0x42, 0x66, 0x2b, 0x36, 0x53,
+       0x34, 0x30, 0x4f, 0x79, 0x76, 0x50, 0x4c, 0x46, 0x48, 0x39, 0x4b, 0x61,
+       0x30, 0x63, 0x68, 0x47, 0x58, 0x37, 0x68, 0x59, 0x7a, 0x44, 0x46, 0x2b,
+       0x4b, 0x2f, 0x47, 0x64, 0x50, 0x73, 0x56, 0x75, 0x55, 0x74, 0x75, 0x56,
+       0x48, 0x4d, 0x4e, 0x63, 0x6e, 0x33, 0x64, 0x5a, 0x2f, 0x6e, 0x4c, 0x33,
+       0x5a, 0x4f, 0x4a, 0x6c, 0x47, 0x0a, 0x46, 0x43, 0x33, 0x44, 0x34, 0x41,
+       0x2f, 0x36, 0x36, 0x62, 0x41, 0x51, 0x72, 0x4e, 0x69, 0x4d, 0x62, 0x48,
+       0x37, 0x35, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, 0x44, 0x61, 0x5a, 0x48,
+       0x68, 0x4a, 0x49, 0x79, 0x49, 0x4c, 0x34, 0x4d, 0x49, 0x6b, 0x54, 0x4f,
+       0x6c, 0x31, 0x63, 0x39, 0x4e, 0x6a, 0x62, 0x48, 0x62, 0x75, 0x32, 0x34,
+       0x41, 0x31, 0x4c, 0x76, 0x37, 0x4e, 0x38, 0x47, 0x52, 0x4f, 0x0a, 0x63,
+       0x55, 0x6b, 0x34, 0x75, 0x70, 0x6e, 0x68, 0x5a, 0x69, 0x6a, 0x7a, 0x6a,
+       0x72, 0x38, 0x53, 0x34, 0x49, 0x6b, 0x71, 0x59, 0x49, 0x49, 0x35, 0x77,
+       0x71, 0x45, 0x30, 0x6f, 0x4a, 0x55, 0x41, 0x75, 0x6c, 0x42, 0x7a, 0x66,
+       0x6e, 0x39, 0x76, 0x4b, 0x76, 0x32, 0x6e, 0x6d, 0x66, 0x62, 0x39, 0x68,
+       0x4e, 0x74, 0x35, 0x32, 0x66, 0x73, 0x64, 0x35, 0x49, 0x4c, 0x54, 0x6e,
+       0x6e, 0x58, 0x73, 0x0a, 0x58, 0x39, 0x47, 0x33, 0x7a, 0x56, 0x2f, 0x36,
+       0x6c, 0x54, 0x33, 0x4b, 0x6f, 0x53, 0x4d, 0x53, 0x41, 0x46, 0x55, 0x31,
+       0x68, 0x37, 0x79, 0x69, 0x64, 0x57, 0x6b, 0x75, 0x6c, 0x38, 0x4e, 0x42,
+       0x56, 0x49, 0x57, 0x6a, 0x48, 0x56, 0x65, 0x55, 0x31, 0x4f, 0x4c, 0x31,
+       0x34, 0x46, 0x45, 0x54, 0x75, 0x62, 0x62, 0x47, 0x42, 0x6f, 0x2f, 0x2b,
+       0x73, 0x48, 0x4a, 0x71, 0x31, 0x65, 0x36, 0x56, 0x0a, 0x39, 0x65, 0x34,
+       0x48, 0x4d, 0x35, 0x67, 0x53, 0x70, 0x38, 0x6c, 0x30, 0x68, 0x55, 0x78,
+       0x47, 0x45, 0x52, 0x63, 0x65, 0x31, 0x49, 0x74, 0x74, 0x6f, 0x6a, 0x48,
+       0x6a, 0x39, 0x61, 0x67, 0x4f, 0x37, 0x58, 0x61, 0x62, 0x75, 0x7a, 0x6b,
+       0x4f, 0x4c, 0x63, 0x4d, 0x65, 0x30, 0x62, 0x43, 0x52, 0x66, 0x64, 0x70,
+       0x37, 0x52, 0x61, 0x44, 0x62, 0x68, 0x70, 0x4a, 0x6d, 0x49, 0x41, 0x4b,
+       0x53, 0x0a, 0x50, 0x58, 0x4c, 0x4c, 0x77, 0x38, 0x57, 0x34, 0x65, 0x34,
+       0x43, 0x6c, 0x35, 0x35, 0x6a, 0x37, 0x59, 0x37, 0x5a, 0x71, 0x48, 0x31,
+       0x47, 0x32, 0x47, 0x4a, 0x77, 0x4b, 0x58, 0x58, 0x6d, 0x7a, 0x4b, 0x56,
+       0x48, 0x5a, 0x77, 0x37, 0x72, 0x4f, 0x49, 0x32, 0x7a, 0x49, 0x2f, 0x46,
+       0x4e, 0x70, 0x47, 0x4b, 0x5a, 0x31, 0x4c, 0x6c, 0x54, 0x74, 0x61, 0x6c,
+       0x35, 0x62, 0x4b, 0x5a, 0x75, 0x43, 0x0a, 0x59, 0x37, 0x45, 0x47, 0x6f,
+       0x5a, 0x37, 0x62, 0x71, 0x64, 0x53, 0x63, 0x2f, 0x51, 0x33, 0x71, 0x64,
+       0x6f, 0x58, 0x55, 0x4d, 0x6e, 0x4a, 0x69, 0x52, 0x4f, 0x4d, 0x30, 0x69,
+       0x6a, 0x37, 0x51, 0x43, 0x56, 0x56, 0x64, 0x4b, 0x63, 0x36, 0x46, 0x57,
+       0x39, 0x48, 0x51, 0x54, 0x72, 0x4a, 0x56, 0x41, 0x6f, 0x49, 0x42, 0x41,
+       0x51, 0x44, 0x49, 0x47, 0x63, 0x7a, 0x43, 0x73, 0x41, 0x4a, 0x2b, 0x0a,
+       0x55, 0x68, 0x75, 0x7a, 0x42, 0x77, 0x6c, 0x50, 0x4b, 0x32, 0x75, 0x70,
+       0x4b, 0x4b, 0x35, 0x57, 0x4f, 0x59, 0x43, 0x63, 0x4b, 0x70, 0x72, 0x65,
+       0x79, 0x6b, 0x6b, 0x65, 0x68, 0x41, 0x70, 0x31, 0x2f, 0x38, 0x49, 0x61,
+       0x68, 0x70, 0x79, 0x79, 0x46, 0x38, 0x4d, 0x6b, 0x64, 0x37, 0x6d, 0x66,
+       0x4b, 0x54, 0x5a, 0x49, 0x5a, 0x75, 0x74, 0x74, 0x52, 0x44, 0x31, 0x6c,
+       0x39, 0x34, 0x30, 0x69, 0x0a, 0x79, 0x44, 0x32, 0x7a, 0x73, 0x63, 0x33,
+       0x6b, 0x45, 0x49, 0x49, 0x2f, 0x46, 0x41, 0x33, 0x6d, 0x46, 0x41, 0x56,
+       0x37, 0x4c, 0x70, 0x4e, 0x4e, 0x51, 0x73, 0x46, 0x7a, 0x59, 0x51, 0x6a,
+       0x48, 0x32, 0x4c, 0x64, 0x69, 0x52, 0x36, 0x6f, 0x2b, 0x4a, 0x43, 0x6a,
+       0x4f, 0x34, 0x75, 0x77, 0x4b, 0x78, 0x75, 0x50, 0x5a, 0x51, 0x43, 0x74,
+       0x6f, 0x50, 0x59, 0x48, 0x6d, 0x48, 0x72, 0x64, 0x6c, 0x0a, 0x30, 0x43,
+       0x4c, 0x47, 0x6e, 0x48, 0x4f, 0x37, 0x59, 0x38, 0x31, 0x67, 0x33, 0x69,
+       0x35, 0x6f, 0x76, 0x54, 0x63, 0x37, 0x63, 0x61, 0x4f, 0x45, 0x73, 0x6f,
+       0x32, 0x6a, 0x67, 0x37, 0x4a, 0x4c, 0x67, 0x49, 0x41, 0x4a, 0x48, 0x55,
+       0x31, 0x71, 0x31, 0x62, 0x4d, 0x75, 0x34, 0x6f, 0x33, 0x56, 0x76, 0x6a,
+       0x63, 0x43, 0x37, 0x41, 0x57, 0x47, 0x30, 0x4d, 0x56, 0x30, 0x50, 0x30,
+       0x64, 0x41, 0x0a, 0x31, 0x7a, 0x46, 0x63, 0x4e, 0x76, 0x6f, 0x6c, 0x47,
+       0x4f, 0x32, 0x75, 0x67, 0x63, 0x34, 0x36, 0x48, 0x63, 0x57, 0x37, 0x44,
+       0x6c, 0x37, 0x4e, 0x6b, 0x43, 0x76, 0x63, 0x48, 0x7a, 0x59, 0x4c, 0x6b,
+       0x68, 0x35, 0x6e, 0x44, 0x48, 0x72, 0x6b, 0x49, 0x69, 0x58, 0x61, 0x6d,
+       0x32, 0x52, 0x48, 0x77, 0x65, 0x75, 0x79, 0x71, 0x72, 0x48, 0x35, 0x4b,
+       0x2f, 0x2f, 0x74, 0x48, 0x71, 0x7a, 0x77, 0x0a, 0x65, 0x4d, 0x47, 0x35,
+       0x54, 0x63, 0x65, 0x33, 0x49, 0x4d, 0x69, 0x67, 0x35, 0x46, 0x55, 0x61,
+       0x66, 0x57, 0x47, 0x33, 0x45, 0x6f, 0x42, 0x47, 0x6c, 0x52, 0x30, 0x36,
+       0x5a, 0x41, 0x69, 0x7a, 0x34, 0x68, 0x37, 0x52, 0x66, 0x72, 0x7a, 0x50,
+       0x44, 0x65, 0x7a, 0x6a, 0x37, 0x66, 0x6d, 0x6c, 0x64, 0x6f, 0x72, 0x62,
+       0x46, 0x45, 0x65, 0x79, 0x42, 0x36, 0x33, 0x69, 0x4b, 0x69, 0x58, 0x6b,
+       0x0a, 0x70, 0x34, 0x7a, 0x53, 0x78, 0x75, 0x42, 0x49, 0x63, 0x70, 0x67,
+       0x4c, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x44, 0x69, 0x75, 0x37, 0x78, 0x46,
+       0x38, 0x6a, 0x75, 0x2b, 0x71, 0x54, 0x48, 0x6d, 0x44, 0x68, 0x4f, 0x79,
+       0x35, 0x50, 0x56, 0x71, 0x47, 0x34, 0x6d, 0x2b, 0x6f, 0x68, 0x53, 0x52,
+       0x49, 0x71, 0x46, 0x58, 0x6e, 0x57, 0x51, 0x47, 0x4c, 0x49, 0x63, 0x67,
+       0x5a, 0x6c, 0x71, 0x73, 0x4d, 0x0a, 0x43, 0x77, 0x44, 0x38, 0x51, 0x64,
+       0x65, 0x79, 0x63, 0x36, 0x65, 0x4f, 0x47, 0x50, 0x37, 0x49, 0x35, 0x33,
+       0x4a, 0x7a, 0x59, 0x33, 0x6b, 0x6d, 0x34, 0x6f, 0x36, 0x33, 0x66, 0x48,
+       0x66, 0x73, 0x48, 0x70, 0x34, 0x4c, 0x74, 0x6a, 0x47, 0x69, 0x39, 0x42,
+       0x77, 0x79, 0x57, 0x5a, 0x30, 0x75, 0x6e, 0x45, 0x34, 0x30, 0x79, 0x4d,
+       0x4b, 0x72, 0x4e, 0x42, 0x47, 0x53, 0x75, 0x71, 0x43, 0x64, 0x0a, 0x62,
+       0x38, 0x5a, 0x53, 0x41, 0x48, 0x70, 0x42, 0x6e, 0x39, 0x77, 0x65, 0x2b,
+       0x50, 0x54, 0x70, 0x71, 0x48, 0x30, 0x78, 0x59, 0x72, 0x70, 0x6f, 0x4a,
+       0x36, 0x39, 0x6f, 0x68, 0x44, 0x7a, 0x61, 0x37, 0x48, 0x57, 0x49, 0x33,
+       0x55, 0x4a, 0x54, 0x5a, 0x33, 0x38, 0x4b, 0x59, 0x51, 0x46, 0x6e, 0x35,
+       0x71, 0x71, 0x59, 0x45, 0x43, 0x37, 0x59, 0x41, 0x41, 0x6e, 0x61, 0x65,
+       0x46, 0x51, 0x50, 0x0a, 0x50, 0x4a, 0x69, 0x44, 0x71, 0x49, 0x4a, 0x66,
+       0x47, 0x54, 0x4d, 0x6c, 0x55, 0x33, 0x4d, 0x48, 0x4d, 0x41, 0x2f, 0x4d,
+       0x79, 0x4f, 0x76, 0x6d, 0x38, 0x6d, 0x77, 0x46, 0x4d, 0x67, 0x2f, 0x65,
+       0x44, 0x4e, 0x44, 0x49, 0x2b, 0x42, 0x30, 0x72, 0x48, 0x7a, 0x50, 0x7a,
+       0x70, 0x49, 0x61, 0x56, 0x37, 0x52, 0x57, 0x59, 0x70, 0x56, 0x49, 0x7a,
+       0x70, 0x4d, 0x49, 0x43, 0x64, 0x43, 0x55, 0x6e, 0x0a, 0x32, 0x51, 0x49,
+       0x32, 0x6c, 0x46, 0x78, 0x62, 0x53, 0x78, 0x4e, 0x4d, 0x51, 0x62, 0x63,
+       0x54, 0x75, 0x42, 0x78, 0x77, 0x6d, 0x6f, 0x2f, 0x48, 0x33, 0x37, 0x69,
+       0x33, 0x74, 0x70, 0x71, 0x65, 0x55, 0x7a, 0x50, 0x76, 0x57, 0x65, 0x37,
+       0x6a, 0x4f, 0x51, 0x45, 0x64, 0x48, 0x32, 0x6e, 0x6e, 0x75, 0x38, 0x6e,
+       0x4a, 0x69, 0x4e, 0x56, 0x55, 0x37, 0x72, 0x4f, 0x62, 0x36, 0x31, 0x46,
+       0x30, 0x0a, 0x53, 0x50, 0x7a, 0x65, 0x4b, 0x57, 0x68, 0x37, 0x6a, 0x73,
+       0x79, 0x2b, 0x73, 0x7a, 0x57, 0x53, 0x54, 0x36, 0x35, 0x70, 0x57, 0x31,
+       0x67, 0x2f, 0x73, 0x2b, 0x70, 0x55, 0x57, 0x59, 0x66, 0x2f, 0x68, 0x76,
+       0x75, 0x63, 0x45, 0x57, 0x6b, 0x43, 0x67, 0x67, 0x45, 0x41, 0x65, 0x46,
+       0x6a, 0x4d, 0x42, 0x65, 0x76, 0x47, 0x46, 0x43, 0x4e, 0x64, 0x39, 0x58,
+       0x61, 0x74, 0x36, 0x71, 0x65, 0x36, 0x0a, 0x77, 0x4b, 0x70, 0x75, 0x37,
+       0x2f, 0x7a, 0x31, 0x6c, 0x50, 0x63, 0x71, 0x33, 0x67, 0x50, 0x62, 0x70,
+       0x6a, 0x62, 0x54, 0x38, 0x39, 0x51, 0x32, 0x38, 0x61, 0x30, 0x30, 0x59,
+       0x51, 0x68, 0x5a, 0x6e, 0x58, 0x31, 0x45, 0x62, 0x71, 0x31, 0x69, 0x73,
+       0x48, 0x6a, 0x31, 0x37, 0x32, 0x6d, 0x7a, 0x59, 0x37, 0x68, 0x58, 0x63,
+       0x69, 0x76, 0x73, 0x73, 0x44, 0x36, 0x6f, 0x44, 0x68, 0x71, 0x2f, 0x0a,
+       0x75, 0x79, 0x42, 0x63, 0x6d, 0x35, 0x77, 0x2f, 0x44, 0x36, 0x38, 0x62,
+       0x65, 0x4d, 0x52, 0x46, 0x68, 0x52, 0x63, 0x2f, 0x4b, 0x4c, 0x4c, 0x32,
+       0x32, 0x47, 0x30, 0x78, 0x76, 0x74, 0x34, 0x51, 0x6a, 0x52, 0x31, 0x39,
+       0x79, 0x5a, 0x32, 0x46, 0x50, 0x41, 0x79, 0x55, 0x44, 0x73, 0x57, 0x76,
+       0x63, 0x71, 0x2f, 0x57, 0x72, 0x61, 0x31, 0x59, 0x76, 0x51, 0x73, 0x72,
+       0x72, 0x2f, 0x42, 0x2b, 0x0a, 0x66, 0x56, 0x77, 0x6a, 0x6e, 0x57, 0x72,
+       0x76, 0x35, 0x52, 0x69, 0x62, 0x75, 0x42, 0x75, 0x4c, 0x68, 0x47, 0x53,
+       0x59, 0x76, 0x30, 0x41, 0x78, 0x77, 0x55, 0x6d, 0x57, 0x58, 0x6b, 0x4c,
+       0x59, 0x32, 0x63, 0x48, 0x34, 0x66, 0x43, 0x31, 0x43, 0x2b, 0x4d, 0x62,
+       0x72, 0x4c, 0x41, 0x49, 0x30, 0x50, 0x34, 0x34, 0x76, 0x56, 0x4a, 0x67,
+       0x59, 0x58, 0x39, 0x58, 0x51, 0x37, 0x4b, 0x37, 0x70, 0x0a, 0x68, 0x4b,
+       0x4d, 0x64, 0x58, 0x57, 0x61, 0x36, 0x6e, 0x5a, 0x34, 0x75, 0x39, 0x6f,
+       0x4e, 0x58, 0x58, 0x62, 0x53, 0x48, 0x31, 0x4c, 0x32, 0x30, 0x2b, 0x31,
+       0x56, 0x4f, 0x4e, 0x42, 0x63, 0x31, 0x6e, 0x52, 0x30, 0x49, 0x57, 0x77,
+       0x41, 0x4f, 0x75, 0x67, 0x35, 0x66, 0x71, 0x2f, 0x55, 0x43, 0x6e, 0x36,
+       0x4a, 0x72, 0x63, 0x4e, 0x57, 0x76, 0x37, 0x62, 0x73, 0x52, 0x2b, 0x74,
+       0x6f, 0x45, 0x0a, 0x30, 0x48, 0x2b, 0x44, 0x6a, 0x38, 0x4d, 0x4a, 0x47,
+       0x67, 0x70, 0x4f, 0x6a, 0x43, 0x54, 0x79, 0x78, 0x30, 0x4f, 0x53, 0x32,
+       0x46, 0x32, 0x50, 0x47, 0x34, 0x43, 0x57, 0x48, 0x68, 0x48, 0x4a, 0x2f,
+       0x2b, 0x77, 0x49, 0x30, 0x62, 0x4e, 0x53, 0x49, 0x43, 0x45, 0x36, 0x58,
+       0x61, 0x43, 0x77, 0x75, 0x2b, 0x32, 0x4e, 0x6a, 0x4e, 0x41, 0x59, 0x71,
+       0x6b, 0x78, 0x69, 0x61, 0x44, 0x42, 0x44, 0x0a, 0x73, 0x51, 0x4b, 0x43,
+       0x41, 0x51, 0x45, 0x41, 0x72, 0x54, 0x66, 0x56, 0x52, 0x74, 0x43, 0x71,
+       0x71, 0x72, 0x72, 0x32, 0x49, 0x45, 0x4b, 0x41, 0x59, 0x63, 0x7a, 0x2b,
+       0x4c, 0x4a, 0x4f, 0x71, 0x66, 0x4b, 0x37, 0x77, 0x4f, 0x6f, 0x66, 0x49,
+       0x54, 0x7a, 0x73, 0x45, 0x47, 0x76, 0x57, 0x6c, 0x75, 0x47, 0x73, 0x72,
+       0x6e, 0x54, 0x33, 0x56, 0x55, 0x71, 0x6c, 0x41, 0x30, 0x2b, 0x45, 0x37,
+       0x0a, 0x33, 0x4a, 0x45, 0x41, 0x56, 0x41, 0x33, 0x6c, 0x65, 0x76, 0x32,
+       0x51, 0x74, 0x72, 0x61, 0x33, 0x47, 0x30, 0x67, 0x56, 0x77, 0x6f, 0x4f,
+       0x6e, 0x63, 0x55, 0x33, 0x50, 0x4f, 0x2b, 0x4c, 0x73, 0x61, 0x56, 0x67,
+       0x6a, 0x33, 0x52, 0x43, 0x2b, 0x31, 0x4c, 0x75, 0x68, 0x67, 0x35, 0x68,
+       0x68, 0x68, 0x52, 0x4b, 0x34, 0x4b, 0x6a, 0x4d, 0x39, 0x41, 0x38, 0x76,
+       0x78, 0x62, 0x2b, 0x6b, 0x76, 0x0a, 0x4c, 0x72, 0x53, 0x50, 0x46, 0x56,
+       0x62, 0x4f, 0x35, 0x49, 0x6d, 0x64, 0x4f, 0x6c, 0x33, 0x6f, 0x4c, 0x6f,
+       0x67, 0x34, 0x43, 0x2b, 0x69, 0x63, 0x79, 0x44, 0x72, 0x39, 0x79, 0x76,
+       0x67, 0x4c, 0x4d, 0x78, 0x67, 0x66, 0x6a, 0x44, 0x55, 0x38, 0x2f, 0x6d,
+       0x47, 0x5a, 0x2f, 0x62, 0x42, 0x6d, 0x47, 0x6f, 0x38, 0x79, 0x55, 0x41,
+       0x75, 0x61, 0x68, 0x39, 0x4e, 0x79, 0x4f, 0x2f, 0x63, 0x76, 0x0a, 0x65,
+       0x6d, 0x4e, 0x49, 0x65, 0x6f, 0x42, 0x6b, 0x33, 0x44, 0x6d, 0x45, 0x34,
+       0x7a, 0x55, 0x35, 0x6b, 0x76, 0x38, 0x43, 0x41, 0x41, 0x4e, 0x55, 0x65,
+       0x31, 0x75, 0x56, 0x6b, 0x4a, 0x2f, 0x74, 0x78, 0x6d, 0x58, 0x31, 0x70,
+       0x56, 0x64, 0x6d, 0x56, 0x61, 0x36, 0x70, 0x38, 0x42, 0x61, 0x41, 0x79,
+       0x51, 0x31, 0x4b, 0x61, 0x71, 0x6e, 0x64, 0x46, 0x43, 0x61, 0x53, 0x4d,
+       0x79, 0x53, 0x56, 0x0a, 0x66, 0x46, 0x77, 0x33, 0x44, 0x42, 0x61, 0x2b,
+       0x39, 0x52, 0x36, 0x69, 0x2f, 0x58, 0x46, 0x4b, 0x36, 0x64, 0x41, 0x67,
+       0x70, 0x70, 0x63, 0x47, 0x53, 0x4d, 0x33, 0x36, 0x41, 0x62, 0x42, 0x73,
+       0x78, 0x36, 0x6c, 0x6f, 0x70, 0x4f, 0x61, 0x62, 0x61, 0x41, 0x41, 0x6f,
+       0x73, 0x71, 0x4f, 0x63, 0x66, 0x73, 0x58, 0x6b, 0x62, 0x6b, 0x68, 0x4c,
+       0x76, 0x67, 0x75, 0x69, 0x41, 0x45, 0x77, 0x6f, 0x0a, 0x47, 0x38, 0x33,
+       0x66, 0x39, 0x50, 0x62, 0x41, 0x56, 0x38, 0x66, 0x72, 0x6e, 0x6f, 0x54,
+       0x62, 0x51, 0x36, 0x51, 0x6c, 0x47, 0x45, 0x6f, 0x4f, 0x67, 0x48, 0x53,
+       0x56, 0x73, 0x77, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45,
+       0x4e, 0x44, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b,
+       0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
+};
+
+#endif /* _TLS_TEST_CERT_H */
index e45678a35599c19122c05ffefd4f4266c0ddae32..ea5de5dbf99eda9638995114033a7cf464d04e32 100644 (file)
@@ -127,8 +127,10 @@ isc_tlsctx_createclient(isc_tlsctx_t **ctxp) {
 #if HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
        SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
 #else
-       SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
-                                        SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
+       SSL_CTX_set_options(
+               ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 |
+                            SSL_OP_NO_TLSv1_1 | SSL_OP_NO_COMPRESSION |
+                            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
 #endif
 
        *ctxp = ctx;
index e64bcd8ae0b835362645255e04cbabbd2a9de924..d4e13ee70a47d35ad6d16cc85d6343dde5e28707 100644 (file)
@@ -650,7 +650,7 @@ isc_url_parse(const char *buf, size_t buflen, bool is_connect,
                INSIST(off + len <= buflen);
 
                v = 0;
-               for (pp = buf + off; pp < end; p++) {
+               for (pp = buf + off; pp < end; pp++) {
                        v *= 10;
                        v += *pp - '0';
 
index e08c04753caea5829d5d25002a000cc716ef6c5d..e460d5757e41b946e55d4b76d7b538090da5f79a 100644 (file)
@@ -448,7 +448,12 @@ isc_nm_cancelread
 isc_nm_closedown
 isc_nm_destroy
 isc_nm_detach
-isc_nm_listenhttps
+isc_nm_http_add_doh_endpoint
+isc_nm_http_add_endpoint
+isc_nm_http_connect_send_request
+isc_nm_httpconnect
+isc_nm_httprequest
+isc_nm_listenhttp
 isc_nm_listentcpdns
 isc_nm_listentls
 isc_nm_listentlsdns
@@ -469,7 +474,6 @@ isc_nm_settimeouts
 isc_nm_tcpdns_keepalive
 isc_nm_tcpdns_sequential
 isc_nm_tid
-isc_nm_tlsconnect
 isc_nm_tlsdnsconnect
 isc_nm_udpconnect
 isc_nmsocket_close
index e210ee3bc0c63d2874e363c048a9ebefb9b75b8f..152ce4b76ccee441acca7b5f6c96a99384d11d29 100644 (file)
@@ -80,6 +80,8 @@ struct ns_interface {
        isc_socket_t *  tcpsocket; /*%< TCP socket. */
        isc_nmsocket_t *udplistensocket;
        isc_nmsocket_t *tcplistensocket;
+       isc_nmsocket_t *http_listensocket;
+       isc_nmsocket_t *http_secure_listensocket;
        isc_dscp_t      dscp;          /*%< "listen-on" DSCP value */
        isc_refcount_t  ntcpaccepting; /*%< Number of clients
                                        *   ready to accept new
index fe6dffde7e01c087a62ede3c3d82a4c66572ed04..de30a5747b1f768dd5c2b797eb01c0aa868d0765 100644 (file)
@@ -42,9 +42,12 @@ typedef struct ns_listenlist ns_listenlist_t;
 struct ns_listenelt {
        isc_mem_t *   mctx;
        in_port_t     port;
+       bool          is_http;
        isc_dscp_t    dscp; /* -1 = not set, 0..63 */
        dns_acl_t *   acl;
        isc_tlsctx_t *sslctx;
+       char **       http_endpoints;
+       size_t        http_endpoints_number;
        ISC_LINK(ns_listenelt_t) link;
 };
 
@@ -66,6 +69,15 @@ ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
  * Create a listen-on list element.
  */
 
+isc_result_t
+ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp,
+                        dns_acl_t *acl, const char *key, const char *cert,
+                        char **endpoints, size_t nendpoints,
+                        ns_listenelt_t **target);
+/*%<
+ * Create a listen-on list element for HTTP(S).
+ */
+
 void
 ns_listenelt_destroy(ns_listenelt_t *elt);
 /*%<
index 8f73279263fae932d5520668ce4fdd33b0a2aa19..a1443410a7ef456332b2032d2c8ef5f3701a5f27 100644 (file)
@@ -437,6 +437,10 @@ ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
                goto failure;
        }
 
+       ifp->tcplistensocket = NULL;
+       ifp->http_listensocket = NULL;
+       ifp->http_secure_listensocket = NULL;
+
        *ifpret = ifp;
 
        return (ISC_R_SUCCESS);
@@ -539,6 +543,54 @@ ns_interface_listentls(ns_interface_t *ifp, isc_tlsctx_t *sslctx) {
        return (result);
 }
 
+static isc_result_t
+ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps,
+                       size_t neps) {
+       isc_result_t result;
+       isc_nmsocket_t *sock = NULL;
+       size_t i = 0;
+
+       result = isc_nm_listenhttp(ifp->mgr->nm, (isc_nmiface_t *)&ifp->addr,
+                                  ifp->mgr->backlog, &ifp->mgr->sctx->tcpquota,
+                                  sslctx, &sock);
+
+       if (result == ISC_R_SUCCESS) {
+               for (i = 0; i < neps; i++) {
+                       result = isc_nm_http_add_doh_endpoint(
+                               sock, eps[i], ns__client_request, ifp,
+                               sizeof(ns_client_t));
+               }
+       }
+
+       if (result != ISC_R_SUCCESS) {
+               isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+                             "creating %s socket: %s",
+                             sslctx ? "HTTPS" : "HTTP",
+                             isc_result_totext(result));
+               return (result);
+       }
+
+       if (sslctx) {
+               ifp->http_secure_listensocket = sock;
+       } else {
+               ifp->http_listensocket = sock;
+       }
+
+       /*
+        * We call this now to update the tcp-highwater statistic:
+        * this is necessary because we are adding to the TCP quota just
+        * by listening.
+        */
+       result = ns__client_tcpconn(NULL, ISC_R_SUCCESS, ifp);
+       if (result != ISC_R_SUCCESS) {
+               isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+                             "updating TCP stats: %s",
+                             isc_result_totext(result));
+       }
+
+       return (result);
+}
+
 static isc_result_t
 ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
                   const char *name, ns_interface_t **ifpret, bool accept_tcp,
@@ -555,6 +607,17 @@ ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
 
        ifp->dscp = elt->dscp;
 
+       if (elt->is_http) {
+               result = ns_interface_listenhttp(ifp, elt->sslctx,
+                                                elt->http_endpoints,
+                                                elt->http_endpoints_number);
+               if (result != ISC_R_SUCCESS) {
+                       goto cleanup_interface;
+               }
+               *ifpret = ifp;
+               return (result);
+       }
+
        if (elt->sslctx != NULL) {
                result = ns_interface_listentls(ifp, elt->sslctx);
                if (result != ISC_R_SUCCESS) {
@@ -611,6 +674,14 @@ ns_interface_shutdown(ns_interface_t *ifp) {
                isc_nm_stoplistening(ifp->tcplistensocket);
                isc_nmsocket_close(&ifp->tcplistensocket);
        }
+       if (ifp->http_listensocket != NULL) {
+               isc_nm_stoplistening(ifp->http_listensocket);
+               isc_nmsocket_close(&ifp->http_listensocket);
+       }
+       if (ifp->http_secure_listensocket != NULL) {
+               isc_nm_stoplistening(ifp->http_secure_listensocket);
+               isc_nmsocket_close(&ifp->http_secure_listensocket);
+       }
        if (ifp->clientmgr != NULL) {
                ns_clientmgr_destroy(&ifp->clientmgr);
        }
index c5153ca60c382c1b532bb70a8aa5c2effa54f32d..5fbb59db9f98ce1a23d114cb5f5d0709efef692e 100644 (file)
@@ -30,24 +30,59 @@ ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp,
                    ns_listenelt_t **target) {
        ns_listenelt_t *elt = NULL;
        isc_result_t result = ISC_R_SUCCESS;
+       isc_tlsctx_t *sslctx = NULL;
+
        REQUIRE(target != NULL && *target == NULL);
+
+       if (tls) {
+               result = isc_tlsctx_createserver(key, cert, &sslctx);
+               if (result != ISC_R_SUCCESS) {
+                       return (result);
+               }
+       }
+
        elt = isc_mem_get(mctx, sizeof(*elt));
        elt->mctx = mctx;
        ISC_LINK_INIT(elt, link);
        elt->port = port;
+       elt->is_http = false;
        elt->dscp = dscp;
        elt->acl = acl;
-       elt->sslctx = NULL;
-       if (tls) {
-               result = isc_tlsctx_createserver(key, cert, &elt->sslctx);
-               if (result != ISC_R_SUCCESS) {
-                       return (result);
-               }
-       }
+       elt->sslctx = sslctx;
+       elt->http_endpoints = NULL;
+       elt->http_endpoints_number = 0;
+
        *target = elt;
        return (ISC_R_SUCCESS);
 }
 
+isc_result_t
+ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp,
+                        dns_acl_t *acl, const char *key, const char *cert,
+                        char **endpoints, size_t nendpoints,
+                        ns_listenelt_t **target) {
+       isc_result_t result;
+
+       REQUIRE(target != NULL && *target == NULL);
+       REQUIRE(endpoints != NULL && *endpoints != NULL);
+       REQUIRE(nendpoints > 0);
+
+       result = ns_listenelt_create(mctx, http_port, dscp, acl, key != NULL,
+                                    key, cert, target);
+       if (result == ISC_R_SUCCESS) {
+               (*target)->is_http = true;
+               (*target)->http_endpoints = endpoints;
+               (*target)->http_endpoints_number = nendpoints;
+       } else {
+               size_t i;
+               for (i = 0; i < nendpoints; i++) {
+                       isc_mem_free(mctx, endpoints[i]);
+               }
+               isc_mem_free(mctx, endpoints);
+       }
+       return (result);
+}
+
 void
 ns_listenelt_destroy(ns_listenelt_t *elt) {
        if (elt->acl != NULL) {
@@ -56,6 +91,14 @@ ns_listenelt_destroy(ns_listenelt_t *elt) {
        if (elt->sslctx != NULL) {
                isc_tlsctx_free(&elt->sslctx);
        }
+       if (elt->http_endpoints != NULL) {
+               size_t i;
+               INSIST(elt->http_endpoints_number > 0);
+               for (i = 0; i < elt->http_endpoints_number; i++) {
+                       isc_mem_free(elt->mctx, elt->http_endpoints[i]);
+               }
+               isc_mem_free(elt->mctx, elt->http_endpoints);
+       }
        isc_mem_put(elt->mctx, elt, sizeof(*elt));
 }
 
index 66d706af4fe19eaf19fe1e97d48ce0f410177f21..a08ba68d1d8342a5c78deaefbc613ea37c78d35e 100644 (file)
@@ -66,6 +66,7 @@ ns_interfacemgr_shutdown
 ns_lib_init
 ns_lib_shutdown
 ns_listenelt_create
+ns_listenelt_create_http
 ns_listenelt_destroy
 ns_listenlist_attach
 ns_listenlist_create
index 4bf65ac9510ecb50d943d40e764f4564eeab39b7..781e9289061c5c77a53cb3019ff372138aa7ae5b 100644 (file)
 ./lib/isc/tests/buffer_test.c                  C       2014,2015,2016,2017,2018,2019,2020,2021
 ./lib/isc/tests/counter_test.c                 C       2014,2016,2018,2019,2020,2021
 ./lib/isc/tests/crc64_test.c                   C       2018,2019,2020,2021
+./lib/isc/tests/doh_test.c                     C       2020,2021
 ./lib/isc/tests/errno_test.c                   C       2016,2018,2019,2020,2021
 ./lib/isc/tests/file_test.c                    C       2014,2016,2017,2018,2019,2020,2021
 ./lib/isc/tests/hash_test.c                    C       2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021
 ./lib/isc/tests/testdata/file/keep             X       2014,2018,2019,2020,2021
 ./lib/isc/tests/time_test.c                    C       2014,2015,2016,2018,2019,2020,2021
 ./lib/isc/tests/timer_test.c                   C       2018,2019,2020,2021
+./lib/isc/tests/tls_test_cert_key.h            C       2021
 ./lib/isc/tests/tlsdns_test.c                  C       2021
 ./lib/isc/tests/udp_test.c                     C       2020,2021
 ./lib/isc/tests/uv_wrap.h                      C       2020,2021
 ./lib/isccc/win32/libisccc.vcxproj.user                X       2013,2018,2019,2020,2021
 ./lib/isccfg/aclconf.c                         C       1999,2000,2001,2002,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021
 ./lib/isccfg/dnsconf.c                         C       2009,2016,2018,2019,2020,2021
+./lib/isccfg/httpconf.c                                C       2021
 ./lib/isccfg/include/isccfg/aclconf.h          C       1999,2000,2001,2004,2005,2006,2007,2010,2011,2012,2013,2014,2016,2018,2019,2020,2021
 ./lib/isccfg/include/isccfg/cfg.h              C       2000,2001,2002,2004,2005,2006,2007,2010,2013,2014,2015,2016,2018,2019,2020,2021
 ./lib/isccfg/include/isccfg/grammar.h          C       2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2013,2014,2015,2016,2017,2018,2019,2020,2021
+./lib/isccfg/include/isccfg/httpconf.h         C       2021
 ./lib/isccfg/include/isccfg/kaspconf.h         C       2019,2020,2021
 ./lib/isccfg/include/isccfg/log.h              C       2001,2004,2005,2006,2007,2009,2016,2018,2019,2020,2021
 ./lib/isccfg/include/isccfg/namedconf.h                C       2002,2004,2005,2006,2007,2009,2010,2014,2016,2018,2019,2020,2021
+./lib/isccfg/include/isccfg/tlsconf.h          C       2021
 ./lib/isccfg/kaspconf.c                                C       2019,2020,2021
 ./lib/isccfg/log.c                             C       2001,2004,2005,2006,2007,2016,2018,2019,2020,2021
 ./lib/isccfg/namedconf.c                       C       2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021
 ./lib/isccfg/parser.c                          C       2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021
 ./lib/isccfg/tests/duration_test.c             C       2019,2020,2021
 ./lib/isccfg/tests/parser_test.c               C       2016,2018,2019,2020,2021
+./lib/isccfg/tlsconf.c                         C       2021
 ./lib/isccfg/win32/DLLMain.c                   C       2001,2004,2007,2016,2018,2019,2020,2021
 ./lib/isccfg/win32/libisccfg.def               X       2001,2002,2005,2009,2010,2011,2013,2014,2015,2016,2018,2019,2020,2021
 ./lib/isccfg/win32/libisccfg.vcxproj.filters.in        X       2013,2014,2015,2016,2018,2019,2020