]> 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)
committerArtem Boldariev <artem@boldariev.com>
Tue, 2 Feb 2021 18:20:01 +0000 (20:20 +0200)
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).

44 files changed:
CHANGES
bin/named/config.c
bin/named/include/named/globals.h
bin/named/main.c
bin/named/named.conf.rst
bin/named/named.rst
bin/named/server.c
bin/tests/system/checkconf/good-doh-global.conf
bin/tests/system/checkconf/good-doh-view.conf [deleted file]
bin/tests/system/conf.sh.common
bin/tests/system/get_ports.sh
bin/tests/system/run.sh.in
doc/man/named.8in
doc/man/named.conf.5in
doc/misc/options
doc/misc/options.active
doc/misc/options.grammar.rst
doc/notes/notes-current.rst
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/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/isccfg/Makefile.am
lib/isccfg/httpconf.c [new file with mode: 0644]
lib/isccfg/include/isccfg/httpconf.h [new file with mode: 0644]
lib/isccfg/include/isccfg/tlsconf.h [new file with mode: 0644]
lib/isccfg/namedconf.c
lib/isccfg/tlsconf.c [new file with mode: 0644]
lib/isccfg/win32/libisccfg.def
lib/isccfg/win32/libisccfg.vcxproj.filters.in
lib/isccfg/win32/libisccfg.vcxproj.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 344afbca5fa80f3e31e8d5a23ef6d40ba5f2cbea..9e822f49b301e3711984768a07974c9eac7473ea 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,8 @@
+5575.  [func]          Initial support for DNS-over-HTTP(S). BIND now
+                       includes DNS-over-HTTP(S) layer built on top of nghttp2.
+                       Both encrypted (via TLS) and unencrypted HTTP/2 connections
+                       are supported.
+
 5574.  [func]          Incoming zone transfers can now use TLS.
                        Addresses in a "primaries" list take an optional
                        "tls" argument, specifying either a previously
index 123e277349b3f21a7e20dd35a4570f5564928a38..99af2dd570cb7c9420b5581b9032cc1d0acd3881 100644 (file)
@@ -94,6 +94,7 @@ options {\n\
 #      pid-file \"" NAMED_LOCALSTATEDIR "/run/named/named.pid\"; \n\
        port 53;\n\
        tls-port 853;\n\
+       http-port 80;\n\
        https-port 443;\n\
        prefetch 2 9;\n\
        recursing-file \"named.recursing\";\n\
index 3671211bb9be60c5e11d1c78af4926d986e7380e..f91a285c4371325d7249253c2cb9e2617a86387d 100644 (file)
@@ -64,17 +64,18 @@ EXTERN isc_timermgr_t *named_g_timermgr INIT(NULL);
 EXTERN isc_socketmgr_t *named_g_socketmgr INIT(NULL);
 EXTERN isc_nm_t *named_g_nm INIT(NULL);
 EXTERN cfg_parser_t *named_g_parser INIT(NULL);
-EXTERN cfg_parser_t *named_g_addparser INIT(NULL);
-EXTERN const char *named_g_version     INIT(PACKAGE_VERSION);
-EXTERN const char *named_g_product     INIT(PACKAGE_NAME);
-EXTERN const char *named_g_description INIT(PACKAGE_DESCRIPTION);
-EXTERN const char *named_g_srcid       INIT(PACKAGE_SRCID);
-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_http_secure_port     INIT(0);
-EXTERN isc_dscp_t named_g_dscp        INIT(-1);
+EXTERN cfg_parser_t *named_g_addparser   INIT(NULL);
+EXTERN const char *named_g_version       INIT(PACKAGE_VERSION);
+EXTERN const char *named_g_product       INIT(PACKAGE_NAME);
+EXTERN const char *named_g_description   INIT(PACKAGE_DESCRIPTION);
+EXTERN const char *named_g_srcid         INIT(PACKAGE_SRCID);
+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_http_secure_port INIT(0);
+EXTERN in_port_t named_g_http_port       INIT(0);
+EXTERN isc_dscp_t named_g_dscp           INIT(-1);
 
 EXTERN named_server_t *named_g_server INIT(NULL);
 
index c803934859b71c110a05d0546826a6a899a32465..d318d63a9f95a9a064cd93ebb5e8062742cbd1f1 100644 (file)
@@ -705,7 +705,7 @@ parse_T_opt(char *option) {
 
 static void
 parse_port(char *arg) {
-       enum { DNSPORT, TLSPORT, HTTP_SECURE_PORT } ptype = DNSPORT;
+       enum { DNSPORT, TLSPORT, HTTP_SECURE_PORT, HTTP_PORT } ptype = DNSPORT;
        char *value = arg;
        int port;
 
@@ -717,6 +717,9 @@ parse_port(char *arg) {
        } else if (strncmp(arg, "https=", 6) == 0) {
                value = arg + 6;
                ptype = HTTP_SECURE_PORT;
+       } else if (strncmp(arg, "http=", 5) == 0) {
+               value = arg + 6;
+               ptype = HTTP_PORT;
        }
 
        port = parse_int(value, "port");
@@ -734,6 +737,9 @@ parse_port(char *arg) {
        case HTTP_SECURE_PORT:
                named_g_http_secure_port = port;
                break;
+       case HTTP_PORT:
+               named_g_http_port = port;
+               break;
        default:
                INSIST(0);
                ISC_UNREACHABLE();
index 2ba2f2f6bc6c8b385cd4b3abeae5207c3094fd31..14e8a68d27361089751bc8b74d2c4c3706f0dbd9 100644 (file)
@@ -86,6 +86,15 @@ DYNDB
   dyndb string quoted_string {
       unspecified-text };
 
+HTTP
+^^^^
+
+::
+
+  http string {
+       endpoints { quoted_string; ... }; // experimental
+  };
+
 KEY
 ^^^
 
@@ -264,12 +273,8 @@ OPTIONS
        glue-cache boolean;// deprecated
        heartbeat-interval integer;
        hostname ( quoted_string | none );
-       https-endpoint quoted_string https-server string;
+       http-port integer;
        https-port integer;
-       https-server string [ port integer ] tls string { (
-           quoted_string [ port integer ] [ dscp integer ] |
-           ipv4_address [ port integer ] [ dscp integer ] |
-           ipv6_address [ port integer ] [ dscp integer ] ); ... };
        inline-signing boolean;
        interface-interval duration;
        ipv4only-contact string;
@@ -281,10 +286,12 @@ OPTIONS
        key-directory quoted_string;
        lame-ttl duration;
        listen-on [ port integer ] [ dscp
-           integer ] [ tls string ] {
+           integer ] [ tls string ] [ http
+           string ] {
            address_match_element; ... };
        listen-on-v6 [ port integer ] [ dscp
-           integer ] [ tls string ] {
+           integer ] [ tls string ] [ http
+           string ] {
            address_match_element; ... };
        lmdb-mapsize sizeval;
        lock-file ( quoted_string | none );
@@ -658,7 +665,6 @@ VIEW
        forwarders [ port integer ] [ dscp integer ] { ( ipv4_address
            | ipv6_address ) [ port integer ] [ dscp integer ]; ... };
        glue-cache boolean;// deprecated
-       https-endpoint quoted_string https-server string;
        inline-signing boolean;
        ipv4only-contact string;
        ipv4only-enable boolean;
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..6ddeba7aad2cfc2033e0cbec8fc0ab50be96b1f7 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_http_port = (in_port_t)cfg_obj_asuint32(obj);
+
+       obj = NULL;
+       result = named_config_get(maps, "https-port", &obj);
+       INSIST(result == ISC_R_SUCCESS);
+       named_g_http_secure_port = (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_http_secure_port != 0) {
+                               port = named_g_http_secure_port;
+                       } else {
+                               result = named_config_getport(
+                                       config, "https-port", &port);
+                               if (result != ISC_R_SUCCESS) {
+                                       return (result);
+                               }
+                       }
+               } else if (http && !tls) {
+                       if (named_g_http_port != 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,66 @@ 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_http_secure_port
+                                  : named_g_http_port;
+       }
+
+       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 83711d6329d9ae3d855578b81d21ce76737754b8..f5eb63477f72da6ec02e8f55036931d663d4c931 100644 (file)
@@ -14,8 +14,14 @@ tls local-tls {
        cert-file "cert.pem";
 };
 
+http local-http-server {
+       endpoints { "/dns-query"; };
+};
+
 options {
        listen-on { 10.53.0.1; };
-       https-server local-server port 443 tls local-tls { 10.53.0.1; };
-       https-endpoint "/dns-query" https-server local-server;
+       http-port 80;
+       https-port 443;
+       listen-on port 443 tls local-tls http local-http-server { 10.53.0.1; };
+       listen-on port 8080 http local-http-server { 10.53.0.1; };
 };
diff --git a/bin/tests/system/checkconf/good-doh-view.conf b/bin/tests/system/checkconf/good-doh-view.conf
deleted file mode 100644 (file)
index cdbce79..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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 http://mozilla.org/MPL/2.0/.
- *
- * See the COPYRIGHT file distributed with this work for additional
- * information regarding copyright ownership.
- */
-
-tls local-tls {
-       key-file "key.pem";
-       cert-file "cert.pem";
-};
-
-options {
-       listen-on { 10.53.0.1; };
-       https-server local-server port 443 tls local-tls { 10.53.0.1; };
-};
-
-view one {
-       https-endpoint "/dns-query" https-server local-server;
-};
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 f758454cdebb4740af190a82dd82986c1b36f3db..c5d81eac5c0843ec3f2ba077e8e6543b0d99cd67 100644 (file)
@@ -117,6 +117,8 @@ for queries. If \fBvalue\fP is of the form \fB<portnum>\fP or
 listen for TLS queries on \fBportnum\fP; the default is 853.
 If \fBvalue\fP is of the form \fBhttps=<portnum>\fP, the server will
 listen for HTTPS queries on \fBportnum\fP; the default is 443.
+If \fBvalue\fP is of the form \fBhttp=<portnum>\fP, the server will
+listen for HTTP queries on \fBportnum\fP; the default is 80.
 .TP
 .B \fB\-s\fP
 This option writes memory usage statistics to \fBstdout\fP on exit.
index ac9d03ded3980547654388d8b485f715c91fc05d..fcdb5dc292ce88785fa4f0e8c09605f14b880809 100644 (file)
@@ -328,6 +328,7 @@ options {
       heartbeat\-interval integer;
       hostname ( quoted_string | none );
       https\-endpoint quoted_string https\-server string;
+      http\-port integer;
       https\-port integer;
       https\-server string [ port integer ] tls string { (
           quoted_string [ port integer ] [ dscp integer ] |
index c1fd82480840ec776c1b4b9de4831c4e3e6961ca..bb8fd84af8386219546824e980ef87bb3949fceb 100644 (file)
@@ -42,6 +42,10 @@ dnssec-policy <string> {
 dyndb <string> <quoted_string> {
     <unspecified-text> }; // may occur multiple times
 
+http <string> {
+        endpoints { <quoted_string>; ... }; // experimental
+}; // may occur multiple times
+
 key <string> {
         algorithm <string>;
         secret <string>;
@@ -193,12 +197,8 @@ options {
         glue-cache <boolean>; // deprecated
         heartbeat-interval <integer>;
         hostname ( <quoted_string> | none );
-        https-endpoint <quoted_string> https-server <string>;
+        http-port <integer>;
         https-port <integer>;
-        https-server <string> [ port <integer> ] tls <string> { (
-            <quoted_string> [ port <integer> ] [ dscp <integer> ] |
-            <ipv4_address> [ port <integer> ] [ dscp <integer> ] |
-            <ipv6_address> [ port <integer> ] [ dscp <integer> ] ); ... };
         inline-signing <boolean>;
         interface-interval <duration>;
         ipv4only-contact <string>;
@@ -210,10 +210,12 @@ options {
         key-directory <quoted_string>;
         lame-ttl <duration>;
         listen-on [ port <integer> ] [ dscp
-            <integer> ] [ tls <string> ] {
+            <integer> ] [ tls <string> ] [ http
+            <string> ] {
             <address_match_element>; ... }; // may occur multiple times
         listen-on-v6 [ port <integer> ] [ dscp
-            <integer> ] [ tls <string> ] {
+            <integer> ] [ tls <string> ] [ http
+            <string> ] {
             <address_match_element>; ... }; // may occur multiple times
         lmdb-mapsize <sizeval>;
         lock-file ( <quoted_string> | none );
@@ -547,7 +549,6 @@ view <string> [ <class> ] {
         forwarders [ port <integer> ] [ dscp <integer> ] { ( <ipv4_address>
             | <ipv6_address> ) [ port <integer> ] [ dscp <integer> ]; ... };
         glue-cache <boolean>; // deprecated
-        https-endpoint <quoted_string> https-server <string>;
         inline-signing <boolean>;
         ipv4only-contact <string>;
         ipv4only-enable <boolean>;
index 2bafa0664eee1195fc4db9fd883d3951357fac3d..42724f91b8c23236c3253695c6e30aac275ba341 100644 (file)
@@ -41,6 +41,10 @@ dnssec-policy <string> {
 dyndb <string> <quoted_string> {
     <unspecified-text> }; // may occur multiple times
 
+http <string> {
+        endpoints { <quoted_string>; ... }; // experimental
+}; // may occur multiple times
+
 key <string> {
         algorithm <string>;
         secret <string>;
@@ -192,12 +196,8 @@ options {
         glue-cache <boolean>; // deprecated
         heartbeat-interval <integer>;
         hostname ( <quoted_string> | none );
-        https-endpoint <quoted_string> https-server <string>;
+        http-port <integer>;
         https-port <integer>;
-        https-server <string> [ port <integer> ] tls <string> { (
-            <quoted_string> [ port <integer> ] [ dscp <integer> ] |
-            <ipv4_address> [ port <integer> ] [ dscp <integer> ] |
-            <ipv6_address> [ port <integer> ] [ dscp <integer> ] ); ... };
         inline-signing <boolean>;
         interface-interval <duration>;
         ipv4only-contact <string>;
@@ -209,10 +209,12 @@ options {
         key-directory <quoted_string>;
         lame-ttl <duration>;
         listen-on [ port <integer> ] [ dscp
-            <integer> ] [ tls <string> ] {
+            <integer> ] [ tls <string> ] [ http
+            <string> ] {
             <address_match_element>; ... }; // may occur multiple times
         listen-on-v6 [ port <integer> ] [ dscp
-            <integer> ] [ tls <string> ] {
+            <integer> ] [ tls <string> ] [ http
+            <string> ] {
             <address_match_element>; ... }; // may occur multiple times
         lmdb-mapsize <sizeval>;
         lock-file ( <quoted_string> | none );
@@ -544,7 +546,6 @@ view <string> [ <class> ] {
         forwarders [ port <integer> ] [ dscp <integer> ] { ( <ipv4_address>
             | <ipv6_address> ) [ port <integer> ] [ dscp <integer> ]; ... };
         glue-cache <boolean>; // deprecated
-        https-endpoint <quoted_string> https-server <string>;
         inline-signing <boolean>;
         ipv4only-contact <string>;
         ipv4only-enable <boolean>;
index 5e8d8d04f544d0e0dc253ea15b4cc3a09c97bc0f..5176f572bd6f09123577b0a1ac34b740c8bab054 100644 (file)
        glue-cache <boolean>; // deprecated
        heartbeat-interval <integer>;
        hostname ( <quoted_string> | none );
-       https-endpoint <quoted_string> https-server <string>;
+       http-port <integer>;
        https-port <integer>;
-       https-server <string> [ port <integer> ] tls <string> { (
-           <quoted_string> [ port <integer> ] [ dscp <integer> ] |
-           <ipv4_address> [ port <integer> ] [ dscp <integer> ] |
-           <ipv6_address> [ port <integer> ] [ dscp <integer> ] ); ... };
        inline-signing <boolean>;
        interface-interval <duration>;
        ipv4only-contact <string>;
        key-directory <quoted_string>;
        lame-ttl <duration>;
        listen-on [ port <integer> ] [ dscp
-           <integer> ] [ tls <string> ] {
+           <integer> ] [ tls <string> ] [ http
+           <string> ] {
            <address_match_element>; ... };
        listen-on-v6 [ port <integer> ] [ dscp
-           <integer> ] [ tls <string> ] {
+           <integer> ] [ tls <string> ] [ http
+           <string> ] {
            <address_match_element>; ... };
        lmdb-mapsize <sizeval>;
        lock-file ( <quoted_string> | none );
index 82677a00ee28369b492a5684c58033a1350341d4..71ea665d7d1059f695eeb860323ae36728ab595d 100644 (file)
@@ -52,6 +52,13 @@ New Features
   an optional ``tls`` option which specifies either a previously configured
   ``tls`` statement or ``ephemeral``. [GL #2392]
 
+- ``named`` now has initial support for DNS-over-HTTP(S). 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 aid in TLS
+  certificates management).
+
 Removed Features
 ~~~~~~~~~~~~~~~~
 
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 b26f587dfa8ccbcb17fc2d5aa3e547ffbc8fd4a1..c14cf9d2c572286abd2d22a28b3c78f853e490bd 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,90 @@ 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;
 };
 
+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 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 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 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 +211,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 +224,35 @@ 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) {
+               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) {
+       INSIST(ISC_LIST_EMPTY(session->sstreams));
+       INSIST(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 +261,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 +285,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 +297,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 +345,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 +415,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 +423,103 @@ 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 */
 
        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 +527,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 +563,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 +608,54 @@ 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 +678,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 +711,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 +728,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 +752,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 +785,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();
+               INSIST(ctx != NULL);
+       }
 
-       UNUSED(local);
-       UNUSED(peer);
-       UNUSED(uri);
-       UNUSED(cb);
-       UNUSED(cbarg);
-       UNUSED(timeout);
-       UNUSED(extrahandlesize);
+       if (peer == NULL || local == NULL) {
+               size_t hostlen = 0;
+               char *host = NULL;
+               struct addrinfo hints;
+               struct addrinfo *res = NULL;
 
-       return (ISC_R_NOTIMPLEMENTED);
+#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) {
+                       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 };
+
+       if (ctx || create_ssl_ctx) {
+               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 (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,61 +1202,252 @@ 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,
-                         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;
-       const char path[] = ":path";
+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));
 
-       UNUSED(flags);
-       UNUSED(user_data);
+       if (request_path == NULL || *request_path == '\0') {
+               return (NULL);
+       }
 
-       switch (frame->hd.type) {
-       case NGHTTP2_HEADERS:
-               if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
-                       break;
+       isc_rwlock_lock(&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;
+                       }
                }
+       }
+       isc_rwlock_unlock(&serversocket->h2.handlers_lock, isc_rwlocktype_read);
 
-               socket = nghttp2_session_get_stream_user_data(
-                       session, frame->hd.stream_id);
-               if (socket == NULL || socket->h2.request_path != NULL) {
-                       break;
+       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;
+       size_t j;
+       for (j = 0; j < valuelen && value[j] != '?'; ++j)
+               ;
+       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, j + 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 (j < valuelen) {
+               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 (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 (isc__nm_parse_doh_query_string((const char *)value + j,
+                                                  &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_send_error_response(const isc_http2_error_responses_t error,
+                          nghttp2_session *ngsession, isc_nmsocket_t *socket);
+
+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);
+
+       switch (frame->hd.type) {
+       case NGHTTP2_HEADERS:
+               if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+                       break;
+               }
+
+               socket = nghttp2_session_get_stream_user_data(
+                       session, frame->hd.stream_id);
+               if (socket == NULL) {
+                       break;
+               }
+
+               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);
 }
 
@@ -771,80 +1495,217 @@ 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 int
-error_reply(nghttp2_session *ngsession, isc_nmsocket_t *socket) {
-       const nghttp2_nv hdrs[] = { MAKE_NV2(":status", "404") };
+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),
+};
 
-       memmove(socket->h2.buf, ERROR_HTML, sizeof(ERROR_HTML));
-       socket->h2.bufsize = sizeof(ERROR_HTML);
+static int
+server_send_error_response(const isc_http2_error_responses_t error,
+                          nghttp2_session *ngsession, isc_nmsocket_t *socket) {
+       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) {
+                       return server_send_response(
+                               ngsession, socket->h2.stream_id,
+                               &error_responses[i].header,
+                               sizeof(error_responses[i].header), socket);
+               }
+       }
+
+       return server_send_error_response(ISC_HTTP_ERROR_GENERIC, ngsession,
+                                         socket);
 }
 
 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;
-
-       if (!socket->h2.request_path) {
-               if (error_reply(ngsession, socket) != 0) {
-                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
-               }
-               return (0);
+       isc_http2_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
+       isc_region_t data;
+
+       /* 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 FIXME do it asynchronously!!! */
-       memcpy(sock->h2.buf, region->base, region->length);
-       sock->h2.bufsize = region->length;
+       /* 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;
+
+       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;
+
+       ievent->req = NULL;
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(VALID_UVREQ(req));
+
+       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 +1745,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 +1786,53 @@ 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, *httpserver;
+
+       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 +1840,54 @@ 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) {
+       (void)ssl;
+       (void)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 +1903,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 +1924,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_rwlock_lock(&sock->h2.handlers_lock, isc_rwlocktype_write);
+               ISC_LINK_INIT(handler, link);
+               ISC_LIST_APPEND(sock->h2.handlers, handler, link);
+               isc_rwlock_unlock(&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 +1968,580 @@ 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->cb = cb;
+       dohcbarg->cbarg = cbarg;
 
        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));
        }
 
+       isc_rwlock_lock(&sock->h2.handlers_lock, isc_rwlocktype_write);
+       ISC_LINK_INIT(dohcbarg, link);
+       ISC_LIST_APPEND(sock->h2.handlers_cbargs, dohcbarg, link);
+       isc_rwlock_unlock(&sock->h2.handlers_lock, isc_rwlocktype_write);
+
        return (result);
 }
+
+void
+isc__nm_http_stoplistening(isc_nmsocket_t *sock) {
+       isc__netievent_httpstop_t *ievent;
+       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 */
+       isc_rwlock_lock(&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;
+       }
+       isc_rwlock_unlock(&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;
+       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;
+
+       INSIST(start != NULL);
+       INSIST(len != 0);
+
+       return true;
+}
+
+static bool
+rule_key_value_pair(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))
+               ;
+       /* EOF */
+       if (!MATCH('\0')) {
+               return false;
+       }
+
+       ADVANCE();
+       return true;
+}
+
+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_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_char(isc_doh_query_parser_state_t *st);
+
+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))
+               ;
+       st->last_value_len = GETP() - st->last_value;
+       return true;
+}
+
+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_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 c8d07aa28819acb9b036c5a69fc0d016a7a4b747..ac59faaf345d6a0d5891e9123f18bf8373cc6cfa 100644 (file)
@@ -48,6 +48,7 @@ TESTS =                       \
        tcp_quota_test  \
        tcpdns_test     \
        tlsdns_test     \
+       doh_test        \
        time_test       \
        timer_test      \
        udp_test
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..0e1fe393aff694b4a380c12a27c336ffab4cccce 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
index 3205e3b93bb9ff7b5bc2db137c7b3e95761a4507..e13294281b477961eab051a9797a4956e8a1dcea 100644 (file)
@@ -7,17 +7,21 @@ libisccfg_la_HEADERS =                        \
        include/isccfg/aclconf.h        \
        include/isccfg/cfg.h            \
        include/isccfg/grammar.h        \
+       include/isccfg/httpconf.h       \
        include/isccfg/kaspconf.h       \
        include/isccfg/log.h            \
-       include/isccfg/namedconf.h
+       include/isccfg/namedconf.h      \
+       include/isccfg/tlsconf.h
 
 libisccfg_la_SOURCES =                 \
        $(libisccfg_la_HEADERS)         \
        aclconf.c                       \
+       httpconf.c                      \
        dnsconf.c                       \
        kaspconf.c                      \
        log.c                           \
        namedconf.c                     \
+       tlsconf.c                       \
        parser.c
 
 libisccfg_la_CPPFLAGS =                        \
diff --git a/lib/isccfg/httpconf.c b/lib/isccfg/httpconf.c
new file mode 100644 (file)
index 0000000..7ae3037
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/util.h>
+
+#include <isccfg/grammar.h>
+#include <isccfg/httpconf.h>
+
+void
+cfg_http_storage_init(isc_mem_t *mctx, isc_cfg_http_storage_t *storage) {
+       REQUIRE(mctx != NULL);
+       REQUIRE(storage != NULL);
+
+       memset(storage, 0, sizeof(*storage));
+       isc_mem_attach(mctx, &storage->mctx);
+       ISC_LIST_INIT(storage->list);
+}
+
+void
+cfg_http_storage_uninit(isc_cfg_http_storage_t *storage) {
+       REQUIRE(storage != NULL);
+
+       cfg_http_storage_clear(storage);
+       isc_mem_detach(&storage->mctx);
+}
+
+void
+cfg_http_storage_clear(isc_cfg_http_storage_t *storage) {
+       REQUIRE(storage != NULL);
+
+       isc_mem_t *mctx = storage->mctx;
+
+       if (!ISC_LIST_EMPTY(storage->list)) {
+               isc_cfg_http_obj_t *http = ISC_LIST_HEAD(storage->list);
+               while (http != NULL) {
+                       isc_cfg_http_obj_t *next = ISC_LIST_NEXT(http, link);
+                       ISC_LIST_DEQUEUE(storage->list, http, link);
+                       storage->nservers--;
+
+                       isc_mem_free(mctx, http->name);
+
+                       if (!ISC_LIST_EMPTY(http->endpoints)) {
+                               isc_cfg_http_endpoint_t *ep =
+                                       ISC_LIST_HEAD(http->endpoints);
+                               while (ep != NULL) {
+                                       isc_cfg_http_endpoint_t *epnext =
+                                               ISC_LIST_NEXT(ep, link);
+                                       isc_mem_free(mctx, ep->path);
+                                       isc_mem_put(mctx, ep, sizeof(*ep));
+                                       ep = epnext;
+                                       http->nendponts--;
+                               }
+                       }
+
+                       isc_mem_put(mctx, http, sizeof(*http));
+                       http = next;
+               }
+       }
+
+       INSIST(storage->nservers == 0);
+}
+
+isc_cfg_http_obj_t *
+cfg_http_find(const char *name, isc_cfg_http_storage_t *storage) {
+       isc_cfg_http_obj_t *http = NULL;
+       REQUIRE(name != NULL && *name != '\0');
+       REQUIRE(storage != NULL);
+
+       for (http = ISC_LIST_HEAD(storage->list); http != NULL;
+            http = ISC_LIST_NEXT(http, link))
+       {
+               if (strcasecmp(name, http->name) == 0) {
+                       break;
+               }
+       }
+
+       return (http);
+}
+
+static isc_result_t
+push_http_obj(const cfg_obj_t *map, isc_cfg_http_storage_t *storage) {
+       isc_mem_t *mctx = storage->mctx;
+       isc_cfg_http_obj_t *new;
+       const cfg_obj_t *endpoints = NULL;
+       const cfg_listelt_t *elt;
+
+       if (!cfg_obj_ismap(map) || map->value.map.id == NULL ||
+           !cfg_obj_isstring(map->value.map.id))
+       {
+               return (ISC_R_FAILURE);
+       }
+
+       if (cfg_http_find(cfg_obj_asstring(map->value.map.id), storage) != NULL)
+       {
+               return (ISC_R_FAILURE);
+       }
+
+       if (cfg_map_get(map, "endpoints", &endpoints) != ISC_R_SUCCESS ||
+           !cfg_obj_islist(endpoints))
+       {
+               return (ISC_R_FAILURE);
+       }
+
+       INSIST(endpoints != NULL);
+
+       new = isc_mem_get(mctx, sizeof(*new));
+       memset(new, 0, sizeof(*new));
+       ISC_LIST_INIT(new->endpoints);
+       new->name = isc_mem_strdup(mctx, cfg_obj_asstring(map->value.map.id));
+
+       for (elt = cfg_list_first(endpoints); elt != NULL;
+            elt = cfg_list_next(elt)) {
+               isc_cfg_http_endpoint_t *newep = NULL;
+               const cfg_obj_t *endp = cfg_listelt_value(elt);
+               newep = isc_mem_get(mctx, sizeof(*newep));
+               ISC_LINK_INIT(newep, link);
+               newep->path = isc_mem_strdup(mctx, cfg_obj_asstring(endp));
+
+               ISC_LIST_PREPEND(new->endpoints, newep, link);
+               new->nendponts++;
+       }
+
+       ISC_LINK_INIT(new, link);
+       ISC_LIST_PREPEND(storage->list, new, link);
+       storage->nservers++;
+       return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+cfg_http_storage_load(const cfg_obj_t *cfg_ctx,
+                     isc_cfg_http_storage_t *storage) {
+       bool found = false;
+       isc_result_t result = ISC_R_SUCCESS;
+       const cfg_obj_t *http = NULL;
+       const cfg_listelt_t *elt;
+       const cfg_obj_t *map = NULL;
+
+       REQUIRE(cfg_ctx != NULL);
+       REQUIRE(storage != NULL);
+
+       cfg_http_storage_clear(storage);
+       result = cfg_map_get(cfg_ctx, "http", &http);
+       if (result != ISC_R_SUCCESS) {
+               /* No statements found, but it is fine. */
+               return (ISC_R_SUCCESS);
+       }
+
+       INSIST(http != NULL);
+
+       for (elt = cfg_list_first(http); elt != NULL; elt = cfg_list_next(elt))
+       {
+               map = cfg_listelt_value(elt);
+               INSIST(map != NULL);
+               found = true;
+               result = push_http_obj(map, storage);
+               if (result != ISC_R_SUCCESS) {
+                       return result;
+               }
+       }
+
+       if (found == true && storage->nservers == 0) {
+               return (ISC_R_FAILURE);
+       }
+
+       return (ISC_R_SUCCESS);
+}
diff --git a/lib/isccfg/include/isccfg/httpconf.h b/lib/isccfg/include/isccfg/httpconf.h
new file mode 100644 (file)
index 0000000..fe836bd
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCFG_HTTPCONF_H
+#define ISCCFG_HTTPCONF_H 1
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/list.h>
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include <dns/types.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/tlsconf.h>
+
+typedef struct isc_cfg_http_endpoint {
+       char *path;
+       LINK(struct isc_cfg_http_endpoint) link;
+} isc_cfg_http_endpoint_t;
+
+typedef struct isc_cfg_http_obj {
+       char *name;
+       LINK(struct isc_cfg_http_obj) link;
+       ISC_LIST(isc_cfg_http_endpoint_t) endpoints;
+       size_t nendponts;
+} isc_cfg_http_obj_t;
+
+typedef struct isc_cfg_http_storage {
+       isc_mem_t *mctx;
+       ISC_LIST(isc_cfg_http_obj_t) list;
+       size_t nservers;
+} isc_cfg_http_storage_t;
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+void
+cfg_http_storage_init(isc_mem_t *mctx, isc_cfg_http_storage_t *storage);
+
+void
+cfg_http_storage_uninit(isc_cfg_http_storage_t *storage);
+
+isc_result_t
+cfg_http_storage_load(const cfg_obj_t *              cfg_ctx,
+                     isc_cfg_http_storage_t *storage);
+
+isc_cfg_http_obj_t *
+cfg_http_find(const char *name, isc_cfg_http_storage_t *storage);
+
+void
+cfg_http_storage_clear(isc_cfg_http_storage_t *storage);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISCCFG_HTTPCONF_H */
diff --git a/lib/isccfg/include/isccfg/tlsconf.h b/lib/isccfg/include/isccfg/tlsconf.h
new file mode 100644 (file)
index 0000000..534236a
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#ifndef ISCCFG_TLSCONF_H
+#define ISCCFG_TLSCONF_H 1
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/list.h>
+#include <isc/mem.h>
+#include <isc/util.h>
+
+#include <dns/types.h>
+
+#include <isccfg/cfg.h>
+
+typedef struct isc_cfg_tls_obj {
+       char *name;
+       char *key_file;
+       char *cert_file;
+       char *dh_param;
+       char *protocols;
+       char *ciphers;
+       LINK(struct isc_cfg_tls_obj) link;
+} isc_cfg_tls_obj_t;
+
+typedef struct isc_cfg_tls_data_storage {
+       isc_mem_t *mctx;
+       size_t     count;
+       ISC_LIST(isc_cfg_tls_obj_t) list;
+} isc_cfg_tls_data_storage_t;
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+void
+cfg_tls_storage_init(isc_mem_t *mctx, isc_cfg_tls_data_storage_t *storage);
+
+void
+cfg_tls_storage_uninit(isc_cfg_tls_data_storage_t *storage);
+
+isc_result_t
+cfg_tls_storage_load(const cfg_obj_t *          cfg_ctx,
+                    isc_cfg_tls_data_storage_t *storage);
+
+isc_cfg_tls_obj_t *
+cfg_tls_storage_find(const char *name, isc_cfg_tls_data_storage_t *storage);
+/*
+ * Looks for TLS key/certificate pair.
+ */
+
+void
+cfg_tls_storage_clear(isc_cfg_tls_data_storage_t *storage);
+
+ISC_LANG_ENDDECLS
+
+#endif /* ISCCFG_TLSCONF_H */
index 75e81cd297d701c80f97b0c116d42e325a2400b9..8656bada9c68b16352682c5fca6c7474321d1a0a 100644 (file)
@@ -81,6 +81,7 @@ static cfg_type_t cfg_type_bracketed_dscpsockaddrlist;
 static cfg_type_t cfg_type_bracketed_namesockaddrkeylist;
 static cfg_type_t cfg_type_bracketed_netaddrlist;
 static cfg_type_t cfg_type_bracketed_sockaddrnameportlist;
+static cfg_type_t cfg_type_bracketed_qstring_list;
 static cfg_type_t cfg_type_controls;
 static cfg_type_t cfg_type_controls_sockaddr;
 static cfg_type_t cfg_type_destinationlist;
@@ -91,8 +92,7 @@ static cfg_type_t cfg_type_dnstap;
 static cfg_type_t cfg_type_dnstapoutput;
 static cfg_type_t cfg_type_dyndb;
 static cfg_type_t cfg_type_plugin;
-static cfg_type_t cfg_type_http_secure_endpoint;
-static cfg_type_t cfg_type_http_secure_server;
+static cfg_type_t cfg_type_http_description;
 static cfg_type_t cfg_type_ixfrdifftype;
 static cfg_type_t cfg_type_ixfrratio;
 static cfg_type_t cfg_type_key;
@@ -110,6 +110,7 @@ static cfg_type_t cfg_type_optional_allow;
 static cfg_type_t cfg_type_optional_class;
 static cfg_type_t cfg_type_optional_dscp;
 static cfg_type_t cfg_type_optional_facility;
+static cfg_type_t cfg_type_optional_http;
 static cfg_type_t cfg_type_optional_keyref;
 static cfg_type_t cfg_type_optional_port;
 static cfg_type_t cfg_type_optional_uint32;
@@ -153,6 +154,7 @@ static cfg_tuplefielddef_t listenon_fields[] = {
        { "port", &cfg_type_optional_port, 0 },
        { "dscp", &cfg_type_optional_dscp, 0 },
        { "tls", &cfg_type_optional_tls, 0 },
+       { "http", &cfg_type_optional_http, 0 },
        { "acl", &cfg_type_bracketed_aml, 0 },
        { NULL, NULL, 0 }
 };
@@ -1090,6 +1092,7 @@ static cfg_clausedef_t namedconf_clauses[] = {
        { "acl", &cfg_type_acl, CFG_CLAUSEFLAG_MULTI },
        { "controls", &cfg_type_controls, CFG_CLAUSEFLAG_MULTI },
        { "dnssec-policy", &cfg_type_dnssecpolicy, CFG_CLAUSEFLAG_MULTI },
+       { "http", &cfg_type_http_description, CFG_CLAUSEFLAG_MULTI },
        { "logging", &cfg_type_logging, 0 },
        { "lwres", NULL, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_ANCIENT },
        { "masters", &cfg_type_primaries, CFG_CLAUSEFLAG_MULTI },
@@ -1207,7 +1210,6 @@ static cfg_clausedef_t options_clauses[] = {
        { "host-statistics", NULL, CFG_CLAUSEFLAG_ANCIENT },
        { "host-statistics-max", NULL, CFG_CLAUSEFLAG_ANCIENT },
        { "hostname", &cfg_type_qstringornone, 0 },
-       { "https-server", &cfg_type_http_secure_server, 0 },
        { "interface-interval", &cfg_type_duration, 0 },
        { "keep-response-order", &cfg_type_bracketed_aml, 0 },
        { "listen-on", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI },
@@ -1224,6 +1226,7 @@ static cfg_clausedef_t options_clauses[] = {
        { "pid-file", &cfg_type_qstringornone, 0 },
        { "port", &cfg_type_uint32, 0 },
        { "tls-port", &cfg_type_uint32, 0 },
+       { "http-port", &cfg_type_uint32, 0 },
        { "https-port", &cfg_type_uint32, 0 },
        { "querylog", &cfg_type_boolean, 0 },
        { "random-device", &cfg_type_qstringornone, 0 },
@@ -1992,7 +1995,6 @@ static cfg_clausedef_t view_clauses[] = {
        { "filter-aaaa-on-v4", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
        { "filter-aaaa-on-v6", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
        { "glue-cache", &cfg_type_boolean, CFG_CLAUSEFLAG_DEPRECATED },
-       { "https-endpoint", &cfg_type_http_secure_endpoint, 0 },
        { "ipv4only-enable", &cfg_type_boolean, 0 },
        { "ipv4only-contact", &cfg_type_astring, 0 },
        { "ipv4only-server", &cfg_type_astring, 0 },
@@ -3858,35 +3860,33 @@ static cfg_type_t cfg_type_optional_tls = {
        "tlsoptional",         parse_optional_keyvalue, print_keyvalue,
        doc_optional_keyvalue, &cfg_rep_string,         &tls_kw
 };
-static cfg_type_t cfg_type_tls = { "tls",          parse_keyvalue,
-                                  print_keyvalue,  doc_keyvalue,
-                                  &cfg_rep_string, &tls_kw };
 
-static keyword_type_t servername_kw = { "https-server", &cfg_type_astring };
-static cfg_type_t cfg_type_servername = { "servername",           parse_keyvalue,
-                                         print_keyvalue,  doc_keyvalue,
-                                         &cfg_rep_string, &servername_kw };
+/* http and https */
 
-/* http-endpoint */
-static cfg_tuplefielddef_t endpoint_fields[] = {
-       { "path", &cfg_type_qstring, 0 },
-       { "servername", &cfg_type_servername, 0 },
+static cfg_type_t cfg_type_bracketed_qstring_list = { "bracketed_qstring_list",
+                                                     cfg_parse_bracketed_list,
+                                                     cfg_print_bracketed_list,
+                                                     cfg_doc_bracketed_list,
+                                                     &cfg_rep_list,
+                                                     &cfg_type_qstring };
+
+static cfg_clausedef_t cfg_http_description_clauses[] = {
+       { "endpoints", &cfg_type_bracketed_qstring_list,
+         CFG_CLAUSEFLAG_EXPERIMENTAL },
        { NULL, NULL, 0 }
 };
-static cfg_type_t cfg_type_http_secure_endpoint = {
-       "endpoint",    cfg_parse_tuple, cfg_print_tuple,
-       cfg_doc_tuple, &cfg_rep_tuple,  endpoint_fields
+
+static cfg_clausedef_t *http_description_clausesets[] = {
+       cfg_http_description_clauses, NULL
 };
 
-/* http-server */
-static cfg_tuplefielddef_t server_fields[] = {
-       { "name", &cfg_type_astring, 0 },
-       { "port", &cfg_type_optional_port, 0 },
-       { "tls", &cfg_type_tls, 0 },
-       { "addresses", &cfg_type_bracketed_sockaddrnameportlist, 0 },
-       { NULL, NULL, 0 }
+static cfg_type_t cfg_type_http_description = {
+       "http_desc", cfg_parse_named_map, cfg_print_map,
+       cfg_doc_map, &cfg_rep_map,        http_description_clausesets
 };
-static cfg_type_t cfg_type_http_secure_server = {
-       "server",      cfg_parse_tuple, cfg_print_tuple,
-       cfg_doc_tuple, &cfg_rep_tuple,  server_fields
+
+static keyword_type_t http_kw = { "http", &cfg_type_astring };
+static cfg_type_t cfg_type_optional_http = {
+       "http_optional",       parse_optional_keyvalue, print_keyvalue,
+       doc_optional_keyvalue, &cfg_rep_string,         &http_kw
 };
diff --git a/lib/isccfg/tlsconf.c b/lib/isccfg/tlsconf.c
new file mode 100644 (file)
index 0000000..52a9245
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * 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.
+ */
+
+#include <string.h>
+
+#include <isc/util.h>
+
+#include <isccfg/grammar.h>
+#include <isccfg/tlsconf.h>
+
+void
+cfg_tls_storage_init(isc_mem_t *mctx, isc_cfg_tls_data_storage_t *storage) {
+       REQUIRE(mctx != NULL);
+       REQUIRE(storage != NULL);
+
+       memset(storage, 0, sizeof(*storage));
+       isc_mem_attach(mctx, &storage->mctx);
+       ISC_LIST_INIT(storage->list);
+}
+
+void
+cfg_tls_storage_uninit(isc_cfg_tls_data_storage_t *storage) {
+       REQUIRE(storage != NULL);
+
+       cfg_tls_storage_clear(storage);
+       isc_mem_detach(&storage->mctx);
+}
+
+void
+cfg_tls_storage_clear(isc_cfg_tls_data_storage_t *storage) {
+       REQUIRE(storage != NULL);
+
+       isc_mem_t *mctx = storage->mctx;
+
+       if (!ISC_LIST_EMPTY(storage->list)) {
+               isc_cfg_tls_obj_t *tls_obj = ISC_LIST_HEAD(storage->list);
+               while (tls_obj != NULL) {
+                       isc_cfg_tls_obj_t *next = ISC_LIST_NEXT(tls_obj, link);
+                       ISC_LIST_DEQUEUE(storage->list, tls_obj, link);
+                       storage->count--;
+
+                       isc_mem_free(mctx, tls_obj->name);
+                       isc_mem_free(mctx, tls_obj->key_file);
+                       isc_mem_free(mctx, tls_obj->cert_file);
+
+                       if (tls_obj->dh_param != NULL) {
+                               isc_mem_free(mctx, tls_obj->dh_param);
+                       }
+
+                       if (tls_obj->protocols != NULL) {
+                               isc_mem_free(mctx, tls_obj->protocols);
+                       }
+
+                       if (tls_obj->ciphers != NULL) {
+                               isc_mem_free(mctx, tls_obj->ciphers);
+                       }
+
+                       isc_mem_put(mctx, tls_obj, sizeof(*tls_obj));
+                       tls_obj = next;
+               }
+       }
+
+       INSIST(storage->count == 0);
+}
+
+static isc_result_t
+push_tls_obj(const cfg_obj_t *map, isc_cfg_tls_data_storage_t *storage) {
+       isc_mem_t *mctx = storage->mctx;
+       isc_cfg_tls_obj_t *new;
+       const cfg_obj_t *key_file = NULL, *cert_file = NULL, *dh_param = NULL,
+                       *protocols = NULL, *ciphers = NULL;
+
+       if (!cfg_obj_ismap(map) || map->value.map.id == NULL ||
+           !cfg_obj_isstring(map->value.map.id))
+       {
+               return (ISC_R_FAILURE);
+       }
+
+       if (cfg_tls_storage_find(cfg_obj_asstring(map->value.map.id),
+                                storage) != NULL) {
+               return (ISC_R_FAILURE);
+       }
+
+       if (cfg_map_get(map, "key-file", &key_file) != ISC_R_SUCCESS ||
+           !cfg_obj_isstring(key_file))
+       {
+               return (ISC_R_FAILURE);
+       }
+
+       if (cfg_map_get(map, "cert-file", &cert_file) != ISC_R_SUCCESS) {
+               return (ISC_R_FAILURE);
+       }
+
+       (void)cfg_map_get(map, "dh-param", &dh_param);
+       (void)cfg_map_get(map, "protocols", &protocols);
+       (void)cfg_map_get(map, "ciphers", &ciphers);
+
+       INSIST(key_file != NULL);
+       INSIST(cert_file != NULL);
+
+       new = isc_mem_get(mctx, sizeof(*new));
+       memset(new, 0, sizeof(*new));
+       new->name = isc_mem_strdup(mctx, cfg_obj_asstring(map->value.map.id));
+       new->key_file = isc_mem_strdup(mctx, cfg_obj_asstring(key_file));
+       new->cert_file = isc_mem_strdup(mctx, cfg_obj_asstring(cert_file));
+
+       if (dh_param != NULL && cfg_obj_isstring(dh_param)) {
+               new->dh_param = isc_mem_strdup(mctx,
+                                              cfg_obj_asstring(dh_param));
+       }
+
+       if (protocols != NULL && cfg_obj_isstring(protocols)) {
+               new->protocols = isc_mem_strdup(mctx,
+                                               cfg_obj_asstring(protocols));
+       }
+
+       if (ciphers != NULL && cfg_obj_isstring(ciphers)) {
+               new->ciphers = isc_mem_strdup(mctx, cfg_obj_asstring(ciphers));
+       }
+
+       ISC_LINK_INIT(new, link);
+       ISC_LIST_PREPEND(storage->list, new, link);
+       storage->count++;
+       return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+cfg_tls_storage_load(const cfg_obj_t *cfg_ctx,
+                    isc_cfg_tls_data_storage_t *storage) {
+       isc_result_t result = ISC_R_SUCCESS;
+       bool found = false;
+       const cfg_obj_t *tls = NULL;
+       const cfg_listelt_t *elt;
+       const cfg_obj_t *map = NULL;
+
+       REQUIRE(cfg_ctx != NULL);
+       REQUIRE(storage != NULL);
+
+       result = cfg_map_get(cfg_ctx, "tls", &tls);
+       if (result != ISC_R_SUCCESS) {
+               /* No tls statements found, but it is fine. */
+               return (ISC_R_SUCCESS);
+       }
+
+       cfg_tls_storage_clear(storage);
+       INSIST(tls != NULL);
+
+       for (elt = cfg_list_first(tls); elt != NULL; elt = cfg_list_next(elt)) {
+               map = cfg_listelt_value(elt);
+               INSIST(map != NULL);
+               found = true;
+               result = push_tls_obj(map, storage);
+               if (result != ISC_R_SUCCESS) {
+                       return result;
+               }
+       }
+
+       if (found == true && storage->count == 0) {
+               return (ISC_R_FAILURE);
+       }
+
+       return (ISC_R_SUCCESS);
+}
+
+isc_cfg_tls_obj_t *
+cfg_tls_storage_find(const char *name, isc_cfg_tls_data_storage_t *storage) {
+       isc_cfg_tls_obj_t *tls_obj = NULL;
+       REQUIRE(storage != NULL);
+
+       if (name == NULL) {
+               return NULL;
+       }
+
+       for (tls_obj = ISC_LIST_HEAD(storage->list); tls_obj != NULL;
+            tls_obj = ISC_LIST_NEXT(tls_obj, link))
+       {
+               if (strcasecmp(name, tls_obj->name) == 0) {
+                       break;
+               }
+       }
+
+       return tls_obj;
+}
index eba897ec3015a9401cc4e041ce050e4b984eadf0..d9bf8d333df8ef9733439bf766a0aa2e92ef12c2 100644 (file)
@@ -23,6 +23,11 @@ cfg_doc_terminal
 cfg_doc_tuple
 cfg_doc_void
 cfg_gettoken
+cfg_http_storage_clear
+cfg_http_storage_init
+cfg_http_storage_uninit
+cfg_http_storage_load
+cfg_http_find
 cfg_is_enum
 cfg_kasp_fromconfig
 cfg_list_first
@@ -65,6 +70,11 @@ cfg_obj_isuint64
 cfg_obj_isvoid
 cfg_obj_line
 cfg_obj_log
+cfg_tls_storage_clear
+cfg_tls_storage_init
+cfg_tls_storage_uninit
+cfg_tls_storage_load
+cfg_tls_storage_find
 cfg_parse_addressed_map
 cfg_parse_astring
 cfg_parse_boolean
index 3d2faaf98008945f47401329f84363fe976ae0ac..8e95a09a91c8aa57fda4d03b766c3376bd854924 100644 (file)
     <ClCompile Include="..\dnsconf.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\httpconf.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\kaspconf.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\log.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\tlsconf.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\namedconf.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClInclude Include="..\include\isccfg\grammar.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\include\isccfg\httpconf.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\include\isccfg\kaspconf.h">
       <Filter>Header Files</Filter>
     </ClInclude>
     <ClInclude Include="..\include\isccfg\log.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\include\isccfg\tlsconf.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\include\isccfg\namedconf.h">
       <Filter>Header Files</Filter>
     </ClInclude>
index e92129ed1f0df18180b62c0c5cdd548fbdc7d9bd..eae6970d6decef38e79c32ee90b439ad9a333836 100644 (file)
   <ItemGroup>
     <ClCompile Include="..\aclconf.c" />
     <ClCompile Include="..\dnsconf.c" />
+    <ClCompile Include="..\httpconf.c" />
     <ClCompile Include="..\kaspconf.c" />
     <ClCompile Include="..\log.c" />
     <ClCompile Include="..\namedconf.c" />
+    <ClCompile Include="..\tlsconf.c" />
     <ClCompile Include="..\parser.c" />
     <ClCompile Include="DLLMain.c" />
   </ItemGroup>
     <ClInclude Include="..\include\isccfg\aclconf.h" />
     <ClInclude Include="..\include\isccfg\cfg.h" />
     <ClInclude Include="..\include\isccfg\grammar.h" />
+    <ClInclude Include="..\include\isccfg\httpconf.h" />
     <ClInclude Include="..\include\isccfg\kaspconf.h" />
     <ClInclude Include="..\include\isccfg\log.h" />
     <ClInclude Include="..\include\isccfg\namedconf.h" />
+    <ClInclude Include="..\include\isccfg\tlsconf.h" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\..\lib\isc\win32\libisc.vcxproj">
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..6a4c244f9925a5c81916dfb64f39e2dbd3b5772f 100644 (file)
@@ -30,24 +30,56 @@ 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 +88,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 50e8d87122724183b33fce44972a9d64f9f646be..61050eaa985dba2170df543a3967455988a3aede 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/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/httpconf.c                        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/tlsconf.c                 C       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