From 9c8b7a5c450d54332f25830aa47035d87490bb3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Wed, 27 Jan 2021 15:49:27 +0100 Subject: [PATCH 1/8] add preliminary DoH client support to dig add options "+https", "+https-get" and "+http-plain" to allow dig to connect over HTTP/2 channels. --- bin/dig/dig.c | 97 +++++++++++++++++++++++++- bin/dig/dig.rst | 40 +++++++++-- bin/dig/dighost.c | 170 +++++++++++++++++++++------------------------- bin/dig/dighost.h | 9 +++ doc/man/dig.1in | 40 +++++++++-- 5 files changed, 248 insertions(+), 108 deletions(-) diff --git a/bin/dig/dig.c b/bin/dig/dig.c index 76c491f073..c4f75c4fdd 100644 --- a/bin/dig/dig.c +++ b/bin/dig/dig.c @@ -228,6 +228,10 @@ help(void) { "SERVFAIL)\n" " +[no]header-only (Send query without a " "question section)\n" + " +[no]https[=###] (DNS over HTTPS mode) " + "[/]\n" + " +[no]https-get (Use GET instead of " + "default POST method\n" " +[no]identify (ID responders in short " "answers)\n" #ifdef HAVE_LIBIDN2 @@ -348,12 +352,18 @@ received(unsigned int bytes, isc_sockaddr_t *from, dig_query_t *query) { } if (query->lookup->tls_mode) { proto = "TLS"; + } else if (query->lookup->https_mode) { + if (query->lookup->http_plain) { + proto = "HTTP"; + } else { + proto = "HTTPS"; + } } else if (query->lookup->tcp_mode) { proto = "TCP"; } else { proto = "UDP"; } - printf(";; SERVER: %s(%s) (%s)\n", fromtext, query->servname, + printf(";; SERVER: %s(%s) (%s)\n", fromtext, query->userarg, proto); time(&tnow); (void)localtime_r(&tnow, &tmnow); @@ -1066,6 +1076,17 @@ plus_option(char *option, bool is_batchfile, bool *need_clone, (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0)) \ goto invalid_option; \ } while (0) +#define FULLCHECK6(A, B, C, D, E, F) \ + do { \ + size_t _l = strlen(cmd); \ + if ((_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) && \ + (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0) && \ + (_l >= sizeof(C) || strncasecmp(cmd, C, _l) != 0) && \ + (_l >= sizeof(D) || strncasecmp(cmd, D, _l) != 0) && \ + (_l >= sizeof(E) || strncasecmp(cmd, E, _l) != 0) && \ + (_l >= sizeof(F) || strncasecmp(cmd, F, _l) != 0)) \ + goto invalid_option; \ + } while (0) switch (cmd[0]) { case 'a': @@ -1412,8 +1433,78 @@ plus_option(char *option, bool is_batchfile, bool *need_clone, lookup->servfail_stops = state; break; case 'h': - FULLCHECK("header-only"); - lookup->header_only = state; + switch (cmd[1]) { + case 'e': /* header-only */ + FULLCHECK("header-only"); + lookup->header_only = state; + break; + case 't': + FULLCHECK6("https", "https-get", "https-post", + "http-plain", "http-plain-get", + "http-plain-post"); + if (lookup->https_path != NULL) { + isc_mem_free(mctx, lookup->https_path); + lookup->https_path = NULL; + } + if (!state) { + lookup->https_mode = false; + break; + } + lookup->https_mode = true; + if (cmd[4] == '-') { + lookup->http_plain = true; + switch (cmd[10]) { + case '\0': + FULLCHECK("http-plain"); + break; + case '-': + switch (cmd[6]) { + case 'p': + FULLCHECK("https-plain-post"); + break; + case 'g': + FULLCHECK("https-plain-get"); + lookup->https_get = true; + break; + } + break; + default: + goto invalid_option; + } + } else { + switch (cmd[5]) { + case '\0': + FULLCHECK("https"); + break; + case '-': + switch (cmd[6]) { + case 'p': + FULLCHECK("https-post"); + break; + case 'g': + FULLCHECK("https-get"); + lookup->https_get = true; + break; + } + break; + default: + goto invalid_option; + } + } + if (!lookup->tcp_mode_set) { + lookup->tcp_mode = state; + } + if (value == NULL) { + lookup->https_path = isc_mem_strdup( + mctx, DEFAULT_HTTPS_PATH); + } else { + lookup->https_path = isc_mem_strdup(mctx, + value); + } + break; + default: + goto invalid_option; + } break; case 'i': switch (cmd[1]) { diff --git a/bin/dig/dig.rst b/bin/dig/dig.rst index 9df9ef2c1e..c28b51e4ad 100644 --- a/bin/dig/dig.rst +++ b/bin/dig/dig.rst @@ -349,11 +349,38 @@ abbreviation is unambiguous; for example, ``+cd`` is equivalent to default is to add a question section. The query type and query name are ignored when this is set. +``+[no]https[=value]`` + This option indicates whether to use DNS-over-HTTPS (DoH) when querying + name servers. When this option is in use, the port number defaults to 443. + The HTTP POST request mode is used when sending the query. + + If ``value`` is specified, it will be used as the HTTP endpoint in the + query URI; the default is ``/dns-query``. So, for example, ``dig + @example.com +https`` will use the URI ``https://example.com/dns-query``. + +``+[no]https-get[=value]`` + Similar to ``+https``, except that the HTTP GET request mode is used + when sending the query. + +``+[no]https-post[=value]`` + Same as ``+https``. + +``+[no]http-plain[=value]`` + Similar to ``+https``, except that HTTP queries will be sent over a + non-encrypted channel. When this option is in use, the port number + defaults to 80 and the HTTP request mode is POST. + +``+[no]http-plain-get[=value]`` + Similar to ``+http-plain``, except that the HTTP request mode is GET. + +``+[no]http-plain-post[=value]`` + Same as ``+http-plain``. + ``+[no]identify`` - This option shows [or does not show] the IP address and port number that supplied - the answer, when the ``+short`` option is enabled. If short form - answers are requested, the default is not to show the source address - and port number of the server that provided the answer. + This option shows [or does not show] the IP address and port number that + supplied the answer, when the ``+short`` option is enabled. If short + form answers are requested, the default is not to show the source + address and port number of the server that provided the answer. ``+[no]idnin`` This option processes [or does not process] IDN domain names on input. This requires @@ -519,8 +546,9 @@ abbreviation is unambiguous; for example, ``+cd`` is equivalent to 5 seconds. An attempt to set ``T`` to less than 1 is silently set to 1. ``+[no]tls`` - This option indicates whether to use DNS over TLS (DoT) when querying - name servers. + This option indicates whether to use DNS-over-TLS (DoT) when querying + name servers. When this option is in use, the port number defaults + to 853. ``+[no]topdown`` This feature is related to ``dig +sigchase``, which is obsolete and diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c index ff72aea4a5..54bb63e5d1 100644 --- a/bin/dig/dighost.c +++ b/bin/dig/dighost.c @@ -603,102 +603,43 @@ clone_server_list(dig_serverlist_t src, dig_serverlist_t *dest) { dig_lookup_t * make_empty_lookup(void) { dig_lookup_t *looknew; +#ifdef HAVE_LIBIDN2 + bool idn_allowed = isatty(1) ? (getenv("IDN_DISABLE") == NULL) : false; +#endif /* HAVE_LIBIDN2 */ debug("make_empty_lookup()"); INSIST(!free_now); - looknew = isc_mem_allocate(mctx, sizeof(struct dig_lookup)); - looknew->pending = true; - looknew->textname[0] = 0; - looknew->cmdline[0] = 0; - looknew->rdtype = dns_rdatatype_a; - looknew->qrdtype = dns_rdatatype_a; - looknew->rdclass = dns_rdataclass_in; - looknew->rdtypeset = false; - looknew->rdclassset = false; - looknew->sendspace = NULL; - looknew->sendmsg = NULL; - looknew->name = NULL; - looknew->oname = NULL; - looknew->xfr_q = NULL; - looknew->current_query = NULL; - looknew->doing_xfr = false; - looknew->ixfr_serial = 0; - looknew->trace = false; - looknew->trace_root = false; - looknew->identify = false; - looknew->identify_previous_line = false; - looknew->ignore = false; - looknew->servfail_stops = true; - looknew->besteffort = true; - looknew->dns64prefix = false; - looknew->dnssec = false; - looknew->ednsflags = 0; - looknew->opcode = dns_opcode_query; - looknew->expire = false; - looknew->nsid = false; - looknew->tcp_keepalive = false; - looknew->padding = 0; - looknew->header_only = false; - looknew->sendcookie = false; - looknew->seenbadcookie = false; - looknew->badcookie = true; - looknew->multiline = false; - looknew->nottl = false; - looknew->noclass = false; - looknew->onesoa = false; - looknew->use_usec = false; - looknew->nocrypto = false; - looknew->ttlunits = false; - looknew->expandaaaa = false; - looknew->qr = false; + looknew = isc_mem_allocate(mctx, sizeof(*looknew)); + *looknew = (dig_lookup_t){ + .pending = true, + .rdtype = dns_rdatatype_a, + .qrdtype = dns_rdatatype_a, + .rdclass = dns_rdataclass_in, + .servfail_stops = true, + .besteffort = true, + .opcode = dns_opcode_query, + .badcookie = true, #ifdef HAVE_LIBIDN2 - looknew->idnin = isatty(1) ? (getenv("IDN_DISABLE") == NULL) : false; - looknew->idnout = looknew->idnin; -#else /* ifdef HAVE_LIBIDN2 */ - looknew->idnin = false; - looknew->idnout = false; + .idnin = idn_allowed, + .idnout = idn_allowed, #endif /* HAVE_LIBIDN2 */ - looknew->udpsize = -1; - looknew->edns = -1; - looknew->recurse = true; - looknew->aaonly = false; - looknew->adflag = false; - looknew->cdflag = false; - looknew->raflag = false; - looknew->tcflag = false; - looknew->print_unknown_format = false; - looknew->zflag = false; - looknew->setqid = false; - looknew->qid = 0; - looknew->ns_search_only = false; - looknew->origin = NULL; - looknew->tsigctx = NULL; - looknew->querysig = NULL; - looknew->retries = tries; - looknew->nsfound = 0; - looknew->tcp_mode = false; - looknew->tcp_mode_set = false; - looknew->tls_mode = false; - looknew->comments = true; - looknew->stats = true; - looknew->section_question = true; - looknew->section_answer = true; - looknew->section_authority = true; - looknew->section_additional = true; - looknew->new_search = false; - looknew->done_as_is = false; - looknew->need_search = false; - looknew->ecs_addr = NULL; - looknew->cookie = NULL; - looknew->ednsopts = NULL; - looknew->ednsoptscnt = 0; - looknew->ednsneg = true; - looknew->mapped = true; - looknew->dscp = -1; - looknew->rrcomments = 0; - looknew->eoferr = 0; + .udpsize = -1, + .edns = -1, + .recurse = true, + .retries = tries, + .comments = true, + .stats = true, + .section_question = true, + .section_answer = true, + .section_authority = true, + .section_additional = true, + .ednsneg = true, + .mapped = true, + .dscp = -1, + }; + dns_fixedname_init(&looknew->fdomain); ISC_LINK_INIT(looknew, link); ISC_LIST_INIT(looknew->q); @@ -787,6 +728,12 @@ clone_lookup(dig_lookup_t *lookold, bool servers) { looknew->nsid = lookold->nsid; looknew->tcp_keepalive = lookold->tcp_keepalive; looknew->header_only = lookold->header_only; + looknew->https_mode = lookold->https_mode; + if (lookold->https_path != NULL) { + looknew->https_path = isc_mem_strdup(mctx, lookold->https_path); + } + looknew->https_get = lookold->https_get; + looknew->http_plain = lookold->http_plain; looknew->sendcookie = lookold->sendcookie; looknew->seenbadcookie = lookold->seenbadcookie; looknew->badcookie = lookold->badcookie; @@ -1638,6 +1585,10 @@ _destroy_lookup(dig_lookup_t *lookup) { isc_mem_free(mctx, lookup->ednsopts); } + if (lookup->https_path) { + isc_mem_free(mctx, lookup->https_path); + } + isc_mem_free(mctx, lookup); } @@ -2760,7 +2711,20 @@ start_tcp(dig_query_t *query) { * For TLS connections, we want to override the default * port number. */ - port = port_set ? port : (query->lookup->tls_mode ? 853 : 53); + if (!port_set) { + if (query->lookup->tls_mode) { + port = 853; + } else if (query->lookup->https_mode && + !query->lookup->http_plain) { + port = 443; + } else if (query->lookup->https_mode) { + port = 80; + } else { + port = 53; + } + } + + debug("query->servname = %s\n", query->servname); result = get_address(query->servname, port, &query->sockaddr); if (result != ISC_R_SUCCESS) { @@ -2835,7 +2799,27 @@ start_tcp(dig_query_t *query) { (isc_nmiface_t *)&query->sockaddr, tcp_connected, query, local_timeout, 0, query->tlsctx); - check_result(result, "isc_nm_tcpdnsconnect"); + check_result(result, "isc_nm_tlsdnsconnect"); + } else if (query->lookup->https_mode) { + char uri[4096] = { 0 }; + snprintf(uri, sizeof(uri), "https://%s:%u%s", + query->userarg, (uint16_t)port, + query->lookup->https_path); + + if (!query->lookup->http_plain) { + result = + isc_tlsctx_createclient(&query->tlsctx); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_tlsctx_enable_http2client_alpn( + query->tlsctx); + } + + result = isc_nm_httpconnect( + netmgr, (isc_nmiface_t *)&localaddr, + (isc_nmiface_t *)&query->sockaddr, uri, + !query->lookup->https_get, tcp_connected, query, + query->tlsctx, local_timeout, 0); + check_result(result, "isc_nm_httpconnect"); } else { result = isc_nm_tcpdnsconnect( netmgr, (isc_nmiface_t *)&localaddr, @@ -3211,6 +3195,7 @@ launch_next_query(dig_query_t *query) { } } } + lookup_detach(&l); return; } @@ -3582,8 +3567,7 @@ recv_done(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, isc_sockaddr_t peer; REQUIRE(DIG_VALID_QUERY(query)); - INSIST(query->readhandle != NULL); - INSIST(handle == query->readhandle); + REQUIRE(query->readhandle != NULL); INSIST(!free_now); debug("recv_done(%p, %s, %p, %p)", handle, isc_result_totext(eresult), diff --git a/bin/dig/dighost.h b/bin/dig/dighost.h index b719b4856e..0dfeb56da5 100644 --- a/bin/dig/dighost.h +++ b/bin/dig/dighost.h @@ -76,6 +76,9 @@ #define DEFAULT_EDNS_VERSION 0 #define DEFAULT_EDNS_BUFSIZE 1232 +#define DEFAULT_HTTPS_PATH "/dns-query" +#define DEFAULT_HTTPS_QUERY "?dns=" + /*% * Lookup_limit is just a limiter, keeping too many lookups from being * created. It's job is mainly to prevent the program from running away @@ -168,6 +171,12 @@ struct dig_lookup { int rrcomments; unsigned int eoferr; uint16_t qid; + struct { + bool http_plain; + bool https_mode; + bool https_get; + char *https_path; + }; }; /*% The dig_query structure */ diff --git a/doc/man/dig.1in b/doc/man/dig.1in index 9b78ada40f..e5820f5f42 100644 --- a/doc/man/dig.1in +++ b/doc/man/dig.1in @@ -361,11 +361,38 @@ This option sends a query with a DNS header without a question section. The default is to add a question section. The query type and query name are ignored when this is set. .TP +.B \fB+[no]https[=value]\fP +This option indicates whether to use DNS\-over\-HTTPS (DoH) when querying +name servers. When this option is in use, the port number defaults to 443. +The HTTP POST request mode is used when sending the query. +.sp +If \fBvalue\fP is specified, it will be used as the HTTP endpoint in the +query URI; the default is \fB/dns\-query\fP\&. So, for example, \fBdig +@example.com +https\fP will use the URI \fBhttps://example.com/dns\-query\fP\&. +.TP +.B \fB+[no]https\-get[=value]\fP +Similar to \fB+https\fP, except that the HTTP GET request mode is used +when sending the query. +.TP +.B \fB+[no]https\-post[=value]\fP +Same as \fB+https\fP\&. +.TP +.B \fB+[no]http\-plain[=value]\fP +Similar to \fB+https\fP, except that HTTP queries will be sent over a +non\-encrypted channel. When this option is in use, the port number +defaults to 80 and the HTTP request mode is POST. +.TP +.B \fB+[no]http\-plain\-get[=value]\fP +Similar to \fB+http\-plain\fP, except that the HTTP request mode is GET. +.TP +.B \fB+[no]http\-plain\-post[=value]\fP +Same as \fB+http\-plain\fP\&. +.TP .B \fB+[no]identify\fP -This option shows [or does not show] the IP address and port number that supplied -the answer, when the \fB+short\fP option is enabled. If short form -answers are requested, the default is not to show the source address -and port number of the server that provided the answer. +This option shows [or does not show] the IP address and port number that +supplied the answer, when the \fB+short\fP option is enabled. If short +form answers are requested, the default is not to show the source +address and port number of the server that provided the answer. .TP .B \fB+[no]idnin\fP This option processes [or does not process] IDN domain names on input. This requires @@ -531,8 +558,9 @@ This option sets the timeout for a query to \fBT\fP seconds. The default timeout 5 seconds. An attempt to set \fBT\fP to less than 1 is silently set to 1. .TP .B \fB+[no]tls\fP -This option indicates whether to use DNS over TLS (DoT) when querying -name servers. +This option indicates whether to use DNS\-over\-TLS (DoT) when querying +name servers. When this option is in use, the port number defaults +to 853. .TP .B \fB+[no]topdown\fP This feature is related to \fBdig +sigchase\fP, which is obsolete and From 88752b1121e29d82daf0ea0943258f597c35faae Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Wed, 3 Feb 2021 16:59:49 -0800 Subject: [PATCH 2/8] refactor outgoing HTTP connection support - style, cleanup, and removal of unnecessary code. - combined isc_nm_http_add_endpoint() and isc_nm_http_add_doh_endpoint() into one function, renamed isc_http_endpoint(). - moved isc_nm_http_connect_send_request() into doh_test.c as a helper function; remove it from the public API. - renamed isc_http2 and isc_nm_http2 types and functions to just isc_http and isc_nm_http, for consistency with other existing names. - shortened a number of long names. - the caller is now responsible for determining the peer address. in isc_nm_httpconnect(); this eliminates the need to parse the URI and the dependency on an external resolver. - the caller is also now responsible for creating the SSL client context, for consistency with isc_nm_tlsdnsconnect(). - added setter functions for HTTP/2 ALPN. instead of setting up ALPN in isc_tlsctx_createclient(), we now have a function isc_tlsctx_enable_http2client_alpn() that can be run from isc_nm_httpconnect(). - refactored isc_nm_httprequest() into separate read and send functions. isc_nm_send() or isc_nm_read() is called on an http socket, it will be stored until a corresponding isc_nm_read() or _send() arrives; when we have both halves of the pair the HTTP request will be initiated. - isc_nm_httprequest() is renamed isc__nm_http_request() for use as an internal helper function by the DoH unit test. (eventually doh_test should be rewritten to use read and send, and this function should be removed.) - added implementations of isc__nm_tls_settimeout() and isc__nm_http_settimeout(). - increased NGHTTP2 header block length for client connections to 128K. - use isc_mem_t for internal memory allocations inside nghttp2, to help track memory leaks. - send "Cache-Control" header in requests and responses. (note: currently we try to bypass HTTP caching proxies, but ideally we should interact with them: https://tools.ietf.org/html/rfc8484#section-5.1) --- bin/named/transportconf.c | 2 +- lib/dns/include/dns/transport.h | 20 +- lib/dns/transport.c | 18 +- lib/isc/include/isc/netmgr.h | 32 +- lib/isc/include/isc/result.h | 3 +- lib/isc/include/isc/tls.h | 57 +- lib/isc/netmgr/http.c | 1708 ++++++++++++++++--------------- lib/isc/netmgr/netmgr-int.h | 125 ++- lib/isc/netmgr/netmgr.c | 114 +-- lib/isc/netmgr/tcp.c | 34 +- lib/isc/netmgr/tlsdns.c | 71 +- lib/isc/netmgr/tlsstream.c | 622 +++++------ lib/isc/result.c | 146 +-- lib/isc/tests/doh_test.c | 318 +++--- lib/isc/tls.c | 132 ++- lib/isc/win32/libisc.def.in | 16 +- lib/ns/interfacemgr.c | 6 +- 17 files changed, 1794 insertions(+), 1630 deletions(-) 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)); } } From 71668437d4313f910c817366a30d295b208e6ab2 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Tue, 16 Feb 2021 16:54:51 +0200 Subject: [PATCH 3/8] Put sane limitations in place to handle bad requests gracefully This commit makes the server-side code polite. It fixes the error handling code on the server side and fixes returning error code in responses (there was a nasty bug which could potentially crash the server). Also, in this commit we limit max size POST request data to 96K, max processed data size in headers to 128K (should be enough to handle any GET requests). If these limits are surpassed, server will terminate the request with RST_STREAM without responding with error code. Otherwise it politely responds with error code. This commit also limits number of concurrent HTTP/2 streams per transport connection on server to 100 (as nghttp2 advises by default). Ideally, these parameters should be configurable both globally and per every HTTP endpoint description in the configuration file, but for now putting sane limits should be enough. --- lib/isc/netmgr/http.c | 207 ++++++++++++++++++++---------------- lib/isc/netmgr/netmgr-int.h | 4 +- 2 files changed, 118 insertions(+), 93 deletions(-) diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c index bf66ef0bd2..8cce4fadfe 100644 --- a/lib/isc/netmgr/http.c +++ b/lib/isc/netmgr/http.c @@ -32,6 +32,25 @@ #define DEFAULT_CACHE_CONTROL "no-cache, no-store" +/* + * If server during request processing surpasses any of the limits + * below, it will just reset the stream without returning any error + * codes in a response. Ideally, these parameters should be + * configurable both globally and per every HTTP endpoint description + * in the configuration file, but for now it should be enough. + */ + +/* + * 128K should be enough to encode 64K of data into base64url inside GET + * request and have extra space for other headers + */ +#define MAX_ALLOWED_DATA_IN_HEADERS (MAX_DNS_MESSAGE_SIZE * 2) + +#define MAX_ALLOWED_DATA_IN_POST \ + (MAX_DNS_MESSAGE_SIZE + MAX_DNS_MESSAGE_SIZE / 2) + +#define MAX_STREAMS_PER_SESSION (NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS) + #define HEADER_MATCH(header, name, namelen) \ (((namelen) == sizeof(header) - 1) && \ (strncasecmp((header), (const char *)(name), (namelen)) == 0)) @@ -92,6 +111,7 @@ struct isc_nm_http_session { ISC_LIST(http_cstream_t) cstreams; ISC_LIST(isc_nmsocket_h2_t) sstreams; + size_t nsstreams; isc_nmhandle_t *handle; isc_nmsocket_t *serversocket; @@ -619,10 +639,8 @@ initialize_nghttp2_client_session(isc_nm_http_session_t *session) { 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); + option, MAX_ALLOWED_DATA_IN_HEADERS); #endif nghttp2_session_callbacks_set_on_data_chunk_recv_callback( @@ -1207,6 +1225,12 @@ server_on_begin_headers_callback(nghttp2_session *ngsession, frame->headers.cat != NGHTTP2_HCAT_REQUEST) { return (0); + } else if (frame->hd.length > MAX_ALLOWED_DATA_IN_HEADERS) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + + if (session->nsstreams >= MAX_STREAMS_PER_SESSION) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); } socket = isc_mem_get(session->mctx, sizeof(isc_nmsocket_t)); @@ -1217,7 +1241,9 @@ server_on_begin_headers_callback(nghttp2_session *ngsession, .buf = isc_mem_allocate(session->mctx, MAX_DNS_MESSAGE_SIZE), .psock = socket, .stream_id = frame->hd.stream_id, + .headers_error_code = ISC_HTTP_ERROR_SUCCESS }; + session->nsstreams++; isc__nm_httpsession_attach(session, &socket->h2.session); socket->tid = session->handle->sock->tid; ISC_LINK_INIT(&socket->h2, link); @@ -1291,8 +1317,7 @@ server_handle_path_header(isc_nmsocket_t *socket, const uint8_t *value, } if (isc__nm_parse_httpquery((const char *)qstr, &dns_value, - &dns_value_len)) - { + &dns_value_len)) { const size_t decoded_size = dns_value_len / 4 * 3; if (decoded_size <= MAX_DNS_MESSAGE_SIZE) { if (socket->h2.query_data != NULL) { @@ -1370,13 +1395,14 @@ 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[] = DNS_MEDIA_TYPE; + isc_http_error_responses_t resp = ISC_HTTP_ERROR_SUCCESS; - if (HEADER_MATCH(type_dns_message, value, valuelen)) { - socket->h2.content_type_verified = true; - } else { - return (ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE); + UNUSED(socket); + + if (!HEADER_MATCH(type_dns_message, value, valuelen)) { + resp = ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE; } - return (ISC_HTTP_ERROR_SUCCESS); + return (resp); } static isc_http_error_responses_t @@ -1384,25 +1410,24 @@ 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[] = DNS_MEDIA_TYPE; + isc_http_error_responses_t resp = ISC_HTTP_ERROR_SUCCESS; - if (HEADER_MATCH(type_dns_message, value, valuelen) || - HEADER_MATCH(type_accept_all, value, valuelen)) + UNUSED(socket); + + if (!(HEADER_MATCH(type_dns_message, value, valuelen) || + HEADER_MATCH(type_accept_all, value, valuelen))) { - socket->h2.accept_type_verified = true; - } else { - return (ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE); + resp = ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE; } - return (ISC_HTTP_ERROR_SUCCESS); + return (resp); } -static int -server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, - const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, uint8_t flags, - void *user_data) { - isc_result_t result; - isc_nmsocket_t *socket = NULL; +static isc_http_error_responses_t +server_handle_header(isc_nmsocket_t *socket, const uint8_t *name, + size_t namelen, const uint8_t *value, + const size_t valuelen) { isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS; + bool was_error; const char path[] = ":path"; const char method[] = ":method"; const char scheme[] = ":scheme"; @@ -1410,54 +1435,74 @@ server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, const char content_length[] = "Content-Length"; const char content_type[] = "Content-Type"; + was_error = socket->h2.headers_error_code != ISC_HTTP_ERROR_SUCCESS; + /* + * process Content-Length even when there was an error, + * to drop the connection earlier if required. + */ + if (HEADER_MATCH(content_length, name, namelen)) { + code = server_handle_content_length_header(socket, value, + valuelen); + } else if (!was_error && HEADER_MATCH(path, name, namelen)) { + code = server_handle_path_header(socket, value, valuelen); + } else if (!was_error && HEADER_MATCH(method, name, namelen)) { + code = server_handle_method_header(socket, value, valuelen); + } else if (!was_error && HEADER_MATCH(scheme, name, namelen)) { + code = server_handle_scheme_header(socket, value, valuelen); + } else if (!was_error && HEADER_MATCH(content_type, name, namelen)) { + code = server_handle_content_type_header(socket, value, + valuelen); + } else if (!was_error && + HEADER_MATCH(accept, (const char *)name, namelen)) { + code = server_handle_accept_header(socket, value, valuelen); + } + + return (code); +} + +static int +server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data) { + isc_nmsocket_t *socket = NULL; + isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS; + UNUSED(flags); UNUSED(user_data); + socket = nghttp2_session_get_stream_user_data(session, + frame->hd.stream_id); + if (socket == NULL) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + + socket->h2.headers_data_processed += (namelen + valuelen); + switch (frame->hd.type) { case NGHTTP2_HEADERS: if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { break; } - - socket = nghttp2_session_get_stream_user_data( - session, frame->hd.stream_id); - if (socket == NULL) { - break; - } - - if (HEADER_MATCH(path, name, namelen)) { - code = server_handle_path_header(socket, value, - valuelen); - } else if (HEADER_MATCH(method, name, namelen)) { - code = server_handle_method_header(socket, value, - valuelen); - } else if (HEADER_MATCH(scheme, name, namelen)) { - code = server_handle_scheme_header(socket, value, - valuelen); - } else if (HEADER_MATCH(content_length, name, namelen)) { - code = server_handle_content_length_header( - socket, value, valuelen); - } else if (HEADER_MATCH(content_type, name, namelen)) { - code = server_handle_content_type_header(socket, value, - valuelen); - } else if (HEADER_MATCH(accept, (const char *)name, namelen)) { - code = server_handle_accept_header(socket, value, - valuelen); - } + code = server_handle_header(socket, name, namelen, value, + valuelen); break; } + INSIST(socket != NULL); + + if (socket->h2.headers_data_processed > MAX_ALLOWED_DATA_IN_HEADERS) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } else if (socket->h2.content_length > MAX_ALLOWED_DATA_IN_POST) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + if (code == ISC_HTTP_ERROR_SUCCESS) { return (0); + } else { + socket->h2.headers_error_code = code; } - INSIST(socket != NULL); - 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); } @@ -1511,6 +1556,11 @@ server_send_response(nghttp2_session *ngsession, int32_t stream_id, tag, MAKE_NV2(":status", #code) \ } +/* + * Here we use roughly the same error codes that Unbound uses. + * (https://blog.nlnetlabs.nl/dns-over-https-in-unbound/) + */ + static struct http_error_responses { const isc_http_error_responses_t type; const nghttp2_nv header; @@ -1537,8 +1587,7 @@ server_send_error_response(const isc_http_error_responses_t error, if (error_responses[i].type == error) { return (server_send_response( ngsession, socket->h2.stream_id, - &error_responses[i].header, - sizeof(error_responses[i].header), socket)); + &error_responses[i].header, 1, socket)); } } @@ -1555,39 +1604,13 @@ server_on_request_recv(nghttp2_session *ngsession, isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS; isc_region_t data; - /* - * Sanity checks. Here we use the same error codes that - * Unbound uses. - * (https://blog.nlnetlabs.nl/dns-over-https-in-unbound/) - */ + code = socket->h2.headers_error_code; + if (code != ISC_HTTP_ERROR_SUCCESS) { + goto error; + } + 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) || - !socket->h2.accept_type_verified) - { - code = ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE; - } else if (socket->h2.request_type == ISC_HTTP_REQ_UNSUPPORTED) { - code = ISC_HTTP_ERROR_NOT_IMPLEMENTED; - } else if (socket->h2.request_scheme == ISC_HTTP_SCHEME_UNSUPPORTED) { - /* - * TODO: additional checks if we have enabled encryption - * on the socket or not - */ - code = ISC_HTTP_ERROR_BAD_REQUEST; - } else if (socket->h2.content_length > MAX_DNS_MESSAGE_SIZE || - socket->h2.query_too_large) - { - code = ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE; - } else if (socket->h2.request_type == ISC_HTTP_REQ_GET && - (socket->h2.content_length > 0 || - socket->h2.query_data_len == 0)) - { - code = ISC_HTTP_ERROR_BAD_REQUEST; - } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && - socket->h2.content_length == 0) - { - code = ISC_HTTP_ERROR_BAD_REQUEST; } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && socket->h2.bufsize > socket->h2.content_length) { @@ -1863,7 +1886,8 @@ initialize_nghttp2_server_session(isc_nm_http_session_t *session) { static int server_send_connection_header(isc_nm_http_session_t *session) { nghttp2_settings_entry iv[1] = { - { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 } + { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, + MAX_STREAMS_PER_SESSION } }; int rv; @@ -2114,6 +2138,7 @@ http_close_direct(isc_nmsocket_t *sock) { if (ISC_LINK_LINKED(&sock->h2, link)) { ISC_LIST_UNLINK(session->sstreams, &sock->h2, link); + session->nsstreams--; } sessions_empty = ISC_LIST_EMPTY(session->sstreams); @@ -2497,7 +2522,7 @@ isc__nm_parse_httpquery(const char *query_string, const char **start, return (false); } - state = (isc_httpparser_state_t) { .str = query_string }; + state = (isc_httpparser_state_t){ .str = query_string }; if (!rule_query_string(&state)) { return (false); } diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index f43be4b60d..e74eba82a0 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -749,8 +749,8 @@ typedef struct isc_nmsocket_h2 { size_t content_length; char clenbuf[128]; - bool content_type_verified; - bool accept_type_verified; + int headers_error_code; + size_t headers_data_processed; isc_nm_recv_cb_t cb; void *cbarg; From ca9a15e3bc5b9b7751ddcb17dac12fb7b1bca3dc Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Sun, 28 Feb 2021 16:11:49 +0200 Subject: [PATCH 4/8] DoH: call send callbacks after data was actually sent --- bin/tests/test_client.c | 78 ++++++++-- bin/tests/test_server.c | 33 ++-- lib/isc/netmgr/http.c | 328 +++++++++++++++++++++++----------------- 3 files changed, 283 insertions(+), 156 deletions(-) diff --git a/bin/tests/test_client.c b/bin/tests/test_client.c index f561ca0c22..ba96e4a9e2 100644 --- a/bin/tests/test_client.c +++ b/bin/tests/test_client.c @@ -27,13 +27,27 @@ #include #include #include +#include #include #include #include -typedef enum { UDP, TCP, DOT, DOH } protocol_t; +#define DEFAULT_DOH_PATH "/dns-query" -static const char *protocols[] = { "udp", "tcp", "dot", "doh" }; +typedef enum { + UDP, + TCP, + DOT, + HTTPS_POST, + HTTPS_GET, + HTTP_POST, + HTTP_GET +} protocol_t; + +static const char *protocols[] = { "udp", "tcp", + "dot", "https-post", + "https-get", "http-plain-post", + "http-plain-get" }; static isc_mem_t *mctx = NULL; static isc_nm_t *netmgr = NULL; @@ -50,6 +64,8 @@ static uint8_t messagebuf[2 * 65536]; static isc_region_t message = { .length = 0, .base = messagebuf }; static int out = -1; +static isc_tlsctx_t *tls_ctx = NULL; + static isc_result_t parse_port(const char *input) { char *endptr = NULL; @@ -304,6 +320,9 @@ teardown(void) { isc_nm_destroy(&netmgr); isc_mem_destroy(&mctx); + if (tls_ctx) { + isc_tlsctx_free(&tls_ctx); + } } static void @@ -374,6 +393,33 @@ connect_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) { isc_nm_send(handle, &message, send_cb, NULL); } +static void +sockaddr_to_url(isc_sockaddr_t *sa, const bool https, char *outbuf, + size_t outbuf_len, const char *append) { + uint16_t sa_port; + char saddr[INET6_ADDRSTRLEN] = { 0 }; + int sa_family; + + if (sa == NULL || outbuf == NULL || outbuf_len == 0) { + return; + } + + sa_family = ((struct sockaddr *)&sa->type.sa)->sa_family; + + sa_port = ntohs(sa_family == AF_INET ? sa->type.sin.sin_port + : sa->type.sin6.sin6_port); + inet_ntop(sa_family, + sa_family == AF_INET + ? (struct sockaddr *)&sa->type.sin.sin_addr + : (struct sockaddr *)&sa->type.sin6.sin6_addr, + saddr, sizeof(saddr)); + + snprintf(outbuf, outbuf_len, "%s://%s%s%s:%u%s", + https ? "https" : "http", sa_family == AF_INET ? "" : "[", + saddr, sa_family == AF_INET ? "" : "]", sa_port, + append ? append : ""); +} + static void run(void) { isc_result_t result; @@ -392,19 +438,33 @@ run(void) { connect_cb, NULL, timeout, 0); break; case DOT: { - isc_tlsctx_t *tlsdns_ctx = NULL; - isc_tlsctx_createclient(&tlsdns_ctx); + isc_tlsctx_createclient(&tls_ctx); result = isc_nm_tlsdnsconnect( netmgr, (isc_nmiface_t *)&sockaddr_local, (isc_nmiface_t *)&sockaddr_remote, connect_cb, NULL, - timeout, 0, tlsdns_ctx); + timeout, 0, tls_ctx); break; } - case DOH: - INSIST(0); - ISC_UNREACHABLE(); - break; + case HTTP_GET: + case HTTPS_GET: + case HTTPS_POST: + case HTTP_POST: { + bool is_https = (protocol == HTTPS_POST || + protocol == HTTPS_GET); + bool is_post = (protocol == HTTPS_POST || + protocol == HTTP_POST); + char req_url[256]; + sockaddr_to_url(&sockaddr_remote, is_https, req_url, + sizeof(req_url), DEFAULT_DOH_PATH); + if (is_https) { + isc_tlsctx_createclient(&tls_ctx); + } + result = isc_nm_httpconnect( + netmgr, (isc_nmiface_t *)&sockaddr_local, + (isc_nmiface_t *)&sockaddr_remote, req_url, is_post, + connect_cb, NULL, tls_ctx, timeout, 0); + } break; default: INSIST(0); ISC_UNREACHABLE(); diff --git a/bin/tests/test_server.c b/bin/tests/test_server.c index b0af7a26a3..9276b62022 100644 --- a/bin/tests/test_server.c +++ b/bin/tests/test_server.c @@ -25,9 +25,11 @@ #include #include -typedef enum { UDP, TCP, DOT, DOH } protocol_t; +#define DEFAULT_DOH_PATH "/dns-query" -static const char *protocols[] = { "udp", "tcp", "dot", "doh" }; +typedef enum { UDP, TCP, DOT, HTTPS, HTTP } protocol_t; + +static const char *protocols[] = { "udp", "tcp", "dot", "https", "http-plain" }; static isc_mem_t *mctx = NULL; static isc_nm_t *netmgr = NULL; @@ -38,6 +40,8 @@ static isc_netaddr_t netaddr; static isc_sockaddr_t sockaddr __attribute__((unused)); static int workers; +static isc_tlsctx_t *tls_ctx = NULL; + static void read_cb(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, void *cbarg); @@ -191,6 +195,9 @@ static void teardown(void) { isc_nm_destroy(&netmgr); isc_mem_destroy(&mctx); + if (tls_ctx) { + isc_tlsctx_free(&tls_ctx); + } } static void @@ -267,18 +274,26 @@ run(void) { 0, NULL, &sock); break; case DOT: { - isc_tlsctx_t *tlsdns_ctx = NULL; - isc_tlsctx_createserver(NULL, NULL, &tlsdns_ctx); + isc_tlsctx_createserver(NULL, NULL, &tls_ctx); result = isc_nm_listentlsdns(netmgr, (isc_nmiface_t *)&sockaddr, read_cb, NULL, accept_cb, NULL, 0, - 0, NULL, tlsdns_ctx, &sock); + 0, NULL, tls_ctx, &sock); break; } - case DOH: - INSIST(0); - ISC_UNREACHABLE(); - break; + case HTTPS: + case HTTP: { + bool is_https = protocol == HTTPS; + if (is_https) { + isc_tlsctx_createserver(NULL, NULL, &tls_ctx); + } + result = isc_nm_listenhttp(netmgr, (isc_nmiface_t *)&sockaddr, + 0, NULL, tls_ctx, &sock); + if (result == ISC_R_SUCCESS) { + result = isc_nm_http_endpoint(sock, DEFAULT_DOH_PATH, + read_cb, NULL, 0); + } + } break; default: INSIST(0); ISC_UNREACHABLE(); diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c index 8cce4fadfe..0ce8881b71 100644 --- a/lib/isc/netmgr/http.c +++ b/lib/isc/netmgr/http.c @@ -102,9 +102,10 @@ struct isc_nm_http_session { isc_refcount_t references; isc_mem_t *mctx; - bool sending; + size_t sending; bool reading; bool closed; + bool closing; nghttp2_session *ngsession; bool client; @@ -117,7 +118,6 @@ struct isc_nm_http_session { isc_nmsocket_t *serversocket; isc_nmiface_t server_iface; - isc_region_t r; uint8_t buf[MAX_DNS_MESSAGE_SIZE]; size_t bufsize; @@ -136,8 +136,22 @@ typedef enum isc_http_error_responses { ISC_HTTP_ERROR_MAX } isc_http_error_responses_t; +typedef struct isc_http_send_req { + isc_nm_http_session_t *session; + isc_nmhandle_t *transphandle; + isc_nmhandle_t *httphandle; + isc_region_t data; + isc_nm_cb_t cb; + void *cbarg; +} isc_http_send_req_t; + +static bool +http_send_outgoing(isc_nm_http_session_t *session, isc_nmhandle_t *httphandle, + isc_nm_cb_t cb, void *cbarg); + static void -http_do_bio(isc_nm_http_session_t *session); +http_do_bio(isc_nm_http_session_t *session, isc_nmhandle_t *send_httphandle, + isc_nm_cb_t send_cb, void *send_cbarg); static void failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result, @@ -156,6 +170,12 @@ client_send(isc_nmhandle_t *handle, const isc_region_t *region); static void finish_http_session(isc_nm_http_session_t *session); +static bool +http_session_active(isc_nm_http_session_t *session) { + REQUIRE(VALID_HTTP2_SESSION(session)); + return (!session->closed && !session->closing); +} + static bool inactive(isc_nmsocket_t *sock) { return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) || @@ -246,13 +266,14 @@ isc__nm_httpsession_detach(isc_nm_http_session_t **sessionp) { 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)); + if (session->ngsession != NULL) { + nghttp2_session_del(session->ngsession); + session->ngsession = NULL; + } + /* We need an acquire memory barrier here */ (void)isc_refcount_current(&session->references); @@ -266,6 +287,10 @@ find_http_cstream(int32_t stream_id, isc_nm_http_session_t *session) { http_cstream_t *cstream = NULL; REQUIRE(VALID_HTTP2_SESSION(session)); + if (ISC_LIST_EMPTY(session->cstreams)) { + return (NULL); + } + for (cstream = ISC_LIST_HEAD(session->cstreams); cstream != NULL; cstream = ISC_LIST_NEXT(cstream, link)) { @@ -274,6 +299,12 @@ find_http_cstream(int32_t stream_id, isc_nm_http_session_t *session) { } } + /* LRU-like behaviour */ + if (cstream && ISC_LIST_HEAD(session->cstreams) != cstream) { + ISC_LIST_UNLINK(session->cstreams, cstream, link); + ISC_LIST_PREPEND(session->cstreams, cstream, link); + } + return (cstream); } @@ -368,42 +399,41 @@ put_http_cstream(isc_mem_t *mctx, http_cstream_t *stream) { static void finish_http_session(isc_nm_http_session_t *session) { + if (session->closed) { + return; + } if (session->handle != NULL) { - isc_nm_pauseread(session->handle); + if (!session->closed) { + session->closed = true; + isc_nm_cancelread(session->handle); + } + 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; + } + } isc_nmhandle_detach(&session->handle); } - if (session->ngsession != NULL) { - nghttp2_session_del(session->ngsession); - session->ngsession = NULL; - } - - 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; - } - } INSIST(ISC_LIST_EMPTY(session->cstreams)); /* detach from server socket */ if (session->serversocket != NULL) { isc__nmsocket_detach(&session->serversocket); } - - /* - * There might be leftover callbacks waiting to be received - */ - if (session->sending) { - session->closed = true; - } + session->closed = true; } static int @@ -498,10 +528,6 @@ on_client_stream_close_callback(int32_t stream_id, 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); @@ -517,11 +543,13 @@ on_server_stream_close_callback(int32_t stream_id, session->ngsession, stream_id); int rv = 0; + ISC_LIST_UNLINK(session->sstreams, &sock->h2, link); if (ISC_LIST_EMPTY(session->sstreams)) { rv = nghttp2_session_terminate_session(session->ngsession, NGHTTP2_NO_ERROR); } - isc__nmsocket_prep_destroy(sock); + session->nsstreams--; + isc__nmsocket_detach(&sock); return (rv); } @@ -536,11 +564,6 @@ on_stream_close_callback(nghttp2_session *ngsession, int32_t stream_id, UNUSED(error_code); - /* - * NOTE: calling isc_nm_cancelread() or isc__nmsocket_prep_destroy() - * on a socket will lead to an indirect call to detach the session, - * which will, in turn, perform required stream cleanup. - */ if (session->client) { rv = on_client_stream_close_callback(stream_id, session); } else { @@ -789,7 +812,6 @@ client_submit_request(isc_nm_http_session_t *session, http_cstream_t *stream) { } stream->stream_id = stream_id; - http_do_bio(session); return (ISC_R_SUCCESS); } @@ -829,34 +851,95 @@ http_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, } /* We might have something to receive or send, do IO */ - http_do_bio(session); + http_do_bio(session, NULL, NULL, NULL); } static void http_writecb(isc_nmhandle_t *handle, isc_result_t result, void *arg) { - isc_nm_http_session_t *session = (isc_nm_http_session_t *)arg; + isc_http_send_req_t *req = (isc_http_send_req_t *)arg; + isc_nm_http_session_t *session = req->session; + isc_nmhandle_t *transphandle = req->transphandle; REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(VALID_NMHANDLE(handle)); - UNUSED(handle); - - session->sending = false; - isc_mem_put(session->mctx, session->r.base, session->r.length); - session->r.base = NULL; - if (result == ISC_R_SUCCESS) { - http_do_bio(session); + if (http_session_active(session)) { + INSIST(session->handle == handle); } + + if (req->cb) { + req->cb(req->httphandle, result, req->cbarg); + isc_nmhandle_detach(&req->httphandle); + } + + isc_mem_put(session->mctx, req->data.base, req->data.length); + isc_mem_put(session->mctx, req, sizeof(*req)); + + http_do_bio(session, NULL, NULL, NULL); + session->sending--; + isc_nmhandle_detach(&transphandle); + isc__nm_httpsession_detach(&session); +} + +static bool +http_send_outgoing(isc_nm_http_session_t *session, isc_nmhandle_t *httphandle, + isc_nm_cb_t cb, void *cbarg) { + isc_http_send_req_t *send = NULL; + const uint8_t *data = NULL; + size_t pending; + + if (!http_session_active(session) || + !nghttp2_session_want_write(session->ngsession)) + { + return (false); + } + + pending = nghttp2_session_mem_send(session->ngsession, &data); + if (pending == 0) { + /* No data returned */ + return (false); + } + + send = isc_mem_get(session->mctx, sizeof(*send)); + *send = (isc_http_send_req_t){ + .data.base = isc_mem_get(session->mctx, pending), + .data.length = pending, + }; + memmove(send->data.base, data, pending); + isc_nmhandle_attach(session->handle, &send->transphandle); + isc__nm_httpsession_attach(session, &send->session); + + if (cb != NULL) { + INSIST(VALID_NMHANDLE(httphandle)); + send->cb = cb; + send->cbarg = cbarg; + isc_nmhandle_attach(httphandle, &send->httphandle); + } + + session->sending++; + isc_nm_send(session->handle, &send->data, http_writecb, send); + return (true); } static void -http_do_bio(isc_nm_http_session_t *session) { +http_do_bio(isc_nm_http_session_t *session, isc_nmhandle_t *send_httphandle, + isc_nm_cb_t send_cb, void *send_cbarg) { REQUIRE(VALID_HTTP2_SESSION(session)); - if (session->closed || - (nghttp2_session_want_read(session->ngsession) == 0 && - nghttp2_session_want_write(session->ngsession) == 0)) + if (session->closed) { + return; + } else if (session->closing) { + /* + * There might be leftover callbacks waiting to be received + */ + if (session->sending == 0) { + finish_http_session(session); + } + return; + } else if ((nghttp2_session_want_read(session->ngsession) == 0 && + nghttp2_session_want_write(session->ngsession) == 0)) { - finish_http_session(session); + session->closing = true; return; } @@ -879,7 +962,8 @@ http_do_bio(isc_nm_http_session_t *session) { session->bufsize -= readlen; } - http_do_bio(session); + http_do_bio(session, send_httphandle, send_cb, + send_cbarg); return; } else { /* Resume reading, it's idempotent, wait for more */ @@ -890,34 +974,15 @@ http_do_bio(isc_nm_http_session_t *session) { isc_nm_pauseread(session->handle); } - if (!session->sending && - nghttp2_session_want_write(session->ngsession) != 0) { - const uint8_t *data = NULL; - size_t sz; - - /* - * XXXWPK TODO - * This function may produce a very small byte string. If - * that is the case, and application disables Nagle - * algorithm (``TCP_NODELAY``), then writing this small - * chunk leads to a very small packet, and it is very - * inefficient. An application should be responsible to - * buffer up small chunks of data as necessary to avoid - * this situation. - */ - sz = nghttp2_session_mem_send(session->ngsession, &data); - if (sz == 0) { - /* No data returned */ - return; - } - INSIST(session->r.base == NULL); - session->r.base = isc_mem_get(session->mctx, sz); - session->r.length = sz; - memmove(session->r.base, data, sz); - session->sending = true; - isc_nm_send(session->handle, &session->r, http_writecb, - session); - return; + if (send_cb != NULL) { + INSIST(VALID_NMHANDLE(send_httphandle)); + (void)http_send_outgoing(session, send_httphandle, send_cb, + send_cbarg); + } else { + INSIST(send_httphandle == NULL); + INSIST(send_cb == NULL); + INSIST(send_cbarg == NULL); + (void)http_send_outgoing(session, NULL, NULL, NULL); } return; @@ -1030,7 +1095,7 @@ transport_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { } http_call_connect_cb(http_sock, result); - http_do_bio(session); + http_do_bio(session, NULL, NULL, NULL); isc__nmsocket_detach(&http_sock); return; @@ -1167,17 +1232,14 @@ client_send(isc_nmhandle_t *handle, const isc_region_t *region) { cstream->sending = true; if (!ISC_LINK_LINKED(cstream, link)) { - ISC_LIST_APPEND(session->cstreams, cstream, link); + ISC_LIST_PREPEND(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); + 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; } error: @@ -1200,11 +1262,16 @@ isc__nm_http_request(isc_nmhandle_t *handle, isc_region_t *region, sock = handle->sock; isc__nm_http_read(handle, cb, cbarg); + if (!http_session_active(handle->sock->h2.session)) { + /* the callback was called by isc__nm_http_read() */ + return (ISC_R_CANCELED); + } result = client_send(handle, region); if (result != ISC_R_SUCCESS) { goto error; } + http_do_bio(sock->h2.session, NULL, NULL, NULL); return (ISC_R_SUCCESS); error: @@ -1387,6 +1454,8 @@ server_handle_content_length_header(isc_nmsocket_t *socket, socket->h2.content_length = strtoul(tmp, NULL, 10); if (socket->h2.content_length > MAX_DNS_MESSAGE_SIZE) { return (ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE); + } else if (socket->h2.content_length == 0) { + return (ISC_HTTP_ERROR_BAD_REQUEST); } return (ISC_HTTP_ERROR_SUCCESS); } @@ -1712,7 +1781,7 @@ client_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock, return; } - cb(handle, result, cbarg); + http_do_bio(sock->h2.session, handle, cb, cbarg); isc__nm_uvreq_put(&req, sock); } @@ -1723,7 +1792,7 @@ server_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock, 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) { + if (inactive(sock) || !http_session_active(handle->httpsession)) { failed_send_cb(sock, req, ISC_R_CANCELED); return; } @@ -1752,8 +1821,11 @@ server_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock, sock->h2.stream_id, hdrs, sizeof(hdrs) / sizeof(nghttp2_nv), sock); - http_do_bio(handle->httpsession); - cb(handle, result, cbarg); + if (result == ISC_R_SUCCESS) { + http_do_bio(handle->httpsession, handle, cb, cbarg); + } else { + cb(handle, result, cbarg); + } isc__nm_uvreq_put(&req, sock); } @@ -1793,6 +1865,10 @@ isc__nm_http_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { REQUIRE(VALID_NMHANDLE(handle)); session = handle->sock->h2.session; + if (!http_session_active(session)) { + cb(handle, ISC_R_CANCELED, NULL, cbarg); + return; + } result = get_http_cstream(handle->sock, &cstream); if (result != ISC_R_SUCCESS) { @@ -1805,7 +1881,7 @@ isc__nm_http_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { cstream->reading = true; if (!ISC_LINK_LINKED(cstream, link)) { - ISC_LIST_APPEND(session->cstreams, cstream, link); + ISC_LIST_PREPEND(session->cstreams, cstream, link); } if (cstream->sending) { @@ -1815,7 +1891,7 @@ isc__nm_http_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { return; } - http_do_bio(session); + http_do_bio(session, NULL, NULL, NULL); } } @@ -1950,7 +2026,7 @@ httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { server_send_connection_header(session); /* TODO H2 */ - http_do_bio(session); + http_do_bio(session, NULL, NULL, NULL); return (ISC_R_SUCCESS); } @@ -1981,7 +2057,7 @@ isc_nm_listenhttp(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog, return (result); } - sock->outer->h2.httpserver = sock; + isc__nmsocket_attach(sock, &sock->outer->h2.httpserver); sock->nchildren = sock->outer->nchildren; sock->result = ISC_R_DEFAULT; @@ -2114,7 +2190,6 @@ isc__nm_async_httpstop(isc__networker_t *worker, isc__netievent_t *ev0) { atomic_store(&sock->closing, false); atomic_store(&sock->closed, true); if (sock->outer != NULL) { - sock->outer->h2.httpserver = NULL; isc_nm_stoplistening(sock->outer); isc_nmsocket_close(&sock->outer); } @@ -2122,44 +2197,15 @@ 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_http_session_t *session = NULL; REQUIRE(VALID_NMSOCK(sock)); 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); - session->nsstreams--; - } - - sessions_empty = ISC_LIST_EMPTY(session->sstreams); - if (!sessions_empty) { - 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->closed) { - INSIST(session->handle != NULL); - isc__nmsocket_detach(&sock); - } else { - INSIST(isc_refcount_current(&sock->references) == 1); + if (session != NULL && session->handle) { + http_do_bio(session, NULL, NULL, NULL); } } @@ -2239,7 +2285,6 @@ failed_read_cb(isc_result_t result, isc_nm_http_session_t *session) { } } else { isc_nmsocket_h2_t *h2data = NULL; /* stream socket */ - session->closed = true; for (h2data = ISC_LIST_HEAD(session->sstreams); h2data != NULL; h2data = ISC_LIST_NEXT(h2data, link)) { @@ -2410,6 +2455,13 @@ isc__nm_http_initsocket(isc_nmsocket_t *sock) { void isc__nm_http_cleanup_data(isc_nmsocket_t *sock) { + if ((sock->type == isc_nm_tcplistener || + sock->type == isc_nm_tlslistener) && + sock->h2.httpserver != NULL) + { + isc__nmsocket_detach(&sock->h2.httpserver); + } + if (sock->type == isc_nm_httplistener || sock->type == isc_nm_httpsocket) { if (sock->type == isc_nm_httplistener) { From 66d20cf28bd1aefde220885e7b6d3d45a8507517 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Thu, 11 Feb 2021 15:03:44 +0200 Subject: [PATCH 5/8] Fix deadlock in isc_nm_tlsconnect() when called from within the context of a network thread, isc_nm_tlsconnect() hangs. it is waiting for the socket's result code to be updated, but that update is supposed to happen asynchronously in the network thread, and if we're already blocking in the network thread, it can never occur. we can kluge around this by setting the socket result code early; this works for most clients (including "dig"), but it causes inconsistent behaviors that manifest as test failures in the DoH unit test. so we kluged around it even more by setting the socket result code early *only when running in the network thread*. we need a better solution for this problem, but this will do for now. --- lib/isc/netmgr/tlsstream.c | 41 ++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/isc/netmgr/tlsstream.c b/lib/isc/netmgr/tlsstream.c index 9a75dc0f2a..5505f3e2b4 100644 --- a/lib/isc/netmgr/tlsstream.c +++ b/lib/isc/netmgr/tlsstream.c @@ -78,18 +78,21 @@ inactive(isc_nmsocket_t *sock) { static void update_result(isc_nmsocket_t *sock, const isc_result_t result) { - if (!sock->tlsstream.server) { - LOCK(&sock->lock); - sock->result = result; - SIGNAL(&sock->cond); - while (!atomic_load(&sock->active)) { - WAIT(&sock->scond, &sock->lock); + if (!atomic_load(&sock->tlsstream.result_updated)) { + atomic_store(&sock->tlsstream.result_updated, true); + 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); - } else { - LOCK(&sock->lock); - sock->result = result; - UNLOCK(&sock->lock); } } @@ -751,6 +754,10 @@ isc_nm_tlsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, isc_nmsocket_t *nsock = NULL, *tsock = NULL; isc__netievent_tlsconnect_t *ievent = NULL; isc_result_t result = ISC_R_DEFAULT; +#if defined(NETMGR_TRACE) && defined(NETMGR_TRACE_VERBOSE) + fprintf(stderr, "TLS: isc_nm_tlsconnect(): in net thread: %s\n", + isc__nm_in_netthread() ? "yes" : "no"); +#endif /* NETMGR_TRACE */ REQUIRE(VALID_NM(mgr)); @@ -765,6 +772,7 @@ isc_nm_tlsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, nsock->connect_cbarg = cbarg; nsock->connect_timeout = timeout; nsock->tlsstream.ctx = ctx; + nsock->tlsstream.connect_from_networker = isc__nm_in_netthread(); ievent = isc__nm_get_netievent_tlsconnect(mgr, nsock); ievent->local = local->addr; @@ -844,16 +852,19 @@ isc__nm_async_tlsconnect(isc__networker_t *worker, isc__netievent_t *ev0) { 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(); tlssock->tlsstream.state = TLS_INIT; - (void)isc_nm_tcpconnect(worker->mgr, (isc_nmiface_t *)&ievent->local, - (isc_nmiface_t *)&ievent->peer, tcp_connected, - tlssock, tlssock->connect_timeout, 0); + result = isc_nm_tcpconnect(worker->mgr, (isc_nmiface_t *)&ievent->local, + (isc_nmiface_t *)&ievent->peer, + tcp_connected, tlssock, + tlssock->connect_timeout, 0); + if (tlssock->tlsstream.connect_from_networker) { + update_result(tlssock, result); + } return; error: From 7a59fb8207bea462ef172d56670a5b678d9069dc Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Sun, 28 Feb 2021 19:33:16 +0200 Subject: [PATCH 6/8] Disable Nagle's algorithm for HTTP/2 connections It is advisable to disable Nagle's algorithm for HTTP/2 connections because multiple HTTP/2 streams could be multiplexed over one transport connection. Thus, delays when delivering small packets could bring down performance for the whole session. HTTP/2 is meant to be used this way. --- lib/isc/netmgr/http.c | 40 +++++++++++++++++++++++++++++++++---- lib/isc/netmgr/netmgr-int.h | 6 ++++++ lib/isc/netmgr/netmgr.c | 14 +++++++++++++ lib/isc/netmgr/tlsstream.c | 1 + 4 files changed, 57 insertions(+), 4 deletions(-) diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c index 0ce8881b71..6de06a55cf 100644 --- a/lib/isc/netmgr/http.c +++ b/lib/isc/netmgr/http.c @@ -170,6 +170,9 @@ client_send(isc_nmhandle_t *handle, const isc_region_t *region); static void finish_http_session(isc_nm_http_session_t *session); +static void +http_transpost_tcp_nodelay(isc_nmhandle_t *transphandle); + static bool http_session_active(isc_nm_http_session_t *session) { REQUIRE(VALID_HTTP2_SESSION(session)); @@ -867,7 +870,7 @@ http_writecb(isc_nmhandle_t *handle, isc_result_t result, void *arg) { INSIST(session->handle == handle); } - if (req->cb) { + if (req->cb != NULL) { req->cb(req->httphandle, result, req->cbarg); isc_nmhandle_detach(&req->httphandle); } @@ -1094,6 +1097,7 @@ transport_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { goto error; } + http_transpost_tcp_nodelay(handle); http_call_connect_cb(http_sock, result); http_do_bio(session, NULL, NULL, NULL); isc__nmsocket_detach(&http_sock); @@ -1678,7 +1682,7 @@ server_on_request_recv(nghttp2_session *ngsession, goto error; } - if (!socket->h2.request_path || !socket->h2.cb) { + if (socket->h2.request_path == NULL || socket->h2.cb == NULL) { code = ISC_HTTP_ERROR_NOT_FOUND; } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && socket->h2.bufsize > socket->h2.content_length) @@ -1975,6 +1979,31 @@ server_send_connection_header(isc_nm_http_session_t *session) { return (0); } +/* + * It is advisable to disable Nagle's algorithm for HTTP/2 + * connections because multiple HTTP/2 streams could be multiplexed + * over one transport connection. Thus, delays when delivering small + * packets could bring down performance for the whole session. + * HTTP/2 is meant to be used this way. + */ +static void +http_transpost_tcp_nodelay(isc_nmhandle_t *transphandle) { +#ifndef _WIN32 + isc_nmsocket_t *tcpsock = NULL; + uv_os_fd_t tcp_fd = (uv_os_fd_t)-1; + + if (transphandle->sock->type == isc_nm_tlssocket) { + tcpsock = transphandle->sock->outerhandle->sock; + } else { + tcpsock = transphandle->sock; + } + + (void)uv_fileno((uv_handle_t *)&tcpsock->uv_handle.tcp, &tcp_fd); + RUNTIME_CHECK(tcp_fd != (uv_os_fd_t)-1); + (void)isc__nm_socket_tcp_nodelay((uv_os_sock_t)tcp_fd); +#endif +} + static isc_result_t httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { isc_nmsocket_t *httplistensock = (isc_nmsocket_t *)cbarg; @@ -1983,6 +2012,7 @@ httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { REQUIRE(VALID_NMHANDLE(handle)); REQUIRE(VALID_NMSOCK(handle->sock)); + if (handle->sock->type == isc_nm_tlssocket) { REQUIRE(VALID_NMSOCK(handle->sock->listener)); listener = handle->sock->listener; @@ -2016,6 +2046,8 @@ httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { return (ISC_R_CANCELED); } + http_transpost_tcp_nodelay(handle); + new_session(httplistensock->mgr->mctx, NULL, &session); initialize_nghttp2_server_session(session); handle->sock->h2.session = session; @@ -2249,7 +2281,7 @@ failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result, REQUIRE(VALID_NMSOCK(sock)); INSIST(sock->type == isc_nm_httpsocket); - if (!sock->h2.request_path) { + if (sock->h2.request_path == NULL) { return; } @@ -2378,7 +2410,7 @@ isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url, INSIST(i == len); - if (res_len) { + if (res_len != NULL) { *res_len = len; } diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index e74eba82a0..5514355520 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -1687,6 +1687,12 @@ isc__nm_socket_connectiontimeout(uv_os_sock_t fd, int timeout_ms); * the minimum value must be at least 1000 (1 second). */ +isc_result_t +isc__nm_socket_tcp_nodelay(uv_os_sock_t fd); +/*%< + * Disables Nagle's algorithm on a TCP socket (sets TCP_NODELAY). + */ + /* * typedef all the netievent types */ diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c index a29ec728a3..eb6ef11961 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -2407,6 +2407,20 @@ isc__nm_socket_connectiontimeout(uv_os_sock_t fd, int timeout_ms) { #endif } +isc_result_t +isc__nm_socket_tcp_nodelay(uv_os_sock_t fd) { +#ifdef TCP_NODELAY + if (setsockopt_on(fd, IPPROTO_TCP, TCP_NODELAY) == -1) { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#else + UNUSED(fd); + return (ISC_R_SUCCESS); +#endif +} + #ifdef NETMGR_TRACE /* * Dump all active sockets in netmgr. We output to stderr diff --git a/lib/isc/netmgr/tlsstream.c b/lib/isc/netmgr/tlsstream.c index 5505f3e2b4..976970cba0 100644 --- a/lib/isc/netmgr/tlsstream.c +++ b/lib/isc/netmgr/tlsstream.c @@ -410,6 +410,7 @@ tls_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, REQUIRE(VALID_NMSOCK(tlssock)); REQUIRE(VALID_NMHANDLE(handle)); REQUIRE(tlssock->tid == isc_nm_tid()); + if (result != ISC_R_SUCCESS) { tls_failed_read_cb(tlssock, tlssock->statichandle, result); return; From dbffb212ce5c8a07f84190c651a422035ca7055f Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Thu, 11 Feb 2021 18:42:00 -0800 Subject: [PATCH 7/8] add basic DoH system tests - rename dot to doth, as it now covers both dot and doh. - merge xot into doth as it's closely related. - added long-lived key and cert files (expiring 2121). - add tests with https-get, https-post, http-plain, alternate endpoints, and both static and ephemeral TLS configuration. - incidentally fixed a memory leak in dig that occurred if +https was specified more than once. --- bin/tests/system/Makefile.am | 3 +- bin/tests/system/conf.sh.common | 2 +- bin/tests/system/dot/clean.sh | 19 -- bin/tests/system/dot/ns1/named.conf.in | 28 --- bin/tests/system/dot/setup.sh | 16 -- bin/tests/system/dot/tests.sh | 41 ---- bin/tests/system/{xot => doth}/clean.sh | 2 +- bin/tests/system/{xot => doth}/dig1.good | 0 .../system/{xot => doth}/ns1/named.conf.in | 15 +- bin/tests/system/{dot => doth}/ns1/root.db | 0 bin/tests/system/doth/ns2/cert.pem | 14 ++ bin/tests/system/doth/ns2/key.pem | 8 + .../system/{xot => doth}/ns2/named.conf.in | 16 +- bin/tests/system/{xot => doth}/setup.sh | 0 bin/tests/system/doth/tests.sh | 186 ++++++++++++++++++ bin/tests/system/xot/tests.sh | 50 ----- util/copyrights | 13 +- 17 files changed, 243 insertions(+), 170 deletions(-) delete mode 100644 bin/tests/system/dot/clean.sh delete mode 100644 bin/tests/system/dot/ns1/named.conf.in delete mode 100644 bin/tests/system/dot/setup.sh delete mode 100644 bin/tests/system/dot/tests.sh rename bin/tests/system/{xot => doth}/clean.sh (96%) rename bin/tests/system/{xot => doth}/dig1.good (100%) rename bin/tests/system/{xot => doth}/ns1/named.conf.in (70%) rename bin/tests/system/{dot => doth}/ns1/root.db (100%) create mode 100644 bin/tests/system/doth/ns2/cert.pem create mode 100644 bin/tests/system/doth/ns2/key.pem rename bin/tests/system/{xot => doth}/ns2/named.conf.in (74%) rename bin/tests/system/{xot => doth}/setup.sh (100%) create mode 100644 bin/tests/system/doth/tests.sh delete mode 100755 bin/tests/system/xot/tests.sh diff --git a/bin/tests/system/Makefile.am b/bin/tests/system/Makefile.am index feb7598241..a407716c04 100644 --- a/bin/tests/system/Makefile.am +++ b/bin/tests/system/Makefile.am @@ -95,6 +95,7 @@ TESTS += \ dlz \ dlzexternal \ dns64 \ + doth \ dscp \ dsdigest \ dyndb \ @@ -153,7 +154,6 @@ TESTS += \ views \ wildcard \ xferquota \ - xot \ zonechecks # The "stress" test is not run by default since it creates enough @@ -176,7 +176,6 @@ TESTS += \ nsupdate \ resolver \ statistics \ - dot \ upforwd \ zero diff --git a/bin/tests/system/conf.sh.common b/bin/tests/system/conf.sh.common index 76f8c7cdf4..3fbe83fbea 100644 --- a/bin/tests/system/conf.sh.common +++ b/bin/tests/system/conf.sh.common @@ -668,7 +668,7 @@ copy_setports() { atsign="@" sed -e "s/${atsign}PORT${atsign}/${PORT}/g" \ -e "s/${atsign}TLSPORT${atsign}/${TLSPORT}/g" \ - -e "s/${atsign}HTTPPORT${atsign}/${HTTPSPORT}/g" \ + -e "s/${atsign}HTTPPORT${atsign}/${HTTPPORT}/g" \ -e "s/${atsign}HTTPSPORT${atsign}/${HTTPSPORT}/g" \ -e "s/${atsign}EXTRAPORT1${atsign}/${EXTRAPORT1}/g" \ -e "s/${atsign}EXTRAPORT2${atsign}/${EXTRAPORT2}/g" \ diff --git a/bin/tests/system/dot/clean.sh b/bin/tests/system/dot/clean.sh deleted file mode 100644 index e396acac0a..0000000000 --- a/bin/tests/system/dot/clean.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -# -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, you can obtain one at https://mozilla.org/MPL/2.0/. -# -# See the COPYRIGHT file distributed with this work for additional -# information regarding copyright ownership. - -rm -f */named.memstats -rm -f */named.run -rm -f */named.conf -rm -f */named.stats* -rm -f dig.out* -rm -f rndc.out* -rm -f ns*/named.lock -rm -f ns*/managed-keys.bind* diff --git a/bin/tests/system/dot/ns1/named.conf.in b/bin/tests/system/dot/ns1/named.conf.in deleted file mode 100644 index 41c65f251f..0000000000 --- a/bin/tests/system/dot/ns1/named.conf.in +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) Internet Systems Consortium, Inc. ("ISC") - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * See the COPYRIGHT file distributed with this work for additional - * information regarding copyright ownership. - */ - -options { - port @PORT@; - tls-port @TLSPORT@; - pid-file "named.pid"; - listen-on { 10.53.0.1; }; - listen-on-v6 { none; }; - listen-on tls ephemeral { 10.53.0.1; }; - recursion no; - notify no; - statistics-file "named.stats"; -}; - -zone "." { - type primary; - file "root.db"; - allow-transfer { any; }; -}; diff --git a/bin/tests/system/dot/setup.sh b/bin/tests/system/dot/setup.sh deleted file mode 100644 index 3210f1b218..0000000000 --- a/bin/tests/system/dot/setup.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -# -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, you can obtain one at https://mozilla.org/MPL/2.0/. -# -# See the COPYRIGHT file distributed with this work for additional -# information regarding copyright ownership. - -. ../conf.sh - -$SHELL clean.sh - -copy_setports ns1/named.conf.in ns1/named.conf diff --git a/bin/tests/system/dot/tests.sh b/bin/tests/system/dot/tests.sh deleted file mode 100644 index ffba0bbfbe..0000000000 --- a/bin/tests/system/dot/tests.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh -# -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, you can obtain one at https://mozilla.org/MPL/2.0/. -# -# See the COPYRIGHT file distributed with this work for additional -# information regarding copyright ownership. - -set -e - -# shellcheck source=../conf.sh -. ../conf.sh - -dig_dot_with_opts() { - "${DIG}" -p "${TLSPORT}" +tls "$@" -} - -status=0 -n=0 - -n=$((n + 1)) -echo_i "checking DoT query response ($n)" -ret=0 -dig_dot_with_opts @10.53.0.1 . SOA > dig.out.test$n -grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 -if [ $ret != 0 ]; then echo_i "failed"; fi -status=$((status + ret)) - -n=$((n + 1)) -echo_i "checking DoT XFR ($n)" -ret=0 -dig_dot_with_opts +comment @10.53.0.1 . AXFR > dig.out.test$n -grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 -if [ $ret != 0 ]; then echo_i "failed"; fi -status=$((status + ret)) - -echo_i "exit status: $status" -[ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/xot/clean.sh b/bin/tests/system/doth/clean.sh similarity index 96% rename from bin/tests/system/xot/clean.sh rename to bin/tests/system/doth/clean.sh index d4711ba63d..3f86885245 100644 --- a/bin/tests/system/xot/clean.sh +++ b/bin/tests/system/doth/clean.sh @@ -18,4 +18,4 @@ rm -f ./*/named.memstats rm -f ./*/named.run rm -f ./*/named.run.prev rm -f ./dig.out.* -rm -f ./*/*.db +rm -f ./*/example.db diff --git a/bin/tests/system/xot/dig1.good b/bin/tests/system/doth/dig1.good similarity index 100% rename from bin/tests/system/xot/dig1.good rename to bin/tests/system/doth/dig1.good diff --git a/bin/tests/system/xot/ns1/named.conf.in b/bin/tests/system/doth/ns1/named.conf.in similarity index 70% rename from bin/tests/system/xot/ns1/named.conf.in rename to bin/tests/system/doth/ns1/named.conf.in index 699e46af69..6438b7bbe0 100644 --- a/bin/tests/system/xot/ns1/named.conf.in +++ b/bin/tests/system/doth/ns1/named.conf.in @@ -15,13 +15,21 @@ controls { inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; +http local { + endpoints { "/dns-query"; "/alter"; }; +}; + options { port @PORT@; tls-port @TLSPORT@; + https-port @HTTPSPORT@; + http-port @HTTPPORT@; pid-file "named.pid"; listen-on { 10.53.0.1; }; + listen-on tls ephemeral { 10.53.0.1; }; // DoT + listen-on tls ephemeral http local { 10.53.0.1; }; // DoH + listen-on tls none http local { 10.53.0.1; }; // unencrypted DoH listen-on-v6 { none; }; - listen-on tls ephemeral { 10.53.0.1; }; recursion no; notify explicit; also-notify { 10.53.0.2 port @PORT@; }; @@ -30,8 +38,9 @@ options { }; zone "." { - type hint; - file "../../common/root.hint"; + type primary; + file "root.db"; + allow-transfer { any; }; }; zone "example" { diff --git a/bin/tests/system/dot/ns1/root.db b/bin/tests/system/doth/ns1/root.db similarity index 100% rename from bin/tests/system/dot/ns1/root.db rename to bin/tests/system/doth/ns1/root.db diff --git a/bin/tests/system/doth/ns2/cert.pem b/bin/tests/system/doth/ns2/cert.pem new file mode 100644 index 0000000000..f9c7e6188d --- /dev/null +++ b/bin/tests/system/doth/ns2/cert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHTCCAcOgAwIBAgIUATq1E48Hj7vAQBwn8H/1oQvqvJ0wCgYIKoZIzj0EAwIw +YzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRUwEwYDVQQHDAxSZWR3b29kIENp +dHkxDDAKBgNVBAoMA0lTQzEOMAwGA1UECwwFQklORDkxEjAQBgNVBAMMCWxvY2Fs +aG9zdDAgFw0yMTAyMTIwMzIxMzFaGA8yMTIxMDExOTAzMjEzMVowYzELMAkGA1UE +BhMCVVMxCzAJBgNVBAgMAkNBMRUwEwYDVQQHDAxSZWR3b29kIENpdHkxDDAKBgNV +BAoMA0lTQzEOMAwGA1UECwwFQklORDkxEjAQBgNVBAMMCWxvY2FsaG9zdDBZMBMG +ByqGSM49AgEGCCqGSM49AwEHA0IABC1uCviud7QFTJ8DfdrLwjkBolYHJJR9c9HP +bshvKDXahhRU9+HCbWBNLlqFR6aMs8wyE32cXHLZ70XaILkH88SjUzBRMB0GA1Ud +DgQWBBRPpE9aC2MO0TAlCp18vR9vqe4R2TAfBgNVHSMEGDAWgBRPpE9aC2MO0TAl +Cp18vR9vqe4R2TAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIE3L +zx4iRVqjnOACc+/G0Shru+AIk/MEglfrvP5wxZaVAiEArcmut+hYb+cG0UW5ct/U +Q183Kk25XYJkTj39GSBiiiA= +-----END CERTIFICATE----- diff --git a/bin/tests/system/doth/ns2/key.pem b/bin/tests/system/doth/ns2/key.pem new file mode 100644 index 0000000000..90716c8884 --- /dev/null +++ b/bin/tests/system/doth/ns2/key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFBLYPWvhrGBMyfi04oC53LOl00LZRZbVOVnC0K30XOCoAoGCCqGSM49 +AwEHoUQDQgAELW4K+K53tAVMnwN92svCOQGiVgcklH1z0c9uyG8oNdqGFFT34cJt +YE0uWoVHpoyzzDITfZxcctnvRdoguQfzxA== +-----END EC PRIVATE KEY----- diff --git a/bin/tests/system/xot/ns2/named.conf.in b/bin/tests/system/doth/ns2/named.conf.in similarity index 74% rename from bin/tests/system/xot/ns2/named.conf.in rename to bin/tests/system/doth/ns2/named.conf.in index aabe987d58..3cdc952270 100644 --- a/bin/tests/system/xot/ns2/named.conf.in +++ b/bin/tests/system/doth/ns2/named.conf.in @@ -15,17 +15,29 @@ controls { inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; +tls local { + key-file "key.pem"; + cert-file "cert.pem"; +}; + +http local { + endpoints { "/dns-query"; }; +}; + options { query-source address 10.53.0.2; notify-source 10.53.0.2; transfer-source 10.53.0.2; port @PORT@; tls-port @TLSPORT@; + https-port @HTTPSPORT@; + http-port @HTTPPORT@; pid-file "named.pid"; listen-on { 10.53.0.2; }; - listen-on tls ephemeral { 10.53.0.2; }; + listen-on tls local { 10.53.0.2; }; // DoT + listen-on tls local http local { 10.53.0.2; }; // DoH + listen-on tls none http local { 10.53.0.2; }; // unencrypted DoH listen-on-v6 { none; }; - listen-on tls ephemeral { 10.53.0.2; }; recursion no; notify no; ixfr-from-differences yes; diff --git a/bin/tests/system/xot/setup.sh b/bin/tests/system/doth/setup.sh similarity index 100% rename from bin/tests/system/xot/setup.sh rename to bin/tests/system/doth/setup.sh diff --git a/bin/tests/system/doth/tests.sh b/bin/tests/system/doth/tests.sh new file mode 100644 index 0000000000..0ca31d0d47 --- /dev/null +++ b/bin/tests/system/doth/tests.sh @@ -0,0 +1,186 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +. ../conf.sh + +dig_with_tls_opts() { + "$DIG" +tls +noadd +nosea +nostat +noquest +nocmd -p "${TLSPORT}" "$@" +} + +dig_with_https_opts() { + "$DIG" +https +noadd +nosea +nostat +noquest +nocmd -p "${HTTPSPORT}" "$@" +} + +dig_with_http_opts() { + "$DIG" +http-plain +noadd +nosea +nostat +noquest +nocmd -p "${HTTPPORT}" "$@" +} + +wait_for_tls_xfer() ( + dig_with_tls_opts -b 10.53.0.3 @10.53.0.2 example. AXFR > "dig.out.ns2.test$n" || return 1 + grep "^;" "dig.out.ns2.test$n" > /dev/null && return 1 + return 0 +) + +status=0 +n=0 + +n=$((n+1)) +echo_i "testing XoT server functionality (using dig) ($n)" +ret=0 +dig_with_tls_opts example. -b 10.53.0.3 @10.53.0.1 axfr > dig.out.ns1.test$n || ret=1 +grep "^;" dig.out.ns1.test$n | cat_i +digcomp dig1.good dig.out.ns1.test$n || ret=1 +if test $ret != 0 ; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "testing incoming XoT functionality (from secondary) ($n)" +ret=0 +if retry_quiet 10 wait_for_tls_xfer; then + grep "^;" "dig.out.ns2.test$n" | cat_i + digcomp dig1.good "dig.out.ns2.test$n" || ret=1 +else + echo_i "timed out waiting for zone transfer" + ret=1 +fi +if test $ret != 0 ; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n + 1)) +echo_i "checking DoT query (ephemeral key) ($n)" +ret=0 +dig_with_tls_opts @10.53.0.1 . SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoT query (static key) ($n)" +ret=0 +dig_with_tls_opts @10.53.0.2 example SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoT XFR ($n)" +ret=0 +dig_with_tls_opts +comm @10.53.0.1 . AXFR > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH query (POST) ($n)" +ret=0 +dig_with_https_opts @10.53.0.1 . SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH query (POST, static key) ($n)" +ret=0 +dig_with_https_opts @10.53.0.2 example SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH query (POST, nonstandard endpoint) ($n)" +ret=0 +dig_with_https_opts +https=/alter @10.53.0.1 . SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH query (POST, undefined endpoint, failure expected) ($n)" +ret=0 +dig_with_https_opts +tries=1 +time=1 +https=/fake @10.53.0.1 . SOA > dig.out.test$n +grep "communications error" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH XFR (POST) (failure expected) ($n)" +ret=0 +dig_with_https_opts +comm @10.53.0.1 . AXFR > dig.out.test$n +grep "status: FORMERR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH query (GET) ($n)" +ret=0 +dig_with_https_opts +https-get @10.53.0.1 . SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH query (GET, static key) ($n)" +ret=0 +dig_with_https_opts +https-get @10.53.0.2 example SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH query (GET, nonstandard endpoint) ($n)" +ret=0 +dig_with_https_opts +https-get=/alter @10.53.0.1 . SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH query (GET, undefined endpoint, failure expected) ($n)" +ret=0 +dig_with_https_opts +tries=1 +time=1 +https-get=/fake @10.53.0.1 . SOA > dig.out.test$n +grep "communications error" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DoH XFR (GET) (failure expected) ($n)" +ret=0 +dig_with_https_opts +https-get +comm @10.53.0.1 . AXFR > dig.out.test$n +grep "status: FORMERR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking unencrypted DoH query (POST) ($n)" +ret=0 +dig_with_http_opts @10.53.0.1 . SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking unencrypted DoH query (GET) ($n)" +ret=0 +dig_with_http_opts +http-plain-get @10.53.0.1 . SOA > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking unencrypted DoH XFR (failure expected) ($n)" +ret=0 +dig_with_http_opts +comm @10.53.0.1 . AXFR > dig.out.test$n +grep "status: FORMERR" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "exit status: $status" +[ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/xot/tests.sh b/bin/tests/system/xot/tests.sh deleted file mode 100755 index c8c1ecbf5a..0000000000 --- a/bin/tests/system/xot/tests.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh -# -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, you can obtain one at https://mozilla.org/MPL/2.0/. -# -# See the COPYRIGHT file distributed with this work for additional -# information regarding copyright ownership. - -. ../conf.sh - -dig_with_opts() { - "$DIG" +tls +noadd +nosea +nostat +noquest +nocomm +nocmd -p "${TLSPORT}" "$@" -} - -wait_for_xfer() ( - dig_with_opts -b 10.53.0.3 @10.53.0.2 example. AXFR > "dig.out.ns2.test$n" || return 1 - grep "^;" "dig.out.ns2.test$n" > /dev/null && return 1 - return 0 -) - -status=0 -n=0 - -n=$((n+1)) -echo_i "testing XoT server functionality (using dig) ($n)" -ret=0 -dig_with_opts example. -b 10.53.0.3 @10.53.0.1 axfr > dig.out.ns1.test$n || ret=1 -grep "^;" dig.out.ns1.test$n | cat_i -digcomp dig1.good dig.out.ns1.test$n || ret=1 -if test $ret != 0 ; then echo_i "failed"; fi -status=$((status+ret)) - -n=$((n+1)) -echo_i "testing basic incoming XoT functionality (from secondary) ($n)" -ret=0 -if retry_quiet 10 wait_for_xfer; then - grep "^;" "dig.out.ns2.test$n" | cat_i - digcomp dig1.good "dig.out.ns2.test$n" || ret=1 -else - echo_i "timed out waiting for zone transfer" - ret=1 -fi -if test $ret != 0 ; then echo_i "failed"; fi -status=$((status+ret)) - -echo_i "exit status: $status" -[ $status -eq 0 ] || exit 1 diff --git a/util/copyrights b/util/copyrights index e992de8e8a..0282f9bc6b 100644 --- a/util/copyrights +++ b/util/copyrights @@ -368,9 +368,12 @@ ./bin/tests/system/dnstap/setup.sh SH 2018,2019,2020,2021 ./bin/tests/system/dnstap/tests.sh SH 2015,2016,2017,2018,2019,2020,2021 ./bin/tests/system/dnstap/ydump.py PYTHON 2016,2017,2018,2019,2020,2021 -./bin/tests/system/dot/clean.sh SH 2020,2021 -./bin/tests/system/dot/setup.sh SH 2020,2021 -./bin/tests/system/dot/tests.sh SH 2020,2021 +./bin/tests/system/doth/clean.sh SH 2020,2021 +./bin/tests/system/doth/dig1.good X 2021 +./bin/tests/system/doth/ns2/cert.pem X 2021 +./bin/tests/system/doth/ns2/key.pem X 2021 +./bin/tests/system/doth/setup.sh SH 2021 +./bin/tests/system/doth/tests.sh SH 2021 ./bin/tests/system/dscp/clean.sh SH 2013,2014,2015,2016,2018,2019,2020,2021 ./bin/tests/system/dscp/ns1/named.args X 2013,2014,2018,2019,2020,2021 ./bin/tests/system/dscp/ns2/named.args X 2013,2014,2018,2019,2020,2021 @@ -987,10 +990,6 @@ ./bin/tests/system/xferquota/setup.pl PERL 2000,2001,2004,2007,2011,2012,2016,2018,2019,2020,2021 ./bin/tests/system/xferquota/setup.sh SH 2000,2001,2004,2007,2012,2016,2018,2019,2020,2021 ./bin/tests/system/xferquota/tests.sh SH 2000,2001,2004,2007,2012,2016,2018,2019,2020,2021 -./bin/tests/system/xot/clean.sh SH 2021 -./bin/tests/system/xot/dig1.good X 2021 -./bin/tests/system/xot/setup.sh SH 2021 -./bin/tests/system/xot/tests.sh SH 2021 ./bin/tests/system/zero/ans5/ans.pl PERL 2016,2018,2019,2020,2021 ./bin/tests/system/zero/clean.sh SH 2013,2014,2015,2016,2018,2019,2020,2021 ./bin/tests/system/zero/setup.sh SH 2013,2014,2016,2018,2019,2020,2021 From f3b13c6027ebfd68ee137449edf1157649981014 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Fri, 12 Feb 2021 13:01:10 -0800 Subject: [PATCH 8/8] CHANGES, release notes --- CHANGES | 4 ++++ doc/notes/notes-current.rst | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 5aa8aa2161..c182d8b4b1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +5596. [func] Client-side support for DNS-over-HTTPS (DoH) has + been added to dig. "dig +https" can now query + a server via HTTP/2. [GL #1641] + 5595. [cleanup] Public header files for BIND 9 libraries no longer directly include third-party library headers. This prevents the need to include paths to third-party header diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index 3d615f149f..0d291caa64 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -24,7 +24,8 @@ Known Issues New Features ~~~~~~~~~~~~ -- None. +- ``dig`` has been extended to support DNS-over-HTTPS (DoH) queries, + using ``dig +https`` and related options. [GL #1641] - A new option, ``purge-keys``, has been added to ``dnssec-policy``. It sets the time how long key files should be retained after they have become