]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
refactor outgoing HTTP connection support
authorEvan Hunt <each@isc.org>
Thu, 4 Feb 2021 00:59:49 +0000 (16:59 -0800)
committerArtem Boldariev <artem@boldariev.com>
Fri, 5 Mar 2021 11:29:26 +0000 (13:29 +0200)
- style, cleanup, and removal of unnecessary code.
- combined isc_nm_http_add_endpoint() and isc_nm_http_add_doh_endpoint()
  into one function, renamed isc_http_endpoint().
- moved isc_nm_http_connect_send_request() into doh_test.c as a helper
  function; remove it from the public API.
- renamed isc_http2 and isc_nm_http2 types and functions to just isc_http
  and isc_nm_http, for consistency with other existing names.
- shortened a number of long names.
- the caller is now responsible for determining the peer address.
  in isc_nm_httpconnect(); this eliminates the need to parse the URI
  and the dependency on an external resolver.
- the caller is also now responsible for creating the SSL client context,
  for consistency with isc_nm_tlsdnsconnect().
- added setter functions for HTTP/2 ALPN. instead of setting up ALPN in
  isc_tlsctx_createclient(), we now have a function
  isc_tlsctx_enable_http2client_alpn() that can be run from
  isc_nm_httpconnect().
- refactored isc_nm_httprequest() into separate read and send functions.
  isc_nm_send() or isc_nm_read() is called on an http socket, it will
  be stored until a corresponding isc_nm_read() or _send() arrives; when
  we have both halves of the pair the HTTP request will be initiated.
- isc_nm_httprequest() is renamed isc__nm_http_request() for use as an
  internal helper function by the DoH unit test. (eventually doh_test
  should be rewritten to use read and send, and this function should
  be removed.)
- added implementations of isc__nm_tls_settimeout() and
  isc__nm_http_settimeout().
- increased NGHTTP2 header block length for client connections to 128K.
- use isc_mem_t for internal memory allocations inside nghttp2, to
  help track memory leaks.
- send "Cache-Control" header in requests and responses. (note:
  currently we try to bypass HTTP caching proxies, but ideally we should
  interact with them: https://tools.ietf.org/html/rfc8484#section-5.1)

17 files changed:
bin/named/transportconf.c
lib/dns/include/dns/transport.h
lib/dns/transport.c
lib/isc/include/isc/netmgr.h
lib/isc/include/isc/result.h
lib/isc/include/isc/tls.h
lib/isc/netmgr/http.c
lib/isc/netmgr/netmgr-int.h
lib/isc/netmgr/netmgr.c
lib/isc/netmgr/tcp.c
lib/isc/netmgr/tlsdns.c
lib/isc/netmgr/tlsstream.c
lib/isc/result.c
lib/isc/tests/doh_test.c
lib/isc/tls.c
lib/isc/win32/libisc.def.in
lib/ns/interfacemgr.c

index 3a1bfb4202f656532d4d3470bfaaf5b80da43c1d..ea696af73b5205750b8cecbdde00e454aa8fff9f 100644 (file)
@@ -64,7 +64,7 @@ add_doh_transports(const cfg_obj_t *transportlist, dns_transport_list_t *list) {
 
                create_name(dohid, &dohname);
 
-               transport = dns_transport_new(&dohname, DNS_TRANSPORT_DOH,
+               transport = dns_transport_new(&dohname, DNS_TRANSPORT_HTTP,
                                              list);
 
                parse_transport_option(doh, transport, "key-file",
index 0b031528749dd7ba412e98be419fb2ae9b7ae029..c4a0e945d77fa6a0a29cab2adeda79d6baa0fd9f 100644 (file)
@@ -18,14 +18,14 @@ typedef enum {
        DNS_TRANSPORT_UDP = 1,
        DNS_TRANSPORT_TCP = 2,
        DNS_TRANSPORT_TLS = 3,
-       DNS_TRANSPORT_DOH = 4,
+       DNS_TRANSPORT_HTTP = 4,
        DNS_TRANSPORT_COUNT = 5,
 } dns_transport_type_t;
 
 typedef enum {
-       DNS_DOH_GET = 0,
-       DNS_DOH_POST = 1,
-} dns_doh_mode_t;
+       DNS_HTTP_GET = 0,
+       DNS_HTTP_POST = 1,
+} dns_http_mode_t;
 
 typedef struct dns_transport     dns_transport_t;
 typedef struct dns_transport_list dns_transport_list_t;
@@ -50,11 +50,11 @@ char *
 dns_transport_get_hostname(dns_transport_t *transport);
 char *
 dns_transport_get_endpoint(dns_transport_t *transport);
-dns_doh_mode_t
+dns_http_mode_t
 dns_transport_get_mode(dns_transport_t *transport);
 /*%<
  * Getter functions: return the type, cert file, key file, CA file,
- * hostname, DoH endpoint, or DoH mode (GET or POST) for 'transport'.
+ * hostname, HTTP endpoint, or HTTP mode (GET or POST) for 'transport'.
  */
 
 void
@@ -68,16 +68,16 @@ dns_transport_set_hostname(dns_transport_t *transport, const char *hostname);
 void
 dns_transport_set_endpoint(dns_transport_t *transport, const char *endpoint);
 void
-dns_transport_set_mode(dns_transport_t *transport, dns_doh_mode_t mode);
+dns_transport_set_mode(dns_transport_t *transport, dns_http_mode_t mode);
 /*%<
  * Setter functions: set the type, cert file, key file, CA file,
- * hostname, DoH endpoint, or DoH mode (GET or POST) for 'transport'.
+ * hostname, HTTP endpoint, or HTTP mode (GET or POST) for 'transport'.
  *
  * Requires:
  *\li  'transport' is valid.
- *\li  'transport' is of type DNS_TRANSPORT_TLS or DNS_TRANSPORT_DOH
+ *\li  'transport' is of type DNS_TRANSPORT_TLS or DNS_TRANSPORT_HTTP
  *     (for certfile, keyfile, cafile, or hostname).
- *\li  'transport' is of type DNS_TRANSPORT_DOH (for endpoint or mode).
+ *\li  'transport' is of type DNS_TRANSPORT_HTTP (for endpoint or mode).
  */
 
 void
index 2dcc8a9d0f87a130702486904d65482073dcfdab..0ad1159fb3c1c564b11bb6e127172cb0e8fba2dc 100644 (file)
@@ -50,7 +50,7 @@ struct dns_transport {
        } tls;
        struct {
                char *endpoint;
-               dns_doh_mode_t mode;
+               dns_http_mode_t mode;
        } doh;
 };
 
@@ -124,7 +124,7 @@ dns_transport_get_endpoint(dns_transport_t *transport) {
        return (transport->doh.endpoint);
 }
 
-dns_doh_mode_t
+dns_http_mode_t
 dns_transport_get_mode(dns_transport_t *transport) {
        REQUIRE(VALID_TRANSPORT(transport));
 
@@ -150,7 +150,7 @@ void
 dns_transport_set_certfile(dns_transport_t *transport, const char *certfile) {
        REQUIRE(VALID_TRANSPORT(transport));
        REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
-               transport->type == DNS_TRANSPORT_DOH);
+               transport->type == DNS_TRANSPORT_HTTP);
 
        if (certfile != NULL) {
                transport->tls.certfile = isc_mem_strdup(transport->mctx,
@@ -162,7 +162,7 @@ void
 dns_transport_set_keyfile(dns_transport_t *transport, const char *keyfile) {
        REQUIRE(VALID_TRANSPORT(transport));
        REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
-               transport->type == DNS_TRANSPORT_DOH);
+               transport->type == DNS_TRANSPORT_HTTP);
 
        if (keyfile != NULL) {
                transport->tls.keyfile = isc_mem_strdup(transport->mctx,
@@ -174,7 +174,7 @@ void
 dns_transport_set_cafile(dns_transport_t *transport, const char *cafile) {
        REQUIRE(VALID_TRANSPORT(transport));
        REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
-               transport->type == DNS_TRANSPORT_DOH);
+               transport->type == DNS_TRANSPORT_HTTP);
 
        if (cafile != NULL) {
                transport->tls.cafile = isc_mem_strdup(transport->mctx, cafile);
@@ -185,7 +185,7 @@ void
 dns_transport_set_hostname(dns_transport_t *transport, const char *hostname) {
        REQUIRE(VALID_TRANSPORT(transport));
        REQUIRE(transport->type == DNS_TRANSPORT_TLS ||
-               transport->type == DNS_TRANSPORT_DOH);
+               transport->type == DNS_TRANSPORT_HTTP);
 
        if (hostname != NULL) {
                transport->tls.hostname = isc_mem_strdup(transport->mctx,
@@ -196,7 +196,7 @@ dns_transport_set_hostname(dns_transport_t *transport, const char *hostname) {
 void
 dns_transport_set_endpoint(dns_transport_t *transport, const char *endpoint) {
        REQUIRE(VALID_TRANSPORT(transport));
-       REQUIRE(transport->type == DNS_TRANSPORT_DOH);
+       REQUIRE(transport->type == DNS_TRANSPORT_HTTP);
 
        if (endpoint != NULL) {
                transport->doh.endpoint = isc_mem_strdup(transport->mctx,
@@ -205,9 +205,9 @@ dns_transport_set_endpoint(dns_transport_t *transport, const char *endpoint) {
 }
 
 void
-dns_transport_set_mode(dns_transport_t *transport, dns_doh_mode_t mode) {
+dns_transport_set_mode(dns_transport_t *transport, dns_http_mode_t mode) {
        REQUIRE(VALID_TRANSPORT(transport));
-       REQUIRE(transport->type == DNS_TRANSPORT_DOH);
+       REQUIRE(transport->type == DNS_TRANSPORT_HTTP);
 
        transport->doh.mode = mode;
 }
index cb505f92378492bb7a961a35fa85312c62f9a964..fd670b190c968676810d05a9ed68fe8dbb901962 100644 (file)
@@ -506,45 +506,17 @@ isc_nm_tlsdnsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
  * 'cb'.
  */
 
-typedef void (*isc_nm_http_cb_t)(isc_nmhandle_t *handle, isc_result_t eresult,
-                                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.
- * 'data' contains the received data, if any. It will be freed
- *          after return by caller.
- * 'cbarg'  the callback argument passed to listen function.
- */
-
-isc_result_t
-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_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_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,
-                        isc_nm_http_cb_t cb, void *cbarg,
-                        size_t extrahandlesize);
-
-isc_result_t
-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_nm_http_endpoint(isc_nmsocket_t *sock, const char *uri, isc_nm_recv_cb_t cb,
+                    void *cbarg, size_t extrahandlesize);
index cfc3f03ad7ae078ab626cdef55a71a3b16b1b5f1..22969775dfef09e2a5616bd6545be11d19f23764 100644 (file)
 #define ISC_R_DEFAULT         68           /*%< default */
 #define ISC_R_IPV4PREFIX       69          /*%< IPv4 prefix */
 #define ISC_R_TLSERROR        70           /*%< TLS error */
+#define ISC_R_HTTP2ALPNERROR   71          /*%< ALPN for HTTP/2 failed */
 
 /*% Not a result code: the number of results. */
-#define ISC_R_NRESULTS 71
+#define ISC_R_NRESULTS 72
 
 ISC_LANG_BEGINDECLS
 
index 3741f19f5efadf22d0a7940c4b40f7d53fe11ea1..38c990648ba4407bf1ce427945a8fca6391b6fc3 100644 (file)
 #include <isc/mem.h>
 #include <isc/region.h>
 #include <isc/result.h>
-#include <isc/tls.h>
 #include <isc/types.h>
 
 typedef struct ssl_ctx_st isc_tlsctx_t;
+typedef struct ssl_st    isc_tls_t;
 
 void
 isc_tlsctx_free(isc_tlsctx_t **ctpx);
-/*%
- * Free the TLS client/server context.
+/*%<
+ * Free a TLS client or server context.
  *
- * Require:
+ * Requires:
  *\li  'ctxp' != NULL and '*ctxp' != NULL.
  */
 
 isc_result_t
 isc_tlsctx_createserver(const char *keyfile, const char *certfile,
                        isc_tlsctx_t **ctxp);
-/*%
- * Set up TLS server context.
+/*%<
+ * Set up a TLS server context, using the key and certificate specified in
+ * 'keyfile' and 'certfile', or a self-generated ephemeral key and
+ * certificdate if both 'keyfile' and 'certfile' are NULL.
  *
- * Require:
+ * Requires:
  *\li  'ctxp' != NULL and '*ctxp' == NULL.
+ *\li  'keyfile' and 'certfile' are either both NULL or both non-NULL.
  */
 
 isc_result_t
 isc_tlsctx_createclient(isc_tlsctx_t **ctxp);
-/*%
- * Set up TLS client context.
+/*%<
+ * Set up TLS client context.
  *
- * Require:
+ * Requires:
  *\li  'ctxp' != NULL and '*ctxp' == NULL.
  */
+
+isc_tls_t *
+isc_tls_create(isc_tlsctx_t *ctx);
+/*%<
+ * Set up the structure to hold data for a new TLS connection.
+ *
+ * Requires:
+ *\li  'ctx' != NULL.
+ */
+
+void
+isc_tls_free(isc_tls_t **tlsp);
+/*%<
+ * Free a TLS structure.
+ *
+ * Requires:
+ *\li  'tlsp' != NULL and '*tlsp' != NULL.
+ */
+
+void
+isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx);
+void
+isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *ctx);
+/*%<
+ *
+ * Enable HTTP/2 Application Layer Protocol Negotation for 'ctx'.
+ *
+ * Requires:
+ *\li  'ctx' is not NULL.
+ */
+
+void
+isc_tls_get_http2_alpn(isc_tls_t *tls, const unsigned char **alpn,
+                      unsigned int *alpnlen);
index 51adbea6f6888720c09d9a48779ef7257217eb48..bf66ef0bd219a734028dfd634f4a1143b1ae4d37 100644 (file)
 #include <signal.h>
 #include <string.h>
 
-#include <openssl/conf.h>
-#include <openssl/err.h>
-#include <openssl/ssl.h>
-
 #include <isc/base64.h>
 #include <isc/netmgr.h>
 #include <isc/print.h>
 
 #define MAX_DNS_MESSAGE_SIZE (UINT16_MAX)
 
+#define DNS_MEDIA_TYPE "application/dns-message"
+
+#define DEFAULT_CACHE_CONTROL "no-cache, no-store"
+
 #define HEADER_MATCH(header, name, namelen)   \
        (((namelen) == sizeof(header) - 1) && \
         (strncasecmp((header), (const char *)(name), (namelen)) == 0))
 
-typedef struct isc_nm_http2_response_status {
+typedef struct isc_nm_http_response_status {
        size_t code;
        size_t content_length;
        bool content_type_valid;
-} isc_nm_http2_response_status_t;
+} isc_nm_http_response_status_t;
 
-typedef struct http2_client_stream {
+typedef struct http_cstream {
        isc_nm_recv_cb_t read_cb;
        void *read_cbarg;
-
        isc_nm_cb_t connect_cb;
        void *connect_cbarg;
 
+       bool sending;
+       bool reading;
+
        char *uri;
        isc_url_parser_t up;
 
@@ -68,42 +70,41 @@ typedef struct http2_client_stream {
        char *GET_path;
        size_t GET_path_len;
 
-       isc_nm_http2_response_status_t response_status;
+       isc_nm_http_response_status_t response_status;
 
-       LINK(struct http2_client_stream) link;
-} http2_client_stream_t;
+       LINK(struct http_cstream) link;
+} http_cstream_t;
 
 #define HTTP2_SESSION_MAGIC    ISC_MAGIC('H', '2', 'S', 'S')
 #define VALID_HTTP2_SESSION(t) ISC_MAGIC_VALID(t, HTTP2_SESSION_MAGIC)
 
-struct isc_nm_http2_session {
+struct isc_nm_http_session {
        unsigned int magic;
+       isc_refcount_t references;
        isc_mem_t *mctx;
+
        bool sending;
-       bool closed;
        bool reading;
+       bool closed;
 
        nghttp2_session *ngsession;
        bool client;
 
-       ISC_LIST(http2_client_stream_t) cstreams;
+       ISC_LIST(http_cstream_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_nmiface_t server_iface;
 
        isc_region_t r;
        uint8_t buf[MAX_DNS_MESSAGE_SIZE];
        size_t bufsize;
 
-       bool ssl_ctx_created;
-       SSL_CTX *ssl_ctx;
+       isc_tlsctx_t *tlsctx;
 };
 
-typedef enum isc_http2_error_responses {
+typedef enum isc_http_error_responses {
        ISC_HTTP_ERROR_SUCCESS,                /* 200 */
        ISC_HTTP_ERROR_NOT_FOUND,              /* 404 */
        ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE,      /* 413 */
@@ -113,22 +114,28 @@ typedef enum isc_http2_error_responses {
        ISC_HTTP_ERROR_NOT_IMPLEMENTED,        /* 501 */
        ISC_HTTP_ERROR_GENERIC,                /* 500 Internal Server Error */
        ISC_HTTP_ERROR_MAX
-} isc_http2_error_responses_t;
+} isc_http_error_responses_t;
 
 static void
-http2_do_bio(isc_nm_http2_session_t *session);
+http_do_bio(isc_nm_http_session_t *session);
 
 static void
 failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result,
-                         isc_nm_http2_session_t *session);
+                         isc_nm_http_session_t *session);
 
 static void
-failed_read_cb(isc_result_t result, isc_nm_http2_session_t *session);
+failed_read_cb(isc_result_t result, isc_nm_http_session_t *session);
 
-static int
-server_send_error_response(const isc_http2_error_responses_t error,
+static isc_result_t
+server_send_error_response(const isc_http_error_responses_t error,
                           nghttp2_session *ngsession, isc_nmsocket_t *socket);
 
+static isc_result_t
+client_send(isc_nmhandle_t *handle, const isc_region_t *region);
+
+static void
+finish_http_session(isc_nm_http_session_t *session);
+
 static bool
 inactive(isc_nmsocket_t *sock) {
        return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
@@ -136,9 +143,107 @@ inactive(isc_nmsocket_t *sock) {
                (sock->server != NULL && !isc__nmsocket_active(sock->server)));
 }
 
-static http2_client_stream_t *
-find_http2_client_stream(int32_t stream_id, isc_nm_http2_session_t *session) {
-       http2_client_stream_t *cstream = NULL;
+static void *
+http_malloc(size_t sz, isc_mem_t *mctx) {
+       return (isc_mem_allocate(mctx, sz));
+}
+
+static void *
+http_calloc(size_t n, size_t sz, isc_mem_t *mctx) {
+       const size_t msize = n * sz;
+       void *data = isc_mem_allocate(mctx, msize);
+
+       memset(data, 0, msize);
+       return (data);
+}
+
+static void *
+http_realloc(void *p, size_t newsz, isc_mem_t *mctx) {
+       return (isc_mem_reallocate(mctx, p, newsz));
+}
+
+static void
+http_free(void *p, isc_mem_t *mctx) {
+       if (p == NULL) { /* as standard free() behaves */
+               return;
+       }
+       isc_mem_free(mctx, p);
+}
+
+static void
+init_nghttp2_mem(isc_mem_t *mctx, nghttp2_mem *mem) {
+       *mem = (nghttp2_mem){ .malloc = (nghttp2_malloc)http_malloc,
+                             .calloc = (nghttp2_calloc)http_calloc,
+                             .realloc = (nghttp2_realloc)http_realloc,
+                             .free = (nghttp2_free)http_free,
+                             .mem_user_data = mctx };
+}
+
+static void
+new_session(isc_mem_t *mctx, isc_tlsctx_t *tctx,
+           isc_nm_http_session_t **sessionp) {
+       isc_nm_http_session_t *session = NULL;
+
+       REQUIRE(sessionp != NULL && *sessionp == NULL);
+       REQUIRE(mctx != NULL);
+
+       session = isc_mem_get(mctx, sizeof(isc_nm_http_session_t));
+       *session = (isc_nm_http_session_t){ .magic = HTTP2_SESSION_MAGIC,
+                                           .tlsctx = tctx };
+       isc_refcount_init(&session->references, 1);
+       isc_mem_attach(mctx, &session->mctx);
+       ISC_LIST_INIT(session->cstreams);
+       ISC_LIST_INIT(session->sstreams);
+
+       *sessionp = session;
+}
+
+void
+isc__nm_httpsession_attach(isc_nm_http_session_t *source,
+                          isc_nm_http_session_t **targetp) {
+       REQUIRE(VALID_HTTP2_SESSION(source));
+       REQUIRE(targetp != NULL && *targetp == NULL);
+
+       isc_refcount_increment(&source->references);
+
+       *targetp = source;
+}
+
+void
+isc__nm_httpsession_detach(isc_nm_http_session_t **sessionp) {
+       isc_nm_http_session_t *session = NULL;
+
+       REQUIRE(sessionp != NULL);
+
+       session = *sessionp;
+       *sessionp = NULL;
+
+       REQUIRE(VALID_HTTP2_SESSION(session));
+
+       if (isc_refcount_decrement(&session->references) > 1) {
+               return;
+       }
+
+       finish_http_session(session);
+
+       if (session->r.base) {
+               isc_mem_put(session->mctx, session->r.base, session->r.length);
+       }
+
+       INSIST(ISC_LIST_EMPTY(session->sstreams));
+       INSIST(ISC_LIST_EMPTY(session->cstreams));
+
+       /* We need an acquire memory barrier here */
+       (void)isc_refcount_current(&session->references);
+
+       session->magic = 0;
+       isc_mem_putanddetach(&session->mctx, session,
+                            sizeof(isc_nm_http_session_t));
+}
+
+static http_cstream_t *
+find_http_cstream(int32_t stream_id, isc_nm_http_session_t *session) {
+       http_cstream_t *cstream = NULL;
        REQUIRE(VALID_HTTP2_SESSION(session));
 
        for (cstream = ISC_LIST_HEAD(session->cstreams); cstream != NULL;
@@ -153,24 +258,27 @@ find_http2_client_stream(int32_t stream_id, isc_nm_http2_session_t *session) {
 }
 
 static isc_result_t
-get_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t **streamp,
-                       const char *uri) {
-       http2_client_stream_t *stream = NULL;
+new_http_cstream(isc_nmsocket_t *sock, http_cstream_t **streamp) {
+       isc_mem_t *mctx = sock->mgr->mctx;
+       const char *uri = NULL;
+       bool post;
+       http_cstream_t *stream = NULL;
        isc_result_t result;
 
-       REQUIRE(streamp != NULL && *streamp == NULL);
-       REQUIRE(uri != NULL);
-
-       stream = isc_mem_get(mctx, sizeof(http2_client_stream_t));
-       *stream = (http2_client_stream_t){ .stream_id = -1, .rbufsize = 0 };
+       uri = sock->h2.session->handle->sock->h2.connect.uri;
+       post = sock->h2.session->handle->sock->h2.connect.post;
 
-       stream->uri = isc_mem_strdup(mctx, uri);
+       stream = isc_mem_get(mctx, sizeof(http_cstream_t));
+       *stream = (http_cstream_t){ .stream_id = -1,
+                                   .post = post,
+                                   .uri = isc_mem_strdup(mctx, uri) };
+       ISC_LINK_INIT(stream, link);
 
        result = isc_url_parse(stream->uri, strlen(stream->uri), 0,
                               &stream->up);
        if (result != ISC_R_SUCCESS) {
                isc_mem_free(mctx, stream->uri);
-               isc_mem_put(mctx, stream, sizeof(http2_client_stream_t));
+               isc_mem_put(mctx, stream, sizeof(http_cstream_t));
                return (result);
        }
 
@@ -215,18 +323,13 @@ get_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t **streamp,
                        stream->up.field_data[ISC_UF_QUERY].len);
        }
 
-       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;
 
        return (ISC_R_SUCCESS);
 }
 
 static void
-put_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t *stream) {
+put_http_cstream(isc_mem_t *mctx, http_cstream_t *stream) {
        isc_mem_put(mctx, stream->path, stream->pathlen);
        isc_mem_put(mctx, stream->authority,
                    stream->up.field_data[ISC_UF_HOST].len + AUTHEXTRA);
@@ -240,41 +343,31 @@ put_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t *stream) {
                isc_mem_put(mctx, stream->postdata.base,
                            stream->postdata.length);
        }
-       isc_mem_put(mctx, stream, sizeof(http2_client_stream_t));
-}
-
-static void
-delete_http2_session(isc_nm_http2_session_t *session) {
-       REQUIRE(ISC_LIST_EMPTY(session->sstreams));
-       REQUIRE(ISC_LIST_EMPTY(session->cstreams));
-
-       session->magic = 0;
-       if (session->ssl_ctx_created) {
-               SSL_CTX_free(session->ssl_ctx);
-       }
-       isc_mem_putanddetach(&session->mctx, session,
-                            sizeof(isc_nm_http2_session_t));
+       isc_mem_put(mctx, stream, sizeof(http_cstream_t));
 }
 
 static void
-finish_http2_session(isc_nm_http2_session_t *session) {
+finish_http_session(isc_nm_http_session_t *session) {
        if (session->handle != NULL) {
                isc_nm_pauseread(session->handle);
                isc_nmhandle_detach(&session->handle);
        }
+
        if (session->ngsession != NULL) {
                nghttp2_session_del(session->ngsession);
                session->ngsession = NULL;
        }
+
        if (!ISC_LIST_EMPTY(session->cstreams)) {
-               http2_client_stream_t *cstream =
-                       ISC_LIST_HEAD(session->cstreams);
+               http_cstream_t *cstream = ISC_LIST_HEAD(session->cstreams);
                while (cstream != NULL) {
-                       http2_client_stream_t *next = ISC_LIST_NEXT(cstream,
-                                                                   link);
+                       http_cstream_t *next = ISC_LIST_NEXT(cstream, link);
                        ISC_LIST_DEQUEUE(session->cstreams, cstream, link);
-                       put_http2_client_stream(session->mctx, cstream);
-
+                       cstream->read_cb(session->handle, ISC_R_UNEXPECTED,
+                                        &(isc_region_t){ cstream->rbuf,
+                                                         cstream->rbufsize },
+                                        cstream->read_cbarg);
+                       put_http_cstream(session->mctx, cstream);
                        cstream = next;
                }
        }
@@ -294,193 +387,179 @@ finish_http2_session(isc_nm_http2_session_t *session) {
 }
 
 static int
-on_data_chunk_recv_callback(nghttp2_session *ngsession, uint8_t flags,
-                           int32_t stream_id, const uint8_t *data, size_t len,
-                           void *user_data) {
-       isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data;
-
-       UNUSED(ngsession);
-       UNUSED(flags);
-
-       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);
-                       }
+on_client_data_chunk_recv_callback(int32_t stream_id, const uint8_t *data,
+                                  size_t len, isc_nm_http_session_t *session) {
+       http_cstream_t *cstream = find_http_cstream(stream_id, session);
+
+       if (cstream != NULL) {
+               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_CALLBACK_FAILURE);
+                       return (NGHTTP2_ERR_TEMPORAL_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) {
-                               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);
-                               }
+               return (NGHTTP2_ERR_CALLBACK_FAILURE);
+       }
+
+       return (0);
+}
+
+static int
+on_server_data_chunk_recv_callback(int32_t stream_id, const uint8_t *data,
+                                  size_t len, isc_nm_http_session_t *session) {
+       isc_nmsocket_h2_t *h2 = ISC_LIST_HEAD(session->sstreams);
+       while (h2 != NULL) {
+               if (stream_id == h2->stream_id) {
+                       size_t new_bufsize = h2->bufsize + len;
+                       if (new_bufsize <= MAX_DNS_MESSAGE_SIZE &&
+                           new_bufsize <= h2->content_length) {
+                               memmove(h2->buf + h2->bufsize, data, len);
+                               h2->bufsize = new_bufsize;
                                break;
                        }
-                       sock_h2 = ISC_LIST_NEXT(sock_h2, link);
-               }
-               if (sock_h2 == NULL) {
-                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
+
+                       return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
                }
+               h2 = ISC_LIST_NEXT(h2, link);
+       }
+       if (h2 == NULL) {
+               return (NGHTTP2_ERR_CALLBACK_FAILURE);
        }
 
        return (0);
 }
 
 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));
+on_data_chunk_recv_callback(nghttp2_session *ngsession, uint8_t flags,
+                           int32_t stream_id, const uint8_t *data, size_t len,
+                           void *user_data) {
+       isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+       int rv;
 
-       UNUSED(error_code);
+       UNUSED(ngsession);
+       UNUSED(flags);
 
-       /* 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);
-               }
+               rv = on_client_data_chunk_recv_callback(stream_id, data, len,
+                                                       session);
        } else {
-               isc_nmsocket_t *sock = nghttp2_session_get_stream_user_data(
-                       ngsession, stream_id);
-               if (ISC_LIST_EMPTY(session->sstreams)) {
+               rv = on_server_data_chunk_recv_callback(stream_id, data, len,
+                                                       session);
+       }
+
+       return (rv);
+}
+
+static int
+on_client_stream_close_callback(int32_t stream_id,
+                               isc_nm_http_session_t *session) {
+       http_cstream_t *cstream = find_http_cstream(stream_id, session);
+
+       if (cstream != NULL) {
+               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_http_cstream(session->mctx, cstream);
+               if (ISC_LIST_EMPTY(session->cstreams)) {
+                       int rv = 0;
                        rv = nghttp2_session_terminate_session(
-                               ngsession, NGHTTP2_NO_ERROR);
-               }
-               isc__nmsocket_prep_destroy(sock);
-               if (rv != 0) {
-                       return (NGHTTP2_ERR_CALLBACK_FAILURE);
+                               session->ngsession, NGHTTP2_NO_ERROR);
+                       if (rv != 0) {
+                               return (rv);
+                       }
+                       if (session->handle->sock->h2.session->reading) {
+                               isc_nm_cancelread(session->handle->sock->h2
+                                                         .session->handle);
+                       }
                }
+       } else {
+               return (NGHTTP2_ERR_CALLBACK_FAILURE);
        }
 
        return (0);
 }
 
-#ifndef OPENSSL_NO_NEXTPROTONEG
-/*
- * NPN TLS extension client callback. We check that server advertised
- * the HTTP/2 protocol the nghttp2 library supports.
- */
 static int
-select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
-                    const unsigned char *in, unsigned int inlen, void *arg) {
-       UNUSED(ssl);
-       UNUSED(arg);
+on_server_stream_close_callback(int32_t stream_id,
+                               isc_nm_http_session_t *session) {
+       isc_nmsocket_t *sock = nghttp2_session_get_stream_user_data(
+               session->ngsession, stream_id);
+       int rv = 0;
 
-       if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
-               return (SSL_TLSEXT_ERR_NOACK);
+       if (ISC_LIST_EMPTY(session->sstreams)) {
+               rv = nghttp2_session_terminate_session(session->ngsession,
+                                                      NGHTTP2_NO_ERROR);
        }
-       return (SSL_TLSEXT_ERR_OK);
+       isc__nmsocket_prep_destroy(sock);
+       return (rv);
 }
-#endif /* !OPENSSL_NO_NEXTPROTONEG */
 
-/* Create SSL_CTX. */
-static SSL_CTX *
-create_client_ssl_ctx(void) {
-       SSL_CTX *ssl_ctx = NULL;
+static int
+on_stream_close_callback(nghttp2_session *ngsession, int32_t stream_id,
+                        uint32_t error_code, void *user_data) {
+       isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+       int rv = 0;
 
-       RUNTIME_CHECK(isc_tlsctx_createclient(&ssl_ctx) == ISC_R_SUCCESS);
-       RUNTIME_CHECK(ssl_ctx != NULL);
+       REQUIRE(VALID_HTTP2_SESSION(session));
+       REQUIRE(session->ngsession == ngsession);
 
-#ifndef OPENSSL_NO_NEXTPROTONEG
-       SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
-#endif /* !OPENSSL_NO_NEXTPROTONEG */
+       UNUSED(error_code);
 
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L
-       SSL_CTX_set_alpn_protos(ssl_ctx,
-                               (const unsigned char *)NGHTTP2_PROTO_ALPN,
-                               NGHTTP2_PROTO_ALPN_LEN);
-#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
+       /*
+        * NOTE: calling isc_nm_cancelread() or isc__nmsocket_prep_destroy()
+        * on a socket will lead to an indirect call to detach the session,
+        * which will, in turn, perform required stream cleanup.
+        */
+       if (session->client) {
+               rv = on_client_stream_close_callback(stream_id, session);
+       } else {
+               rv = on_server_stream_close_callback(stream_id, session);
+       }
 
-       ENSURE(ssl_ctx != NULL);
-       return (ssl_ctx);
+       return (rv);
 }
 
 static void
-client_handle_status_header(http2_client_stream_t *cstream,
-                           const uint8_t *value, const size_t valuelen) {
+client_handle_status_header(http_cstream_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);
+       strncpy(tmp, (const char *)value, ISC_MIN(tmplen, valuelen));
        cstream->response_status.code = strtoul(tmp, NULL, 10);
 }
 
 static void
-client_handle_content_length_header(http2_client_stream_t *cstream,
+client_handle_content_length_header(http_cstream_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);
+       strncpy(tmp, (const char *)value, ISC_MIN(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";
+client_handle_content_type_header(http_cstream_t *cstream, const uint8_t *value,
+                                 const size_t valuelen) {
+       const char type_dns_message[] = DNS_MEDIA_TYPE;
+       const size_t len = sizeof(type_dns_message) - 1;
 
        UNUSED(valuelen);
 
-       if (strncasecmp((const char *)value, type_dns_message,
-                       sizeof(type_dns_message) - 1) == 0)
-       {
+       if (strncasecmp((const char *)value, type_dns_message, len) == 0) {
                cstream->response_status.content_type_valid = true;
        }
 }
@@ -490,7 +569,8 @@ 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;
+       isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+       http_cstream_t *cstream = NULL;
        const char status[] = ":status";
        const char content_length[] = "Content-Length";
        const char content_type[] = "Content-Type";
@@ -502,8 +582,7 @@ client_on_header_callback(nghttp2_session *ngsession,
        UNUSED(flags);
        UNUSED(ngsession);
 
-       http2_client_stream_t *cstream =
-               find_http2_client_stream(frame->hd.stream_id, session);
+       cstream = find_http_cstream(frame->hd.stream_id, session);
 
        switch (frame->hd.type) {
        case NGHTTP2_HEADERS:
@@ -519,6 +598,9 @@ client_on_header_callback(nghttp2_session *ngsession,
                } else if (HEADER_MATCH(content_type, name, namelen)) {
                        client_handle_content_type_header(cstream, value,
                                                          valuelen);
+                       if (!cstream->response_status.content_type_valid) {
+                               return (NGHTTP2_ERR_HTTP_HEADER);
+                       }
                }
                break;
        }
@@ -527,10 +609,21 @@ client_on_header_callback(nghttp2_session *ngsession,
 }
 
 static void
-initialize_nghttp2_client_session(isc_nm_http2_session_t *session) {
+initialize_nghttp2_client_session(isc_nm_http_session_t *session) {
        nghttp2_session_callbacks *callbacks = NULL;
+       nghttp2_option *option = NULL;
+       nghttp2_mem mem;
 
+       init_nghttp2_mem(session->mctx, &mem);
        RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0);
+       RUNTIME_CHECK(nghttp2_option_new(&option) == 0);
+
+#if NGHTTP2_VERSION_NUM >= (0x010c00)
+       /* 128K should be enough for headers to allow more space for base64len
+        * encoded GET requests */
+       nghttp2_option_set_max_send_header_block_length(
+               option, MAX_DNS_MESSAGE_SIZE * 2);
+#endif
 
        nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
                callbacks, on_data_chunk_recv_callback);
@@ -541,13 +634,16 @@ initialize_nghttp2_client_session(isc_nm_http2_session_t *session) {
        nghttp2_session_callbacks_set_on_header_callback(
                callbacks, client_on_header_callback);
 
-       nghttp2_session_client_new(&session->ngsession, callbacks, session);
+       RUNTIME_CHECK(nghttp2_session_client_new3(&session->ngsession,
+                                                 callbacks, session, option,
+                                                 &mem) == 0);
 
+       nghttp2_option_del(option);
        nghttp2_session_callbacks_del(callbacks);
 }
 
 static bool
-send_client_connection_header(isc_nm_http2_session_t *session) {
+send_client_connection_header(isc_nm_http_session_t *session) {
        nghttp2_settings_entry iv[] = { { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 } };
        int rv;
 
@@ -577,8 +673,8 @@ static ssize_t
 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;
+       isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+       http_cstream_t *cstream = NULL;
 
        REQUIRE(session->client);
        REQUIRE(!ISC_LIST_EMPTY(session->cstreams));
@@ -586,10 +682,9 @@ client_read_callback(nghttp2_session *ngsession, int32_t stream_id,
        UNUSED(ngsession);
        UNUSED(source);
 
-       cstream = find_http2_client_stream(stream_id, session);
+       cstream = find_http_cstream(stream_id, session);
        if (!cstream || cstream->stream_id != stream_id) {
-               /* We haven't found the stream, so we are not reading anything
-                */
+               /* We haven't found the stream, so we are not reading */
                return (NGHTTP2_ERR_CALLBACK_FAILURE);
        }
 
@@ -617,10 +712,11 @@ client_read_callback(nghttp2_session *ngsession, int32_t stream_id,
        return (0);
 }
 
-/* Send HTTP request to the remote peer */
+/*
+ * Send HTTP request to the remote peer.
+ */
 static isc_result_t
-client_submit_request(isc_nm_http2_session_t *session,
-                     http2_client_stream_t *stream) {
+client_submit_request(isc_nm_http_session_t *session, http_cstream_t *stream) {
        int32_t stream_id;
        char *uri = stream->uri;
        isc_url_parser_t *up = &stream->up;
@@ -637,9 +733,10 @@ client_submit_request(isc_nm_http2_session_t *session,
                        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_NV2("content-type", DNS_MEDIA_TYPE),
+                       MAKE_NV2("accept", DNS_MEDIA_TYPE),
                        MAKE_NV("content-length", p, strlen(p)),
+                       MAKE_NV2("cache-control", DEFAULT_CACHE_CONTROL)
                };
 
                dp = (nghttp2_data_provider){ .read_callback =
@@ -659,7 +756,8 @@ client_submit_request(isc_nm_http2_session_t *session,
                                stream->authoritylen),
                        MAKE_NV(":path", stream->GET_path,
                                stream->GET_path_len),
-                       MAKE_NV2("accept", "application/dns-message")
+                       MAKE_NV2("accept", DNS_MEDIA_TYPE),
+                       MAKE_NV2("cache-control", DEFAULT_CACHE_CONTROL)
                };
 
                dp = (nghttp2_data_provider){ .read_callback =
@@ -673,7 +771,7 @@ client_submit_request(isc_nm_http2_session_t *session,
        }
 
        stream->stream_id = stream_id;
-       http2_do_bio(session);
+       http_do_bio(session);
 
        return (ISC_R_SUCCESS);
 }
@@ -682,9 +780,9 @@ client_submit_request(isc_nm_http2_session_t *session,
  * Read callback from TLS socket.
  */
 static void
-https_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
-            void *data) {
-       isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)data;
+http_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
+           void *data) {
+       isc_nm_http_session_t *session = (isc_nm_http_session_t *)data;
        ssize_t readlen;
 
        REQUIRE(VALID_HTTP2_SESSION(session));
@@ -700,7 +798,7 @@ https_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
        readlen = nghttp2_session_mem_recv(session->ngsession, region->base,
                                           region->length);
        if (readlen < 0) {
-               failed_read_cb(ISC_R_CANCELED, session);
+               failed_read_cb(ISC_R_UNEXPECTED, session);
                return;
        }
 
@@ -713,12 +811,12 @@ https_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
        }
 
        /* We might have something to receive or send, do IO */
-       http2_do_bio(session);
+       http_do_bio(session);
 }
 
 static void
-https_writecb(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
-       isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)arg;
+http_writecb(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
+       isc_nm_http_session_t *session = (isc_nm_http_session_t *)arg;
 
        REQUIRE(VALID_HTTP2_SESSION(session));
 
@@ -728,26 +826,26 @@ https_writecb(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
        isc_mem_put(session->mctx, session->r.base, session->r.length);
        session->r.base = NULL;
        if (result == ISC_R_SUCCESS) {
-               http2_do_bio(session);
+               http_do_bio(session);
        }
 }
 
 static void
-http2_do_bio(isc_nm_http2_session_t *session) {
+http_do_bio(isc_nm_http_session_t *session) {
        REQUIRE(VALID_HTTP2_SESSION(session));
 
        if (session->closed ||
            (nghttp2_session_want_read(session->ngsession) == 0 &&
             nghttp2_session_want_write(session->ngsession) == 0))
        {
-               finish_http2_session(session);
+               finish_http_session(session);
                return;
        }
 
        if (nghttp2_session_want_read(session->ngsession) != 0) {
                if (!session->reading) {
                        /* We have not yet started reading from this handle */
-                       isc_nm_read(session->handle, https_readcb, session);
+                       isc_nm_read(session->handle, http_readcb, session);
                        session->reading = true;
                } else if (session->bufsize > 0) {
                        /* Leftover data in the buffer, use it */
@@ -763,7 +861,7 @@ http2_do_bio(isc_nm_http2_session_t *session) {
                                session->bufsize -= readlen;
                        }
 
-                       http2_do_bio(session);
+                       http_do_bio(session);
                        return;
                } else {
                        /* Resume reading, it's idempotent, wait for more */
@@ -799,7 +897,7 @@ http2_do_bio(isc_nm_http2_session_t *session) {
                session->r.length = sz;
                memmove(session->r.base, data, sz);
                session->sending = true;
-               isc_nm_send(session->handle, &session->r, https_writecb,
+               isc_nm_send(session->handle, &session->r, http_writecb,
                            session);
                return;
        }
@@ -807,276 +905,210 @@ http2_do_bio(isc_nm_http2_session_t *session) {
        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 isc_result_t
+get_http_cstream(isc_nmsocket_t *sock, http_cstream_t **streamp) {
+       http_cstream_t *cstream = sock->h2.connect.cstream;
+       isc_result_t result;
+
+       REQUIRE(streamp != NULL && *streamp == NULL);
+
+       sock->h2.connect.cstream = NULL;
+
+       if (cstream == NULL) {
+               result = new_http_cstream(sock, &cstream);
+               if (result != ISC_R_SUCCESS) {
+                       INSIST(cstream == NULL);
+                       return (result);
+               }
+       }
+
+       *streamp = cstream;
+       return (ISC_R_SUCCESS);
+}
+
+static void
+http_call_connect_cb(isc_nmsocket_t *sock, isc_result_t result) {
+       isc__nm_uvreq_t *req = NULL;
+
+       REQUIRE(sock->connect_cb != NULL);
+
+       req = isc__nm_uvreq_get(sock->mgr, sock);
+       req->cb.connect = sock->connect_cb;
+       req->cbarg = sock->connect_cbarg;
+       req->handle = isc__nmhandle_get(sock, &sock->peer, &sock->iface->addr);
+
+       isc__nmsocket_clearcb(sock);
+       isc__nm_connectcb_force_async(sock, req, result);
+}
 
 static void
 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;
+       isc_nmsocket_t *http_sock = (isc_nmsocket_t *)cbarg;
+       isc_nm_http_session_t *session = NULL;
+       isc_nmsocket_t *transp_sock = NULL;
+       http_cstream_t *cstream = NULL;
+       isc_mem_t *mctx = NULL;
 
+       REQUIRE(VALID_NMSOCK(http_sock));
        REQUIRE(VALID_NMHANDLE(handle));
-       REQUIRE(cbarg != NULL);
 
-       handle->sock->h2.session = NULL;
-       mctx = handle->sock->mgr->mctx;
+       transp_sock = handle->sock;
+
+       REQUIRE(VALID_NMSOCK(transp_sock));
 
-       conn_data = *pconn_data;
-       uri = conn_data.uri;
-       isc_mem_put(mctx, pconn_data, sizeof(*pconn_data));
+       mctx = transp_sock->mgr->mctx;
 
-       INSIST(conn_data.connect_cb != NULL);
-       INSIST(conn_data.uri != NULL);
+       INSIST(http_sock->h2.connect.uri != NULL);
 
+       http_sock->tid = transp_sock->tid;
        if (result != ISC_R_SUCCESS) {
                goto error;
        }
 
-       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;
+       new_session(mctx, http_sock->h2.connect.tlsctx, &session);
+       session->client = true;
+       transp_sock->h2.session = session;
+       http_sock->h2.connect.tlsctx = NULL;
 
-       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;
+       transp_sock->h2.connect.post = http_sock->h2.connect.post;
+       transp_sock->h2.connect.uri = http_sock->h2.connect.uri;
+       http_sock->h2.connect.uri = NULL;
+       isc__nm_httpsession_attach(session, &http_sock->h2.session);
 
-       if (session->ssl_ctx != NULL) {
+       if (session->tlsctx != 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(handle->sock->tlsstream.ssl,
-                                              &alpn, &alpnlen);
-#endif
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L
-               if (alpn == NULL) {
-                       SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
-               }
-#endif
+               INSIST(transp_sock->type == isc_nm_tlssocket);
 
-               if (alpn == NULL || alpnlen != 2 ||
+               isc_tls_get_http2_alpn(transp_sock->tlsstream.tls, &alpn,
+                                      &alpnlen);
+               if (alpn == NULL || alpnlen != NGHTTP2_PROTO_VERSION_ID_LEN ||
                    memcmp(NGHTTP2_PROTO_VERSION_ID, alpn,
                           NGHTTP2_PROTO_VERSION_ID_LEN) != 0)
                {
-                       result = ISC_R_CANCELED;
+                       /*
+                        * HTTP/2 negotiation error. Any sensible DoH
+                        * client will fail if HTTP/2 cannot be
+                        * negotiated via ALPN.
+                        */
+                       isc__nmsocket_prep_destroy(transp_sock);
+                       result = ISC_R_HTTP2ALPNERROR;
                        goto error;
                }
        }
 
        isc_nmhandle_attach(handle, &session->handle);
-       handle->sock->h2.session = session;
 
        initialize_nghttp2_client_session(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);
+
+       result = get_http_cstream(http_sock, &cstream);
+       http_sock->h2.connect.cstream = cstream;
+       if (result != ISC_R_SUCCESS) {
+               goto error;
        }
 
+       http_call_connect_cb(http_sock, result);
+       http_do_bio(session);
+       isc__nmsocket_detach(&http_sock);
        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);
-       }
+       http_call_connect_cb(http_sock, result);
 
-       if (session != NULL) {
-               delete_http2_session(session);
+       if (http_sock->h2.connect.uri != NULL) {
+               isc_mem_free(mctx, http_sock->h2.connect.uri);
        }
 
-       if (uri != NULL) {
-               isc_mem_free(mctx, uri);
-       }
+       isc__nmsocket_detach(&http_sock);
 }
 
 isc_result_t
 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_tlsctx_t *tlsctx, unsigned int timeout,
+                  size_t extrahandlesize) {
        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;
+       isc_nmiface_t local_interface;
+       isc_nmsocket_t *sock = NULL;
 
        REQUIRE(VALID_NM(mgr));
        REQUIRE(cb != NULL);
+       REQUIRE(peer != NULL);
        REQUIRE(uri != NULL);
        REQUIRE(*uri != '\0');
 
-       result = isc_url_parse(uri, strlen(uri), 0, &url_parser);
-       if (result != ISC_R_SUCCESS) {
-               return (result);
-       }
-
-       schema_len = url_parser.field_data[ISC_UF_SCHEMA].len;
-       INSIST(schema_len > 0);
-       schema = &uri[url_parser.field_data[ISC_UF_SCHEMA].off];
-
-       if (schema_len == sizeof(http_secured) - 1 &&
-           strncasecmp(http_secured, schema, sizeof(http_secured) - 1) == 0)
-       {
-               create_ssl_ctx = true;
-       } else if (schema_len == sizeof(http) - 1 &&
-                  strncasecmp(http, schema, sizeof(http) - 1) == 0)
-       {
-               create_ssl_ctx = false;
-       } else {
-               INSIST(0);
-               ISC_UNREACHABLE();
-       }
-
-       if (ctx == NULL && create_ssl_ctx) {
-               port = 443;
-               ctx = create_client_ssl_ctx();
+       if (local == NULL) {
+               isc_sockaddr_anyofpf(&local_interface.addr,
+                                    (peer->addr).type.sa.sa_family);
+               local = &local_interface;
        }
 
-       if (peer == NULL || local == NULL) {
-               size_t hostlen = 0;
-               char *host = NULL;
-               struct addrinfo hints;
-               struct addrinfo *res = NULL;
-
-#ifndef WIN32 /* FIXME */
-               /* extract host name */
-               hostlen = url_parser.field_data[ISC_UF_HOST].len + 1;
-               host = isc_mem_get(mgr->mctx, hostlen);
-               memmove(host, &uri[url_parser.field_data[ISC_UF_HOST].off],
-                       hostlen - 1);
-               host[hostlen - 1] = '\0';
-
-               memset(&hints, 0, sizeof(hints));
-               hints.ai_family = AF_UNSPEC;
-               hints.ai_socktype = SOCK_STREAM;
-               hints.ai_protocol = IPPROTO_TCP;
-               hints.ai_flags = AI_CANONNAME;
-
-               result = getaddrinfo(host, NULL, &hints, &res);
-               isc_mem_put(mgr->mctx, host, hostlen);
-               if (result != 0) {
-                       return (ISC_R_FAILURE);
-               }
-#endif /* WIN32 */
-
-               if ((url_parser.field_set & (1 << ISC_UF_PORT)) != 0) {
-                       port = url_parser.port;
-               }
+       sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+       isc__nmsocket_init(sock, mgr, isc_nm_httpsocket, local);
 
-               if (peer == NULL) {
-                       (void)isc_sockaddr_fromsockaddr(&resolved_peer.addr,
-                                                       res->ai_addr);
-                       isc_sockaddr_setport(&resolved_peer.addr, port);
-                       peer = &resolved_peer;
-               }
-               if (local == NULL) {
-                       isc_sockaddr_anyofpf(&resolved_local.addr,
-                                            res->ai_family);
-                       local = &resolved_local;
-               }
+       sock->extrahandlesize = extrahandlesize;
+       sock->connect_timeout = timeout;
+       sock->result = ISC_R_DEFAULT;
+       sock->connect_cb = cb;
+       sock->connect_cbarg = cbarg;
+       sock->h2 = (isc_nmsocket_h2_t){ .connect.uri = isc_mem_strdup(mgr->mctx,
+                                                                     uri),
+                                       .connect.post = post,
+                                       .connect.tlsctx = tlsctx };
+       ISC_LINK_INIT(&sock->h2, link);
+       atomic_init(&sock->client, true);
 
-               freeaddrinfo(res);
+       /*
+        * We need to prevent the interface object data from going out of
+        * scope too early.
+        */
+       if (local == &local_interface) {
+               sock->h2.connect.local_interface = local_interface;
+               sock->iface = &sock->h2.connect.local_interface;
        }
 
-       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 != NULL) {
+       if (tlsctx != NULL) {
                result = isc_nm_tlsconnect(mgr, local, peer,
-                                          transport_connect_cb, pconn_data,
-                                          ctx, timeout, extrahandlesize);
+                                          transport_connect_cb, sock, tlsctx,
+                                          timeout, 0);
        } else {
                result = isc_nm_tcpconnect(mgr, local, peer,
-                                          transport_connect_cb, pconn_data,
-                                          timeout, extrahandlesize);
+                                          transport_connect_cb, sock, timeout,
+                                          0);
        }
 
        return (result);
 }
 
-isc_result_t
-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;
+static isc_result_t
+client_send(isc_nmhandle_t *handle, const isc_region_t *region) {
        isc_result_t result = ISC_R_SUCCESS;
-       isc_nm_http2_session_t *session = NULL;
-       isc_mem_t *mctx;
+       isc_nmsocket_t *sock = handle->sock;
+       isc_mem_t *mctx = sock->mgr->mctx;
+       isc_nm_http_session_t *session = sock->h2.session;
+       http_cstream_t *cstream = sock->h2.connect.cstream;
 
-       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(session->client);
        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;
-       }
+       REQUIRE(cstream != NULL);
 
-       cstream->read_cb = reply_cb;
-       cstream->read_cbarg = cbarg;
-       cstream->post = handle->sock->h2.connect.post;
-
-       if (cstream->post) { /* 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 */
+       } else {
+               /* GET */
                size_t path_size = 0;
                char *base64url_data = NULL;
                size_t base64url_data_len = 0;
@@ -1087,8 +1119,8 @@ isc_nm_httprequest(isc_nmhandle_t *handle, isc_region_t *region,
 
                isc_buffer_allocate(mctx, &buf, base64_len);
 
-               if ((result = isc_base64_totext(&data, -1, "", buf)) !=
-                   ISC_R_SUCCESS) {
+               result = isc_base64_totext(&data, -1, "", buf);
+               if (result != ISC_R_SUCCESS) {
                        isc_buffer_free(&buf);
                        goto error;
                }
@@ -1115,98 +1147,61 @@ isc_nm_httprequest(isc_nmhandle_t *handle, isc_region_t *region,
                isc_mem_free(mctx, base64url_data);
        }
 
-       ISC_LINK_INIT(cstream, link);
-       ISC_LIST_APPEND(session->cstreams, cstream, link);
-
-       INSIST(cstream != NULL);
+       cstream->sending = true;
+       if (!ISC_LINK_LINKED(cstream, link)) {
+               ISC_LIST_APPEND(session->cstreams, cstream, link);
+       }
+       if (cstream->reading) {
+               sock->h2.connect.cstream = NULL;
+               result = client_submit_request(session, cstream);
+               if (result != ISC_R_SUCCESS) {
+                       ISC_LIST_UNLINK(session->cstreams, cstream, link);
+                       goto error;
+               }
 
-       result = client_submit_request(session, cstream);
-       if (result != ISC_R_SUCCESS) {
-               ISC_LIST_UNLINK(session->cstreams, cstream, link);
-               goto error;
+               http_do_bio(session);
        }
-       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;
+isc_result_t
+isc__nm_http_request(isc_nmhandle_t *handle, isc_region_t *region,
+                    isc_nm_recv_cb_t cb, void *cbarg) {
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nmsocket_t *sock = NULL;
+       http_cstream_t *cstream = NULL;
 
        REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(VALID_NMSOCK(handle->sock));
+       REQUIRE(handle->sock->tid == isc_nm_tid());
 
-       memmove(&data, arg, sizeof(data));
-       isc_mem_put(handle->sock->mgr->mctx, arg, sizeof(data));
-       if (result != ISC_R_SUCCESS) {
-               goto error;
-       }
+       REQUIRE(cb != NULL);
+
+       sock = handle->sock;
 
-       result = isc_nm_httprequest(handle, &data.region, data.reply_cb,
-                                   data.cb_arg);
+       isc__nm_http_read(handle, cb, cbarg);
+       result = client_send(handle, region);
        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 (ISC_R_SUCCESS);
 
+error:
+       cstream = sock->h2.connect.cstream;
+       if (cstream->read_cb != NULL) {
+               cstream->read_cb(handle, result, NULL, cstream->read_cbarg);
+       }
        return (result);
 }
 
 static int
 server_on_begin_headers_callback(nghttp2_session *ngsession,
                                 const nghttp2_frame *frame, void *user_data) {
-       isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data;
+       isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
        isc_nmsocket_t *socket = NULL;
-       isc_sockaddr_t iface;
 
        if (frame->hd.type != NGHTTP2_HEADERS ||
            frame->headers.cat != NGHTTP2_HCAT_REQUEST)
@@ -1215,41 +1210,28 @@ server_on_begin_headers_callback(nghttp2_session *ngsession,
        }
 
        socket = isc_mem_get(session->mctx, sizeof(isc_nmsocket_t));
-       iface = isc_nmhandle_localaddr(session->handle);
        isc__nmsocket_init(socket, session->serversocket->mgr,
-                          isc_nm_httpstream, (isc_nmiface_t *)&iface);
+                          isc_nm_httpsocket,
+                          (isc_nmiface_t *)&session->server_iface);
        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
        };
+       isc__nm_httpsession_attach(session, &socket->h2.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 isc_nm_http2_server_handler_t *
+static isc_nm_httphandler_t *
 find_server_request_handler(const char *request_path,
                            isc_nmsocket_t *serversocket) {
-       isc_nm_http2_server_handler_t *handler = NULL;
+       isc_nm_httphandler_t *handler = NULL;
 
        REQUIRE(VALID_NMSOCK(serversocket));
 
@@ -1257,7 +1239,7 @@ find_server_request_handler(const char *request_path,
                return (NULL);
        }
 
-       RWLOCK(&serversocket->h2.handlers_lock, isc_rwlocktype_read);
+       RWLOCK(&serversocket->h2.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))
@@ -1267,15 +1249,15 @@ find_server_request_handler(const char *request_path,
                        }
                }
        }
-       RWUNLOCK(&serversocket->h2.handlers_lock, isc_rwlocktype_read);
+       RWUNLOCK(&serversocket->h2.lock, isc_rwlocktype_read);
 
        return (handler);
 }
 
-static isc_http2_error_responses_t
+static isc_http_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;
+       isc_nm_httphandler_t *handler = NULL;
        const uint8_t *qstr = NULL;
        size_t vlen = valuelen;
 
@@ -1292,8 +1274,8 @@ server_handle_path_header(isc_nmsocket_t *socket, const uint8_t *value,
        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->h2.cb = handler->cb;
+               socket->h2.cbarg = handler->cbarg;
                socket->extrahandlesize = handler->extrahandlesize;
        } else {
                isc_mem_free(socket->mgr->mctx, socket->h2.request_path);
@@ -1308,8 +1290,8 @@ server_handle_path_header(isc_nmsocket_t *socket, const uint8_t *value,
                        return (ISC_HTTP_ERROR_BAD_REQUEST);
                }
 
-               if (isc__nm_parse_doh_query_string((const char *)qstr,
-                                                  &dns_value, &dns_value_len))
+               if (isc__nm_parse_httpquery((const char *)qstr, &dns_value,
+                                           &dns_value_len))
                {
                        const size_t decoded_size = dns_value_len / 4 * 3;
                        if (decoded_size <= MAX_DNS_MESSAGE_SIZE) {
@@ -1333,7 +1315,7 @@ server_handle_path_header(isc_nmsocket_t *socket, const uint8_t *value,
        return (ISC_HTTP_ERROR_SUCCESS);
 }
 
-static isc_http2_error_responses_t
+static isc_http_error_responses_t
 server_handle_method_header(isc_nmsocket_t *socket, const uint8_t *value,
                            const size_t valuelen) {
        const char get[] = "GET";
@@ -1349,7 +1331,7 @@ server_handle_method_header(isc_nmsocket_t *socket, const uint8_t *value,
        return (ISC_HTTP_ERROR_SUCCESS);
 }
 
-static isc_http2_error_responses_t
+static isc_http_error_responses_t
 server_handle_scheme_header(isc_nmsocket_t *socket, const uint8_t *value,
                            const size_t valuelen) {
        const char http[] = "http";
@@ -1365,7 +1347,7 @@ server_handle_scheme_header(isc_nmsocket_t *socket, const uint8_t *value,
        return (ISC_HTTP_ERROR_SUCCESS);
 }
 
-static isc_http2_error_responses_t
+static isc_http_error_responses_t
 server_handle_content_length_header(isc_nmsocket_t *socket,
                                    const uint8_t *value,
                                    const size_t valuelen) {
@@ -1384,10 +1366,10 @@ server_handle_content_length_header(isc_nmsocket_t *socket,
        return (ISC_HTTP_ERROR_SUCCESS);
 }
 
-static isc_http2_error_responses_t
+static isc_http_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";
+       const char type_dns_message[] = DNS_MEDIA_TYPE;
 
        if (HEADER_MATCH(type_dns_message, value, valuelen)) {
                socket->h2.content_type_verified = true;
@@ -1397,11 +1379,11 @@ server_handle_content_type_header(isc_nmsocket_t *socket, const uint8_t *value,
        return (ISC_HTTP_ERROR_SUCCESS);
 }
 
-static isc_http2_error_responses_t
+static isc_http_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";
+       const char type_dns_message[] = DNS_MEDIA_TYPE;
 
        if (HEADER_MATCH(type_dns_message, value, valuelen) ||
            HEADER_MATCH(type_accept_all, value, valuelen))
@@ -1418,8 +1400,9 @@ 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_result_t result;
        isc_nmsocket_t *socket = NULL;
-       isc_http2_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
+       isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
        const char path[] = ":path";
        const char method[] = ":method";
        const char scheme[] = ":scheme";
@@ -1469,9 +1452,11 @@ server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
        }
 
        INSIST(socket != NULL);
-       if (server_send_error_response(code, session, socket) != 0) {
+       result = server_send_error_response(code, session, socket);
+       if (result != ISC_R_SUCCESS) {
                return (NGHTTP2_ERR_CALLBACK_FAILURE);
-       };
+       }
+
        failed_httpstream_read_cb(socket, ISC_R_CANCELED, socket->h2.session);
        return (0);
 }
@@ -1480,7 +1465,7 @@ static ssize_t
 server_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;
+       isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
        isc_nmsocket_t *socket = (isc_nmsocket_t *)source->ptr;
        size_t buflen;
 
@@ -1503,7 +1488,7 @@ server_read_callback(nghttp2_session *ngsession, int32_t stream_id,
        return (buflen);
 }
 
-static int
+static isc_result_t
 server_send_response(nghttp2_session *ngsession, int32_t stream_id,
                     const nghttp2_nv *nva, size_t nvlen,
                     isc_nmsocket_t *socket) {
@@ -1516,9 +1501,9 @@ server_send_response(nghttp2_session *ngsession, int32_t stream_id,
        rv = nghttp2_submit_response(ngsession, stream_id, nva, nvlen,
                                     &data_prd);
        if (rv != 0) {
-               return (-1);
+               return (ISC_R_FAILURE);
        }
-       return (0);
+       return (ISC_R_SUCCESS);
 }
 
 #define MAKE_ERROR_REPLY(tag, code)             \
@@ -1526,8 +1511,8 @@ server_send_response(nghttp2_session *ngsession, int32_t stream_id,
                tag, MAKE_NV2(":status", #code) \
        }
 
-static struct http2_error_responses {
-       const isc_http2_error_responses_t type;
+static struct http_error_responses {
+       const isc_http_error_responses_t type;
        const nghttp2_nv header;
 } error_responses[] = {
        MAKE_ERROR_REPLY(ISC_HTTP_ERROR_SUCCESS, 200),
@@ -1540,11 +1525,9 @@ static struct http2_error_responses {
        MAKE_ERROR_REPLY(ISC_HTTP_ERROR_GENERIC, 500),
 };
 
-static int
-server_send_error_response(const isc_http2_error_responses_t error,
+static isc_result_t
+server_send_error_response(const isc_http_error_responses_t error,
                           nghttp2_session *ngsession, isc_nmsocket_t *socket) {
-       int rv;
-
        socket->h2.bufsize = 0;
        socket->h2.bufpos = 0;
 
@@ -1552,26 +1535,24 @@ server_send_error_response(const isc_http2_error_responses_t error,
             i < sizeof(error_responses) / sizeof(error_responses[0]); i++)
        {
                if (error_responses[i].type == error) {
-                       rv = server_send_response(
+                       return (server_send_response(
                                ngsession, socket->h2.stream_id,
                                &error_responses[i].header,
-                               sizeof(error_responses[i].header), socket);
-                       return (rv);
+                               sizeof(error_responses[i].header), socket));
                }
        }
 
-       rv = server_send_error_response(ISC_HTTP_ERROR_GENERIC, ngsession,
-                                       socket);
-       return (rv);
+       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_http_session_t *session, isc_nmsocket_t *socket) {
+       isc_result_t result;
        isc_nmhandle_t *handle = NULL;
        isc_sockaddr_t addr;
-       isc_http2_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
+       isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
        isc_region_t data;
 
        /*
@@ -1579,7 +1560,7 @@ server_on_request_recv(nghttp2_session *ngsession,
         * Unbound uses.
         * (https://blog.nlnetlabs.nl/dns-over-https-in-unbound/)
         */
-       if (!socket->h2.request_path || !socket->h2.handler_cb) {
+       if (!socket->h2.request_path || !socket->h2.cb) {
                code = ISC_HTTP_ERROR_NOT_FOUND;
        } else if ((socket->h2.request_type == ISC_HTTP_REQ_POST &&
                    !socket->h2.content_type_verified) ||
@@ -1642,13 +1623,13 @@ server_on_request_recv(nghttp2_session *ngsession,
 
        addr = isc_nmhandle_peeraddr(session->handle);
        handle = isc__nmhandle_get(socket, &addr, NULL);
-       socket->h2.handler_cb(handle, ISC_R_SUCCESS, &data,
-                             socket->h2.handler_cbarg);
+       socket->h2.cb(handle, ISC_R_SUCCESS, &data, socket->h2.cbarg);
        isc_nmhandle_detach(&handle);
        return (0);
 
 error:
-       if (server_send_error_response(code, ngsession, socket) != 0) {
+       result = server_send_error_response(code, ngsession, socket);
+       if (result != ISC_R_SUCCESS) {
                return (NGHTTP2_ERR_CALLBACK_FAILURE);
        }
        return (0);
@@ -1657,23 +1638,15 @@ error:
 void
 isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region,
                  isc_nm_cb_t cb, void *cbarg) {
-       isc_nmsocket_t *sock = handle->sock;
-       isc__nm_uvreq_t *uvreq = NULL;
+       isc_nmsocket_t *sock = NULL;
        isc__netievent_httpsend_t *ievent = NULL;
+       isc__nm_uvreq_t *uvreq = NULL;
 
        REQUIRE(VALID_NMHANDLE(handle));
-       REQUIRE(handle->httpsession != NULL);
-       REQUIRE(VALID_NMSOCK(sock));
 
-       /* TODO: should be called from within the context of an NM thread? */
-       if (inactive(sock) || handle->httpsession->closed) {
-               cb(handle, ISC_R_CANCELED, cbarg);
-               return;
-       }
+       sock = handle->sock;
 
-       INSIST(VALID_NMHANDLE(handle->httpsession->handle));
-       INSIST(VALID_NMSOCK(handle->httpsession->handle->sock));
-       INSIST(sock->tid == handle->httpsession->handle->sock->tid);
+       REQUIRE(VALID_NMSOCK(sock));
 
        uvreq = isc__nm_uvreq_get(sock->mgr, sock);
        isc_nmhandle_attach(handle, &uvreq->handle);
@@ -1688,65 +1661,145 @@ isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region,
                               (isc__netievent_t *)ievent);
 }
 
+static void
+failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+              isc_result_t eresult) {
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(VALID_UVREQ(req));
+
+       if (req->cb.send != NULL) {
+               isc__nm_sendcb(sock, req, eresult);
+       } else {
+               isc__nm_uvreq_put(&req, sock);
+       }
+}
+
+static void
+client_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock,
+               isc__nm_uvreq_t *req) {
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nm_cb_t cb = req->cb.send;
+       void *cbarg = req->cbarg;
+
+       result = client_send(
+               handle,
+               &(isc_region_t){ (uint8_t *)req->uvbuf.base, req->uvbuf.len });
+       if (result != ISC_R_SUCCESS) {
+               failed_send_cb(sock, req, result);
+               return;
+       }
+
+       cb(handle, result, cbarg);
+       isc__nm_uvreq_put(&req, sock);
+}
+
+static void
+server_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock,
+               isc__nm_uvreq_t *req) {
+       size_t len;
+       isc_result_t result = ISC_R_SUCCESS;
+       isc_nm_cb_t cb = req->cb.send;
+       void *cbarg = req->cbarg;
+       if (inactive(sock) || handle->httpsession->closed) {
+               failed_send_cb(sock, req, ISC_R_CANCELED);
+               return;
+       }
+
+       INSIST(handle->httpsession->handle->sock->tid == isc_nm_tid());
+       INSIST(VALID_NMHANDLE(handle->httpsession->handle));
+       INSIST(VALID_NMSOCK(handle->httpsession->handle->sock));
+
+       memmove(sock->h2.buf, req->uvbuf.base, req->uvbuf.len);
+       sock->h2.bufsize = req->uvbuf.len;
+
+       len = snprintf(sock->h2.clenbuf, sizeof(sock->h2.clenbuf), "%lu",
+                      (unsigned long)req->uvbuf.len);
+       const nghttp2_nv hdrs[] = {
+               MAKE_NV2(":status", "200"),
+               MAKE_NV2("Content-Type", DNS_MEDIA_TYPE),
+               MAKE_NV("Content-Length", sock->h2.clenbuf, len),
+               /*
+                * TODO: implement Cache-Control: max-age=<seconds>
+                * (https://tools.ietf.org/html/rfc8484#section-5.1)
+                */
+               MAKE_NV2("cache-control", DEFAULT_CACHE_CONTROL)
+       };
+
+       result = server_send_response(handle->httpsession->ngsession,
+                                     sock->h2.stream_id, hdrs,
+                                     sizeof(hdrs) / sizeof(nghttp2_nv), sock);
+
+       http_do_bio(handle->httpsession);
+       cb(handle, result, cbarg);
+       isc__nm_uvreq_put(&req, sock);
+}
+
 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;
+       isc_nm_http_session_t *session = NULL;
+
+       UNUSED(worker);
 
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(VALID_UVREQ(req));
+       REQUIRE(VALID_HTTP2_SESSION(sock->h2.session));
 
        ievent->req = NULL;
        handle = req->handle;
-       cb = req->cb.send;
-       cbarg = req->cbarg;
 
        REQUIRE(VALID_NMHANDLE(handle));
-       REQUIRE(VALID_NMHANDLE(handle->httpsession->handle));
-       REQUIRE(VALID_NMSOCK(handle->httpsession->handle->sock));
-       REQUIRE(handle->httpsession->handle->sock->tid == isc_nm_tid());
 
-       UNUSED(worker);
+       session = sock->h2.session;
+       if (session != NULL && session->client) {
+               client_httpsend(handle, sock, req);
+       } else {
+               server_httpsend(handle, sock, req);
+       }
+}
 
-       memmove(sock->h2.buf, req->uvbuf.base, req->uvbuf.len);
-       sock->h2.bufsize = req->uvbuf.len;
+void
+isc__nm_http_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+       isc_result_t result;
+       http_cstream_t *cstream = NULL;
+       isc_nm_http_session_t *session = NULL;
 
-       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)
-       {
-               res = ISC_R_FAILURE;
-       } else {
-               res = ISC_R_SUCCESS;
+       REQUIRE(VALID_NMHANDLE(handle));
+
+       session = handle->sock->h2.session;
+
+       result = get_http_cstream(handle->sock, &cstream);
+       if (result != ISC_R_SUCCESS) {
+               return;
        }
 
-       http2_do_bio(handle->httpsession); /*TODO: Should we call it only
-                                           * on success? */
-       cb(handle, res, cbarg);
+       handle->sock->h2.connect.cstream = cstream;
+       cstream->read_cb = cb;
+       cstream->read_cbarg = cbarg;
+       cstream->reading = true;
 
-       isc__nm_uvreq_put(&req, sock);
+       if (!ISC_LINK_LINKED(cstream, link)) {
+               ISC_LIST_APPEND(session->cstreams, cstream, link);
+       }
+
+       if (cstream->sending) {
+               result = client_submit_request(session, cstream);
+               if (result != ISC_R_SUCCESS) {
+                       ISC_LIST_UNLINK(session->cstreams, cstream, link);
+                       return;
+               }
+
+               http_do_bio(session);
+       }
 }
 
 static int
 server_on_frame_recv_callback(nghttp2_session *ngsession,
                              const nghttp2_frame *frame, void *user_data) {
-       isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data;
+       isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
        isc_nmsocket_t *socket = NULL;
 
        switch (frame->hd.type) {
@@ -1777,8 +1830,11 @@ server_on_frame_recv_callback(nghttp2_session *ngsession,
 }
 
 static void
-initialize_nghttp2_server_session(isc_nm_http2_session_t *session) {
+initialize_nghttp2_server_session(isc_nm_http_session_t *session) {
        nghttp2_session_callbacks *callbacks = NULL;
+       nghttp2_mem mem;
+
+       init_nghttp2_mem(session->mctx, &mem);
 
        RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0);
 
@@ -1797,13 +1853,15 @@ initialize_nghttp2_server_session(isc_nm_http2_session_t *session) {
        nghttp2_session_callbacks_set_on_frame_recv_callback(
                callbacks, server_on_frame_recv_callback);
 
-       nghttp2_session_server_new(&session->ngsession, callbacks, session);
+       RUNTIME_CHECK(nghttp2_session_server_new3(&session->ngsession,
+                                                 callbacks, session, NULL,
+                                                 &mem) == 0);
 
        nghttp2_session_callbacks_del(callbacks);
 }
 
 static int
-server_send_connection_header(isc_nm_http2_session_t *session) {
+server_send_connection_header(isc_nm_http_session_t *session) {
        nghttp2_settings_entry iv[1] = {
                { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 }
        };
@@ -1820,7 +1878,7 @@ server_send_connection_header(isc_nm_http2_session_t *session) {
 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_nm_http_session_t *session = NULL;
        isc_nmsocket_t *listener = NULL, *httpserver = NULL;
 
        REQUIRE(VALID_NMHANDLE(handle));
@@ -1858,61 +1916,24 @@ httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
                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,
-                                            .ssl_ctx_created = false,
-                                            .client = false };
+       new_session(httplistensock->mgr->mctx, NULL, &session);
        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;
+       session->server_iface.addr = isc_nmhandle_localaddr(session->handle);
        server_send_connection_header(session);
 
        /* TODO H2 */
-       http2_do_bio(session);
+       http_do_bio(session);
        return (ISC_R_SUCCESS);
 }
 
-#ifndef OPENSSL_NO_NEXTPROTONEG
-static int
-next_proto_cb(SSL *ssl, const unsigned char **data, unsigned int *len,
-             void *arg) {
-       UNUSED(ssl);
-       UNUSED(arg);
-
-       *data = (const unsigned char *)NGHTTP2_PROTO_ALPN;
-       *len = (unsigned int)NGHTTP2_PROTO_ALPN_LEN;
-       return (SSL_TLSEXT_ERR_OK);
-}
-#endif /* !OPENSSL_NO_NEXTPROTONEG */
-
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L
-static int
-alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
-                    const unsigned char *in, unsigned int inlen, void *arg) {
-       int ret;
-
-       UNUSED(ssl);
-       UNUSED(arg);
-
-       ret = nghttp2_select_next_protocol((unsigned char **)(uintptr_t)out,
-                                          outlen, in, inlen);
-
-       if (ret != 1) {
-               return (SSL_TLSEXT_ERR_NOACK);
-       }
-
-       return (SSL_TLSEXT_ERR_OK);
-}
-#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
-
 isc_result_t
 isc_nm_listenhttp(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog,
-                 isc_quota_t *quota, SSL_CTX *ctx, isc_nmsocket_t **sockp) {
+                 isc_quota_t *quota, isc_tlsctx_t *ctx,
+                 isc_nmsocket_t **sockp) {
        isc_nmsocket_t *sock = NULL;
        isc_result_t result;
 
@@ -1920,18 +1941,13 @@ isc_nm_listenhttp(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog,
        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
+               isc_tlsctx_enable_http2server_alpn(ctx);
                result = isc_nm_listentls(mgr, iface, httplisten_acceptcb, sock,
-                                         sizeof(isc_nm_http2_session_t),
+                                         sizeof(isc_nm_http_session_t),
                                          backlog, quota, ctx, &sock->outer);
        } else {
                result = isc_nm_listentcp(mgr, iface, httplisten_acceptcb, sock,
-                                         sizeof(isc_nm_http2_session_t),
+                                         sizeof(isc_nm_http_session_t),
                                          backlog, quota, &sock->outer);
        }
 
@@ -1953,81 +1969,62 @@ isc_nm_listenhttp(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog,
        return (ISC_R_SUCCESS);
 }
 
-isc_result_t
-isc_nm_http_add_endpoint(isc_nmsocket_t *sock, const char *uri,
-                        isc_nm_http_cb_t cb, void *cbarg,
-                        size_t extrahandlesize) {
-       isc_nm_http2_server_handler_t *handler = NULL;
-
-       REQUIRE(VALID_NMSOCK(sock));
-       REQUIRE(sock->type == isc_nm_httplistener);
-
-       if (find_server_request_handler(uri, sock) == NULL) {
-               handler = isc_mem_get(sock->mgr->mctx, sizeof(*handler));
-               *handler = (isc_nm_http2_server_handler_t){
-                       .cb = cb,
-                       .cbarg = cbarg,
-                       .extrahandlesize = extrahandlesize,
-                       .path = isc_mem_strdup(sock->mgr->mctx, uri)
-               };
-               ISC_LINK_INIT(handler, link);
-
-               RWLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
-               ISC_LIST_APPEND(sock->h2.handlers, handler, link);
-               RWUNLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
-       }
-
-       return (ISC_R_SUCCESS);
-}
-
 /*
  * 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.
+ * http content.
  */
 static void
-doh_callback(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *data,
-            void *arg) {
-       isc_nm_http_doh_cbarg_t *dohcbarg = arg;
+http_callback(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *data,
+             void *arg) {
+       isc_nm_httpcbarg_t *httpcbarg = arg;
 
        REQUIRE(VALID_NMHANDLE(handle));
 
        if (result != ISC_R_SUCCESS) {
                /* Shut down the client, then ourselves */
-               dohcbarg->cb(handle, result, NULL, dohcbarg->cbarg);
+               httpcbarg->cb(handle, result, NULL, httpcbarg->cbarg);
                /* XXXWPK FREE */
                return;
        }
-       dohcbarg->cb(handle, result, data, dohcbarg->cbarg);
+       httpcbarg->cb(handle, result, data, httpcbarg->cbarg);
 }
 
 isc_result_t
-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;
-       isc_nm_http_doh_cbarg_t *dohcbarg = NULL;
+isc_nm_http_endpoint(isc_nmsocket_t *sock, const char *uri, isc_nm_recv_cb_t cb,
+                    void *cbarg, size_t extrahandlesize) {
+       isc_nm_httphandler_t *handler = NULL;
+       isc_nm_httpcbarg_t *httpcbarg = NULL;
+       bool newhandler = false;
 
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(sock->type == isc_nm_httplistener);
 
-       dohcbarg = isc_mem_get(sock->mgr->mctx,
-                              sizeof(isc_nm_http_doh_cbarg_t));
-       *dohcbarg = (isc_nm_http_doh_cbarg_t){ .cb = cb, .cbarg = cbarg };
-       ISC_LINK_INIT(dohcbarg, link);
+       httpcbarg = isc_mem_get(sock->mgr->mctx, sizeof(isc_nm_httpcbarg_t));
+       *httpcbarg = (isc_nm_httpcbarg_t){ .cb = cb, .cbarg = cbarg };
+       ISC_LINK_INIT(httpcbarg, link);
 
-       result = isc_nm_http_add_endpoint(sock, uri, doh_callback, dohcbarg,
-                                         extrahandlesize);
-       if (result != ISC_R_SUCCESS) {
-               isc_mem_put(sock->mgr->mctx, dohcbarg,
-                           sizeof(isc_nm_http_doh_cbarg_t));
+       if (find_server_request_handler(uri, sock) == NULL) {
+               handler = isc_mem_get(sock->mgr->mctx, sizeof(*handler));
+               *handler = (isc_nm_httphandler_t){
+                       .cb = http_callback,
+                       .cbarg = httpcbarg,
+                       .extrahandlesize = extrahandlesize,
+                       .path = isc_mem_strdup(sock->mgr->mctx, uri)
+               };
+               ISC_LINK_INIT(handler, link);
+
+               newhandler = true;
        }
 
-       RWLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
-       ISC_LIST_APPEND(sock->h2.handlers_cbargs, dohcbarg, link);
-       RWUNLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
+       RWLOCK(&sock->h2.lock, isc_rwlocktype_write);
+       if (newhandler) {
+               ISC_LIST_APPEND(sock->h2.handlers, handler, link);
+       }
+       ISC_LIST_APPEND(sock->h2.handler_cbargs, httpcbarg, link);
+       RWUNLOCK(&sock->h2.lock, isc_rwlocktype_write);
 
-       return (result);
+       return (ISC_R_SUCCESS);
 }
 
 void
@@ -2048,16 +2045,16 @@ isc__nm_http_stoplistening(isc_nmsocket_t *sock) {
                               (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;
+static void
+clear_handlers(isc_nmsocket_t *sock) {
+       isc_nm_httphandler_t *handler = NULL;
+       isc_nm_httpcbarg_t *httpcbarg = NULL;
 
-       /* delete all handlers */
-       RWLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
+       /* Delete all handlers */
+       RWLOCK(&sock->h2.lock, isc_rwlocktype_write);
        handler = ISC_LIST_HEAD(sock->h2.handlers);
        while (handler != NULL) {
-               isc_nm_http2_server_handler_t *next;
+               isc_nm_httphandler_t *next = NULL;
 
                next = ISC_LIST_NEXT(handler, link);
                ISC_LIST_DEQUEUE(sock->h2.handlers, handler, link);
@@ -2066,32 +2063,17 @@ isc__nm_http_clear_handlers(isc_nmsocket_t *sock) {
                handler = next;
        }
 
-       dohcbarg = ISC_LIST_HEAD(sock->h2.handlers_cbargs);
-       while (dohcbarg != NULL) {
-               isc_nm_http_doh_cbarg_t *next;
-
-               next = ISC_LIST_NEXT(dohcbarg, link);
-               ISC_LIST_DEQUEUE(sock->h2.handlers_cbargs, dohcbarg, link);
-               isc_mem_put(sock->mgr->mctx, dohcbarg,
-                           sizeof(isc_nm_http_doh_cbarg_t));
-               dohcbarg = next;
-       }
-       RWUNLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write);
-}
+       httpcbarg = ISC_LIST_HEAD(sock->h2.handler_cbargs);
+       while (httpcbarg != NULL) {
+               isc_nm_httpcbarg_t *next = NULL;
 
-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;
+               next = ISC_LIST_NEXT(httpcbarg, link);
+               ISC_LIST_DEQUEUE(sock->h2.handler_cbargs, httpcbarg, link);
+               isc_mem_put(sock->mgr->mctx, httpcbarg,
+                           sizeof(isc_nm_httpcbarg_t));
+               httpcbarg = next;
        }
+       RWUNLOCK(&sock->h2.lock, isc_rwlocktype_write);
 }
 
 void
@@ -2117,32 +2099,37 @@ isc__nm_async_httpstop(isc__networker_t *worker, isc__netievent_t *ev0) {
 static void
 http_close_direct(isc_nmsocket_t *sock) {
        bool sessions_empty;
-       isc_nm_http2_session_t *session;
+       isc_nm_http_session_t *session = NULL;
 
        REQUIRE(VALID_NMSOCK(sock));
-       REQUIRE(VALID_HTTP2_SESSION(sock->h2.session));
 
        atomic_store(&sock->closed, true);
+
+       if (atomic_load(&sock->client)) {
+               return;
+       }
+       INSIST(VALID_HTTP2_SESSION(sock->h2.session));
+
        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);
+               http_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 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);
@@ -2154,7 +2141,7 @@ http_close_direct(isc_nmsocket_t *sock) {
 void
 isc__nm_http_close(isc_nmsocket_t *sock) {
        REQUIRE(VALID_NMSOCK(sock));
-       REQUIRE(sock->type == isc_nm_httpstream);
+       REQUIRE(sock->type == isc_nm_httpsocket);
        REQUIRE(!isc__nmsocket_active(sock));
 
        if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
@@ -2184,48 +2171,45 @@ isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0) {
 
 static void
 failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result,
-                         isc_nm_http2_session_t *session) {
+                         isc_nm_http_session_t *session) {
        isc_nmhandle_t *handle = NULL;
        isc_sockaddr_t addr;
 
        REQUIRE(VALID_NMSOCK(sock));
-       INSIST(sock->type == isc_nm_httpstream);
+       INSIST(sock->type == isc_nm_httpsocket);
 
        if (!sock->h2.request_path) {
                return;
        }
 
-       INSIST(sock->h2.handler_cbarg != NULL);
+       INSIST(sock->h2.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);
+       sock->h2.cb(handle, result,
+                   &(isc_region_t){ sock->h2.buf, sock->h2.bufsize },
+                   sock->h2.cbarg);
        isc_nmhandle_detach(&handle);
 }
 
 static void
-failed_read_cb(isc_result_t result, isc_nm_http2_session_t *session) {
+failed_read_cb(isc_result_t result, isc_nm_http_session_t *session) {
        REQUIRE(VALID_HTTP2_SESSION(session));
 
        if (session->client) {
-               http2_client_stream_t *cstream = NULL;
+               http_cstream_t *cstream = NULL;
                cstream = ISC_LIST_HEAD(session->cstreams);
                while (cstream != NULL) {
-                       http2_client_stream_t *next = ISC_LIST_NEXT(cstream,
-                                                                   link);
+                       http_cstream_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);
+                       put_http_cstream(session->mctx, cstream);
                        cstream = next;
                }
        } else {
@@ -2250,7 +2234,8 @@ failed_read_cb(isc_result_t result, isc_nm_http2_session_t *session) {
                        h2data = next;
                }
        }
-       finish_http2_session(session);
+
+       finish_http_session(session);
 }
 
 static const bool base64url_validation_table[256] = {
@@ -2356,9 +2341,11 @@ isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64,
                        goto end;
                        break;
                default:
-                       /* All other characters from the alphabet are the same
+                       /*
+                        * 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. */
+                        * validation table for the rest of the characters.
+                        */
                        if (base64[i] != '-' && base64[i] != '_' &&
                            base64url_validation_table[(size_t)base64[i]])
                        {
@@ -2380,24 +2367,101 @@ end:
        return (res);
 }
 
-/* DoH GET Query String Scanner-less Recursive Descent Parser/Verifier
+void
+isc__nm_http_initsocket(isc_nmsocket_t *sock) {
+       REQUIRE(sock != NULL);
 
-It is based on the following grammar (using WSN/EBNF):
+       sock->h2 = (isc_nmsocket_h2_t){
+               .request_type = ISC_HTTP_REQ_UNSUPPORTED,
+               .request_scheme = ISC_HTTP_SCHEME_UNSUPPORTED,
+       };
 
-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.
-...
+       if (sock->type == isc_nm_httplistener) {
+               ISC_LIST_INIT(sock->h2.handlers);
+               ISC_LIST_INIT(sock->h2.handler_cbargs);
+               isc_rwlock_init(&sock->h2.lock, 0, 1);
+       }
+}
 
-Should be good enough.
-*/
+void
+isc__nm_http_cleanup_data(isc_nmsocket_t *sock) {
+       if (sock->type == isc_nm_httplistener ||
+           sock->type == isc_nm_httpsocket) {
+               if (sock->type == isc_nm_httplistener) {
+                       clear_handlers(sock);
+                       isc_rwlock_destroy(&sock->h2.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.cstream != NULL) {
+                       put_http_cstream(sock->mgr->mctx,
+                                        sock->h2.connect.cstream);
+                       sock->h2.connect.cstream = NULL;
+               }
+
+               if (sock->h2.buf != NULL) {
+                       isc_mem_free(sock->mgr->mctx, sock->h2.buf);
+                       sock->h2.buf = NULL;
+               }
+       }
+
+       if ((sock->type == isc_nm_httplistener ||
+            sock->type == isc_nm_httpsocket ||
+            sock->type == isc_nm_tcpsocket ||
+            sock->type == isc_nm_tlssocket) &&
+           sock->h2.session != NULL)
+       {
+               if (sock->h2.connect.uri != NULL) {
+                       isc_mem_free(sock->mgr->mctx, sock->h2.connect.uri);
+                       sock->h2.connect.uri = NULL;
+               }
+               isc__nm_httpsession_detach(&sock->h2.session);
+       }
+}
+
+void
+isc__nm_http_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
+       isc_nmsocket_t *sock = NULL;
 
-typedef struct isc_doh_query_parser_state {
+       REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(VALID_NMSOCK(handle->sock));
+       REQUIRE(handle->sock->type == isc_nm_httpsocket);
+
+       sock = handle->sock;
+       if (sock->h2.session != NULL && sock->h2.session->handle) {
+               INSIST(VALID_HTTP2_SESSION(sock->h2.session));
+               INSIST(VALID_NMHANDLE(sock->h2.session->handle));
+               isc_nmhandle_settimeout(sock->h2.session->handle, timeout);
+       }
+}
+
+/*
+ * 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_httpparser_state {
        const char *str;
 
        const char *last_key;
@@ -2406,10 +2470,10 @@ typedef struct isc_doh_query_parser_state {
        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;
+       bool query_found;
+       const char *query;
+       size_t query_len;
+} isc_httpparser_state_t;
 
 #define MATCH(ch)      (st->str[0] == (ch))
 #define MATCH_ALPHA()  isalpha(st->str[0])
@@ -2419,57 +2483,55 @@ typedef struct isc_doh_query_parser_state {
 #define GETP()        (st->str)
 
 static bool
-rule_query_string(isc_doh_query_parser_state_t *st);
+rule_query_string(isc_httpparser_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;
+isc__nm_parse_httpquery(const char *query_string, const char **start,
+                       size_t *len) {
+       isc_httpparser_state_t state;
 
        REQUIRE(start != NULL);
-       REQUIRE(len != 0);
+       REQUIRE(len != NULL);
 
-       if (query_string == NULL || *query_string == '\0' || start == NULL ||
-           len == 0) {
+       if (query_string == NULL || query_string[0] == '\0') {
                return (false);
        }
 
-       memset(&state, 0, sizeof(state));
-       state.str = query_string;
+       state = (isc_httpparser_state_t) { .str = query_string };
        if (!rule_query_string(&state)) {
                return (false);
        }
 
-       if (!state.doh_query_found) {
+       if (!state.query_found) {
                return (false);
        }
 
-       *start = state.doh_query;
-       *len = state.doh_query_length;
+       *start = state.query;
+       *len = state.query_len;
 
        return (true);
 }
 
 static bool
-rule_key_value_pair(isc_doh_query_parser_state_t *st);
+rule_key_value_pair(isc_httpparser_state_t *st);
 
 static bool
-rule_key(isc_doh_query_parser_state_t *st);
+rule_key(isc_httpparser_state_t *st);
 
 static bool
-rule_value(isc_doh_query_parser_state_t *st);
+rule_value(isc_httpparser_state_t *st);
 
 static bool
-rule_value_char(isc_doh_query_parser_state_t *st);
+rule_value_char(isc_httpparser_state_t *st);
 
 static bool
-rule_percent_charcode(isc_doh_query_parser_state_t *st);
+rule_percent_charcode(isc_httpparser_state_t *st);
 
 static bool
-rule_unreserved_char(isc_doh_query_parser_state_t *st);
+rule_unreserved_char(isc_httpparser_state_t *st);
 
 static bool
-rule_query_string(isc_doh_query_parser_state_t *st) {
+rule_query_string(isc_httpparser_state_t *st) {
        if (MATCH('?')) {
                ADVANCE();
        }
@@ -2487,7 +2549,7 @@ rule_query_string(isc_doh_query_parser_state_t *st) {
 }
 
 static bool
-rule_key_value_pair(isc_doh_query_parser_state_t *st) {
+rule_key_value_pair(isc_httpparser_state_t *st) {
        if (!rule_key(st)) {
                return (false);
        }
@@ -2503,9 +2565,9 @@ rule_key_value_pair(isc_doh_query_parser_state_t *st) {
                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;
+                       st->query_found = true;
+                       st->query = st->last_value;
+                       st->query_len = st->last_value_len;
                }
        } else {
                return (false);
@@ -2519,7 +2581,7 @@ rule_key_value_pair(isc_doh_query_parser_state_t *st) {
 }
 
 static bool
-rule_key(isc_doh_query_parser_state_t *st) {
+rule_key(isc_httpparser_state_t *st) {
        if (MATCH('_') || MATCH_ALPHA()) {
                st->last_key = GETP();
                ADVANCE();
@@ -2536,7 +2598,7 @@ rule_key(isc_doh_query_parser_state_t *st) {
 }
 
 static bool
-rule_value(isc_doh_query_parser_state_t *st) {
+rule_value(isc_httpparser_state_t *st) {
        const char *s = GETP();
        if (!rule_value_char(st)) {
                return (false);
@@ -2551,7 +2613,7 @@ rule_value(isc_doh_query_parser_state_t *st) {
 }
 
 static bool
-rule_value_char(isc_doh_query_parser_state_t *st) {
+rule_value_char(isc_httpparser_state_t *st) {
        if (rule_unreserved_char(st)) {
                return (true);
        }
@@ -2560,7 +2622,7 @@ rule_value_char(isc_doh_query_parser_state_t *st) {
 }
 
 static bool
-rule_unreserved_char(isc_doh_query_parser_state_t *st) {
+rule_unreserved_char(isc_httpparser_state_t *st) {
        if (MATCH_ALNUM() || MATCH('_') || MATCH('.') || MATCH('-') ||
            MATCH('~')) {
                ADVANCE();
@@ -2570,7 +2632,7 @@ rule_unreserved_char(isc_doh_query_parser_state_t *st) {
 }
 
 static bool
-rule_percent_charcode(isc_doh_query_parser_state_t *st) {
+rule_percent_charcode(isc_httpparser_state_t *st) {
        if (MATCH('%')) {
                ADVANCE();
        } else {
index 105ea4d0bbb3e2ae043d559bf108fd76ebce9ba1..f43be4b60d009bc39715c953211324d7ce6b0c2b 100644 (file)
@@ -34,6 +34,7 @@
 #include <isc/sockaddr.h>
 #include <isc/stats.h>
 #include <isc/thread.h>
+#include <isc/tls.h>
 #include <isc/util.h>
 
 #include "uv-compat.h"
@@ -156,8 +157,6 @@ isc__nm_dump_active(isc_nm_t *nm);
 #define isc__nmsocket_prep_destroy(sock) isc___nmsocket_prep_destroy(sock)
 #endif
 
-typedef struct isc_nm_http2_session isc_nm_http2_session_t;
-
 /*
  * Single network event loop worker.
  */
@@ -195,6 +194,7 @@ typedef struct isc__networker {
         atomic_load(&(t)->references) > 0)
 
 typedef void (*isc__nm_closecb)(isc_nmhandle_t *);
+typedef struct isc_nm_http_session isc_nm_http_session_t;
 
 struct isc_nmhandle {
        int magic;
@@ -210,7 +210,7 @@ struct isc_nmhandle {
        isc_nmsocket_t *sock;
        size_t ah_pos; /* Position in the socket's 'active handles' array */
 
-       isc_nm_http2_session_t *httpsession;
+       isc_nm_http_session_t *httpsession;
 
        isc_sockaddr_t peer;
        isc_sockaddr_t local;
@@ -302,20 +302,18 @@ typedef enum isc__netievent_type {
 
 typedef union {
        isc_nm_recv_cb_t recv;
-       isc_nm_http_cb_t http;
        isc_nm_cb_t send;
        isc_nm_cb_t connect;
        isc_nm_accept_cb_t accept;
 } isc__nm_cb_t;
 
-typedef struct isc_nm_http2_server_handler isc_nm_http2_server_handler_t;
-
-struct isc_nm_http2_server_handler {
+typedef struct isc_nm_httphandler isc_nm_httphandler_t;
+struct isc_nm_httphandler {
        char *path;
-       isc_nm_http_cb_t cb;
+       isc_nm_recv_cb_t cb;
        void *cbarg;
        size_t extrahandlesize;
-       LINK(isc_nm_http2_server_handler_t) link;
+       LINK(isc_nm_httphandler_t) link;
 };
 
 /*
@@ -674,7 +672,7 @@ typedef enum isc_nmsocket_type {
        isc_nm_tlsdnslistener,
        isc_nm_tlsdnssocket,
        isc_nm_httplistener,
-       isc_nm_httpstream
+       isc_nm_httpsocket
 } isc_nmsocket_type;
 
 /*%
@@ -704,25 +702,29 @@ enum {
 typedef struct isc_nmsocket_tls_send_req {
        isc_nmsocket_t *tlssock;
        isc_region_t data;
+       isc_nm_cb_t cb;
+       void *cbarg;
+       isc_nmhandle_t *handle;
+       bool finish;
 } isc_nmsocket_tls_send_req_t;
 
-typedef enum isc_doh_request_type {
+typedef enum isc_http_request_type {
        ISC_HTTP_REQ_GET,
        ISC_HTTP_REQ_POST,
        ISC_HTTP_REQ_UNSUPPORTED
-} isc_http2_request_type_t;
+} isc_http_request_type_t;
 
-typedef enum isc_http2_scheme_type {
+typedef enum isc_http_scheme_type {
        ISC_HTTP_SCHEME_HTTP,
        ISC_HTTP_SCHEME_HTTP_SECURE,
        ISC_HTTP_SCHEME_UNSUPPORTED
-} isc_http2_scheme_type_t;
+} isc_http_scheme_type_t;
 
-typedef struct isc_nm_http_doh_cbarg {
+typedef struct isc_nm_httpcbarg {
        isc_nm_recv_cb_t cb;
        void *cbarg;
-       LINK(struct isc_nm_http_doh_cbarg) link;
-} isc_nm_http_doh_cbarg_t;
+       LINK(struct isc_nm_httpcbarg) link;
+} isc_nm_httpcbarg_t;
 
 typedef struct isc_nmsocket_h2 {
        isc_nmsocket_t *psock; /* owner of the structure */
@@ -730,38 +732,43 @@ typedef struct isc_nmsocket_h2 {
        char *query_data;
        size_t query_data_len;
        bool query_too_large;
-       isc_nm_http2_server_handler_t *handler;
+       isc_nm_httphandler_t *handler;
 
        uint8_t *buf;
        size_t bufsize;
        size_t bufpos;
 
        int32_t stream_id;
-       isc_nm_http2_session_t *session;
+       isc_nm_http_session_t *session;
 
        isc_nmsocket_t *httpserver;
 
-       isc_http2_request_type_t request_type;
-       isc_http2_scheme_type_t request_scheme;
+       isc_http_request_type_t request_type;
+       isc_http_scheme_type_t request_scheme;
+
        size_t content_length;
+       char clenbuf[128];
+
        bool content_type_verified;
        bool accept_type_verified;
 
-       isc_nm_http_cb_t handler_cb;
-       void *handler_cbarg;
+       isc_nm_recv_cb_t cb;
+       void *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];
+       ISC_LIST(isc_nm_httphandler_t) handlers;
+       ISC_LIST(isc_nm_httpcbarg_t) handler_cbargs;
+       isc_rwlock_t lock;
 
-       struct isc_nmsocket_h2_connect_data {
+       struct {
                char *uri;
                bool post;
+               isc_tlsctx_t *tlsctx;
+               isc_nmiface_t local_interface;
+               void *cstream;
        } connect;
 } isc_nmsocket_h2_t;
+
 struct isc_nmsocket {
        /*% Unlocked, RO */
        int magic;
@@ -778,8 +785,8 @@ struct isc_nmsocket {
 
        /*% TLS stuff */
        struct tls {
-               SSL *ssl;
-               SSL_CTX *ctx;
+               isc_tls_t *tls;
+               isc_tlsctx_t *ctx;
                BIO *app_rbio;
                BIO *app_wbio;
                BIO *ssl_rbio;
@@ -802,21 +809,22 @@ struct isc_nmsocket {
        struct tlsstream {
                bool server;
                BIO *app_bio;
-               SSL *ssl;
-               SSL_CTX *ctx;
+               isc_tls_t *tls;
+               isc_tlsctx_t *ctx;
                BIO *ssl_bio;
                isc_nmsocket_t *tlslistener;
+               isc_nmiface_t server_iface;
+               isc_nmiface_t local_iface;
+               bool connect_from_networker;
+               atomic_bool result_updated;
                enum {
                        TLS_INIT,
                        TLS_HANDSHAKE,
                        TLS_IO,
-                       TLS_ERROR,
                        TLS_CLOSING,
                        TLS_CLOSED
-               } state;
+               } state; /*%< The order of these is significant */
                size_t nsending;
-               /* List of active send requests. */
-               ISC_LIST(isc__nm_uvreq_t) sends;
        } tlsstream;
 
        isc_nmsocket_h2_t h2;
@@ -1149,11 +1157,15 @@ isc__nmsocket_clearcb(isc_nmsocket_t *sock);
 void
 isc__nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
                  isc_result_t eresult);
+
+void
+isc__nm_connectcb_force_async(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
+                             isc_result_t eresult);
+
 void
 isc__nm_async_connectcb(isc__networker_t *worker, isc__netievent_t *ev0);
 /*%<
  * Issue a connect callback on the socket, used to call the callback
-
  */
 
 void
@@ -1518,19 +1530,42 @@ isc__nm_tls_cleanup_data(isc_nmsocket_t *sock);
 void
 isc__nm_tls_stoplistening(isc_nmsocket_t *sock);
 
+void
+isc__nm_tls_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+/*%<
+ * Set the read timeout and reset the timer for the socket
+ * associated with 'handle', and the TCP socket it wraps
+ * around.
+ */
+
 void
 isc__nm_http_stoplistening(isc_nmsocket_t *sock);
 
 void
-isc__nm_http_clear_handlers(isc_nmsocket_t *sock);
+isc__nm_http_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+/*%<
+ * Set the read timeout and reset the timer for the socket
+ * associated with 'handle', and the TLS/TCP socket it wraps
+ * around.
+ */
+
+void
+isc__nm_http_initsocket(isc_nmsocket_t *sock);
 
 void
-isc__nm_http_clear_session(isc_nmsocket_t *sock);
+isc__nm_http_cleanup_data(isc_nmsocket_t *sock);
+
+isc_result_t
+isc__nm_http_request(isc_nmhandle_t *handle, isc_region_t *region,
+                    isc_nm_recv_cb_t reply_cb, void *cbarg);
 
 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_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg);
+
 void
 isc__nm_http_close(isc_nmsocket_t *sock);
 
@@ -1544,8 +1579,8 @@ 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);
+isc__nm_parse_httpquery(const char *query_string, const char **start,
+                       size_t *len);
 
 char *
 isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url,
@@ -1555,6 +1590,12 @@ char *
 isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64,
                            const size_t base64_len, size_t *res_len);
 
+void
+isc__nm_httpsession_attach(isc_nm_http_session_t *source,
+                          isc_nm_http_session_t **targetp);
+void
+isc__nm_httpsession_detach(isc_nm_http_session_t **sessionp);
+
 #define isc__nm_uverr2result(x) \
        isc___nm_uverr2result(x, true, __FILE__, __LINE__, __func__)
 isc_result_t
index eb7a23e3a47d4e35955953ff39b91b4e5ea61b97..a29ec728a3d38e868bc5d402c8c7f5de7957db88 100644 (file)
@@ -1004,33 +1004,7 @@ nmsocket_cleanup(isc_nmsocket_t *sock, bool dofree FLARG) {
        isc_condition_destroy(&sock->cond);
        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);
+       isc__nm_http_cleanup_data(sock);
 #ifdef NETMGR_TRACE
        LOCK(&sock->mgr->lock);
        ISC_LIST_UNLINK(sock->mgr->active_sockets, sock, active_link);
@@ -1145,7 +1119,7 @@ isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG) {
                case isc_nm_tlsdnssocket:
                        isc__nm_tlsdns_close(sock);
                        return;
-               case isc_nm_httpstream:
+               case isc_nm_httpsocket:
                        isc__nm_http_close(sock);
                        return;
                default:
@@ -1255,7 +1229,7 @@ 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_httpsocket:
        case isc_nm_httplistener:
                if (family == AF_INET) {
                        sock->statsindex = tcp4statsindex;
@@ -1274,7 +1248,6 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type,
        isc_refcount_init(&sock->references, 1);
 
        memset(&sock->tlsstream, 0, sizeof(sock->tlsstream));
-       ISC_LIST_INIT(sock->tlsstream.sends);
 
        NETMGR_TRACE_LOG("isc__nmsocket_init():%p->references = %lu\n", sock,
                         isc_refcount_current(&sock->references));
@@ -1286,27 +1259,7 @@ 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;
+       isc__nm_http_initsocket(sock);
 
        sock->magic = NMSOCK_MAGIC;
 }
@@ -1449,8 +1402,9 @@ 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;
+       if (sock->type == isc_nm_httpsocket && sock->h2.session) {
+               isc__nm_httpsession_attach(sock->h2.session,
+                                          &handle->httpsession);
        }
 
        return (handle);
@@ -1582,6 +1536,10 @@ nmhandle_detach_cb(isc_nmhandle_t **handlep FLARG) {
                handle->doreset(handle->opaque);
        }
 
+       if (sock->type == isc_nm_httpsocket && handle->httpsession != NULL) {
+               isc__nm_httpsession_detach(&handle->httpsession);
+       }
+
        nmhandle_deactivate(sock, handle);
 
        /*
@@ -1642,6 +1600,12 @@ isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
        case isc_nm_tlsdnssocket:
                isc__nm_tlsdns_settimeout(handle, timeout);
                break;
+       case isc_nm_tlssocket:
+               isc__nm_tls_settimeout(handle, timeout);
+               break;
+       case isc_nm_httpsocket:
+               isc__nm_http_settimeout(handle, timeout);
+               break;
        default:
                INSIST(0);
                ISC_UNREACHABLE();
@@ -1758,7 +1722,7 @@ isc_nm_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb,
        case isc_nm_tlsdnssocket:
                isc__nm_tlsdns_send(handle, region, cb, cbarg);
                break;
-       case isc_nm_httpstream:
+       case isc_nm_httpsocket:
                isc__nm_http_send(handle, region, cb, cbarg);
                break;
        default:
@@ -1794,6 +1758,9 @@ isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
        case isc_nm_tlsdnssocket:
                isc__nm_tlsdns_read(handle, cb, cbarg);
                break;
+       case isc_nm_httpsocket:
+               isc__nm_http_read(handle, cb, cbarg);
+               break;
        default:
                INSIST(0);
                ISC_UNREACHABLE();
@@ -1893,27 +1860,38 @@ isc_nm_stoplistening(isc_nmsocket_t *sock) {
        }
 }
 
-void
-isc__nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
-                 isc_result_t eresult) {
+static void
+nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq, isc_result_t eresult,
+            bool force_async) {
+       isc__netievent_connectcb_t *ievent = NULL;
+
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(VALID_UVREQ(uvreq));
        REQUIRE(VALID_NMHANDLE(uvreq->handle));
 
-       if (eresult == ISC_R_SUCCESS) {
-               isc__netievent_connectcb_t ievent = { .sock = sock,
-                                                     .req = uvreq,
-                                                     .result = eresult };
-               isc__nm_async_connectcb(NULL, (isc__netievent_t *)&ievent);
-       } else {
-               isc__netievent_connectcb_t *ievent =
-                       isc__nm_get_netievent_connectcb(sock->mgr, sock, uvreq,
-                                                       eresult);
+       ievent = isc__nm_get_netievent_connectcb(sock->mgr, sock, uvreq,
+                                                eresult);
+       if (force_async) {
                isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
                                       (isc__netievent_t *)ievent);
+       } else {
+               isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+                                            (isc__netievent_t *)ievent);
        }
 }
 
+void
+isc__nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
+                 isc_result_t eresult) {
+       nm_connectcb(sock, uvreq, eresult, false);
+}
+
+void
+isc__nm_connectcb_force_async(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
+                             isc_result_t eresult) {
+       nm_connectcb(sock, uvreq, eresult, true);
+}
+
 void
 isc__nm_async_connectcb(isc__networker_t *worker, isc__netievent_t *ev0) {
        isc__netievent_connectcb_t *ievent = (isc__netievent_connectcb_t *)ev0;
@@ -2460,8 +2438,8 @@ nmsocket_type_totext(isc_nmsocket_type type) {
                return ("isc_nm_tlsdnssocket");
        case isc_nm_httplistener:
                return ("isc_nm_httplistener");
-       case isc_nm_httpstream:
-               return ("isc_nm_httpstream");
+       case isc_nm_httpsocket:
+               return ("isc_nm_httpsocket");
        default:
                INSIST(0);
                ISC_UNREACHABLE();
index e7ad6cd634c04d79dbb1599f2b12fc79318db4e4..7884c1a20b371e7dadf0911df2aaf93cb0bba08d 100644 (file)
@@ -167,23 +167,6 @@ 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);
@@ -231,7 +214,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);
@@ -260,7 +243,6 @@ 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);
@@ -333,17 +315,31 @@ 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);
 
        req = isc__nm_uvreq_get(mgr, sock);
index eaa8921b150acf265d220106cb9d012b0345bb1d..67690a2ebfaef1d9a24c37eec27a6359f7baf899 100644 (file)
@@ -338,8 +338,8 @@ tlsdns_connect_cb(uv_connect_t *uvreq, int status) {
        }
 
        sock->tls.state = TLS_STATE_NONE;
-       sock->tls.ssl = SSL_new(sock->tls.ctx);
-       RUNTIME_CHECK(sock->tls.ssl != NULL);
+       sock->tls.tls = isc_tls_create(sock->tls.ctx);
+       RUNTIME_CHECK(sock->tls.tls != NULL);
 
        /*
         *
@@ -359,13 +359,13 @@ tlsdns_connect_cb(uv_connect_t *uvreq, int status) {
         * may be necessary to increment the number of references available
         * using BIO_up_ref(3) before calling the set0 functions.
         */
-       SSL_set0_rbio(sock->tls.ssl, sock->tls.ssl_rbio);
-       SSL_set0_wbio(sock->tls.ssl, sock->tls.ssl_wbio);
+       SSL_set0_rbio(sock->tls.tls, sock->tls.ssl_rbio);
+       SSL_set0_wbio(sock->tls.tls, sock->tls.ssl_wbio);
 #else
-       SSL_set_bio(sock->tls.ssl, sock->tls.ssl_rbio, sock->tls.ssl_wbio);
+       SSL_set_bio(sock->tls.tls, sock->tls.ssl_rbio, sock->tls.ssl_wbio);
 #endif
 
-       SSL_set_connect_state(sock->tls.ssl);
+       SSL_set_connect_state(sock->tls.tls);
 
        result = isc_sockaddr_fromsockaddr(&sock->peer, (struct sockaddr *)&ss);
        RUNTIME_CHECK(result == ISC_R_SUCCESS);
@@ -782,7 +782,7 @@ isc__nm_async_tlsdnsshutdown(isc__networker_t *worker, isc__netievent_t *ev0) {
                return;
        }
 
-       rv = SSL_shutdown(sock->tls.ssl);
+       rv = SSL_shutdown(sock->tls.tls);
 
        if (rv == 1) {
                sock->tls.state = TLS_STATE_NONE;
@@ -802,7 +802,7 @@ isc__nm_async_tlsdnsshutdown(isc__networker_t *worker, isc__netievent_t *ev0) {
                return;
        }
 
-       err = SSL_get_error(sock->tls.ssl, rv);
+       err = SSL_get_error(sock->tls.tls, rv);
 
        switch (err) {
        case SSL_ERROR_WANT_READ:
@@ -1167,9 +1167,9 @@ tls_cycle_input(isc_nmsocket_t *sock) {
                size_t len;
 
                for (;;) {
-                       (void)SSL_peek(sock->tls.ssl, &(char){ '\0' }, 0);
+                       (void)SSL_peek(sock->tls.tls, &(char){ '\0' }, 0);
 
-                       int pending = SSL_pending(sock->tls.ssl);
+                       int pending = SSL_pending(sock->tls.tls);
                        if (pending > TLS_BUF_SIZE) {
                                pending = TLS_BUF_SIZE;
                        }
@@ -1179,7 +1179,7 @@ tls_cycle_input(isc_nmsocket_t *sock) {
                        }
 
                        len = 0;
-                       rv = SSL_read_ex(sock->tls.ssl,
+                       rv = SSL_read_ex(sock->tls.tls,
                                         sock->buf + sock->buf_len,
                                         sock->buf_size - sock->buf_len, &len);
                        if (rv != 1) {
@@ -1196,11 +1196,11 @@ tls_cycle_input(isc_nmsocket_t *sock) {
 
                        process_sock_buffer(sock);
                }
-       } else if (!SSL_is_init_finished(sock->tls.ssl)) {
-               if (SSL_is_server(sock->tls.ssl)) {
-                       rv = SSL_accept(sock->tls.ssl);
+       } else if (!SSL_is_init_finished(sock->tls.tls)) {
+               if (SSL_is_server(sock->tls.tls)) {
+                       rv = SSL_accept(sock->tls.tls);
                } else {
-                       rv = SSL_connect(sock->tls.ssl);
+                       rv = SSL_connect(sock->tls.tls);
                }
 
        } else {
@@ -1208,13 +1208,13 @@ tls_cycle_input(isc_nmsocket_t *sock) {
        }
 
        if (rv <= 0) {
-               err = SSL_get_error(sock->tls.ssl, rv);
+               err = SSL_get_error(sock->tls.tls, rv);
        }
 
        switch (err) {
        case SSL_ERROR_WANT_READ:
                if (sock->tls.state == TLS_STATE_NONE &&
-                   !SSL_is_init_finished(sock->tls.ssl)) {
+                   !SSL_is_init_finished(sock->tls.tls)) {
                        sock->tls.state = TLS_STATE_HANDSHAKE;
                        start_reading(sock);
                }
@@ -1237,11 +1237,11 @@ tls_cycle_input(isc_nmsocket_t *sock) {
 
        /* Stop state after handshake */
        if (sock->tls.state == TLS_STATE_HANDSHAKE &&
-           SSL_is_init_finished(sock->tls.ssl))
+           SSL_is_init_finished(sock->tls.tls))
        {
                sock->tls.state = TLS_STATE_IO;
 
-               if (SSL_is_server(sock->tls.ssl)) {
+               if (SSL_is_server(sock->tls.tls)) {
                        REQUIRE(sock->recv_handle != NULL);
                        result = sock->accept_cb(sock->recv_handle,
                                                 ISC_R_SUCCESS,
@@ -1656,18 +1656,8 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
 
        csock->tls.state = TLS_STATE_NONE;
 
-       csock->tls.ssl = SSL_new(ssock->tls.ctx);
-
-       if (csock->tls.ssl == NULL) {
-               char errbuf[256];
-               unsigned long err = ERR_get_error();
-
-               ERR_error_string_n(err, errbuf, sizeof(errbuf));
-               fprintf(stderr, "%s:SSL_new(%p) -> %s\n", __func__,
-                       ssock->tls.ctx, errbuf);
-       }
-
-       RUNTIME_CHECK(csock->tls.ssl != NULL);
+       csock->tls.tls = isc_tls_create(ssock->tls.ctx);
+       RUNTIME_CHECK(csock->tls.tls != NULL);
 
        r = BIO_new_bio_pair(&csock->tls.ssl_wbio, TLS_BUF_SIZE,
                             &csock->tls.app_rbio, TLS_BUF_SIZE);
@@ -1684,13 +1674,13 @@ accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
         * may be necessary to increment the number of references available
         * using BIO_up_ref(3) before calling the set0 functions.
         */
-       SSL_set0_rbio(csock->tls.ssl, csock->tls.ssl_rbio);
-       SSL_set0_wbio(csock->tls.ssl, csock->tls.ssl_wbio);
+       SSL_set0_rbio(csock->tls.tls, csock->tls.ssl_rbio);
+       SSL_set0_wbio(csock->tls.tls, csock->tls.ssl_wbio);
 #else
-       SSL_set_bio(csock->tls.ssl, csock->tls.ssl_rbio, csock->tls.ssl_wbio);
+       SSL_set_bio(csock->tls.tls, csock->tls.ssl_rbio, csock->tls.ssl_wbio);
 #endif
 
-       SSL_set_accept_state(csock->tls.ssl);
+       SSL_set_accept_state(csock->tls.tls);
 
        /* FIXME: Set SSL_MODE_RELEASE_BUFFERS */
 
@@ -1823,7 +1813,7 @@ tlsdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
        }
 
        /* Writes won't succeed until handshake end */
-       if (!SSL_is_init_finished(sock->tls.ssl)) {
+       if (!SSL_is_init_finished(sock->tls.tls)) {
                goto requeue;
        }
 
@@ -1837,7 +1827,7 @@ tlsdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
        memmove(worker->sendbuf + sizeof(uint16_t), req->uvbuf.base,
                req->uvbuf.len);
 
-       rv = SSL_write_ex(sock->tls.ssl, worker->sendbuf, sendlen, &bytes);
+       rv = SSL_write_ex(sock->tls.tls, worker->sendbuf, sendlen, &bytes);
        if (rv > 0) {
                /* SSL_write_ex() doesn't do partial writes */
                INSIST(sendlen == bytes);
@@ -1848,7 +1838,7 @@ tlsdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
        }
 
        /* Nothing was written, maybe enqueue? */
-       err = SSL_get_error(sock->tls.ssl, rv);
+       err = SSL_get_error(sock->tls.tls, rv);
 
        switch (err) {
        case SSL_ERROR_WANT_WRITE:
@@ -1921,9 +1911,8 @@ tlsdns_close_cb(uv_handle_t *handle) {
 
        atomic_store(&sock->connected, false);
 
-       if (sock->tls.ssl) {
-               SSL_free(sock->tls.ssl);
-               sock->tls.ssl = NULL;
+       if (sock->tls.tls != NULL) {
+               isc_tls_free(&sock->tls.tls);
        }
 
        BIO_free_all(sock->tls.app_rbio);
index 87242e0a0bae21014c63fc274dded22d67c7d064..9a75dc0f2aca470f61c1ab1e626b82d2d837adb6 100644 (file)
@@ -44,13 +44,15 @@ tls_error_to_result(int tls_err) {
        switch (tls_err) {
        case SSL_ERROR_ZERO_RETURN:
                return (ISC_R_EOF);
+       case SSL_ERROR_SSL:
+               return (ISC_R_TLSERROR);
        default:
                return (ISC_R_UNEXPECTED);
        }
 }
 
 static void
-tls_do_bio(isc_nmsocket_t *sock);
+tls_do_bio(isc_nmsocket_t *sock, isc__nm_uvreq_t *send_data, bool finish);
 
 static void
 tls_close_direct(isc_nmsocket_t *sock);
@@ -67,6 +69,8 @@ static bool
 inactive(isc_nmsocket_t *sock) {
        return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
                sock->outerhandle == NULL ||
+               !isc__nmsocket_active(sock->outerhandle->sock) ||
+               atomic_load(&sock->outerhandle->sock->closing) ||
                (sock->listener != NULL &&
                 !isc__nmsocket_active(sock->listener)) ||
                atomic_load(&sock->mgr->closing));
@@ -74,17 +78,31 @@ inactive(isc_nmsocket_t *sock) {
 
 static void
 update_result(isc_nmsocket_t *sock, const isc_result_t result) {
-       LOCK(&sock->lock);
-       sock->result = result;
-       SIGNAL(&sock->cond);
-       if (!atomic_load(&sock->active)) {
-               WAIT(&sock->scond, &sock->lock);
+       if (!sock->tlsstream.server) {
+               LOCK(&sock->lock);
+               sock->result = result;
+               SIGNAL(&sock->cond);
+               while (!atomic_load(&sock->active)) {
+                       WAIT(&sock->scond, &sock->lock);
+               }
+               UNLOCK(&sock->lock);
+       } else {
+               LOCK(&sock->lock);
+               sock->result = result;
+               UNLOCK(&sock->lock);
        }
-       UNLOCK(&sock->lock);
-       if (sock->parent) {
-               LOCK(&sock->parent->lock);
-               sock->parent->result = result;
-               UNLOCK(&sock->parent->lock);
+}
+
+static void
+tls_call_connect_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle,
+                   const isc_result_t result) {
+       if (sock->connect_cb == NULL) {
+               return;
+       }
+       sock->connect_cb(handle, result, sock->connect_cbarg);
+       update_result(sock, result);
+       if (result != ISC_R_SUCCESS) {
+               isc__nmsocket_clearcb(handle->sock);
        }
 }
 
@@ -92,26 +110,38 @@ static void
 tls_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
        isc_nmsocket_tls_send_req_t *send_req =
                (isc_nmsocket_tls_send_req_t *)cbarg;
-       isc_nmsocket_t *sock = send_req->tlssock;
+       isc_nmsocket_t *tlssock = NULL;
+       bool finish = send_req->finish;
+
        REQUIRE(VALID_NMHANDLE(handle));
        REQUIRE(VALID_NMSOCK(handle->sock));
-       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(VALID_NMSOCK(send_req->tlssock));
+
+       tlssock = send_req->tlssock;
+       send_req->tlssock = NULL;
 
-       /*  XXXWPK TODO */
-       UNUSED(eresult);
+       if (send_req->cb != NULL) {
+               send_req->cb(send_req->handle, eresult, send_req->cbarg);
+               isc_nmhandle_detach(&send_req->handle);
+       }
 
        isc_mem_put(handle->sock->mgr->mctx, send_req->data.base,
                    send_req->data.length);
        isc_mem_put(handle->sock->mgr->mctx, send_req, sizeof(*send_req));
+       tlssock->tlsstream.nsending--;
 
-       sock->tlsstream.nsending--;
-       async_tls_do_bio(sock);
-       isc__nmsocket_detach(&sock);
+       if (finish && eresult == ISC_R_SUCCESS) {
+               isc_nm_cancelread(handle);
+       } else if (eresult == ISC_R_SUCCESS) {
+               tls_do_bio(tlssock, NULL, false);
+       }
+
+       isc__nmsocket_detach(&tlssock);
 }
 
 static void
 tls_failed_read_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle,
-                  const isc_result_t result, const bool close) {
+                  const isc_result_t result) {
        REQUIRE(VALID_NMSOCK(sock));
 
        if (!sock->tlsstream.server &&
@@ -121,9 +151,7 @@ tls_failed_read_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle,
        {
                INSIST(handle == NULL);
                handle = isc__nmhandle_get(sock, NULL, NULL);
-               sock->connect_cb(handle, result, sock->connect_cbarg);
-               update_result(sock, result);
-               isc__nmsocket_clearcb(sock);
+               tls_call_connect_cb(sock, handle, result);
                isc_nmhandle_detach(&handle);
        } else if (sock->recv_cb != NULL) {
                isc__nm_uvreq_t *req = NULL;
@@ -131,7 +159,7 @@ tls_failed_read_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle,
                req->cb.recv = sock->recv_cb;
                req->cbarg = sock->recv_cbarg;
                req->handle = NULL;
-               if (handle) {
+               if (handle != NULL) {
                        REQUIRE(VALID_NMHANDLE(handle));
                        isc_nmhandle_attach(handle, &req->handle);
                } else {
@@ -140,11 +168,9 @@ tls_failed_read_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle,
                isc__nmsocket_clearcb(sock);
                isc__nm_readcb(sock, req, result);
        }
-       sock->tlsstream.state = TLS_ERROR;
 
-       if (close) {
-               isc__nmsocket_prep_destroy(sock);
-       }
+       isc__nmsocket_prep_destroy(sock);
+       isc__nmsocket_detach(&sock);
 }
 
 static void
@@ -155,12 +181,70 @@ async_tls_do_bio(isc_nmsocket_t *sock) {
                               (isc__netievent_t *)ievent);
 }
 
+static int
+tls_send_outgoing(isc_nmsocket_t *sock, bool finish, isc_nmhandle_t *tlshandle,
+                 isc_nm_cb_t cb, void *cbarg) {
+       isc_nmsocket_tls_send_req_t *send_req = NULL;
+       int pending;
+       int rv;
+
+       if (inactive(sock)) {
+               if (cb != NULL) {
+                       INSIST(VALID_NMHANDLE(tlshandle));
+                       cb(tlshandle, ISC_R_CANCELED, cbarg);
+               }
+               return (0);
+       }
+
+       if (finish && (SSL_get_shutdown(sock->tlsstream.tls) &
+                      SSL_SENT_SHUTDOWN) != SSL_SENT_SHUTDOWN)
+       {
+               (void)SSL_shutdown(sock->tlsstream.tls);
+       }
+
+       pending = BIO_pending(sock->tlsstream.app_bio);
+       if (pending <= 0) {
+               return (pending);
+       }
+
+       /* TODO Should we keep track of these requests in a list? */
+       if (pending > TLS_BUF_SIZE) {
+               pending = TLS_BUF_SIZE;
+       }
+
+       send_req = isc_mem_get(sock->mgr->mctx, sizeof(*send_req));
+       *send_req = (isc_nmsocket_tls_send_req_t){
+               .finish = finish,
+               .data.base = isc_mem_get(sock->mgr->mctx, pending),
+               .data.length = pending
+       };
+
+       isc__nmsocket_attach(sock, &send_req->tlssock);
+       if (cb != NULL) {
+               send_req->cb = cb;
+               send_req->cbarg = cbarg;
+               isc_nmhandle_attach(tlshandle, &send_req->handle);
+       }
+
+       rv = BIO_read(sock->tlsstream.app_bio, send_req->data.base, pending);
+       /* There's something pending, read must succeed */
+       RUNTIME_CHECK(rv == pending);
+
+       INSIST(VALID_NMHANDLE(sock->outerhandle));
+
+       sock->tlsstream.nsending++;
+       isc_nm_send(sock->outerhandle, &send_req->data, tls_senddone, send_req);
+
+       return (pending);
+}
+
 static void
-tls_do_bio(isc_nmsocket_t *sock) {
+tls_do_bio(isc_nmsocket_t *sock, isc__nm_uvreq_t *send_data, bool finish) {
        isc_result_t result = ISC_R_SUCCESS;
        int pending, tls_err = 0;
        int rv;
-       isc__nm_uvreq_t *req;
+       char buf[1];
+       bool sent_shutdown, received_shutdown;
 
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(sock->tid == isc_nm_tid());
@@ -172,79 +256,91 @@ tls_do_bio(isc_nmsocket_t *sock) {
        }
 
        if (sock->tlsstream.state == TLS_INIT) {
-               (void)SSL_do_handshake(sock->tlsstream.ssl);
+               (void)SSL_do_handshake(sock->tlsstream.tls);
                sock->tlsstream.state = TLS_HANDSHAKE;
-       } else if (sock->tlsstream.state == TLS_ERROR) {
-               result = ISC_R_FAILURE;
-               goto low_level_error;
        } else if (sock->tlsstream.state == TLS_CLOSED) {
                return;
        }
 
+       received_shutdown = (SSL_get_shutdown(sock->tlsstream.tls) &
+                            SSL_RECEIVED_SHUTDOWN) == SSL_RECEIVED_SHUTDOWN;
+
        /* Data from TLS to client */
-       char buf[1];
-       if (sock->tlsstream.state == TLS_IO && sock->recv_cb != NULL &&
+       if (sock->tlsstream.state >= TLS_IO && sock->recv_cb != NULL &&
            !atomic_load(&sock->readpaused))
        {
-               (void)SSL_peek(sock->tlsstream.ssl, buf, 1);
-               while ((pending = SSL_pending(sock->tlsstream.ssl)) > 0) {
+               (void)SSL_peek(sock->tlsstream.tls, buf, 1);
+               while ((pending = SSL_pending(sock->tlsstream.tls)) > 0) {
+                       uint8_t recv_buf[TLS_BUF_SIZE];
+                       isc_region_t region, dregion;
+
                        if (pending > TLS_BUF_SIZE) {
                                pending = TLS_BUF_SIZE;
                        }
-                       isc_region_t region = {
-                               isc_mem_get(sock->mgr->mctx, pending), pending
-                       };
-                       isc_region_t dregion;
-                       memset(region.base, 0, region.length);
-                       rv = SSL_read(sock->tlsstream.ssl, region.base,
+                       region = (isc_region_t){ .base = &recv_buf[0],
+                                                .length = pending };
+
+                       rv = SSL_read(sock->tlsstream.tls, region.base,
                                      region.length);
                        /* Pending succeded, so should read */
                        RUNTIME_CHECK(rv == pending);
+
                        dregion = (isc_region_t){ region.base, rv };
                        sock->recv_cb(sock->statichandle, ISC_R_SUCCESS,
                                      &dregion, sock->recv_cbarg);
-                       isc_mem_put(sock->mgr->mctx, region.base,
-                                   region.length);
                }
        }
 
+       if (send_data != NULL) {
+               INSIST(sock->tlsstream.state > TLS_HANDSHAKE);
+               rv = SSL_write(sock->tlsstream.tls, send_data->uvbuf.base,
+                              send_data->uvbuf.len);
+               if (rv != (int)send_data->uvbuf.len) {
+                       result = received_shutdown ? ISC_R_CANCELED
+                                                  : ISC_R_TLSERROR;
+                       send_data->cb.send(send_data->handle, result,
+                                          send_data->cbarg);
+                       send_data = NULL;
+                       if (!received_shutdown) {
+                               isc__nmsocket_detach(&sock);
+                               return;
+                       }
+               }
+       }
+
+       sent_shutdown = (SSL_get_shutdown(sock->tlsstream.tls) &
+                        SSL_SENT_SHUTDOWN) == SSL_SENT_SHUTDOWN;
+
        /* Peek to move the session forward */
-       (void)SSL_peek(sock->tlsstream.ssl, buf, 1);
+       (void)SSL_peek(sock->tlsstream.tls, buf, 1);
 
        /* Data from TLS to network */
-       pending = BIO_pending(sock->tlsstream.app_bio);
-       if (pending > 0) {
-               /*TODO Should we keep the track of these requests in a list? */
-               isc_nmsocket_tls_send_req_t *send_req = NULL;
-               if (pending > TLS_BUF_SIZE) {
-                       pending = TLS_BUF_SIZE;
+       if (send_data != NULL) {
+               pending = tls_send_outgoing(sock, finish, send_data->handle,
+                                           send_data->cb.send,
+                                           send_data->cbarg);
+       } else {
+               if (received_shutdown && !sent_shutdown) {
+                       finish = true;
+                       (void)SSL_shutdown(sock->tlsstream.tls);
                }
-               send_req = isc_mem_get(sock->mgr->mctx, sizeof(*send_req));
-               send_req->data.base = isc_mem_get(sock->mgr->mctx, pending);
-               send_req->data.length = pending;
-               send_req->tlssock = NULL;
-               isc__nmsocket_attach(sock, &send_req->tlssock);
-               rv = BIO_read(sock->tlsstream.app_bio, send_req->data.base,
-                             pending);
-               /* There's something pending, read must succeed */
-               RUNTIME_CHECK(rv == pending);
-               INSIST(VALID_NMHANDLE(sock->outerhandle));
-               isc_nm_send(sock->outerhandle, &send_req->data, tls_senddone,
-                           send_req);
+               pending = tls_send_outgoing(sock, finish, NULL, NULL, NULL);
+       }
+
+       if (pending > 0) {
                /* We'll continue in tls_senddone */
                return;
        }
 
        /* Get the potential error code */
-       rv = SSL_peek(sock->tlsstream.ssl, buf, 1);
-
+       rv = SSL_peek(sock->tlsstream.tls, buf, 1);
        if (rv < 0) {
-               tls_err = SSL_get_error(sock->tlsstream.ssl, rv);
+               tls_err = SSL_get_error(sock->tlsstream.tls, rv);
        }
 
        /* Only after doing the IO we can check if SSL handshake is done */
        if (sock->tlsstream.state == TLS_HANDSHAKE &&
-           SSL_is_init_finished(sock->tlsstream.ssl) == 1)
+           SSL_is_init_finished(sock->tlsstream.tls) == 1)
        {
                isc_nmhandle_t *tlshandle = isc__nmhandle_get(sock, NULL, NULL);
                if (sock->tlsstream.server) {
@@ -252,9 +348,7 @@ tls_do_bio(isc_nmsocket_t *sock) {
                                                  ISC_R_SUCCESS,
                                                  sock->listener->accept_cbarg);
                } else {
-                       sock->connect_cb(tlshandle, ISC_R_SUCCESS,
-                                        sock->connect_cbarg);
-                       update_result(tlshandle->sock, ISC_R_SUCCESS);
+                       tls_call_connect_cb(sock, tlshandle, ISC_R_SUCCESS);
                }
                isc_nmhandle_detach(&tlshandle);
                sock->tlsstream.state = TLS_IO;
@@ -263,8 +357,14 @@ tls_do_bio(isc_nmsocket_t *sock) {
        }
 
        switch (tls_err) {
-       case 0:
+       case SSL_ERROR_NONE:
+               if (sent_shutdown && received_shutdown) {
+                       /* clean shutdown */
+                       isc_nm_cancelread(sock->outerhandle);
+                       isc__nm_tls_close(sock);
+               };
                return;
+               break;
        case SSL_ERROR_WANT_WRITE:
                if (sock->tlsstream.nsending == 0) {
                        /*
@@ -272,83 +372,30 @@ tls_do_bio(isc_nmsocket_t *sock) {
                         * already the send callback will call it.
                         */
                        async_tls_do_bio(sock);
+                       return;
                } else {
                        return;
                }
                break;
        case SSL_ERROR_WANT_READ:
-               INSIST(VALID_NMHANDLE(sock->outerhandle));
-               isc_nm_resumeread(sock->outerhandle);
+               if (sock->outerhandle != NULL) {
+                       INSIST(VALID_NMHANDLE(sock->outerhandle));
+                       isc_nm_resumeread(sock->outerhandle);
+               }
+               return;
                break;
        default:
                result = tls_error_to_result(tls_err);
                goto error;
        }
 
-       while ((req = ISC_LIST_HEAD(sock->tlsstream.sends)) != NULL) {
-               INSIST(VALID_UVREQ(req));
-               rv = SSL_write(sock->tlsstream.ssl, req->uvbuf.base,
-                              req->uvbuf.len);
-               if (rv < 0) {
-                       if (sock->tlsstream.nsending == 0) {
-                               async_tls_do_bio(sock);
-                       }
-                       return;
-               }
-               if (rv != (int)req->uvbuf.len) {
-                       if (!sock->tlsstream.server &&
-                           (sock->tlsstream.state == TLS_HANDSHAKE ||
-                            TLS_INIT))
-                       {
-                               isc_nmhandle_t *tlshandle =
-                                       isc__nmhandle_get(sock, NULL, NULL);
-                               sock->connect_cb(tlshandle, result,
-                                                sock->connect_cbarg);
-                               update_result(tlshandle->sock, result);
-                               isc_nmhandle_detach(&tlshandle);
-                       }
-                       sock->tlsstream.state = TLS_ERROR;
-                       async_tls_do_bio(sock);
-                       return;
-               }
-               ISC_LIST_UNLINK(sock->tlsstream.sends, req, link);
-               req->cb.send(sock->statichandle, ISC_R_SUCCESS, req->cbarg);
-               isc__nm_uvreq_put(&req, sock);
-       }
-
        return;
 
 error:
        isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
                      ISC_LOG_ERROR, "SSL error in BIO: %d %s", tls_err,
                      isc_result_totext(result));
-low_level_error:
-       if (sock->tlsstream.state == TLS_HANDSHAKE) {
-               isc_nmhandle_t *tlshandle = isc__nmhandle_get(sock, NULL, NULL);
-               if (!sock->tlsstream.server) {
-                       sock->connect_cb(tlshandle, result,
-                                        sock->connect_cbarg);
-                       update_result(tlshandle->sock, result);
-               }
-               isc_nmhandle_detach(&tlshandle);
-       } else if (sock->tlsstream.state == TLS_IO) {
-               if (ISC_LIST_HEAD(sock->tlsstream.sends) != NULL) {
-                       while ((req = ISC_LIST_HEAD(sock->tlsstream.sends)) !=
-                              NULL) {
-                               req->cb.send(sock->statichandle, result,
-                                            req->cbarg);
-                               ISC_LIST_UNLINK(sock->tlsstream.sends, req,
-                                               link);
-                               isc__nm_uvreq_put(&req, sock);
-                       }
-               } else if (sock->recv_cb != NULL) {
-                       tls_failed_read_cb(sock, sock->statichandle, result,
-                                          false);
-               } else {
-                       tls_close_direct(sock);
-               }
-       }
-       sock->tlsstream.state = TLS_ERROR;
+       tls_failed_read_cb(sock, sock->statichandle, result);
 }
 
 static void
@@ -361,18 +408,18 @@ tls_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
        REQUIRE(VALID_NMHANDLE(handle));
        REQUIRE(tlssock->tid == isc_nm_tid());
        if (result != ISC_R_SUCCESS) {
-               tls_failed_read_cb(tlssock, tlssock->statichandle, result,
-                                  true);
+               tls_failed_read_cb(tlssock, tlssock->statichandle, result);
                return;
        }
        rv = BIO_write(tlssock->tlsstream.app_bio, region->base,
                       region->length);
-
        if (rv != (int)region->length) {
                /* XXXWPK log it? */
-               tlssock->tlsstream.state = TLS_ERROR;
+               tls_failed_read_cb(tlssock, tlssock->statichandle,
+                                  ISC_R_TLSERROR);
+               return;
        }
-       tls_do_bio(tlssock);
+       tls_do_bio(tlssock, NULL, false);
 }
 
 static isc_result_t
@@ -382,20 +429,20 @@ initialize_tls(isc_nmsocket_t *sock, bool server) {
        if (BIO_new_bio_pair(&(sock->tlsstream.ssl_bio), TLS_BUF_SIZE,
                             &(sock->tlsstream.app_bio), TLS_BUF_SIZE) != 1)
        {
-               SSL_free(sock->tlsstream.ssl);
+               isc_tls_free(&sock->tlsstream.tls);
                return (ISC_R_TLSERROR);
        }
 
-       SSL_set_bio(sock->tlsstream.ssl, sock->tlsstream.ssl_bio,
+       SSL_set_bio(sock->tlsstream.tls, sock->tlsstream.ssl_bio,
                    sock->tlsstream.ssl_bio);
        if (server) {
-               SSL_set_accept_state(sock->tlsstream.ssl);
+               SSL_set_accept_state(sock->tlsstream.tls);
        } else {
-               SSL_set_connect_state(sock->tlsstream.ssl);
+               SSL_set_connect_state(sock->tlsstream.tls);
        }
        sock->tlsstream.nsending = 0;
        isc_nm_read(sock->outerhandle, tls_readcb, sock);
-       tls_do_bio(sock);
+       tls_do_bio(sock, NULL, false);
        return (ISC_R_SUCCESS);
 }
 
@@ -403,7 +450,6 @@ static isc_result_t
 tlslisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
        isc_nmsocket_t *tlslistensock = (isc_nmsocket_t *)cbarg;
        isc_nmsocket_t *tlssock = NULL;
-       int r;
 
        /* If accept() was unsuccessful we can't do anything */
        if (result != ISC_R_SUCCESS) {
@@ -420,14 +466,12 @@ tlslisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
         */
        tlssock = isc_mem_get(handle->sock->mgr->mctx, sizeof(*tlssock));
        isc__nmsocket_init(tlssock, handle->sock->mgr, isc_nm_tlssocket,
-                          handle->sock->iface);
+                          &tlslistensock->tlsstream.server_iface);
 
        /* We need to initialize SSL now to reference SSL_CTX properly */
        tlssock->tlsstream.ctx = tlslistensock->tlsstream.ctx;
-       tlssock->tlsstream.ssl = SSL_new(tlssock->tlsstream.ctx);
-       ISC_LIST_INIT(tlssock->tlsstream.sends);
-       if (tlssock->tlsstream.ssl == NULL) {
-               update_result(tlssock, ISC_R_TLSERROR);
+       tlssock->tlsstream.tls = isc_tls_create(tlssock->tlsstream.ctx);
+       if (tlssock->tlsstream.tls == NULL) {
                atomic_store(&tlssock->closed, true);
                isc__nmsocket_detach(&tlssock);
                return (ISC_R_TLSERROR);
@@ -442,12 +486,6 @@ tlslisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
        tlssock->tlsstream.server = true;
        tlssock->tlsstream.state = TLS_INIT;
 
-       r = uv_timer_init(&tlssock->mgr->workers[isc_nm_tid()].loop,
-                         &tlssock->timer);
-       RUNTIME_CHECK(r == 0);
-
-       tlssock->timer.data = tlssock;
-       tlssock->timer_initialized = true;
        tlssock->tlsstream.ctx = tlslistensock->tlsstream.ctx;
 
        result = initialize_tls(tlssock, true);
@@ -469,12 +507,15 @@ isc_nm_listentls(isc_nm_t *mgr, isc_nmiface_t *iface,
        REQUIRE(VALID_NM(mgr));
 
        isc__nmsocket_init(tlssock, mgr, isc_nm_tlslistener, iface);
+       tlssock->tlsstream.server_iface = *iface;
+       ISC_LINK_INIT(&tlssock->tlsstream.server_iface.addr, link);
+       tlssock->iface = &tlssock->tlsstream.server_iface;
        tlssock->result = ISC_R_DEFAULT;
        tlssock->accept_cb = accept_cb;
        tlssock->accept_cbarg = accept_cbarg;
        tlssock->extrahandlesize = extrahandlesize;
        tlssock->tlsstream.ctx = sslctx;
-       tlssock->tlsstream.ssl = NULL;
+       tlssock->tlsstream.tls = NULL;
 
        /*
         * tlssock will be a TLS 'wrapper' around an unencrypted stream.
@@ -515,46 +556,25 @@ isc_nm_listentls(isc_nm_t *mgr, isc_nmiface_t *iface,
 
 void
 isc__nm_async_tlssend(isc__networker_t *worker, isc__netievent_t *ev0) {
-       int rv;
        isc__netievent_tlssend_t *ievent = (isc__netievent_tlssend_t *)ev0;
        isc_nmsocket_t *sock = ievent->sock;
        isc__nm_uvreq_t *req = ievent->req;
-       ievent->req = NULL;
+
        REQUIRE(VALID_UVREQ(req));
        REQUIRE(sock->tid == isc_nm_tid());
+
        UNUSED(worker);
 
+       ievent->req = NULL;
+
        if (inactive(sock)) {
                req->cb.send(req->handle, ISC_R_CANCELED, req->cbarg);
                isc__nm_uvreq_put(&req, sock);
                return;
        }
-       if (!ISC_LIST_EMPTY(sock->tlsstream.sends)) {
-               /* We're not the first */
-               ISC_LIST_APPEND(sock->tlsstream.sends, req, link);
-               tls_do_bio(sock);
-               return;
-       }
 
-       rv = SSL_write(sock->tlsstream.ssl, req->uvbuf.base, req->uvbuf.len);
-       if (rv < 0) {
-               /*
-                * We might need to read, we might need to write, or the
-                * TLS socket might be dead - in any case, we need to
-                * enqueue the uvreq and let the TLS BIO layer do the rest.
-                */
-               ISC_LIST_APPEND(sock->tlsstream.sends, req, link);
-               tls_do_bio(sock);
-               return;
-       }
-       if (rv != (int)req->uvbuf.len) {
-               sock->tlsstream.state = TLS_ERROR;
-               async_tls_do_bio(sock);
-               return;
-       }
-       req->cb.send(sock->statichandle, ISC_R_SUCCESS, req->cbarg);
+       tls_do_bio(sock, req, false);
        isc__nm_uvreq_put(&req, sock);
-       tls_do_bio(sock);
        return;
 }
 
@@ -600,20 +620,24 @@ isc__nm_async_tlsstartread(isc__networker_t *worker, isc__netievent_t *ev0) {
        isc_nmsocket_t *sock = ievent->sock;
 
        REQUIRE(sock->tid == isc_nm_tid());
+
        UNUSED(worker);
 
-       tls_do_bio(sock);
+       tls_do_bio(sock, NULL, false);
 }
 
 void
 isc__nm_tls_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+       isc__netievent_tlsstartread_t *ievent = NULL;
+       isc_nmsocket_t *sock = NULL;
+
        REQUIRE(VALID_NMHANDLE(handle));
-       REQUIRE(VALID_NMSOCK(handle->sock));
-       REQUIRE(handle->sock->statichandle == handle);
-       REQUIRE(handle->sock->tid == isc_nm_tid());
 
-       isc__netievent_tlsstartread_t *ievent = NULL;
-       isc_nmsocket_t *sock = handle->sock;
+       sock = handle->sock;
+
+       REQUIRE(VALID_NMSOCK(sock));
+       REQUIRE(sock->statichandle == handle);
+       REQUIRE(sock->tid == isc_nm_tid());
 
        if (inactive(sock)) {
                cb(handle, ISC_R_NOTCONNECTED, NULL, cbarg);
@@ -632,86 +656,49 @@ void
 isc__nm_tls_pauseread(isc_nmhandle_t *handle) {
        REQUIRE(VALID_NMHANDLE(handle));
        REQUIRE(VALID_NMSOCK(handle->sock));
-       isc_nmsocket_t *sock = handle->sock;
 
-       atomic_store(&sock->readpaused, true);
+       atomic_store(&handle->sock->readpaused, true);
+       if (handle->sock->outerhandle != NULL) {
+               isc_nm_pauseread(handle->sock->outerhandle);
+       }
 }
 
 void
 isc__nm_tls_resumeread(isc_nmhandle_t *handle) {
        REQUIRE(VALID_NMHANDLE(handle));
        REQUIRE(VALID_NMSOCK(handle->sock));
-       isc_nmsocket_t *sock = handle->sock;
-
-       atomic_store(&sock->readpaused, false);
-       async_tls_do_bio(sock);
-}
 
-static void
-timer_close_cb(uv_handle_t *handle) {
-       isc_nmsocket_t *sock = (isc_nmsocket_t *)uv_handle_get_data(handle);
-       tls_close_direct(sock);
+       atomic_store(&handle->sock->readpaused, false);
+       async_tls_do_bio(handle->sock);
 }
 
 static void
 tls_close_direct(isc_nmsocket_t *sock) {
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(sock->tid == isc_nm_tid());
-
-       /* if (!sock->tlsstream.server) { */
-       /*      INSIST(sock->tlsstream.state != TLS_HANDSHAKE && */
-       /*             sock->tlsstream.state != TLS_INIT); */
-       /* } */
-
-       sock->tlsstream.state = TLS_CLOSING;
-
-       if (sock->timer_running) {
-               uv_timer_stop(&sock->timer);
-               sock->timer_running = false;
+       /*
+        * At this point we're certain that there are no
+        * external references, we can close everything.
+        */
+       if (sock->outerhandle != NULL) {
+               isc_nm_pauseread(sock->outerhandle);
+               isc_nmhandle_detach(&sock->outerhandle);
        }
 
-       /* We don't need atomics here, it's all in single network thread
-        */
-       if (sock->timer_initialized) {
-               /*
-                * We need to fire the timer callback to clean it up,
-                * it will then call us again (via detach) so that we
-                * can finally close the socket.
-                */
-               sock->timer_initialized = false;
-               uv_timer_stop(&sock->timer);
-               uv_close((uv_handle_t *)&sock->timer, timer_close_cb);
-       } else {
-               /*
-                * At this point we're certain that there are no
-                * external references, we can close everything.
-                */
-               if (sock->outerhandle != NULL) {
-                       isc_nm_pauseread(sock->outerhandle);
-                       isc_nmhandle_detach(&sock->outerhandle);
-               }
-               if (sock->listener != NULL) {
-                       isc__nmsocket_detach(&sock->listener);
-               }
-               if (sock->tlsstream.ssl != NULL) {
-                       SSL_free(sock->tlsstream.ssl);
-                       sock->tlsstream.ssl = NULL;
-                       /* These are destroyed when we free SSL* */
-                       sock->tlsstream.ctx = NULL;
-                       sock->tlsstream.ssl_bio = NULL;
-               }
-               if (sock->tlsstream.app_bio != NULL) {
-                       BIO_free(sock->tlsstream.app_bio);
-                       sock->tlsstream.app_bio = NULL;
-               }
-               sock->tlsstream.state = TLS_CLOSED;
-               atomic_store(&sock->closed, true);
-               isc__nmsocket_detach(&sock);
+       if (sock->listener != NULL) {
+               isc__nmsocket_detach(&sock->listener);
        }
+
+       /* further cleanup performed in isc__nm_tls_cleanup_data() */
+       atomic_store(&sock->active, false);
+       atomic_store(&sock->closed, true);
+       sock->tlsstream.state = TLS_CLOSED;
 }
 
 void
 isc__nm_tls_close(isc_nmsocket_t *sock) {
+       isc__netievent_tlsclose_t *ievent = NULL;
+
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(sock->type == isc_nm_tlssocket);
 
@@ -720,24 +707,21 @@ isc__nm_tls_close(isc_nmsocket_t *sock) {
                return;
        }
 
-       if (sock->tid == isc_nm_tid()) {
-               tls_close_direct(sock);
-       } else {
-               isc__netievent_tlsclose_t *ievent =
-                       isc__nm_get_netievent_tlsclose(sock->mgr, sock);
-               isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
-                                      (isc__netievent_t *)ievent);
-       }
+       ievent = isc__nm_get_netievent_tlsclose(sock->mgr, sock);
+       isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+                                    (isc__netievent_t *)ievent);
 }
 
 void
 isc__nm_async_tlsclose(isc__networker_t *worker, isc__netievent_t *ev0) {
        isc__netievent_tlsclose_t *ievent = (isc__netievent_tlsclose_t *)ev0;
+       isc_nmsocket_t *sock = ievent->sock;
 
        REQUIRE(ievent->sock->tid == isc_nm_tid());
+
        UNUSED(worker);
 
-       tls_close_direct(ievent->sock);
+       tls_close_direct(sock);
 }
 
 void
@@ -749,9 +733,8 @@ isc__nm_tls_stoplistening(isc_nmsocket_t *sock) {
        atomic_store(&sock->closed, true);
        sock->recv_cb = NULL;
        sock->recv_cbarg = NULL;
-       if (sock->tlsstream.ssl != NULL) {
-               SSL_free(sock->tlsstream.ssl);
-               sock->tlsstream.ssl = NULL;
+       if (sock->tlsstream.tls != NULL) {
+               isc_tls_free(&sock->tlsstream.tls);
                sock->tlsstream.ctx = NULL;
        }
 
@@ -773,6 +756,9 @@ isc_nm_tlsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
 
        nsock = isc_mem_get(mgr->mctx, sizeof(*nsock));
        isc__nmsocket_init(nsock, mgr, isc_nm_tlssocket, local);
+       nsock->tlsstream.local_iface = *local;
+       ISC_LINK_INIT(&nsock->tlsstream.local_iface.addr, link);
+       nsock->iface = &nsock->tlsstream.local_iface;
        nsock->extrahandlesize = extrahandlesize;
        nsock->result = ISC_R_DEFAULT;
        nsock->connect_cb = cb;
@@ -815,29 +801,31 @@ isc_nm_tlsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer,
 }
 
 static void
-tls_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+tcp_connected(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
        isc_nmsocket_t *tlssock = (isc_nmsocket_t *)cbarg;
+       isc_nmhandle_t *tlshandle = NULL;
 
        REQUIRE(VALID_NMSOCK(tlssock));
+       REQUIRE(VALID_NMHANDLE(handle));
 
        if (result != ISC_R_SUCCESS) {
-               tlssock->connect_cb(handle, result, tlssock->connect_cbarg);
-               update_result(tlssock, result);
-               tls_close_direct(tlssock);
-               return;
+               goto error;
        }
 
-       INSIST(VALID_NMHANDLE(handle));
-
        tlssock->peer = isc_nmhandle_peeraddr(handle);
        isc_nmhandle_attach(handle, &tlssock->outerhandle);
        result = initialize_tls(tlssock, false);
        if (result != ISC_R_SUCCESS) {
-               tlssock->connect_cb(handle, result, tlssock->connect_cbarg);
-               update_result(tlssock, result);
-               tls_close_direct(tlssock);
-               return;
+               goto error;
        }
+
+       return;
+error:
+       tlshandle = isc__nmhandle_get(tlssock, NULL, NULL);
+       atomic_store(&tlssock->closed, true);
+       tls_call_connect_cb(tlssock, tlshandle, result);
+       isc_nmhandle_detach(&tlshandle);
+       isc__nmsocket_detach(&tlssock);
 }
 
 void
@@ -846,7 +834,6 @@ isc__nm_async_tlsconnect(isc__networker_t *worker, isc__netievent_t *ev0) {
                (isc__netievent_tlsconnect_t *)ev0;
        isc_nmsocket_t *tlssock = ievent->sock;
        isc_result_t result;
-       int r;
        isc_nmhandle_t *tlshandle = NULL;
 
        UNUSED(worker);
@@ -854,36 +841,36 @@ isc__nm_async_tlsconnect(isc__networker_t *worker, isc__netievent_t *ev0) {
        /*
         * We need to initialize SSL now to reference SSL_CTX properly.
         */
-       tlssock->tlsstream.ssl = SSL_new(tlssock->tlsstream.ctx);
-       if (tlssock->tlsstream.ssl == NULL) {
+       tlssock->tlsstream.tls = isc_tls_create(tlssock->tlsstream.ctx);
+       if (tlssock->tlsstream.tls == NULL) {
                result = ISC_R_TLSERROR;
+               update_result(tlssock, result);
                goto error;
        }
 
        tlssock->tid = isc_nm_tid();
-       r = uv_timer_init(&tlssock->mgr->workers[isc_nm_tid()].loop,
-                         &tlssock->timer);
-       RUNTIME_CHECK(r == 0);
-
-       tlssock->timer.data = tlssock;
-       tlssock->timer_initialized = true;
        tlssock->tlsstream.state = TLS_INIT;
 
-       result = isc_nm_tcpconnect(worker->mgr, (isc_nmiface_t *)&ievent->local,
-                                  (isc_nmiface_t *)&ievent->peer,
-                                  tls_connect_cb, tlssock,
-                                  tlssock->connect_timeout, 0);
-       if (result != ISC_R_SUCCESS) {
-               goto error;
-       }
+       (void)isc_nm_tcpconnect(worker->mgr, (isc_nmiface_t *)&ievent->local,
+                               (isc_nmiface_t *)&ievent->peer, tcp_connected,
+                               tlssock, tlssock->connect_timeout, 0);
        return;
+
 error:
        tlshandle = isc__nmhandle_get(tlssock, NULL, NULL);
        atomic_store(&tlssock->closed, true);
-       tlssock->connect_cb(tlshandle, result, tlssock->connect_cbarg);
+       tls_call_connect_cb(tlssock, tlshandle, result);
        isc_nmhandle_detach(&tlshandle);
-       update_result(tlssock, result);
-       tls_close_direct(tlssock);
+       isc__nmsocket_detach(&tlssock);
+}
+
+static void
+tls_cancelread(isc_nmsocket_t *sock) {
+       if (!inactive(sock) && sock->tlsstream.state == TLS_IO) {
+               tls_do_bio(sock, NULL, true);
+       } else if (sock->outerhandle != NULL) {
+               isc_nm_cancelread(sock->outerhandle);
+       }
 }
 
 void
@@ -897,40 +884,69 @@ isc__nm_tls_cancelread(isc_nmhandle_t *handle) {
 
        REQUIRE(sock->type == isc_nm_tlssocket);
 
-       ievent = isc__nm_get_netievent_tlscancel(sock->mgr, sock, handle);
-       isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
-                              (isc__netievent_t *)ievent);
+       if (sock->tid == isc_nm_tid()) {
+               tls_cancelread(sock);
+       } else {
+               ievent = isc__nm_get_netievent_tlscancel(sock->mgr, sock,
+                                                        handle);
+               isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+                                      (isc__netievent_t *)ievent);
+       }
 }
 
 void
 isc__nm_async_tlscancel(isc__networker_t *worker, isc__netievent_t *ev0) {
        isc__netievent_tlscancel_t *ievent = (isc__netievent_tlscancel_t *)ev0;
        isc_nmsocket_t *sock = ievent->sock;
-       isc_nmhandle_t *handle = ievent->handle;
 
        REQUIRE(VALID_NMSOCK(sock));
        REQUIRE(worker->id == sock->tid);
        REQUIRE(sock->tid == isc_nm_tid());
-       UNUSED(worker);
-
-       tls_failed_read_cb(sock, handle, ISC_R_EOF, false);
 
-       if (sock->outerhandle) {
-               isc__nm_tcp_cancelread(sock->outerhandle);
-       }
+       UNUSED(worker);
+       tls_cancelread(sock);
 }
 
 void
 isc__nm_async_tlsdobio(isc__networker_t *worker, isc__netievent_t *ev0) {
-       UNUSED(worker);
        isc__netievent_tlsdobio_t *ievent = (isc__netievent_tlsdobio_t *)ev0;
-       tls_do_bio(ievent->sock);
+
+       UNUSED(worker);
+
+       tls_do_bio(ievent->sock, NULL, false);
 }
 
 void
 isc__nm_tls_cleanup_data(isc_nmsocket_t *sock) {
-       if (sock->tlsstream.tlslistener) {
+       if (sock->type == isc_nm_tcplistener &&
+           sock->tlsstream.tlslistener != NULL) {
                REQUIRE(VALID_NMSOCK(sock->tlsstream.tlslistener));
                isc__nmsocket_detach(&sock->tlsstream.tlslistener);
+       } else if (sock->type == isc_nm_tlssocket) {
+               if (sock->tlsstream.tls != NULL) {
+                       isc_tls_free(&sock->tlsstream.tls);
+                       /* These are destroyed when we free SSL */
+                       sock->tlsstream.ctx = NULL;
+                       sock->tlsstream.ssl_bio = NULL;
+               }
+               if (sock->tlsstream.app_bio != NULL) {
+                       BIO_free(sock->tlsstream.app_bio);
+                       sock->tlsstream.app_bio = NULL;
+               }
+       }
+}
+
+void
+isc__nm_tls_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
+       isc_nmsocket_t *sock = NULL;
+
+       REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(VALID_NMSOCK(handle->sock));
+       REQUIRE(handle->sock->type == isc_nm_tlssocket);
+
+       sock = handle->sock;
+       if (sock->outerhandle != NULL) {
+               INSIST(VALID_NMHANDLE(sock->outerhandle));
+               isc_nmhandle_settimeout(sock->outerhandle, timeout);
        }
 }
index e5f8c1f5ee1b6d6ef1366487d07a426e525d18c6..565e8406d34af1a9e54a27b337a0cb01ffd8190b 100644 (file)
@@ -102,81 +102,81 @@ static const char *description[ISC_R_NRESULTS] = {
        "default",                          /*%< 68 */
        "IPv4 prefix",                      /*%< 69 */
        "TLS error",                        /*%< 70 */
+       "ALPN for HTTP/2 failed"            /*%< 71 */
 };
 
-static const char *identifier[ISC_R_NRESULTS] = {
-       "ISC_R_SUCCESS",
-       "ISC_R_NOMEMORY",
-       "ISC_R_TIMEDOUT",
-       "ISC_R_NOTHREADS",
-       "ISC_R_ADDRNOTAVAIL",
-       "ISC_R_ADDRINUSE",
-       "ISC_R_NOPERM",
-       "ISC_R_NOCONN",
-       "ISC_R_NETUNREACH",
-       "ISC_R_HOSTUNREACH",
-       "ISC_R_NETDOWN",
-       "ISC_R_HOSTDOWN",
-       "ISC_R_CONNREFUSED",
-       "ISC_R_NORESOURCES",
-       "ISC_R_EOF",
-       "ISC_R_BOUND",
-       "ISC_R_RELOAD",
-       "ISC_R_LOCKBUSY",
-       "ISC_R_EXISTS",
-       "ISC_R_NOSPACE",
-       "ISC_R_CANCELED",
-       "ISC_R_NOTBOUND",
-       "ISC_R_SHUTTINGDOWN",
-       "ISC_R_NOTFOUND",
-       "ISC_R_UNEXPECTEDEND",
-       "ISC_R_FAILURE",
-       "ISC_R_IOERROR",
-       "ISC_R_NOTIMPLEMENTED",
-       "ISC_R_UNBALANCED",
-       "ISC_R_NOMORE",
-       "ISC_R_INVALIDFILE",
-       "ISC_R_BADBASE64",
-       "ISC_R_UNEXPECTEDTOKEN",
-       "ISC_R_QUOTA",
-       "ISC_R_UNEXPECTED",
-       "ISC_R_ALREADYRUNNING",
-       "ISC_R_IGNORE",
-       "ISC_R_MASKNONCONTIG",
-       "ISC_R_FILENOTFOUND",
-       "ISC_R_FILEEXISTS",
-       "ISC_R_NOTCONNECTED",
-       "ISC_R_RANGE",
-       "ISC_R_NOENTROPY",
-       "ISC_R_MULTICAST",
-       "ISC_R_NOTFILE",
-       "ISC_R_NOTDIRECTORY",
-       "ISC_R_QUEUEFULL",
-       "ISC_R_FAMILYMISMATCH",
-       "ISC_R_FAMILYNOSUPPORT",
-       "ISC_R_BADHEX",
-       "ISC_R_TOOMANYOPENFILES",
-       "ISC_R_NOTBLOCKING",
-       "ISC_R_UNBALANCEDQUOTES",
-       "ISC_R_INPROGRESS",
-       "ISC_R_CONNECTIONRESET",
-       "ISC_R_SOFTQUOTA",
-       "ISC_R_BADNUMBER",
-       "ISC_R_DISABLED",
-       "ISC_R_MAXSIZE",
-       "ISC_R_BADADDRESSFORM",
-       "ISC_R_BADBASE32",
-       "ISC_R_UNSET",
-       "ISC_R_MULTIPLE",
-       "ISC_R_WOULDBLOCK",
-       "ISC_R_COMPLETE",
-       "ISC_R_CRYPTOFAILURE",
-       "ISC_R_DISCQUOTA",
-       "ISC_R_DISCFULL",
-       "ISC_R_DEFAULT",
-       "ISC_R_IPV4PREFIX",
-       "ISC_R_TLSERROR",
-};
+static const char *identifier[ISC_R_NRESULTS] = { "ISC_R_SUCCESS",
+                                                 "ISC_R_NOMEMORY",
+                                                 "ISC_R_TIMEDOUT",
+                                                 "ISC_R_NOTHREADS",
+                                                 "ISC_R_ADDRNOTAVAIL",
+                                                 "ISC_R_ADDRINUSE",
+                                                 "ISC_R_NOPERM",
+                                                 "ISC_R_NOCONN",
+                                                 "ISC_R_NETUNREACH",
+                                                 "ISC_R_HOSTUNREACH",
+                                                 "ISC_R_NETDOWN",
+                                                 "ISC_R_HOSTDOWN",
+                                                 "ISC_R_CONNREFUSED",
+                                                 "ISC_R_NORESOURCES",
+                                                 "ISC_R_EOF",
+                                                 "ISC_R_BOUND",
+                                                 "ISC_R_RELOAD",
+                                                 "ISC_R_LOCKBUSY",
+                                                 "ISC_R_EXISTS",
+                                                 "ISC_R_NOSPACE",
+                                                 "ISC_R_CANCELED",
+                                                 "ISC_R_NOTBOUND",
+                                                 "ISC_R_SHUTTINGDOWN",
+                                                 "ISC_R_NOTFOUND",
+                                                 "ISC_R_UNEXPECTEDEND",
+                                                 "ISC_R_FAILURE",
+                                                 "ISC_R_IOERROR",
+                                                 "ISC_R_NOTIMPLEMENTED",
+                                                 "ISC_R_UNBALANCED",
+                                                 "ISC_R_NOMORE",
+                                                 "ISC_R_INVALIDFILE",
+                                                 "ISC_R_BADBASE64",
+                                                 "ISC_R_UNEXPECTEDTOKEN",
+                                                 "ISC_R_QUOTA",
+                                                 "ISC_R_UNEXPECTED",
+                                                 "ISC_R_ALREADYRUNNING",
+                                                 "ISC_R_IGNORE",
+                                                 "ISC_R_MASKNONCONTIG",
+                                                 "ISC_R_FILENOTFOUND",
+                                                 "ISC_R_FILEEXISTS",
+                                                 "ISC_R_NOTCONNECTED",
+                                                 "ISC_R_RANGE",
+                                                 "ISC_R_NOENTROPY",
+                                                 "ISC_R_MULTICAST",
+                                                 "ISC_R_NOTFILE",
+                                                 "ISC_R_NOTDIRECTORY",
+                                                 "ISC_R_QUEUEFULL",
+                                                 "ISC_R_FAMILYMISMATCH",
+                                                 "ISC_R_FAMILYNOSUPPORT",
+                                                 "ISC_R_BADHEX",
+                                                 "ISC_R_TOOMANYOPENFILES",
+                                                 "ISC_R_NOTBLOCKING",
+                                                 "ISC_R_UNBALANCEDQUOTES",
+                                                 "ISC_R_INPROGRESS",
+                                                 "ISC_R_CONNECTIONRESET",
+                                                 "ISC_R_SOFTQUOTA",
+                                                 "ISC_R_BADNUMBER",
+                                                 "ISC_R_DISABLED",
+                                                 "ISC_R_MAXSIZE",
+                                                 "ISC_R_BADADDRESSFORM",
+                                                 "ISC_R_BADBASE32",
+                                                 "ISC_R_UNSET",
+                                                 "ISC_R_MULTIPLE",
+                                                 "ISC_R_WOULDBLOCK",
+                                                 "ISC_R_COMPLETE",
+                                                 "ISC_R_CRYPTOFAILURE",
+                                                 "ISC_R_DISCQUOTA",
+                                                 "ISC_R_DISCFULL",
+                                                 "ISC_R_DEFAULT",
+                                                 "ISC_R_IPV4PREFIX",
+                                                 "ISC_R_TLSERROR",
+                                                 "ISC_R_HTTP2ALPNERROR" };
 
 #define ISC_RESULT_RESULTSET     2
 #define ISC_RESULT_UNAVAILABLESET 3
index 5159f0e5dfa1b43e6b712a34750295a5b486676d..671383ae5641814355275ef5ca2b2bb115d7e727 100644 (file)
@@ -72,7 +72,8 @@ static bool reuse_supported = true;
 static atomic_bool POST = ATOMIC_VAR_INIT(true);
 
 static atomic_bool use_TLS = ATOMIC_VAR_INIT(false);
-static SSL_CTX *server_ssl_ctx = NULL;
+static isc_tlsctx_t *server_tlsctx = NULL;
+static isc_tlsctx_t *client_tlsctx = NULL;
 
 #define NSENDS 100
 #define NWRITES 10
@@ -101,6 +102,63 @@ static SSL_CTX *server_ssl_ctx = NULL;
 #define X(v)
 #endif
 
+typedef struct csdata {
+       isc_nm_recv_cb_t reply_cb;
+       void *cb_arg;
+       isc_region_t region;
+} csdata_t;
+
+static void
+connect_send_cb(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
+       csdata_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_http_request(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);
+}
+
+static isc_result_t
+connect_send_request(isc_nm_t *mgr, const char *uri, bool post,
+                    isc_region_t *region, isc_nm_recv_cb_t cb, void *cbarg,
+                    bool tls, unsigned int timeout) {
+       isc_result_t result;
+       isc_region_t copy;
+       csdata_t *data = NULL;
+       isc_tlsctx_t *ctx = 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 = (csdata_t){ .reply_cb = cb, .cb_arg = cbarg, .region = copy };
+       if (tls) {
+               ctx = client_tlsctx;
+       }
+
+       result = isc_nm_httpconnect(
+               mgr, NULL, (isc_nmiface_t *)&tcp_listen_addr, uri, post,
+               connect_send_cb, data, ctx, timeout, 0);
+       return (result);
+}
+
 static int
 setup_ephemeral_port(isc_sockaddr_t *addr, sa_family_t family) {
        isc_result_t result;
@@ -171,7 +229,7 @@ static int
 _setup(void **state) {
        UNUSED(state);
 
-       /*workers = isc_os_ncpus();*/
+       workers = isc_os_ncpus();
 
        if (isc_test_begin(NULL, false, workers) != ISC_R_SUCCESS) {
                return (-1);
@@ -243,8 +301,11 @@ nm_setup(void **state) {
                assert_non_null(nm[i]);
        }
 
-       server_ssl_ctx = NULL;
-       isc_tlsctx_createserver(NULL, NULL, &server_ssl_ctx);
+       server_tlsctx = NULL;
+       isc_tlsctx_createserver(NULL, NULL, &server_tlsctx);
+       client_tlsctx = NULL;
+       isc_tlsctx_createclient(&client_tlsctx);
+       isc_tlsctx_enable_http2client_alpn(client_tlsctx);
 
        *state = nm;
 
@@ -261,8 +322,11 @@ nm_teardown(void **state) {
        }
        isc_mem_put(test_mctx, nm, MAX_NM * sizeof(nm[0]));
 
-       if (server_ssl_ctx) {
-               isc_tlsctx_free(&server_ssl_ctx);
+       if (server_tlsctx != NULL) {
+               isc_tlsctx_free(&server_tlsctx);
+       }
+       if (client_tlsctx != NULL) {
+               isc_tlsctx_free(&client_tlsctx);
        }
 
        return (0);
@@ -276,6 +340,7 @@ sockaddr_to_url(isc_sockaddr_t *sa, const bool https, char *outbuf,
        uint16_t port;
        char saddr[INET6_ADDRSTRLEN] = { 0 };
        int family;
+
        if (sa == NULL || outbuf == NULL || outbuf_len == 0) {
                return;
        }
@@ -321,8 +386,6 @@ doh_receive_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult,
                        }
                }
                atomic_store(&was_error, true);
-               /* Send failed, we need to stop reading too */
-               isc_nm_cancelread(handle);
        }
 }
 
@@ -382,10 +445,6 @@ mock_doh_uv_tcp_bind(void **state) {
        isc_nm_t *listen_nm = nm[0];
        isc_result_t result = ISC_R_SUCCESS;
        isc_nmsocket_t *listen_sock = NULL;
-       isc_sockaddr_t tcp_connect_addr;
-
-       tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
-       isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
 
        WILL_RETURN(uv_tcp_bind, UV_EADDRINUSE);
 
@@ -404,17 +463,13 @@ doh_noop(void **state) {
        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);
+       result = isc_nm_http_endpoint(listen_sock, DOH_PATH, noop_read_cb, NULL,
+                                     0);
 
        isc_nm_stoplistening(listen_sock);
        isc_nmsocket_close(&listen_sock);
@@ -422,11 +477,11 @@ doh_noop(void **state) {
 
        sockaddr_to_url(&tcp_listen_addr, false, req_url, sizeof(req_url),
                        DOH_PATH);
-       (void)isc_nm_http_connect_send_request(
+       (void)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);
+               noop_read_cb, NULL, atomic_load(&use_TLS), 30000);
 
        isc_nm_closedown(connect_nm);
 
@@ -455,27 +510,23 @@ doh_noresponse(void **state) {
        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);
+       result = isc_nm_http_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(
+       (void)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);
+               noop_read_cb, NULL, atomic_load(&use_TLS), 30000);
 
        isc_nm_stoplistening(listen_sock);
        isc_nmsocket_close(&listen_sock);
@@ -509,7 +560,7 @@ doh_receive_send_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult,
                        size_t i;
                        atomic_fetch_sub(&nsends, 1);
                        for (i = 0; i < NWRITES / 2; i++) {
-                               eresult = isc_nm_httprequest(
+                               eresult = isc__nm_http_request(
                                        handle,
                                        &(isc_region_t){
                                                .base = (uint8_t *)send_msg.base,
@@ -535,20 +586,18 @@ doh_receive_send_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult,
 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(
+               (void)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);
+                       doh_receive_send_reply_cb, NULL, atomic_load(&use_TLS),
+                       30000);
        }
 
        return ((isc_threadresult_t)0);
@@ -561,33 +610,26 @@ doh_recv_one(void **state) {
        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);
+               atomic_load(&use_TLS) ? server_tlsctx : 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);
+       result = isc_nm_http_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(
+       result = 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);
+               doh_receive_reply_cb, NULL, atomic_load(&use_TLS), 30000);
 
        assert_int_equal(result, ISC_R_SUCCESS);
 
@@ -657,7 +699,7 @@ doh_connect_send_two_requests_cb(isc_nmhandle_t *handle, isc_result_t result,
                goto error;
        }
 
-       result = isc_nm_httprequest(
+       result = isc__nm_http_request(
                handle,
                &(isc_region_t){ .base = (uint8_t *)send_msg.base,
                                 .length = send_msg.len },
@@ -666,7 +708,7 @@ doh_connect_send_two_requests_cb(isc_nmhandle_t *handle, isc_result_t result,
                goto error;
        }
 
-       result = isc_nm_httprequest(
+       result = isc__nm_http_request(
                handle,
                &(isc_region_t){ .base = (uint8_t *)send_msg.base,
                                 .length = send_msg.len },
@@ -674,8 +716,6 @@ doh_connect_send_two_requests_cb(isc_nmhandle_t *handle, isc_result_t result,
        if (result != ISC_R_SUCCESS) {
                goto error;
        }
-
-       isc_nm_resumeread(handle);
        return;
 error:
        atomic_store(&was_error, true);
@@ -688,31 +728,31 @@ doh_recv_two(void **state) {
        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);
+       isc_tlsctx_t *ctx = NULL;
 
        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);
+               atomic_load(&use_TLS) ? server_tlsctx : 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);
+       result = isc_nm_http_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);
+
+       if (atomic_load(&use_TLS)) {
+               ctx = client_tlsctx;
+       }
+
        result = isc_nm_httpconnect(
-               connect_nm, NULL, NULL, req_url, atomic_load(&POST),
-               doh_connect_send_two_requests_cb, NULL, NULL, 5000, 0);
+               connect_nm, NULL, (isc_nmiface_t *)&tcp_listen_addr, req_url,
+               atomic_load(&POST), doh_connect_send_two_requests_cb, NULL, ctx,
+               5000, 0);
 
        assert_int_equal(result, ISC_R_SUCCESS);
 
@@ -783,21 +823,14 @@ doh_recv_send(void **state) {
        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;
-
-       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);
+               atomic_load(&use_TLS) ? server_tlsctx : 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);
+       result = isc_nm_http_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++) {
@@ -859,21 +892,14 @@ doh_recv_half_send(void **state) {
        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;
-
-       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);
+               atomic_load(&use_TLS) ? server_tlsctx : 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);
+       result = isc_nm_http_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++) {
@@ -940,21 +966,14 @@ doh_half_recv_send(void **state) {
        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;
-
-       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);
+               atomic_load(&use_TLS) ? server_tlsctx : 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);
+       result = isc_nm_http_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++) {
@@ -1021,21 +1040,14 @@ doh_half_recv_half_send(void **state) {
        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;
-
-       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);
+               atomic_load(&use_TLS) ? server_tlsctx : 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);
+       result = isc_nm_http_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++) {
@@ -1105,7 +1117,7 @@ doh_parse_GET_query_string(void **state) {
                        "NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3"
                        "QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_true(ret);
                assert_non_null(queryp);
                assert_true(len > 0);
@@ -1122,7 +1134,7 @@ doh_parse_GET_query_string(void **state) {
                        "NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3"
                        "QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_true(ret);
                assert_non_null(queryp);
                assert_true(len > 0);
@@ -1136,7 +1148,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?dns=123&dns=567";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_true(ret);
                assert_non_null(queryp);
                assert_true(len > 0);
@@ -1150,7 +1162,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?name1=123&dns=567&name2=123&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_true(ret);
                assert_non_null(queryp);
                assert_true(len > 0);
@@ -1167,7 +1179,7 @@ doh_parse_GET_query_string(void **state) {
                        "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);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_true(ret);
                assert_non_null(queryp);
                assert_true(len > 0);
@@ -1184,7 +1196,7 @@ doh_parse_GET_query_string(void **state) {
                        "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);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_false(ret);
                assert_null(queryp);
                assert_true(len == 0);
@@ -1196,7 +1208,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_false(ret);
                assert_null(queryp);
                assert_true(len == 0);
@@ -1208,7 +1220,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_false(ret);
                assert_null(queryp);
                assert_true(len == 0);
@@ -1220,7 +1232,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?dns&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_false(ret);
                assert_null(queryp);
                assert_true(len == 0);
@@ -1232,7 +1244,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?dns=&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_false(ret);
                assert_null(queryp);
                assert_true(len == 0);
@@ -1244,7 +1256,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?dns=123&&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_false(ret);
                assert_null(queryp);
                assert_true(len == 0);
@@ -1256,7 +1268,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?dns=123%12&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_true(ret);
                assert_non_null(queryp);
                assert_true(len > 0);
@@ -1270,7 +1282,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?dns=123%ZZ&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_false(ret);
                assert_null(queryp);
                assert_true(len == 0);
@@ -1282,7 +1294,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?dns=123%%&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_false(ret);
                assert_null(queryp);
                assert_true(len == 0);
@@ -1294,7 +1306,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?dns=123%AZ&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_false(ret);
                assert_null(queryp);
                assert_true(len == 0);
@@ -1306,7 +1318,7 @@ doh_parse_GET_query_string(void **state) {
                size_t len = 0;
                char str[] = "?dns=123%0AZ&";
 
-               ret = isc__nm_parse_doh_query_string(str, &queryp, &len);
+               ret = isc__nm_parse_httpquery(str, &queryp, &len);
                assert_true(ret);
                assert_non_null(queryp);
                assert_true(len > 0);
@@ -1593,66 +1605,6 @@ doh_base64_to_base64url(void **state) {
        }
 }
 
-/*
-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_short[] = {
@@ -1745,17 +1697,21 @@ main(void) {
                cmocka_unit_test_setup_teardown(doh_cloudflare_POST, nm_setup,
                nm_teardown)*/
        };
+       int result = 0;
 
 #if OPENSSL_VERSION_NUMBER < 0x10100000L
        UNUSED(tests_long);
-       return (cmocka_run_group_tests(tests_short, _setup, _teardown));
+       result = (cmocka_run_group_tests(tests_short, _setup, _teardown));
 #else
        if (getenv("CI") != NULL || !reuse_supported) {
-               return (cmocka_run_group_tests(tests_short, _setup, _teardown));
+               result = (cmocka_run_group_tests(tests_short, _setup,
+                                                _teardown));
        } else {
-               return (cmocka_run_group_tests(tests_long, _setup, _teardown));
+               result =
+                       (cmocka_run_group_tests(tests_long, _setup, _teardown));
        }
 #endif
+       return result;
 }
 
 #else /* HAVE_CMOCKA */
index cec759d924cf753c220c62fd6f7cd8acee8858ae..8a3f5cc41b53c12d164dfecc48163434ed5476f7 100644 (file)
@@ -9,6 +9,9 @@
  * information regarding copyright ownership.
  */
 
+#include <inttypes.h>
+#include <nghttp2/nghttp2.h>
+
 #include <openssl/conf.h>
 #include <openssl/err.h>
 #include <openssl/opensslv.h>
@@ -220,14 +223,7 @@ isc_tlsctx_createserver(const char *keyfile, const char *certfile,
        const SSL_METHOD *method = NULL;
 
        REQUIRE(ctxp != NULL && *ctxp == NULL);
-
-       if (ephemeral) {
-               INSIST(keyfile == NULL);
-               INSIST(certfile == NULL);
-       } else {
-               INSIST(keyfile != NULL);
-               INSIST(certfile != NULL);
-       }
+       REQUIRE((keyfile == NULL) == (certfile == NULL));
 
        method = TLS_server_method();
        if (method == NULL) {
@@ -355,3 +351,123 @@ ssl_error:
 
        return (ISC_R_TLSERROR);
 }
+
+isc_tls_t *
+isc_tls_create(isc_tlsctx_t *ctx) {
+       isc_tls_t *newctx = NULL;
+
+       REQUIRE(ctx != NULL);
+
+       newctx = SSL_new(ctx);
+       if (newctx == NULL) {
+               char errbuf[256];
+               unsigned long err = ERR_get_error();
+
+               ERR_error_string_n(err, errbuf, sizeof(errbuf));
+               fprintf(stderr, "%s:SSL_new(%p) -> %s\n", __func__, ctx,
+                       errbuf);
+       }
+
+       return (newctx);
+}
+
+void
+isc_tls_free(isc_tls_t **tlsp) {
+       REQUIRE(tlsp != NULL && *tlsp != NULL);
+
+       SSL_free(*tlsp);
+       *tlsp = NULL;
+}
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+/*
+ * NPN TLS extension client callback.
+ */
+static int
+select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
+                    const unsigned char *in, unsigned int inlen, void *arg) {
+       UNUSED(ssl);
+       UNUSED(arg);
+
+       if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
+               return (SSL_TLSEXT_ERR_NOACK);
+       }
+       return (SSL_TLSEXT_ERR_OK);
+}
+#endif /* !OPENSSL_NO_NEXTPROTONEG */
+
+void
+isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx) {
+       REQUIRE(ctx != NULL);
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+       SSL_CTX_set_next_proto_select_cb(ctx, select_next_proto_cb, NULL);
+#endif /* !OPENSSL_NO_NEXTPROTONEG */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+       SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)NGHTTP2_PROTO_ALPN,
+                               NGHTTP2_PROTO_ALPN_LEN);
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
+}
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+static int
+next_proto_cb(isc_tls_t *ssl, const unsigned char **data, unsigned int *len,
+             void *arg) {
+       UNUSED(ssl);
+       UNUSED(arg);
+
+       *data = (const unsigned char *)NGHTTP2_PROTO_ALPN;
+       *len = (unsigned int)NGHTTP2_PROTO_ALPN_LEN;
+       return (SSL_TLSEXT_ERR_OK);
+}
+#endif /* !OPENSSL_NO_NEXTPROTONEG */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+static int
+alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
+                    const unsigned char *in, unsigned int inlen, void *arg) {
+       int ret;
+
+       UNUSED(ssl);
+       UNUSED(arg);
+
+       ret = nghttp2_select_next_protocol((unsigned char **)(uintptr_t)out,
+                                          outlen, in, inlen);
+
+       if (ret != 1) {
+               return (SSL_TLSEXT_ERR_NOACK);
+       }
+
+       return (SSL_TLSEXT_ERR_OK);
+}
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
+
+void
+isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *tls) {
+       REQUIRE(tls != NULL);
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+       SSL_CTX_set_next_protos_advertised_cb(tls, next_proto_cb, NULL);
+#endif // OPENSSL_NO_NEXTPROTONEG
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+       SSL_CTX_set_alpn_select_cb(tls, alpn_select_proto_cb, NULL);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+}
+
+void
+isc_tls_get_http2_alpn(isc_tls_t *tls, const unsigned char **alpn,
+                      unsigned int *alpnlen) {
+       REQUIRE(tls != NULL);
+       REQUIRE(alpn != NULL);
+       REQUIRE(alpnlen != NULL);
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+       SSL_get0_next_proto_negotiated(tls, alpn, alpnlen);
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+       if (*alpn == NULL) {
+               SSL_get0_alpn_selected(tls, alpn, alpnlen);
+       }
+#endif
+}
index 3662fd57ab3d35493030a7b9dde5915ae7091fef..5b2670c3ee79b43d22f483b2a6980b354c940cd6 100644 (file)
@@ -452,11 +452,8 @@ isc_nm_cancelread
 isc_nm_closedown
 isc_nm_destroy
 isc_nm_detach
-isc_nm_http_add_doh_endpoint
-isc_nm_http_add_endpoint
-isc_nm_http_connect_send_request
+isc_nm_http_endpoint
 isc_nm_httpconnect
-isc_nm_httprequest
 isc_nm_listenhttp
 isc_nm_listentcpdns
 isc_nm_listentls
@@ -710,17 +707,20 @@ isc_timermgr_create
 isc_timermgr_createinctx
 isc_timermgr_destroy
 isc_timermgr_poke
-isc__tls_initialize
-isc__tls_shutdown
+isc_tls_get_http2_alpn
+isc_tls_create
+isc_tls_free
 isc_tlsctx_createclient
 isc_tlsctx_createserver
 isc_tlsctx_free
+isc_tlsctx_enable_http2client_alpn
+isc_tlsctx_enable_http2server_alpn
+isc_tm_timegm
+isc_tm_strptime
 isc__trampoline_initialize
 isc__trampoline_shutdown
 isc__trampoline_get
 isc__trampoline_run
-isc_tm_timegm
-isc_tm_strptime
 isc_url_parse
 isc_utf8_bom
 isc_utf8_valid
index a1443410a7ef456332b2032d2c8ef5f3701a5f27..8ae657abce65e8529507993bef33c0aee33436f8 100644 (file)
@@ -556,9 +556,9 @@ ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps,
 
        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));
+                       result = isc_nm_http_endpoint(sock, eps[i],
+                                                     ns__client_request, ifp,
+                                                     sizeof(ns_client_t));
                }
        }