diff --git a/bin/named/transportconf.c b/bin/named/transportconf.c index 3a1bfb4202..ea696af73b 100644 --- a/bin/named/transportconf.c +++ b/bin/named/transportconf.c @@ -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", diff --git a/lib/dns/include/dns/transport.h b/lib/dns/include/dns/transport.h index 0b03152874..c4a0e945d7 100644 --- a/lib/dns/include/dns/transport.h +++ b/lib/dns/include/dns/transport.h @@ -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 diff --git a/lib/dns/transport.c b/lib/dns/transport.c index 2dcc8a9d0f..0ad1159fb3 100644 --- a/lib/dns/transport.c +++ b/lib/dns/transport.c @@ -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; } diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h index cb505f9237..fd670b190c 100644 --- a/lib/isc/include/isc/netmgr.h +++ b/lib/isc/include/isc/netmgr.h @@ -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); diff --git a/lib/isc/include/isc/result.h b/lib/isc/include/isc/result.h index cfc3f03ad7..22969775df 100644 --- a/lib/isc/include/isc/result.h +++ b/lib/isc/include/isc/result.h @@ -89,9 +89,10 @@ #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 diff --git a/lib/isc/include/isc/tls.h b/lib/isc/include/isc/tls.h index 3741f19f5e..38c990648b 100644 --- a/lib/isc/include/isc/tls.h +++ b/lib/isc/include/isc/tls.h @@ -14,35 +14,72 @@ #include #include #include -#include #include 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 a 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); diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c index 51adbea6f6..bf66ef0bd2 100644 --- a/lib/isc/netmgr/http.c +++ b/lib/isc/netmgr/http.c @@ -16,10 +16,6 @@ #include #include -#include -#include -#include - #include #include #include @@ -32,23 +28,29 @@ #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); + uri = sock->h2.session->handle->sock->h2.connect.uri; + post = sock->h2.session->handle->sock->h2.connect.post; - stream = isc_mem_get(mctx, sizeof(http2_client_stream_t)); - *stream = (http2_client_stream_t){ .stream_id = -1, .rbufsize = 0 }; - - 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)); + isc_mem_put(mctx, stream, sizeof(http_cstream_t)); } static void -delete_http2_session(isc_nm_http2_session_t *session) { - REQUIRE(ISC_LIST_EMPTY(session->sstreams)); - REQUIRE(ISC_LIST_EMPTY(session->cstreams)); - - session->magic = 0; - if (session->ssl_ctx_created) { - SSL_CTX_free(session->ssl_ctx); - } - isc_mem_putanddetach(&session->mctx, session, - sizeof(isc_nm_http2_session_t)); -} - -static void -finish_http2_session(isc_nm_http2_session_t *session) { +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); - while (cstream != NULL) { - http2_client_stream_t *next = ISC_LIST_NEXT(cstream, - link); - ISC_LIST_DEQUEUE(session->cstreams, cstream, link); - put_http2_client_stream(session->mctx, cstream); + if (!ISC_LIST_EMPTY(session->cstreams)) { + http_cstream_t *cstream = ISC_LIST_HEAD(session->cstreams); + while (cstream != NULL) { + http_cstream_t *next = ISC_LIST_NEXT(cstream, link); + ISC_LIST_DEQUEUE(session->cstreams, cstream, link); + 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; } } @@ -293,194 +386,180 @@ finish_http2_session(isc_nm_http2_session_t *session) { } } +static int +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_TEMPORAL_CALLBACK_FAILURE); + } + } else { + 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; + } + + 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_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; + isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data; + int rv; 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); + rv = on_client_data_chunk_recv_callback(stream_id, data, len, + session); + } else { + 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( + 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); } } 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); - } - break; - } - sock_h2 = ISC_LIST_NEXT(sock_h2, link); - } - if (sock_h2 == NULL) { - return (NGHTTP2_ERR_CALLBACK_FAILURE); - } + return (NGHTTP2_ERR_CALLBACK_FAILURE); } return (0); } +static int +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 (ISC_LIST_EMPTY(session->sstreams)) { + rv = nghttp2_session_terminate_session(session->ngsession, + NGHTTP2_NO_ERROR); + } + isc__nmsocket_prep_destroy(sock); + return (rv); +} + 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; + isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data; int rv = 0; REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(session->ngsession == ngsession); UNUSED(error_code); - /* 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.*/ + /* + * 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) { - 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_stream_close_callback(stream_id, session); } else { - isc_nmsocket_t *sock = nghttp2_session_get_stream_user_data( - ngsession, stream_id); - if (ISC_LIST_EMPTY(session->sstreams)) { - rv = nghttp2_session_terminate_session( - ngsession, NGHTTP2_NO_ERROR); - } - isc__nmsocket_prep_destroy(sock); - if (rv != 0) { - return (NGHTTP2_ERR_CALLBACK_FAILURE); - } + rv = on_server_stream_close_callback(stream_id, session); } - return (0); -} - -#ifndef OPENSSL_NO_NEXTPROTONEG -/* - * NPN TLS extension client callback. We check that server advertised - * the HTTP/2 protocol the nghttp2 library supports. - */ -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 */ - -/* Create SSL_CTX. */ -static SSL_CTX * -create_client_ssl_ctx(void) { - SSL_CTX *ssl_ctx = NULL; - - RUNTIME_CHECK(isc_tlsctx_createclient(&ssl_ctx) == ISC_R_SUCCESS); - RUNTIME_CHECK(ssl_ctx != NULL); - -#ifndef OPENSSL_NO_NEXTPROTONEG - SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL); -#endif /* !OPENSSL_NO_NEXTPROTONEG */ - -#if OPENSSL_VERSION_NUMBER >= 0x10002000L - SSL_CTX_set_alpn_protos(ssl_ctx, - (const unsigned char *)NGHTTP2_PROTO_ALPN, - NGHTTP2_PROTO_ALPN_LEN); -#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ - - 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; - conn_data = *pconn_data; - uri = conn_data.uri; - isc_mem_put(mctx, pconn_data, sizeof(*pconn_data)); + REQUIRE(VALID_NMSOCK(transp_sock)); - INSIST(conn_data.connect_cb != NULL); - INSIST(conn_data.uri != NULL); + mctx = transp_sock->mgr->mctx; + 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); + new_session(mctx, http_sock->h2.connect.tlsctx, &session); + session->client = true; + transp_sock->h2.session = session; + http_sock->h2.connect.tlsctx = NULL; - handle->sock->h2.connect.uri = uri; - handle->sock->h2.connect.post = conn_data.post; + 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); - session->ssl_ctx = conn_data.ssl_ctx; - conn_data.ssl_ctx = NULL; - session->ssl_ctx_created = conn_data.ssl_ctx_created; - conn_data.ssl_ctx_created = false; - - if (session->ssl_ctx != NULL) { + 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); + INSIST(transp_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 - - 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 (http_sock->h2.connect.uri != NULL) { + isc_mem_free(mctx, http_sock->h2.connect.uri); } - if (session != NULL) { - delete_http2_session(session); - } - - 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); + if (local == NULL) { + isc_sockaddr_anyofpf(&local_interface.addr, + (peer->addr).type.sa.sa_family); + local = &local_interface; } - schema_len = url_parser.field_data[ISC_UF_SCHEMA].len; - INSIST(schema_len > 0); - schema = &uri[url_parser.field_data[ISC_UF_SCHEMA].off]; + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_httpsocket, local); - 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(); + 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); + + /* + * 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; } - if (ctx == NULL && create_ssl_ctx) { - port = 443; - ctx = create_client_ssl_ctx(); - } - - if (peer == NULL || local == NULL) { - size_t hostlen = 0; - char *host = NULL; - struct addrinfo hints; - struct addrinfo *res = NULL; - -#ifndef WIN32 /* FIXME */ - /* extract host name */ - hostlen = url_parser.field_data[ISC_UF_HOST].len + 1; - host = isc_mem_get(mgr->mctx, hostlen); - memmove(host, &uri[url_parser.field_data[ISC_UF_HOST].off], - hostlen - 1); - host[hostlen - 1] = '\0'; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = AI_CANONNAME; - - result = getaddrinfo(host, NULL, &hints, &res); - isc_mem_put(mgr->mctx, host, hostlen); - if (result != 0) { - return (ISC_R_FAILURE); - } -#endif /* WIN32 */ - - if ((url_parser.field_set & (1 << ISC_UF_PORT)) != 0) { - port = url_parser.port; - } - - if (peer == NULL) { - (void)isc_sockaddr_fromsockaddr(&resolved_peer.addr, - res->ai_addr); - isc_sockaddr_setport(&resolved_peer.addr, port); - peer = &resolved_peer; - } - if (local == NULL) { - isc_sockaddr_anyofpf(&resolved_local.addr, - res->ai_family); - local = &resolved_local; - } - - freeaddrinfo(res); - } - - pconn_data = isc_mem_get(mgr->mctx, sizeof(*pconn_data)); - *pconn_data = - (http_connect_data_t){ .uri = isc_mem_strdup(mgr->mctx, uri), - .post = post, - .connect_cb = cb, - .connect_cbarg = cbarg, - .ssl_ctx_created = create_ssl_ctx, - .ssl_ctx = ctx }; - - 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); + REQUIRE(cstream != 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; - } - - 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); - - result = client_submit_request(session, cstream); - if (result != ISC_R_SUCCESS) { - ISC_LIST_UNLINK(session->cstreams, cstream, link); - goto error; + 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; + } + + 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; - } - - result = isc_nm_httprequest(handle, &data.region, data.reply_cb, - data.cb_arg); - if (result != ISC_R_SUCCESS) { - goto error; - } - - isc_mem_put(handle->sock->mgr->mctx, data.region.base, - data.region.length); - return; -error: - data.reply_cb(handle, result, NULL, data.cb_arg); - isc_mem_put(handle->sock->mgr->mctx, data.region.base, - data.region.length); -} - -isc_result_t -isc_nm_http_connect_send_request(isc_nm_t *mgr, const char *uri, bool post, - isc_region_t *region, isc_nm_recv_cb_t cb, - void *cbarg, SSL_CTX *ctx, - unsigned int timeout) { - isc_region_t copy; - isc_result_t result; - isc_nm_connect_send_data_t *data; - - REQUIRE(VALID_NM(mgr)); - REQUIRE(uri != NULL); - REQUIRE(*uri != '\0'); - REQUIRE(region != NULL); - REQUIRE(region->base != NULL); - REQUIRE(region->length != 0); - REQUIRE(region->length <= MAX_DNS_MESSAGE_SIZE); REQUIRE(cb != NULL); - copy = (isc_region_t){ .base = isc_mem_get(mgr->mctx, region->length), - .length = region->length }; - memmove(copy.base, region->base, region->length); - data = isc_mem_get(mgr->mctx, sizeof(*data)); - *data = (isc_nm_connect_send_data_t){ .reply_cb = cb, - .cb_arg = cbarg, - .region = copy }; - result = isc_nm_httpconnect(mgr, NULL, NULL, uri, post, - https_connect_send_cb, data, ctx, timeout, - 0); + sock = handle->sock; + isc__nm_http_read(handle, cb, cbarg); + result = client_send(handle, region); + if (result != ISC_R_SUCCESS) { + goto error; + } + + 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,24 +1638,16 @@ 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); + + sock = handle->sock; + REQUIRE(VALID_NMSOCK(sock)); - /* TODO: should be called from within the context of an NM thread? */ - if (inactive(sock) || handle->httpsession->closed) { - cb(handle, ISC_R_CANCELED, cbarg); - return; - } - - INSIST(VALID_NMHANDLE(handle->httpsession->handle)); - INSIST(VALID_NMSOCK(handle->httpsession->handle->sock)); - INSIST(sock->tid == handle->httpsession->handle->sock->tid); - uvreq = isc__nm_uvreq_get(sock->mgr, sock); isc_nmhandle_attach(handle, &uvreq->handle); uvreq->cb.send = cb; @@ -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= + * (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; - - REQUIRE(VALID_NMSOCK(sock)); - REQUIRE(VALID_UVREQ(req)); - - ievent->req = NULL; - handle = req->handle; - cb = req->cb.send; - cbarg = req->cbarg; - - REQUIRE(VALID_NMHANDLE(handle)); - REQUIRE(VALID_NMHANDLE(handle->httpsession->handle)); - REQUIRE(VALID_NMSOCK(handle->httpsession->handle->sock)); - REQUIRE(handle->httpsession->handle->sock->tid == isc_nm_tid()); + isc_nm_http_session_t *session = NULL; UNUSED(worker); - memmove(sock->h2.buf, req->uvbuf.base, req->uvbuf.len); - sock->h2.bufsize = req->uvbuf.len; + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + REQUIRE(VALID_HTTP2_SESSION(sock->h2.session)); - 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; + ievent->req = NULL; + handle = req->handle; + + REQUIRE(VALID_NMHANDLE(handle)); + + session = sock->h2.session; + if (session != NULL && session->client) { + client_httpsend(handle, sock, req); } else { - res = ISC_R_SUCCESS; + server_httpsend(handle, sock, req); + } +} + +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; + + 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; + httpcbarg = ISC_LIST_HEAD(sock->h2.handler_cbargs); + while (httpcbarg != NULL) { + isc_nm_httpcbarg_t *next = NULL; - next = ISC_LIST_NEXT(dohcbarg, link); - ISC_LIST_DEQUEUE(sock->h2.handlers_cbargs, dohcbarg, link); - isc_mem_put(sock->mgr->mctx, dohcbarg, - sizeof(isc_nm_http_doh_cbarg_t)); - dohcbarg = next; - } - RWUNLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write); -} - -void -isc__nm_http_clear_session(isc_nmsocket_t *sock) { - if (sock->type != isc_nm_httpstream && sock->h2.session != NULL) { - isc_nm_http2_session_t *session = sock->h2.session; - INSIST(ISC_LIST_EMPTY(session->sstreams)); - session->magic = 0; - if (session->ssl_ctx_created) { - SSL_CTX_free(session->ssl_ctx); - } - isc_mem_putanddetach(&sock->h2.session->mctx, session, - sizeof(isc_nm_http2_session_t)); - sock->h2.session = NULL; + 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); + } -typedef struct isc_doh_query_parser_state { + 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; + + 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 { diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index 105ea4d0bb..f43be4b60d 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #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; + ISC_LIST(isc_nm_httphandler_t) handlers; + ISC_LIST(isc_nm_httpcbarg_t) handler_cbargs; + isc_rwlock_t lock; - char response_content_length_str[128]; - - 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_clear_session(isc_nmsocket_t *sock); +isc__nm_http_initsocket(isc_nmsocket_t *sock); + +void +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 diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c index eb7a23e3a4..a29ec728a3 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -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(); diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c index e7ad6cd634..7884c1a20b 100644 --- a/lib/isc/netmgr/tcp.c +++ b/lib/isc/netmgr/tcp.c @@ -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); diff --git a/lib/isc/netmgr/tlsdns.c b/lib/isc/netmgr/tlsdns.c index eaa8921b15..67690a2ebf 100644 --- a/lib/isc/netmgr/tlsdns.c +++ b/lib/isc/netmgr/tlsdns.c @@ -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); diff --git a/lib/isc/netmgr/tlsstream.c b/lib/isc/netmgr/tlsstream.c index 87242e0a0b..9a75dc0f2a 100644 --- a/lib/isc/netmgr/tlsstream.c +++ b/lib/isc/netmgr/tlsstream.c @@ -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)); - /* XXXWPK TODO */ - UNUSED(eresult); + tlssock = send_req->tlssock; + send_req->tlssock = NULL; + + 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) { - 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; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + 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; - } - - /* We don't need atomics here, it's all in single network thread + /* + * At this point we're certain that there are no + * external references, we can close everything. */ - 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->outerhandle != NULL) { + isc_nm_pauseread(sock->outerhandle); + isc_nmhandle_detach(&sock->outerhandle); } + + 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); - } + 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); } } diff --git a/lib/isc/result.c b/lib/isc/result.c index e5f8c1f5ee..565e8406d3 100644 --- a/lib/isc/result.c +++ b/lib/isc/result.c @@ -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 diff --git a/lib/isc/tests/doh_test.c b/lib/isc/tests/doh_test.c index 5159f0e5df..671383ae56 100644 --- a/lib/isc/tests/doh_test.c +++ b/lib/isc/tests/doh_test.c @@ -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§ion=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§ion=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 */ diff --git a/lib/isc/tls.c b/lib/isc/tls.c index cec759d924..8a3f5cc41b 100644 --- a/lib/isc/tls.c +++ b/lib/isc/tls.c @@ -9,6 +9,9 @@ * information regarding copyright ownership. */ +#include +#include + #include #include #include @@ -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 +} diff --git a/lib/isc/win32/libisc.def.in b/lib/isc/win32/libisc.def.in index 3662fd57ab..5b2670c3ee 100644 --- a/lib/isc/win32/libisc.def.in +++ b/lib/isc/win32/libisc.def.in @@ -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 diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c index a1443410a7..8ae657abce 100644 --- a/lib/ns/interfacemgr.c +++ b/lib/ns/interfacemgr.c @@ -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)); } }