diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6412ea52ae..ae00a081ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -70,11 +70,6 @@ stages: - linux - amd64 -.linux-i386: &linux_i386 - tags: - - linux - - i386 - .linux-stress-amd64: &linux_stress_amd64 tags: - amd64 @@ -133,10 +128,6 @@ stages: image: "$CI_REGISTRY_IMAGE:debian-sid-amd64" <<: *linux_amd64 -.debian-sid-i386: &debian_sid_i386_image - image: "$CI_REGISTRY_IMAGE:debian-sid-i386" - <<: *linux_i386 - # openSUSE Tumbleweed .tumbleweed-latest-amd64: &tumbleweed_latest_amd64_image @@ -285,6 +276,7 @@ stages: "with-openssl=C:/OpenSSL" "with-libxml2=C:/libxml2" "with-libuv=C:/libuv" + "with-nghttp2=C:/nghttp2" "without-python" "with-system-tests" x64' @@ -836,30 +828,6 @@ unit:gcc:tarball: - schedules - tags -# Jobs for regular GCC builds on Debian "sid" (i386) - -gcc:sid:i386: - variables: - CC: gcc - CFLAGS: "${CFLAGS_COMMON}" - EXTRA_CONFIGURE: "--with-libidn2" - <<: *debian_sid_i386_image - <<: *build_job - -system:gcc:sid:i386: - <<: *debian_sid_i386_image - <<: *system_test_job - needs: - - job: gcc:sid:i386 - artifacts: true - -unit:gcc:sid:i386: - <<: *debian_sid_i386_image - <<: *unit_test_job - needs: - - job: gcc:sid:i386 - artifacts: true - # Jobs for debug GCC builds on openSUSE Tumbleweed (amd64) gcc:tumbleweed:amd64: diff --git a/CHANGES b/CHANGES index 4372665812..1f8591e7ae 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +5576. [experimental] Initial server-side implementation of DNS-over-HTTPS + (DoH). Support for both TLS-encrypted and unencrypted + HTTP/2 connections has been added to the network manager + and integrated into named. (Note: there is currently no + client-side support for DNS-over-HTTPS; this will be + added to dig in a future release.) [GL #1144] + 5575. [bug] When migrating to dnssec-policy, BIND considered keys with the "Inactive" and/or "Delete" timing metadata as possible active keys. This has been fixed. [GL #2406] diff --git a/COPYRIGHT b/COPYRIGHT index 830bcaa129..ecb9bc38ea 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -367,3 +367,25 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +----------------------------------------------------------------------------- + +Copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/bin/named/config.c b/bin/named/config.c index 72094f8302..99af2dd570 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -94,6 +94,8 @@ options {\n\ # pid-file \"" NAMED_LOCALSTATEDIR "/run/named/named.pid\"; \n\ port 53;\n\ tls-port 853;\n\ + http-port 80;\n\ + https-port 443;\n\ prefetch 2 9;\n\ recursing-file \"named.recursing\";\n\ recursive-clients 1000;\n\ diff --git a/bin/named/include/named/globals.h b/bin/named/include/named/globals.h index 501bedaea4..b8ea5946a4 100644 --- a/bin/named/include/named/globals.h +++ b/bin/named/include/named/globals.h @@ -73,6 +73,8 @@ EXTERN const char *named_g_configargs INIT(PACKAGE_CONFIGARGS); EXTERN const char *named_g_builder INIT(PACKAGE_BUILDER); EXTERN in_port_t named_g_port INIT(0); EXTERN in_port_t named_g_tlsport INIT(0); +EXTERN in_port_t named_g_httpsport INIT(0); +EXTERN in_port_t named_g_httpport INIT(0); EXTERN isc_dscp_t named_g_dscp INIT(-1); EXTERN named_server_t *named_g_server INIT(NULL); diff --git a/bin/named/main.c b/bin/named/main.c index eacb18cc81..cfdc941e05 100644 --- a/bin/named/main.c +++ b/bin/named/main.c @@ -705,7 +705,7 @@ parse_T_opt(char *option) { static void parse_port(char *arg) { - enum { DNSPORT, TLSPORT } ptype = DNSPORT; + enum { DNSPORT, TLSPORT, HTTPSPORT, HTTPPORT } ptype = DNSPORT; char *value = arg; int port; @@ -714,6 +714,12 @@ parse_port(char *arg) { } else if (strncmp(arg, "tls=", 4) == 0) { value = arg + 4; ptype = TLSPORT; + } else if (strncmp(arg, "https=", 6) == 0) { + value = arg + 6; + ptype = HTTPSPORT; + } else if (strncmp(arg, "http=", 5) == 0) { + value = arg + 6; + ptype = HTTPPORT; } port = parse_int(value, "port"); @@ -728,6 +734,12 @@ parse_port(char *arg) { case TLSPORT: named_g_tlsport = port; break; + case HTTPSPORT: + named_g_httpsport = port; + break; + case HTTPPORT: + named_g_httpport = port; + break; default: INSIST(0); ISC_UNREACHABLE(); diff --git a/bin/named/named.conf.rst b/bin/named/named.conf.rst index 722c514dbc..08a1476bc3 100644 --- a/bin/named/named.conf.rst +++ b/bin/named/named.conf.rst @@ -86,6 +86,15 @@ DYNDB dyndb string quoted_string { unspecified-text }; +HTTP +^^^^ + +:: + + http string { + endpoints { quoted_string; ... }; + }; + KEY ^^^ @@ -264,6 +273,8 @@ OPTIONS glue-cache boolean;// deprecated heartbeat-interval integer; hostname ( quoted_string | none ); + http-port integer; + https-port integer; inline-signing boolean; interface-interval duration; ipv4only-contact string; @@ -275,10 +286,12 @@ OPTIONS key-directory quoted_string; lame-ttl duration; listen-on [ port integer ] [ dscp - integer ] [ tls string ] { + integer ] [ tls string ] [ http + string ] { address_match_element; ... }; listen-on-v6 [ port integer ] [ dscp - integer ] [ tls string ] { + integer ] [ tls string ] [ http + string ] { address_match_element; ... }; lmdb-mapsize sizeval; lock-file ( quoted_string | none ); diff --git a/bin/named/named.rst b/bin/named/named.rst index 9b039cdbf0..3fa96e038c 100644 --- a/bin/named/named.rst +++ b/bin/named/named.rst @@ -115,7 +115,11 @@ Options ``portnum``; if not not specified, the default is port 53. If ``value`` is of the form ``tls=``, the server will listen for TLS queries on ``portnum``; the default is 853. - + If ``value`` is of the form ``https=``, the server will + listen for HTTPS queries on ``portnum``; the default is 443. + If ``value`` is of the form ``http=``, the server will + listen for HTTP queries on ``portnum``; the default is 80. + ``-s`` This option writes memory usage statistics to ``stdout`` on exit. diff --git a/bin/named/server.c b/bin/named/server.c index 3d39d8f657..e4e80f6f81 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -398,13 +398,19 @@ static void named_server_reload(isc_task_t *task, isc_event_t *event); static isc_result_t -ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, - cfg_aclconfctx_t *actx, isc_mem_t *mctx, - uint16_t family, ns_listenelt_t **target); +listenelt_http(const cfg_obj_t *http, bool tls, const char *key, + const char *cert, in_port_t port, isc_mem_t *mctx, + ns_listenelt_t **target); + static isc_result_t -ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, - cfg_aclconfctx_t *actx, isc_mem_t *mctx, - uint16_t family, ns_listenlist_t **target); +listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + ns_listenelt_t **target); + +static isc_result_t +listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + ns_listenlist_t **target); static isc_result_t configure_forward(const cfg_obj_t *config, dns_view_t *view, @@ -8573,6 +8579,16 @@ load_configuration(const char *filename, named_server_t *server, maps[i++] = named_g_defaults; maps[i] = NULL; + obj = NULL; + result = named_config_get(maps, "http-port", &obj); + INSIST(result == ISC_R_SUCCESS); + named_g_httpport = (in_port_t)cfg_obj_asuint32(obj); + + obj = NULL; + result = named_config_get(maps, "https-port", &obj); + INSIST(result == ISC_R_SUCCESS); + named_g_httpsport = (in_port_t)cfg_obj_asuint32(obj); + /* * If bind.keys exists, load it. If "dnssec-validation auto" * is turned on, the root key found there will be used as a @@ -8989,7 +9005,7 @@ load_configuration(const char *filename, named_server_t *server, } if (clistenon != NULL) { /* check return code? */ - (void)ns_listenlist_fromconfig( + (void)listenlist_fromconfig( clistenon, config, named_g_aclconfctx, named_g_mctx, AF_INET, &listenon); } else { @@ -9017,7 +9033,7 @@ load_configuration(const char *filename, named_server_t *server, } if (clistenon != NULL) { /* check return code? */ - (void)ns_listenlist_fromconfig( + (void)listenlist_fromconfig( clistenon, config, named_g_aclconfctx, named_g_mctx, AF_INET6, &listenon); } else { @@ -10985,9 +11001,9 @@ named_server_togglequerylog(named_server_t *server, isc_lex_t *lex) { } static isc_result_t -ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, - cfg_aclconfctx_t *actx, isc_mem_t *mctx, - uint16_t family, ns_listenlist_t **target) { +listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + ns_listenlist_t **target) { isc_result_t result; const cfg_listelt_t *element; ns_listenlist_t *dlist = NULL; @@ -11004,8 +11020,8 @@ ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, { ns_listenelt_t *delt = NULL; const cfg_obj_t *listener = cfg_listelt_value(element); - result = ns_listenelt_fromconfig(listener, config, actx, mctx, - family, &delt); + result = listenelt_fromconfig(listener, config, actx, mctx, + family, &delt); if (result != ISC_R_SUCCESS) { goto cleanup; } @@ -11019,66 +11035,114 @@ cleanup: return (result); } +static const cfg_obj_t * +find_maplist(const cfg_obj_t *config, const char *listname, const char *name) { + isc_result_t result; + const cfg_obj_t *maplist = NULL; + const cfg_listelt_t *elt = NULL; + + REQUIRE(config != NULL); + REQUIRE(name != NULL); + + result = cfg_map_get(config, listname, &maplist); + if (result != ISC_R_SUCCESS) { + return (NULL); + } + + for (elt = cfg_list_first(maplist); elt != NULL; + elt = cfg_list_next(elt)) { + const cfg_obj_t *map = cfg_listelt_value(elt); + if (strcasecmp(cfg_obj_asstring(cfg_map_getname(map)), name) == + 0) { + return (map); + } + } + + return (NULL); +} + /* * Create a listen list from the corresponding configuration * data structure. */ static isc_result_t -ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, - cfg_aclconfctx_t *actx, isc_mem_t *mctx, - uint16_t family, ns_listenelt_t **target) { +listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, + cfg_aclconfctx_t *actx, isc_mem_t *mctx, uint16_t family, + ns_listenelt_t **target) { isc_result_t result; - const cfg_obj_t *tlsobj, *portobj, *dscpobj; - in_port_t port; + const cfg_obj_t *tlsobj = NULL, *httpobj = NULL; + const cfg_obj_t *portobj = NULL, *dscpobj = NULL; + const cfg_obj_t *http_server = NULL; + in_port_t port = 0; isc_dscp_t dscp = -1; const char *key = NULL, *cert = NULL; - bool tls = false; + bool do_tls = false, http = false; ns_listenelt_t *delt = NULL; + REQUIRE(target != NULL && *target == NULL); /* XXXWPK TODO be more verbose on failures. */ tlsobj = cfg_tuple_get(listener, "tls"); if (tlsobj != NULL && cfg_obj_isstring(tlsobj)) { - if (!strcmp(cfg_obj_asstring(tlsobj), "ephemeral")) { - tls = true; - } else { - const cfg_obj_t *tlsconfigs = NULL; - const cfg_listelt_t *element; - (void)cfg_map_get(config, "tls", &tlsconfigs); - for (element = cfg_list_first(tlsconfigs); - element != NULL; element = cfg_list_next(element)) - { - cfg_obj_t *tconfig = cfg_listelt_value(element); - const cfg_obj_t *name = - cfg_map_getname(tconfig); - if (!strcmp(cfg_obj_asstring(name), - cfg_obj_asstring(tlsobj))) { - tls = true; - const cfg_obj_t *keyo = NULL, - *certo = NULL; - (void)cfg_map_get(tconfig, "key-file", - &keyo); - if (keyo == NULL) { - return (ISC_R_FAILURE); - } - (void)cfg_map_get(tconfig, "cert-file", - &certo); - if (certo == NULL) { - return (ISC_R_FAILURE); - } - key = cfg_obj_asstring(keyo); - cert = cfg_obj_asstring(certo); - break; - } + const char *tlsname = cfg_obj_asstring(tlsobj); + + if (strcmp(tlsname, "ephemeral") != 0) { + const cfg_obj_t *keyobj = NULL, *certobj = NULL; + const cfg_obj_t *tlsmap = NULL; + + tlsmap = find_maplist(config, "tls", tlsname); + if (tlsmap == NULL) { + return (ISC_R_FAILURE); } + + CHECK(cfg_map_get(tlsmap, "key-file", &keyobj)); + key = cfg_obj_asstring(keyobj); + + CHECK(cfg_map_get(tlsmap, "cert-file", &certobj)); + cert = cfg_obj_asstring(certobj); } - if (!tls) { + + do_tls = true; + } + + httpobj = cfg_tuple_get(listener, "http"); + if (httpobj != NULL && cfg_obj_isstring(httpobj)) { + const char *httpname = cfg_obj_asstring(httpobj); + + http_server = find_maplist(config, "http", httpname); + if (http_server == NULL) { + cfg_obj_log(httpobj, named_g_lctx, ISC_LOG_ERROR, + "http '%s' is not defined", + cfg_obj_asstring(httpobj)); return (ISC_R_FAILURE); } + + http = true; } + portobj = cfg_tuple_get(listener, "port"); if (!cfg_obj_isuint32(portobj)) { - if (tls) { + if (http && do_tls) { + if (named_g_httpsport != 0) { + port = named_g_httpsport; + } else { + result = named_config_getport( + config, "https-port", &port); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } else if (http && !do_tls) { + if (named_g_httpport != 0) { + port = named_g_port; + } else { + result = named_config_getport( + config, "http-port", &port); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } else if (do_tls) { if (named_g_tlsport != 0) { port = named_g_tlsport; } else { @@ -11103,6 +11167,7 @@ ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, if (cfg_obj_asuint32(portobj) >= UINT16_MAX) { cfg_obj_log(portobj, named_g_lctx, ISC_LOG_ERROR, "port value '%u' is out of range", + cfg_obj_asuint32(portobj)); return (ISC_R_RANGE); } @@ -11122,10 +11187,13 @@ ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, dscp = (isc_dscp_t)cfg_obj_asuint32(dscpobj); } - result = ns_listenelt_create(mctx, port, dscp, NULL, tls, key, cert, - &delt); - if (result != ISC_R_SUCCESS) { - return (result); + if (http) { + INSIST(http_server != NULL); + CHECK(listenelt_http(http_server, do_tls, key, cert, port, mctx, + &delt)); + } else { + CHECK(ns_listenelt_create(mctx, port, dscp, NULL, do_tls, key, + cert, &delt)); } result = cfg_acl_fromconfig2(cfg_tuple_get(listener, "acl"), config, @@ -11136,7 +11204,55 @@ ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, return (result); } *target = delt; - return (ISC_R_SUCCESS); + +cleanup: + return (result); +} + +static isc_result_t +listenelt_http(const cfg_obj_t *http, bool tls, const char *key, + const char *cert, in_port_t port, isc_mem_t *mctx, + ns_listenelt_t **target) { + isc_result_t result = ISC_R_SUCCESS; + ns_listenelt_t *delt = NULL; + char **endpoints = NULL; + const cfg_obj_t *eplist = NULL; + const cfg_listelt_t *elt = NULL; + size_t len, i = 0; + + REQUIRE(target != NULL && *target == NULL); + REQUIRE((key == NULL) == (cert == NULL)); + + if (port == 0) { + port = tls ? named_g_httpsport : named_g_httpport; + } + + CHECK(cfg_map_get(http, "endpoints", &eplist)); + len = cfg_list_length(eplist, false); + endpoints = isc_mem_allocate(mctx, sizeof(endpoints[0]) * len); + + for (elt = cfg_list_first(eplist); elt != NULL; + elt = cfg_list_next(elt)) { + const cfg_obj_t *ep = cfg_listelt_value(elt); + const char *path = cfg_obj_asstring(ep); + endpoints[i++] = isc_mem_strdup(mctx, path); + } + + INSIST(i == len); + + result = ns_listenelt_create_http(mctx, port, named_g_dscp, NULL, tls, + key, cert, endpoints, len, &delt); + if (result != ISC_R_SUCCESS) { + if (delt != NULL) { + ns_listenelt_destroy(delt); + } + return (result); + } + + *target = delt; + +cleanup: + return (result); } isc_result_t diff --git a/bin/tests/system/checkconf/good-doh-global.conf b/bin/tests/system/checkconf/good-doh-global.conf new file mode 100644 index 0000000000..f5eb63477f --- /dev/null +++ b/bin/tests/system/checkconf/good-doh-global.conf @@ -0,0 +1,27 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +tls local-tls { + key-file "key.pem"; + cert-file "cert.pem"; +}; + +http local-http-server { + endpoints { "/dns-query"; }; +}; + +options { + listen-on { 10.53.0.1; }; + http-port 80; + https-port 443; + listen-on port 443 tls local-tls http local-http-server { 10.53.0.1; }; + listen-on port 8080 http local-http-server { 10.53.0.1; }; +}; diff --git a/bin/tests/system/checkconf/good-dot-global.conf b/bin/tests/system/checkconf/good-dot-global.conf new file mode 100644 index 0000000000..2a1729752d --- /dev/null +++ b/bin/tests/system/checkconf/good-dot-global.conf @@ -0,0 +1,19 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +tls local-tls { + key-file "key.pem"; + cert-file "cert.pem"; +}; + +options { + listen-on port 853 tls local-tls { 10.53.0.1; }; +}; diff --git a/bin/tests/system/conf.sh.common b/bin/tests/system/conf.sh.common index de998c050e..76f8c7cdf4 100644 --- a/bin/tests/system/conf.sh.common +++ b/bin/tests/system/conf.sh.common @@ -668,6 +668,8 @@ copy_setports() { atsign="@" sed -e "s/${atsign}PORT${atsign}/${PORT}/g" \ -e "s/${atsign}TLSPORT${atsign}/${TLSPORT}/g" \ + -e "s/${atsign}HTTPPORT${atsign}/${HTTPSPORT}/g" \ + -e "s/${atsign}HTTPSPORT${atsign}/${HTTPSPORT}/g" \ -e "s/${atsign}EXTRAPORT1${atsign}/${EXTRAPORT1}/g" \ -e "s/${atsign}EXTRAPORT2${atsign}/${EXTRAPORT2}/g" \ -e "s/${atsign}EXTRAPORT3${atsign}/${EXTRAPORT3}/g" \ diff --git a/bin/tests/system/get_ports.sh b/bin/tests/system/get_ports.sh index eac854d65c..a8bb7ae6a2 100755 --- a/bin/tests/system/get_ports.sh +++ b/bin/tests/system/get_ports.sh @@ -82,6 +82,8 @@ done echo "export PORT=$(get_port "$baseport")" echo "export TLSPORT=$(get_port)" +echo "export HTTPPORT=$(get_port)" +echo "export HTTPSPORT=$(get_port)" echo "export EXTRAPORT1=$(get_port)" echo "export EXTRAPORT2=$(get_port)" echo "export EXTRAPORT3=$(get_port)" diff --git a/bin/tests/system/run.sh.in b/bin/tests/system/run.sh.in index e802332c8c..7027ab2192 100644 --- a/bin/tests/system/run.sh.in +++ b/bin/tests/system/run.sh.in @@ -149,7 +149,7 @@ stop_servers() { echostart "S:$systest:$(date_with_args)" echoinfo "T:$systest:1:A" echoinfo "A:$systest:System test $systest" -echoinfo "I:$systest:PORTS:${PORT},${TLSPORT},${EXTRAPORT1},${EXTRAPORT2},${EXTRAPORT3},${EXTRAPORT4},${EXTRAPORT5},${EXTRAPORT6},${EXTRAPORT7},${EXTRAPORT8},${CONTROLPORT}" +echoinfo "I:$systest:PORTS:${PORT},${TLSPORT},${HTTPPORT},${HTTPSPORT},${EXTRAPORT1},${EXTRAPORT2},${EXTRAPORT3},${EXTRAPORT4},${EXTRAPORT5},${EXTRAPORT6},${EXTRAPORT7},${EXTRAPORT8},${CONTROLPORT}" $PERL ${srcdir}/testsock.pl -p "$PORT" || { echowarn "I:$systest:Network interface aliases not set up. Skipping test." diff --git a/bin/win32/BINDInstall/BINDInstallDlg.cpp b/bin/win32/BINDInstall/BINDInstallDlg.cpp index 06db516425..b4711ed180 100644 --- a/bin/win32/BINDInstall/BINDInstallDlg.cpp +++ b/bin/win32/BINDInstall/BINDInstallDlg.cpp @@ -138,7 +138,8 @@ const FileData installFiles[] = {"libdns.dll", FileData::BinDir, FileData::Critical, FALSE, TRUE}, {"libirs.dll", FileData::BinDir, FileData::Critical, FALSE, TRUE}, {"libeay32.dll", FileData::BinDir, FileData::Critical, FALSE, TRUE}, - {"libuv.dll", FileData::BinDir, FileData::Critical, FALSE, TRUE}, + {"nghttp2.dll", FileData::BinDir, FileData::Critical, FALSE, TRUE}, + {"uv.dll", FileData::BinDir, FileData::Critical, FALSE, TRUE}, #ifdef HAVE_LIBXML2 {"libxml2.dll", FileData::BinDir, FileData::Critical, FALSE, TRUE}, #endif diff --git a/configure.ac b/configure.ac index 2944b3ca44..1ff23d73c1 100644 --- a/configure.ac +++ b/configure.ac @@ -124,7 +124,7 @@ AS_IF([test "$enable_static" != "no" && test "$enable_developer" != "yes"], # # Set the default CFLAGS and CPPFLAGS # -STD_CFLAGS="-Wall -Wextra -Wwrite-strings -Wcast-qual -Wpointer-arith -Wno-missing-field-initializers -Wformat -Wshadow" +STD_CFLAGS="-Wall -Wextra -Wwrite-strings -Wpointer-arith -Wno-missing-field-initializers -Wformat -Wshadow" # These should be always errors STD_CFLAGS="$STD_CFLAGS -Werror=implicit-function-declaration -Werror=missing-prototypes -Werror=format-security -Werror=parentheses -Werror=implicit -Werror=strict-prototypes" @@ -574,6 +574,15 @@ LIBS="$LIBS $LIBUV_LIBS" AC_CHECK_FUNCS([uv_handle_get_data uv_handle_set_data uv_import uv_udp_connect uv_translate_sys_error]) AX_RESTORE_FLAGS([libuv]) +# libnghttp2 +AC_MSG_CHECKING([for libnghttp2]) +PKG_CHECK_MODULES([LIBNGHTTP2], [libnghttp2 >= 1.6.0], [], + [AC_MSG_ERROR([libnghttp2 not found])]) +AX_SAVE_FLAGS([libnghttp2]) + +CFLAGS="$CFLAGS $LIBNGHTTP2_CFLAGS" +LIBS="$LIBS $LIBNGHTTP2_LIBS" + # # flockfile is usually provided by pthreads # diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index f3599be319..fb9545e059 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -1256,6 +1256,20 @@ default is used. for server testing; a server using a port other than 53 is not able to communicate with the global DNS. +``tls-port`` + This is the TCP port number the server uses to receive and send + DNS-over-TLS protocol traffic. The default is 853. + +``https-port`` + This is the TCP port number the server uses to receive and send + DNS-over-HTTPS protocol traffic. The default is 443. + +``http-port`` + This is the TCP port number the server uses to receive and send + unencrypted DNS traffic via HTTP (a configuration that may be useful + when encryption is handled by third-party software or by a reverse + proxy). + ``dscp`` This is the global Differentiated Services Code Point (DSCP) value to classify outgoing DNS traffic, on operating systems that support DSCP. @@ -2434,17 +2448,36 @@ for details on how to specify IP address lists. Interfaces ^^^^^^^^^^ -The interfaces and ports that the server answers queries from may be -specified using the ``listen-on`` and ``listen-on-v6`` options. +The interfaces, ports, and protocols that the server can use to answer +queries may be specified using the ``listen-on`` and ``listen-on-v6`` options. -``listen-on`` takes an optional port, an optional TLS configuration -identifier, and an ``address_match_list`` of IPv4 addresses. (IPv6 -addresses are ignored, with a logged warning.) The server listens on all -interfaces allowed by the address match list. If a TLS configuration is -specified, ``named`` will listen for DNS-over-TLS (DoT) connections, using -the key and certificate specified in the referenced ``tls`` statement. If a -port number is not specified, the default is 53 for standard DNS and 853 -for DNS-over-TLS. +``listen-on`` and ``listen-on-v6`` statements can each take an optional +port, TLS configuration identifier, and/or HTTP configuration identifier, +in addition to an ``address_match_list``. + +The ``address_match_list`` in ``listen-on`` specifies the IPv4 addresses +on which the server will listen. (IPv6 addresses are ignored, with a +logged warning.) The server listens on all interfaces allowed by the +address match list. If no ``listen-on`` is specified, the default is +to listen for standard DNS queries on port 53 of all IPv4 interfaces. + +``listen-on-v6`` takes an ``address_match_list`` of IPv6 addresses. +The server listens on all interfaces allowed by the address match list. +If no ``listen-on-v6`` is specified, the default is to listen for standard +DNS queries on port 53 of all IPv6 interfaces. + +If a TLS configuration is specified, ``named`` will listen for DNS-over-TLS +(DoT) connections, using the key and certificate specified in the +referenced ``tls`` statement. + +If an HTTP configuration is specified, ``named`` will listen for +DNS-over-HTTPS (DoH) connections using the HTTP endpoint specified in the +referenced ``http`` statement. Normally, ``http`` and ``tls`` +configurations will be used together, but ``tls`` may be omitted if +encryption is being handled by external software. + +If a port number is not specified, the default is 53 for standard DNS, 853 +for DNS-over-TLS, and 443 for DNS-over-HTTPS. Multiple ``listen-on`` statements are allowed. For example: @@ -2453,20 +2486,17 @@ Multiple ``listen-on`` statements are allowed. For example: listen-on { 5.6.7.8; }; listen-on port 1234 { !1.2.3.4; 1.2/16; }; listen-on port 8853 tls ephemeral { 4.3.2.1; }; + listen-on port 8453 tls ephemeral http myserver { 8.7.6.5; }; -enables the name server to listen for standard DNS queries on port 53 of the -IP address 5.6.7.8 and on port 1234 of an address on the machine in net 1.2 -that is not 1.2.3.4, and to listen for DNS-over-TLS connections on port -8853 of the IP address 4.3.2.1, using an ephemeral TLS key and certificate -created for the currently running ``named`` process. - -If no ``listen-on`` is specified, the server listens for standard DNS -on port 53 of all IPv4 interfaces. - -The ``listen-on-v6`` option is used to specify the interfaces and the ports -on which the server listens for incoming queries sent using IPv6. If not -specified, the server listens for standard DNS queries on port 53 of all -IPv6 interfaces. +The first two lines instruct the name server to listen for standard DNS +queries on port 53 of the IP address 5.6.7.8 and on port 1234 of an address +on the machine in net 1.2 that is not 1.2.3.4. The third line instructs the +server to listen for DNS-over-TLS connections on port 8853 of the IP +address 4.3.2.1 using an ephemeral TLS key and certificate created for the +currently running ``named`` process. The fourth line enables DNS-over-HTTPS +connections on port 8453 of address 8.7.6.5, using the same ephemeral +key and certificate, and the HTTP endpoint or endpoints configured in +an ``http`` statement with the name ``myserver``. Multiple ``listen-on-v6`` options can be used. For example: @@ -2475,12 +2505,19 @@ Multiple ``listen-on-v6`` options can be used. For example: listen-on-v6 { any; }; listen-on-v6 port 1234 { !2001:db8::/32; any; }; listen-on port 8853 tls example-tls { 2001:db8::100; }; + listen-on port 8453 tls example-tls http myserver { 2001:db8::100; }; + listen-on port 8000 http myserver { 2001:db8::100; }; -enables the name server to listen for standard DNS queries on port 53 of -any IPv6 addresses and on port 1234 of IPv6 addresses that are not in the -prefix 2001:db8::/32, and for DNS-over-TLS connections on port 8853 of -the address 2001:db8::100, using a TLS key and certificate specified in -the a ``tls`` statement with the name ``example-tls``. +The first two lines instruct the name server to listen for standard DNS +queries on port 53 of any IPv6 addresses, and on port 1234 of IPv6 +addresses that are not in the prefix 2001:db8::/32. The third line +instructs the server to listen for for DNS-over-TLS connections on port +8853 of the address 2001:db8::100, using a TLS key and certificate specified +in the a ``tls`` statement with the name ``example-tls``. The fourth +instructs the server to listen for DNS-over-HTTPS connections, again using +``example-tls``, on the HTTP endpoint specified in ``http myserver``. The +fifth line, in which the ``tls`` parameter is omitted, instructs the server +to listen for *unencrypted* DNS queries over HTTP. To instruct the server not to listen on any IPv6 addresses, use: @@ -4624,6 +4661,45 @@ The following options can be specified in a ``tls`` statement: The built-in ``ephemeral`` TLS connection object represents a temporary key and certificate created for the current ``named`` session only. +.. _http: + +``http`` Statement Grammar +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../misc/http.grammar.rst + +``http`` Statement Definition and Usage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``http`` statement is used to configure HTTP endpoints on which +to listen for DNS-over-HTTPS (DoH) queries. This configuration can +then be referenced by a ``listen-on`` or ``listen-on-v6`` statement to +cause ``named`` to listen for incoming requests over HTTPS. + +``http`` can only be set at the top level of ``named.conf``. + +The following options can be specified in an ``http`` statement: + + ``endpoints`` + A list of HTTP query paths on which to listen. A typical path + is "/dns-query". + +for example, the following configuration enables DNS-over-HTTPS queries on +all local addresses: + +:: + + http local { + endpoints { "/dns-query"; }; + }; + + options { + .... + listen-on tls ephemeral http local { any; }; + listen-on-v6 tls ephemeral http local { any; }; + }; + + .. _trust_anchors: ``trust-anchors`` Statement Grammar diff --git a/doc/man/named.8in b/doc/man/named.8in index db3ddd4176..c5d81eac5c 100644 --- a/doc/man/named.8in +++ b/doc/man/named.8in @@ -115,6 +115,10 @@ for queries. If \fBvalue\fP is of the form \fB\fP or \fBportnum\fP; if not not specified, the default is port 53. If \fBvalue\fP is of the form \fBtls=\fP, the server will listen for TLS queries on \fBportnum\fP; the default is 853. +If \fBvalue\fP is of the form \fBhttps=\fP, the server will +listen for HTTPS queries on \fBportnum\fP; the default is 443. +If \fBvalue\fP is of the form \fBhttp=\fP, the server will +listen for HTTP queries on \fBportnum\fP; the default is 80. .TP .B \fB\-s\fP This option writes memory usage statistics to \fBstdout\fP on exit. diff --git a/doc/man/named.conf.5in b/doc/man/named.conf.5in index 7884fd5f28..a26bcf3c0b 100644 --- a/doc/man/named.conf.5in +++ b/doc/man/named.conf.5in @@ -132,6 +132,19 @@ dyndb string quoted_string { .fi .UNINDENT .UNINDENT +.SS HTTP +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +http string { + endpoints { quoted_string; ... }; +}; +.ft P +.fi +.UNINDENT +.UNINDENT .SS KEY .INDENT 0.0 .INDENT 3.5 @@ -327,6 +340,8 @@ options { glue\-cache boolean;// deprecated heartbeat\-interval integer; hostname ( quoted_string | none ); + http\-port integer; + https\-port integer; inline\-signing boolean; interface\-interval duration; ipv4only\-contact string; @@ -338,10 +353,12 @@ options { key\-directory quoted_string; lame\-ttl duration; listen\-on [ port integer ] [ dscp - integer ] [ tls string ] { + integer ] [ tls string ] [ http + string ] { address_match_element; ... }; listen\-on\-v6 [ port integer ] [ dscp - integer ] [ tls string ] { + integer ] [ tls string ] [ http + string ] { address_match_element; ... }; lmdb\-mapsize sizeval; lock\-file ( quoted_string | none ); diff --git a/doc/misc/Makefile.am b/doc/misc/Makefile.am index d4db7194a6..e78e108171 100644 --- a/doc/misc/Makefile.am +++ b/doc/misc/Makefile.am @@ -37,7 +37,8 @@ OPTIONS_FILES = \ tls.grammar.rst \ trust-anchors.grammar.rst \ managed-keys.grammar.rst \ - trusted-keys.grammar.rst + trusted-keys.grammar.rst \ + http.grammar.rst EXTRA_DIST = \ $(OPTIONS_FILES) \ @@ -175,4 +176,7 @@ managed-keys.grammar.rst: options.active trusted-keys.grammar.rst: options.active $(AM_V_RST_GRAMMARS)$(PERL) $(srcdir)/rst-grammars.pl options.active trusted-keys > $@ +http.grammar.rst: options.active + $(AM_V_RST_GRAMMARS)$(PERL) $(srcdir)/rst-grammars.pl options.active http > $@ + endif diff --git a/doc/misc/http.grammar.rst b/doc/misc/http.grammar.rst new file mode 100644 index 0000000000..dcb66a2a47 --- /dev/null +++ b/doc/misc/http.grammar.rst @@ -0,0 +1,5 @@ +:: + + http { + endpoints { ; ... }; + }; diff --git a/doc/misc/options b/doc/misc/options index 1e5bedec9c..39da1adf99 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -42,6 +42,10 @@ dnssec-policy { dyndb { }; // may occur multiple times +http { + endpoints { ; ... }; +}; // may occur multiple times + key { algorithm ; secret ; @@ -193,6 +197,8 @@ options { glue-cache ; // deprecated heartbeat-interval ; hostname ( | none ); + http-port ; + https-port ; inline-signing ; interface-interval ; ipv4only-contact ; @@ -204,10 +210,12 @@ options { key-directory ; lame-ttl ; listen-on [ port ] [ dscp - ] [ tls ] { + ] [ tls ] [ http + ] { ; ... }; // may occur multiple times listen-on-v6 [ port ] [ dscp - ] [ tls ] { + ] [ tls ] [ http + ] { ; ... }; // may occur multiple times lmdb-mapsize ; lock-file ( | none ); diff --git a/doc/misc/options.active b/doc/misc/options.active index 0e007d6855..9ac0f03351 100644 --- a/doc/misc/options.active +++ b/doc/misc/options.active @@ -41,6 +41,10 @@ dnssec-policy { dyndb { }; // may occur multiple times +http { + endpoints { ; ... }; +}; // may occur multiple times + key { algorithm ; secret ; @@ -192,6 +196,8 @@ options { glue-cache ; // deprecated heartbeat-interval ; hostname ( | none ); + http-port ; + https-port ; inline-signing ; interface-interval ; ipv4only-contact ; @@ -203,10 +209,12 @@ options { key-directory ; lame-ttl ; listen-on [ port ] [ dscp - ] [ tls ] { + ] [ tls ] [ http + ] { ; ... }; // may occur multiple times listen-on-v6 [ port ] [ dscp - ] [ tls ] { + ] [ tls ] [ http + ] { ; ... }; // may occur multiple times lmdb-mapsize ; lock-file ( | none ); diff --git a/doc/misc/options.grammar.rst b/doc/misc/options.grammar.rst index 30aee7b7c9..5176f572bd 100644 --- a/doc/misc/options.grammar.rst +++ b/doc/misc/options.grammar.rst @@ -119,6 +119,8 @@ glue-cache ; // deprecated heartbeat-interval ; hostname ( | none ); + http-port ; + https-port ; inline-signing ; interface-interval ; ipv4only-contact ; @@ -130,10 +132,12 @@ key-directory ; lame-ttl ; listen-on [ port ] [ dscp - ] [ tls ] { + ] [ tls ] [ http + ] { ; ... }; listen-on-v6 [ port ] [ dscp - ] [ tls ] { + ] [ tls ] [ http + ] { ; ... }; lmdb-mapsize ; lock-file ( | none ); diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index 844902dcf4..ba95f08b24 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -52,6 +52,13 @@ New Features an optional ``tls`` option which specifies either a previously configured ``tls`` statement or ``ephemeral``. [GL #2392] +- ``named`` now supports DNS-over-HTTPS (DoH). Both TLS-encrypted and + unencrypted HTTP/2 connections are supported (the latter may be used to + offload encryption to other software). + + Note that there is no client-side support for HTTPS as yet; this will be + added to ``dig`` in a future release. [GL #1144] + Removed Features ~~~~~~~~~~~~~~~~ diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am index c516fd5458..48ead7c17f 100644 --- a/lib/isc/Makefile.am +++ b/lib/isc/Makefile.am @@ -90,6 +90,7 @@ libisc_la_HEADERS = \ include/isc/tls.h \ include/isc/tm.h \ include/isc/types.h \ + include/isc/url.h \ include/isc/utf8.h \ include/isc/util.h \ pthreads/include/isc/condition.h\ @@ -124,11 +125,13 @@ libisc_la_SOURCES = \ $(libisc_la_HEADERS) \ $(pk11_HEADERS) \ $(pkcs11_HEADERS) \ + netmgr/http.c \ netmgr/netmgr-int.h \ netmgr/netmgr.c \ netmgr/tcp.c \ netmgr/tcpdns.c \ netmgr/tlsdns.c \ + netmgr/tlsstream.c \ netmgr/udp.c \ netmgr/uv-compat.c \ netmgr/uv-compat.h \ @@ -149,6 +152,7 @@ libisc_la_SOURCES = \ unix/stdtime.c \ unix/syslog.c \ unix/time.c \ + url.c \ pk11.c \ pk11_result.c \ aes.c \ diff --git a/lib/isc/include/isc/mem.h b/lib/isc/include/isc/mem.h index 97b249cc65..2aee5fb00c 100644 --- a/lib/isc/include/isc/mem.h +++ b/lib/isc/include/isc/mem.h @@ -172,6 +172,8 @@ typedef struct isc_memmethods { void *(*memreallocate)(isc_mem_t *mctx, void *ptr, size_t size _ISC_MEM_FLARG); char *(*memstrdup)(isc_mem_t *mctx, const char *s _ISC_MEM_FLARG); + char *(*memstrndup)(isc_mem_t *mctx, const char *s, + size_t size _ISC_MEM_FLARG); void (*memfree)(isc_mem_t *mctx, void *ptr _ISC_MEM_FLARG); } isc_memmethods_t; @@ -226,7 +228,9 @@ struct isc_mempool { #define isc_mem_reallocate(c, p, s) \ ISCMEMFUNC(reallocate)((c), (p), (s)_ISC_MEM_FILELINE) #define isc_mem_strdup(c, p) ISCMEMFUNC(strdup)((c), (p)_ISC_MEM_FILELINE) -#define isc_mempool_get(c) ISCMEMPOOLFUNC(get)((c)_ISC_MEM_FILELINE) +#define isc_mem_strndup(c, p, l) \ + ISCMEMFUNC(strndup)((c), (p), (l)_ISC_MEM_FILELINE) +#define isc_mempool_get(c) ISCMEMPOOLFUNC(get)((c)_ISC_MEM_FILELINE) #define isc_mem_put(c, p, s) \ do { \ @@ -596,6 +600,7 @@ void *ISCMEMFUNC(allocate)(isc_mem_t *, size_t _ISC_MEM_FLARG); void *ISCMEMFUNC(reallocate)(isc_mem_t *, void *, size_t _ISC_MEM_FLARG); void ISCMEMFUNC(free)(isc_mem_t *, void *_ISC_MEM_FLARG); char *ISCMEMFUNC(strdup)(isc_mem_t *, const char *_ISC_MEM_FLARG); +char *ISCMEMFUNC(strndup)(isc_mem_t *, const char *, size_t _ISC_MEM_FLARG); void *ISCMEMPOOLFUNC(get)(isc_mempool_t *_ISC_MEM_FLARG); void ISCMEMPOOLFUNC(put)(isc_mempool_t *, void *_ISC_MEM_FLARG); diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h index 455101bafb..cb505f9237 100644 --- a/lib/isc/include/isc/netmgr.h +++ b/lib/isc/include/isc/netmgr.h @@ -473,6 +473,17 @@ isc_nm_setstats(isc_nm_t *mgr, isc_stats_t *stats); * full range of socket-related stats counter numbers. */ +isc_result_t +isc_nm_listentls(isc_nm_t *mgr, isc_nmiface_t *iface, + isc_nm_accept_cb_t accept_cb, void *accept_cbarg, + size_t extrahandlesize, int backlog, isc_quota_t *quota, + isc_tlsctx_t *sslctx, isc_nmsocket_t **sockp); + +isc_result_t +isc_nm_tlsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, + isc_nm_cb_t cb, void *cbarg, isc_tlsctx_t *ctx, + unsigned int timeout, size_t extrahandlesize); + isc_result_t isc_nm_tcpdnsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, isc_nm_cb_t cb, void *cbarg, unsigned int timeout, @@ -494,3 +505,46 @@ isc_nm_tlsdnsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, * The connected socket can only be accessed via the handle passed to * '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); diff --git a/lib/isc/include/isc/url.h b/lib/isc/include/isc/url.h new file mode 100644 index 0000000000..9dd95245c3 --- /dev/null +++ b/lib/isc/include/isc/url.h @@ -0,0 +1,80 @@ +/* + * 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. + */ + +/* + * Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include + +#include + +/* + * Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +#define HTTP_PARSER_STRICT 1 +#endif + +typedef enum { + ISC_UF_SCHEMA = 0, + ISC_UF_HOST = 1, + ISC_UF_PORT = 2, + ISC_UF_PATH = 3, + ISC_UF_QUERY = 4, + ISC_UF_FRAGMENT = 5, + ISC_UF_USERINFO = 6, + ISC_UF_MAX = 7 +} isc_url_field_t; + +/* Result structure for isc_url_parse(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +typedef struct { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[ISC_UF_MAX]; +} isc_url_parser_t; + +isc_result_t +isc_url_parse(const char *buf, size_t buflen, bool is_connect, + isc_url_parser_t *up); +/*%< + * Parse a URL; return nonzero on failure + */ diff --git a/lib/isc/mem.c b/lib/isc/mem.c index af3d57f6f8..879b6232f9 100644 --- a/lib/isc/mem.c +++ b/lib/isc/mem.c @@ -244,13 +244,15 @@ static void * isc___mem_reallocate(isc_mem_t *ctx, void *ptr, size_t size FLARG); static char * isc___mem_strdup(isc_mem_t *mctx, const char *s FLARG); +static char * +isc___mem_strndup(isc_mem_t *mctx, const char *s, size_t size FLARG); static void isc___mem_free(isc_mem_t *ctx, void *ptr FLARG); static isc_memmethods_t memmethods = { isc___mem_get, isc___mem_put, isc___mem_putanddetach, isc___mem_allocate, isc___mem_reallocate, isc___mem_strdup, - isc___mem_free, + isc___mem_strndup, isc___mem_free, }; #if ISC_MEM_TRACKLINES @@ -1439,6 +1441,29 @@ isc___mem_strdup(isc_mem_t *mctx0, const char *s FLARG) { return (ns); } +char * +isc___mem_strndup(isc_mem_t *mctx0, const char *s, size_t size FLARG) { + REQUIRE(VALID_CONTEXT(mctx0)); + REQUIRE(s != NULL); + + isc__mem_t *mctx = (isc__mem_t *)mctx0; + size_t len; + char *ns; + + len = strlen(s) + 1; + if (len > size) { + len = size; + } + + ns = isc__mem_allocate((isc_mem_t *)mctx, len FLARG_PASS); + + if (ns != NULL) { + strlcpy(ns, s, len); + } + + return (ns); +} + void isc_mem_setdestroycheck(isc_mem_t *ctx0, bool flag) { REQUIRE(VALID_CONTEXT(ctx0)); @@ -2467,6 +2492,13 @@ isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) { return (mctx->methods->memstrdup(mctx, s FLARG_PASS)); } +char * +isc__mem_strndup(isc_mem_t *mctx, const char *s, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + return (mctx->methods->memstrndup(mctx, s, size FLARG_PASS)); +} + void isc__mem_free(isc_mem_t *mctx, void *ptr FLARG) { REQUIRE(ISCAPI_MCTX_VALID(mctx)); diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c new file mode 100644 index 0000000000..b90abce05c --- /dev/null +++ b/lib/isc/netmgr/http.c @@ -0,0 +1,2591 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "netmgr-int.h" + +#define AUTHEXTRA 7 + +#define MAX_DNS_MESSAGE_SIZE (UINT16_MAX) + +#define HEADER_MATCH(header, name, namelen) \ + (((namelen) == sizeof(header) - 1) && \ + (strncasecmp((header), (const char *)(name), (namelen)) == 0)) + +typedef struct isc_nm_http2_response_status { + size_t code; + size_t content_length; + bool content_type_valid; +} isc_nm_http2_response_status_t; + +typedef struct http2_client_stream { + isc_nm_recv_cb_t read_cb; + void *read_cbarg; + + isc_nm_cb_t connect_cb; + void *connect_cbarg; + + char *uri; + isc_url_parser_t up; + + char *authority; + size_t authoritylen; + char *path; + + uint8_t rbuf[MAX_DNS_MESSAGE_SIZE]; + size_t rbufsize; + + size_t pathlen; + int32_t stream_id; + + bool post; /* POST or GET */ + isc_region_t postdata; + size_t postdata_pos; + char *GET_path; + size_t GET_path_len; + + isc_nm_http2_response_status_t response_status; + + LINK(struct http2_client_stream) link; +} http2_client_stream_t; + +#define HTTP2_SESSION_MAGIC ISC_MAGIC('H', '2', 'S', 'S') +#define VALID_HTTP2_SESSION(t) ISC_MAGIC_VALID(t, HTTP2_SESSION_MAGIC) + +struct isc_nm_http2_session { + unsigned int magic; + isc_mem_t *mctx; + bool sending; + bool closed; + bool reading; + + nghttp2_session *ngsession; + bool client; + + ISC_LIST(http2_client_stream_t) cstreams; + ISC_LIST(isc_nmsocket_h2_t) sstreams; +#ifdef NETMGR_TRACE + size_t sstreams_count; +#endif /* NETMGR_TRACE */ + + isc_nmhandle_t *handle; + isc_nmsocket_t *serversocket; + + isc_region_t r; + uint8_t buf[MAX_DNS_MESSAGE_SIZE]; + size_t bufsize; + + bool ssl_ctx_created; + SSL_CTX *ssl_ctx; +}; + +typedef enum isc_http2_error_responses { + ISC_HTTP_ERROR_SUCCESS, /* 200 */ + ISC_HTTP_ERROR_NOT_FOUND, /* 404 */ + ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE, /* 413 */ + ISC_HTTP_ERROR_URI_TOO_LONG, /* 414 */ + ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE, /* 415 */ + ISC_HTTP_ERROR_BAD_REQUEST, /* 400 */ + ISC_HTTP_ERROR_NOT_IMPLEMENTED, /* 501 */ + ISC_HTTP_ERROR_GENERIC, /* 500 Internal Server Error */ + ISC_HTTP_ERROR_MAX +} isc_http2_error_responses_t; + +static void +http2_do_bio(isc_nm_http2_session_t *session); + +static void +failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result, + isc_nm_http2_session_t *session); + +static void +failed_read_cb(isc_result_t result, isc_nm_http2_session_t *session); + +static int +server_send_error_response(const isc_http2_error_responses_t error, + nghttp2_session *ngsession, isc_nmsocket_t *socket); + +static bool +inactive(isc_nmsocket_t *sock) { + return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) || + atomic_load(&sock->mgr->closing) || + (sock->server != NULL && !isc__nmsocket_active(sock->server))); +} + +static http2_client_stream_t * +find_http2_client_stream(int32_t stream_id, isc_nm_http2_session_t *session) { + http2_client_stream_t *cstream = NULL; + REQUIRE(VALID_HTTP2_SESSION(session)); + + for (cstream = ISC_LIST_HEAD(session->cstreams); cstream != NULL; + cstream = ISC_LIST_NEXT(cstream, link)) + { + if (cstream->stream_id == stream_id) { + break; + } + } + + return (cstream); +} + +static isc_result_t +get_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t **streamp, + const char *uri) { + http2_client_stream_t *stream = NULL; + isc_result_t result; + + REQUIRE(streamp != NULL && *streamp == NULL); + REQUIRE(uri != NULL); + + stream = isc_mem_get(mctx, sizeof(http2_client_stream_t)); + *stream = (http2_client_stream_t){ .stream_id = -1, .rbufsize = 0 }; + + stream->uri = isc_mem_strdup(mctx, uri); + + 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)); + return (result); + } + + stream->authoritylen = stream->up.field_data[ISC_UF_HOST].len; + stream->authority = isc_mem_get(mctx, stream->authoritylen + AUTHEXTRA); + memmove(stream->authority, &uri[stream->up.field_data[ISC_UF_HOST].off], + stream->up.field_data[ISC_UF_HOST].len); + + if (stream->up.field_set & (1 << ISC_UF_PORT)) { + stream->authoritylen += (size_t)snprintf( + stream->authority + + stream->up.field_data[ISC_UF_HOST].len, + AUTHEXTRA, ":%u", stream->up.port); + } + + /* If we don't have path in URI, we use "/" as path. */ + stream->pathlen = 1; + if (stream->up.field_set & (1 << ISC_UF_PATH)) { + stream->pathlen = stream->up.field_data[ISC_UF_PATH].len; + } + if (stream->up.field_set & (1 << ISC_UF_QUERY)) { + /* +1 for '?' character */ + stream->pathlen += + (size_t)(stream->up.field_data[ISC_UF_QUERY].len + 1); + } + + stream->path = isc_mem_get(mctx, stream->pathlen); + if (stream->up.field_set & (1 << ISC_UF_PATH)) { + memmove(stream->path, + &uri[stream->up.field_data[ISC_UF_PATH].off], + stream->up.field_data[ISC_UF_PATH].len); + } else { + stream->path[0] = '/'; + } + + if (stream->up.field_set & (1 << ISC_UF_QUERY)) { + stream->path[stream->pathlen - + stream->up.field_data[ISC_UF_QUERY].len - 1] = '?'; + memmove(stream->path + stream->pathlen - + stream->up.field_data[ISC_UF_QUERY].len, + &uri[stream->up.field_data[ISC_UF_QUERY].off], + 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) { + isc_mem_put(mctx, stream->path, stream->pathlen); + isc_mem_put(mctx, stream->authority, + stream->up.field_data[ISC_UF_HOST].len + AUTHEXTRA); + isc_mem_free(mctx, stream->uri); + if (stream->GET_path != NULL) { + isc_mem_free(mctx, stream->GET_path); + stream->GET_path = NULL; + stream->GET_path_len = 0; + } + if (stream->postdata.base != NULL) { + isc_mem_put(mctx, stream->postdata.base, + stream->postdata.length); + } + isc_mem_put(mctx, stream, sizeof(http2_client_stream_t)); +} + +static void +delete_http2_session(isc_nm_http2_session_t *session) { + REQUIRE(ISC_LIST_EMPTY(session->sstreams)); + REQUIRE(ISC_LIST_EMPTY(session->cstreams)); + + session->magic = 0; + if (session->ssl_ctx_created) { + SSL_CTX_free(session->ssl_ctx); + } + isc_mem_putanddetach(&session->mctx, session, + sizeof(isc_nm_http2_session_t)); +} + +static void +finish_http2_session(isc_nm_http2_session_t *session) { + if (session->handle != NULL) { + isc_nm_pauseread(session->handle); + isc_nmhandle_detach(&session->handle); + } + 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); + + 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; + } +} + +static int +on_data_chunk_recv_callback(nghttp2_session *ngsession, uint8_t flags, + int32_t stream_id, const uint8_t *data, size_t len, + void *user_data) { + isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data; + + UNUSED(ngsession); + UNUSED(flags); + + if (session->client) { + http2_client_stream_t *cstream = + find_http2_client_stream(stream_id, session); + if (cstream) { + size_t new_rbufsize = cstream->rbufsize + len; + if (new_rbufsize <= MAX_DNS_MESSAGE_SIZE && + new_rbufsize <= + cstream->response_status.content_length) + { + memmove(cstream->rbuf + cstream->rbufsize, data, + len); + cstream->rbufsize = new_rbufsize; + } else { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + } 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 (0); +} + +static int +on_stream_close_callback(nghttp2_session *ngsession, int32_t stream_id, + uint32_t error_code, void *user_data) { + isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data; + int rv = 0; + + REQUIRE(VALID_HTTP2_SESSION(session)); + + 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.*/ + if (session->client) { + http2_client_stream_t *cstream = + find_http2_client_stream(stream_id, session); + if (cstream) { + isc_result_t result = + cstream->response_status.code >= 200 && + cstream->response_status.code < + 300 + ? ISC_R_SUCCESS + : ISC_R_FAILURE; + cstream->read_cb(session->handle, result, + &(isc_region_t){ cstream->rbuf, + cstream->rbufsize }, + cstream->read_cbarg); + ISC_LIST_UNLINK(session->cstreams, cstream, link); + put_http2_client_stream(session->mctx, cstream); + if (ISC_LIST_EMPTY(session->cstreams)) { + rv = nghttp2_session_terminate_session( + ngsession, NGHTTP2_NO_ERROR); + if (rv != 0) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + if (session->handle->sock->h2.session->reading) + { + isc_nm_cancelread( + session->handle->sock->h2 + .session->handle); + } + } + } else { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + } else { + 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); + } + } + + 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); +} + +static void +client_handle_status_header(http2_client_stream_t *cstream, + const uint8_t *value, const size_t valuelen) { + char tmp[32] = { 0 }; + const size_t tmplen = sizeof(tmp) - 1; + + strncpy(tmp, (const char *)value, + valuelen > tmplen ? tmplen : valuelen); + cstream->response_status.code = strtoul(tmp, NULL, 10); +} + +static void +client_handle_content_length_header(http2_client_stream_t *cstream, + const uint8_t *value, + const size_t valuelen) { + char tmp[32] = { 0 }; + const size_t tmplen = sizeof(tmp) - 1; + + strncpy(tmp, (const char *)value, + valuelen > tmplen ? tmplen : valuelen); + cstream->response_status.content_length = strtoul(tmp, NULL, 10); +} + +static void +client_handle_content_type_header(http2_client_stream_t *cstream, + const uint8_t *value, const size_t valuelen) { + const char type_dns_message[] = "application/dns-message"; + + UNUSED(valuelen); + + if (strncasecmp((const char *)value, type_dns_message, + sizeof(type_dns_message) - 1) == 0) + { + cstream->response_status.content_type_valid = true; + } +} + +static int +client_on_header_callback(nghttp2_session *ngsession, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data) { + isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data; + const char status[] = ":status"; + const char content_length[] = "Content-Length"; + const char content_type[] = "Content-Type"; + + REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(session->client); + REQUIRE(!ISC_LIST_EMPTY(session->cstreams)); + + UNUSED(flags); + UNUSED(ngsession); + + http2_client_stream_t *cstream = + find_http2_client_stream(frame->hd.stream_id, session); + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE) { + break; + } + + if (HEADER_MATCH(status, name, namelen)) { + client_handle_status_header(cstream, value, valuelen); + } else if (HEADER_MATCH(content_length, name, namelen)) { + client_handle_content_length_header(cstream, value, + valuelen); + } else if (HEADER_MATCH(content_type, name, namelen)) { + client_handle_content_type_header(cstream, value, + valuelen); + } + break; + } + + return (0); +} + +static void +initialize_nghttp2_client_session(isc_nm_http2_session_t *session) { + nghttp2_session_callbacks *callbacks = NULL; + + RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback( + callbacks, client_on_header_callback); + + nghttp2_session_client_new(&session->ngsession, callbacks, session); + + nghttp2_session_callbacks_del(callbacks); +} + +static bool +send_client_connection_header(isc_nm_http2_session_t *session) { + nghttp2_settings_entry iv[] = { { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 } }; + int rv; + + rv = nghttp2_submit_settings(session->ngsession, NGHTTP2_FLAG_NONE, iv, + sizeof(iv) / sizeof(iv[0])); + if (rv != 0) { + return (false); + } + + return (true); +} + +#define MAKE_NV(NAME, VALUE, VALUELEN) \ + { \ + (uint8_t *)(uintptr_t)(NAME), (uint8_t *)(uintptr_t)(VALUE), \ + sizeof(NAME) - 1, VALUELEN, NGHTTP2_NV_FLAG_NONE \ + } + +#define MAKE_NV2(NAME, VALUE) \ + { \ + (uint8_t *)(uintptr_t)(NAME), (uint8_t *)(uintptr_t)(VALUE), \ + sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +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; + + REQUIRE(session->client); + REQUIRE(!ISC_LIST_EMPTY(session->cstreams)); + + UNUSED(ngsession); + UNUSED(source); + + cstream = find_http2_client_stream(stream_id, session); + if (!cstream || cstream->stream_id != stream_id) { + /* We haven't found the stream, so we are not reading anything + */ + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + + if (cstream->post) { + size_t len = cstream->postdata.length - cstream->postdata_pos; + + if (len > length) { + len = length; + } + + memmove(buf, cstream->postdata.base + cstream->postdata_pos, + len); + cstream->postdata_pos += len; + + if (cstream->postdata_pos == cstream->postdata.length) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + + return (len); + } else { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return (0); + } + + return (0); +} + +/* 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) { + int32_t stream_id; + char *uri = stream->uri; + isc_url_parser_t *up = &stream->up; + nghttp2_data_provider dp; + + if (stream->post) { + char p[64]; + snprintf(p, sizeof(p), "%u", stream->postdata.length); + nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "POST"), + MAKE_NV(":scheme", + &uri[up->field_data[ISC_UF_SCHEMA].off], + up->field_data[ISC_UF_SCHEMA].len), + MAKE_NV(":authority", stream->authority, + stream->authoritylen), + MAKE_NV(":path", stream->path, stream->pathlen), + MAKE_NV2("content-type", "application/dns-message"), + MAKE_NV2("accept", "application/dns-message"), + MAKE_NV("content-length", p, strlen(p)), + }; + + dp = (nghttp2_data_provider){ .read_callback = + client_read_callback }; + stream_id = nghttp2_submit_request( + session->ngsession, NULL, hdrs, + sizeof(hdrs) / sizeof(hdrs[0]), &dp, stream); + } else { + INSIST(stream->GET_path != NULL); + INSIST(stream->GET_path_len != 0); + nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "GET"), + MAKE_NV(":scheme", + &uri[up->field_data[ISC_UF_SCHEMA].off], + up->field_data[ISC_UF_SCHEMA].len), + MAKE_NV(":authority", stream->authority, + stream->authoritylen), + MAKE_NV(":path", stream->GET_path, + stream->GET_path_len), + MAKE_NV2("accept", "application/dns-message") + }; + + dp = (nghttp2_data_provider){ .read_callback = + client_read_callback }; + stream_id = nghttp2_submit_request( + session->ngsession, NULL, hdrs, + sizeof(hdrs) / sizeof(hdrs[0]), &dp, stream); + } + if (stream_id < 0) { + return (ISC_R_FAILURE); + } + + stream->stream_id = stream_id; + http2_do_bio(session); + + return (ISC_R_SUCCESS); +} + +/* + * 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; + ssize_t readlen; + + REQUIRE(VALID_HTTP2_SESSION(session)); + + UNUSED(handle); + + if (result != ISC_R_SUCCESS) { + session->reading = false; + failed_read_cb(result, session); + return; + } + + readlen = nghttp2_session_mem_recv(session->ngsession, region->base, + region->length); + if (readlen < 0) { + failed_read_cb(ISC_R_CANCELED, session); + return; + } + + if (readlen < region->length) { + INSIST(session->bufsize == 0); + INSIST(region->length - readlen < MAX_DNS_MESSAGE_SIZE); + memmove(session->buf, region->base, region->length - readlen); + session->bufsize = region->length - readlen; + isc_nm_pauseread(session->handle); + } + + /* We might have something to receive or send, do IO */ + http2_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; + + REQUIRE(VALID_HTTP2_SESSION(session)); + + 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) { + http2_do_bio(session); + } +} + +static void +http2_do_bio(isc_nm_http2_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); + 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); + session->reading = true; + } else if (session->bufsize > 0) { + /* Leftover data in the buffer, use it */ + size_t readlen = nghttp2_session_mem_recv( + session->ngsession, session->buf, + session->bufsize); + + if (readlen == session->bufsize) { + session->bufsize = 0; + } else { + memmove(session->buf, session->buf + readlen, + session->bufsize - readlen); + session->bufsize -= readlen; + } + + http2_do_bio(session); + return; + } else { + /* Resume reading, it's idempotent, wait for more */ + isc_nm_resumeread(session->handle); + } + } else { + /* We don't want more data, stop reading for now */ + 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, https_writecb, + session); + return; + } + + return; +} + +typedef struct http_connect_data { + char *uri; + isc_nm_cb_t connect_cb; + void *connect_cbarg; + bool post; + bool ssl_ctx_created; + SSL_CTX *ssl_ctx; +} http_connect_data_t; + +static void +transport_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + char *uri = NULL; + http_connect_data_t *pconn_data = (http_connect_data_t *)cbarg; + http_connect_data_t conn_data; + isc_nm_http2_session_t *session = NULL; + isc_mem_t *mctx; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(cbarg != NULL); + + handle->sock->h2.session = NULL; + mctx = handle->sock->mgr->mctx; + + conn_data = *pconn_data; + uri = conn_data.uri; + isc_mem_put(mctx, pconn_data, sizeof(*pconn_data)); + + INSIST(conn_data.connect_cb != NULL); + INSIST(conn_data.uri != NULL); + + if (result != ISC_R_SUCCESS) { + goto error; + } + + session = isc_mem_get(mctx, sizeof(isc_nm_http2_session_t)); + *session = (isc_nm_http2_session_t){ .magic = HTTP2_SESSION_MAGIC, +#ifdef NETMGR_TRACE + .sstreams_count = 0, +#endif /* NETMGR_TRACE */ + .ssl_ctx_created = + conn_data.ssl_ctx_created, + .ssl_ctx = conn_data.ssl_ctx, + .handle = NULL, + .client = true }; + isc_mem_attach(mctx, &session->mctx); + + handle->sock->h2.connect.uri = uri; + handle->sock->h2.connect.post = conn_data.post; + + session->ssl_ctx = conn_data.ssl_ctx; + conn_data.ssl_ctx = NULL; + session->ssl_ctx_created = conn_data.ssl_ctx_created; + conn_data.ssl_ctx_created = false; + + if (session->ssl_ctx != NULL) { + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + SSL *ssl = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_tlssocket); + + ssl = handle->sock->tlsstream.ssl; + INSIST(ssl != NULL); +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(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 || + memcmp(NGHTTP2_PROTO_VERSION_ID, alpn, + NGHTTP2_PROTO_VERSION_ID_LEN) != 0) + { + result = ISC_R_CANCELED; + 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); + } + + return; +error: + conn_data.connect_cb(handle, result, conn_data.connect_cbarg); + if (conn_data.ssl_ctx_created && conn_data.ssl_ctx) { + SSL_CTX_free(conn_data.ssl_ctx); + } + + if (session != NULL) { + delete_http2_session(session); + } + + if (uri != NULL) { + isc_mem_free(mctx, uri); + } +} + +isc_result_t +isc_nm_httpconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, + const char *uri, bool post, isc_nm_cb_t cb, void *cbarg, + SSL_CTX *ctx, unsigned int timeout, size_t extrahandlesize) { + isc_nmiface_t resolved_peer, resolved_local; + http_connect_data_t *pconn_data; + isc_result_t result; + uint16_t port = 80; + isc_url_parser_t url_parser; + bool create_ssl_ctx; + const char http[] = "http"; + const char http_secured[] = "https"; + size_t schema_len; + const char *schema; + + REQUIRE(VALID_NM(mgr)); + REQUIRE(cb != NULL); + REQUIRE(uri != NULL); + REQUIRE(*uri != '\0'); + + result = isc_url_parse(uri, strlen(uri), 0, &url_parser); + if (result != ISC_R_SUCCESS) { + return (result); + } + + schema_len = url_parser.field_data[ISC_UF_SCHEMA].len; + INSIST(schema_len > 0); + schema = &uri[url_parser.field_data[ISC_UF_SCHEMA].off]; + + if (schema_len == sizeof(http_secured) - 1 && + strncasecmp(http_secured, schema, sizeof(http_secured) - 1) == 0) + { + create_ssl_ctx = true; + } else if (schema_len == sizeof(http) - 1 && + strncasecmp(http, schema, sizeof(http) - 1) == 0) + { + create_ssl_ctx = false; + } else { + INSIST(0); + ISC_UNREACHABLE(); + } + + if (ctx == NULL && create_ssl_ctx) { + port = 443; + ctx = create_client_ssl_ctx(); + } + + if (peer == NULL || local == NULL) { + size_t hostlen = 0; + char *host = NULL; + struct addrinfo hints; + struct addrinfo *res = NULL; + +#ifndef WIN32 /* FIXME */ + /* extract host name */ + hostlen = url_parser.field_data[ISC_UF_HOST].len + 1; + host = isc_mem_get(mgr->mctx, hostlen); + memmove(host, &uri[url_parser.field_data[ISC_UF_HOST].off], + hostlen - 1); + host[hostlen - 1] = '\0'; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_CANONNAME; + + result = getaddrinfo(host, NULL, &hints, &res); + isc_mem_put(mgr->mctx, host, hostlen); + if (result != 0) { + return (ISC_R_FAILURE); + } +#endif /* WIN32 */ + + if ((url_parser.field_set & (1 << ISC_UF_PORT)) != 0) { + port = url_parser.port; + } + + if (peer == NULL) { + (void)isc_sockaddr_fromsockaddr(&resolved_peer.addr, + res->ai_addr); + isc_sockaddr_setport(&resolved_peer.addr, port); + peer = &resolved_peer; + } + if (local == NULL) { + isc_sockaddr_anyofpf(&resolved_local.addr, + res->ai_family); + local = &resolved_local; + } + + freeaddrinfo(res); + } + + pconn_data = isc_mem_get(mgr->mctx, sizeof(*pconn_data)); + *pconn_data = + (http_connect_data_t){ .uri = isc_mem_strdup(mgr->mctx, uri), + .post = post, + .connect_cb = cb, + .connect_cbarg = cbarg, + .ssl_ctx_created = create_ssl_ctx, + .ssl_ctx = ctx }; + + if (ctx != NULL) { + result = isc_nm_tlsconnect(mgr, local, peer, + transport_connect_cb, pconn_data, + ctx, timeout, extrahandlesize); + } else { + result = isc_nm_tcpconnect(mgr, local, peer, + transport_connect_cb, pconn_data, + timeout, extrahandlesize); + } + + return (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; + isc_result_t result = ISC_R_SUCCESS; + isc_nm_http2_session_t *session = NULL; + isc_mem_t *mctx; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->tid == isc_nm_tid()); + REQUIRE(VALID_HTTP2_SESSION(handle->sock->h2.session)); + REQUIRE(region != NULL); + REQUIRE(region->base != NULL); + REQUIRE(region->length != 0); + REQUIRE(region->length <= MAX_DNS_MESSAGE_SIZE); + REQUIRE(reply_cb != NULL); + + session = handle->sock->h2.session; + mctx = handle->sock->mgr->mctx; + + result = get_http2_client_stream(mctx, &cstream, + handle->sock->h2.connect.uri); + if (result != ISC_R_SUCCESS) { + goto error; + } + + cstream->read_cb = reply_cb; + cstream->read_cbarg = cbarg; + cstream->post = handle->sock->h2.connect.post; + + if (cstream->post) { /* POST */ + cstream->postdata = (isc_region_t){ + .base = isc_mem_get(mctx, region->length), + .length = region->length + }; + memmove(cstream->postdata.base, region->base, region->length); + cstream->postdata_pos = 0; + } else { /* GET */ + size_t path_size = 0; + char *base64url_data = NULL; + size_t base64url_data_len = 0; + isc_buffer_t *buf = NULL; + isc_region_t data = *region; + isc_region_t base64_region; + size_t base64_len = ((4 * data.length / 3) + 3) & ~3; + + isc_buffer_allocate(mctx, &buf, base64_len); + + if ((result = isc_base64_totext(&data, -1, "", buf)) != + ISC_R_SUCCESS) { + isc_buffer_free(&buf); + goto error; + } + + isc__buffer_usedregion(buf, &base64_region); + INSIST(base64_region.length == base64_len); + + base64url_data = isc__nm_base64_to_base64url( + mctx, (const char *)base64_region.base, + base64_region.length, &base64url_data_len); + isc_buffer_free(&buf); + if (base64url_data == NULL) { + goto error; + } + + /* len("?dns=") + len(path) + len(base64url) + len("\0") */ + path_size = cstream->pathlen + base64url_data_len + 5 + 1; + cstream->GET_path = isc_mem_allocate(mctx, path_size); + cstream->GET_path_len = (size_t)snprintf( + cstream->GET_path, path_size, "%.*s?dns=%s", + (int)cstream->pathlen, cstream->path, base64url_data); + + INSIST(cstream->GET_path_len == (path_size - 1)); + 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; + } + http2_do_bio(session); + + return (ISC_R_SUCCESS); +error: + reply_cb(handle, result, NULL, cbarg); + if (cstream) { + put_http2_client_stream(mctx, cstream); + } + return (result); +} + +typedef struct isc_nm_connect_send_data { + isc_nm_recv_cb_t reply_cb; + void *cb_arg; + isc_region_t region; +} isc_nm_connect_send_data_t; + +static void +https_connect_send_cb(isc_nmhandle_t *handle, isc_result_t result, void *arg) { + isc_nm_connect_send_data_t data; + + REQUIRE(VALID_NMHANDLE(handle)); + + memmove(&data, arg, sizeof(data)); + isc_mem_put(handle->sock->mgr->mctx, arg, sizeof(data)); + if (result != ISC_R_SUCCESS) { + goto error; + } + + result = isc_nm_httprequest(handle, &data.region, data.reply_cb, + data.cb_arg); + if (result != ISC_R_SUCCESS) { + goto error; + } + + isc_mem_put(handle->sock->mgr->mctx, data.region.base, + data.region.length); + return; +error: + data.reply_cb(handle, result, NULL, data.cb_arg); + isc_mem_put(handle->sock->mgr->mctx, data.region.base, + data.region.length); +} + +isc_result_t +isc_nm_http_connect_send_request(isc_nm_t *mgr, const char *uri, bool post, + isc_region_t *region, isc_nm_recv_cb_t cb, + void *cbarg, SSL_CTX *ctx, + unsigned int timeout) { + isc_region_t copy; + isc_result_t result; + isc_nm_connect_send_data_t *data; + + REQUIRE(VALID_NM(mgr)); + REQUIRE(uri != NULL); + REQUIRE(*uri != '\0'); + REQUIRE(region != NULL); + REQUIRE(region->base != NULL); + REQUIRE(region->length != 0); + REQUIRE(region->length <= MAX_DNS_MESSAGE_SIZE); + REQUIRE(cb != NULL); + + copy = (isc_region_t){ .base = isc_mem_get(mgr->mctx, region->length), + .length = region->length }; + memmove(copy.base, region->base, region->length); + data = isc_mem_get(mgr->mctx, sizeof(*data)); + *data = (isc_nm_connect_send_data_t){ .reply_cb = cb, + .cb_arg = cbarg, + .region = copy }; + result = isc_nm_httpconnect(mgr, NULL, NULL, uri, post, + https_connect_send_cb, data, ctx, timeout, + 0); + + return (result); +} + +static int +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_nmsocket_t *socket = NULL; + isc_sockaddr_t iface; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) + { + return (0); + } + + 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); + socket->h2 = (isc_nmsocket_h2_t){ + .bufpos = 0, + .bufsize = 0, + .buf = isc_mem_allocate(session->mctx, MAX_DNS_MESSAGE_SIZE), + .psock = socket, + .handler = NULL, + .request_path = NULL, + .query_data = NULL, + .stream_id = frame->hd.stream_id, + .session = session + }; + socket->tid = session->handle->sock->tid; + ISC_LINK_INIT(&socket->h2, link); + ISC_LIST_APPEND(session->sstreams, &socket->h2, link); + +#ifdef NETMGR_TRACE + session->sstreams_count++; + if (session->sstreams_count > 1) { + fprintf(stderr, "HTTP/2 session %p (active streams: %lu)\n", + session, session->sstreams_count); + } +#endif /* NETMGR_TRACE */ + + nghttp2_session_set_stream_user_data(ngsession, frame->hd.stream_id, + socket); + return (0); +} + +static isc_nm_http2_server_handler_t * +find_server_request_handler(const char *request_path, + isc_nmsocket_t *serversocket) { + isc_nm_http2_server_handler_t *handler = NULL; + + REQUIRE(VALID_NMSOCK(serversocket)); + + if (request_path == NULL || *request_path == '\0') { + return (NULL); + } + + RWLOCK(&serversocket->h2.handlers_lock, isc_rwlocktype_read); + if (atomic_load(&serversocket->listening)) { + for (handler = ISC_LIST_HEAD(serversocket->h2.handlers); + handler != NULL; handler = ISC_LIST_NEXT(handler, link)) + { + if (!strcmp(request_path, handler->path)) { + break; + } + } + } + RWUNLOCK(&serversocket->h2.handlers_lock, isc_rwlocktype_read); + + return (handler); +} + +static isc_http2_error_responses_t +server_handle_path_header(isc_nmsocket_t *socket, const uint8_t *value, + const size_t valuelen) { + isc_nm_http2_server_handler_t *handler = NULL; + const uint8_t *qstr = NULL; + size_t vlen = valuelen; + + qstr = memchr(value, '?', valuelen); + if (qstr != NULL) { + vlen = qstr - value; + } + + if (socket->h2.request_path != NULL) { + isc_mem_free(socket->mgr->mctx, socket->h2.request_path); + } + socket->h2.request_path = isc_mem_strndup( + socket->mgr->mctx, (const char *)value, vlen + 1); + handler = find_server_request_handler(socket->h2.request_path, + socket->h2.session->serversocket); + if (handler != NULL) { + socket->h2.handler_cb = handler->cb; + socket->h2.handler_cbarg = handler->cbarg; + socket->extrahandlesize = handler->extrahandlesize; + } else { + isc_mem_free(socket->mgr->mctx, socket->h2.request_path); + socket->h2.request_path = NULL; + return (ISC_HTTP_ERROR_NOT_FOUND); + } + if (qstr != NULL) { + const char *dns_value = NULL; + size_t dns_value_len = 0; + + if (socket->h2.request_type != ISC_HTTP_REQ_GET) { + return (ISC_HTTP_ERROR_BAD_REQUEST); + } + + if (isc__nm_parse_doh_query_string((const char *)qstr, + &dns_value, &dns_value_len)) + { + const size_t decoded_size = dns_value_len / 4 * 3; + if (decoded_size <= MAX_DNS_MESSAGE_SIZE) { + if (socket->h2.query_data != NULL) { + isc_mem_free(socket->mgr->mctx, + socket->h2.query_data); + } + socket->h2.query_data = + isc__nm_base64url_to_base64( + socket->mgr->mctx, dns_value, + dns_value_len, + &socket->h2.query_data_len); + } else { + socket->h2.query_too_large = true; + return (ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE); + } + } else { + return (ISC_HTTP_ERROR_BAD_REQUEST); + } + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static isc_http2_error_responses_t +server_handle_method_header(isc_nmsocket_t *socket, const uint8_t *value, + const size_t valuelen) { + const char get[] = "GET"; + const char post[] = "POST"; + + if (HEADER_MATCH(get, value, valuelen)) { + socket->h2.request_type = ISC_HTTP_REQ_GET; + } else if (HEADER_MATCH(post, value, valuelen)) { + socket->h2.request_type = ISC_HTTP_REQ_POST; + } else { + return (ISC_HTTP_ERROR_NOT_IMPLEMENTED); + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static isc_http2_error_responses_t +server_handle_scheme_header(isc_nmsocket_t *socket, const uint8_t *value, + const size_t valuelen) { + const char http[] = "http"; + const char http_secure[] = "https"; + + if (HEADER_MATCH(http_secure, value, valuelen)) { + socket->h2.request_scheme = ISC_HTTP_SCHEME_HTTP_SECURE; + } else if (HEADER_MATCH(http, value, valuelen)) { + socket->h2.request_scheme = ISC_HTTP_SCHEME_HTTP; + } else { + return (ISC_HTTP_ERROR_BAD_REQUEST); + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static isc_http2_error_responses_t +server_handle_content_length_header(isc_nmsocket_t *socket, + const uint8_t *value, + const size_t valuelen) { + char tmp[32] = { 0 }; + const size_t tmplen = sizeof(tmp) - 1; + + if (socket->h2.request_type != ISC_HTTP_REQ_POST) { + return (ISC_HTTP_ERROR_BAD_REQUEST); + } + strncpy(tmp, (const char *)value, + valuelen > tmplen ? tmplen : valuelen); + socket->h2.content_length = strtoul(tmp, NULL, 10); + if (socket->h2.content_length > MAX_DNS_MESSAGE_SIZE) { + return (ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE); + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static isc_http2_error_responses_t +server_handle_content_type_header(isc_nmsocket_t *socket, const uint8_t *value, + const size_t valuelen) { + const char type_dns_message[] = "application/dns-message"; + + if (HEADER_MATCH(type_dns_message, value, valuelen)) { + socket->h2.content_type_verified = true; + } else { + return (ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE); + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static isc_http2_error_responses_t +server_handle_accept_header(isc_nmsocket_t *socket, const uint8_t *value, + const size_t valuelen) { + const char type_accept_all[] = "*/*"; + const char type_dns_message[] = "application/dns-message"; + + if (HEADER_MATCH(type_dns_message, value, valuelen) || + HEADER_MATCH(type_accept_all, value, valuelen)) + { + socket->h2.accept_type_verified = true; + } else { + return (ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE); + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static int +server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data) { + isc_nmsocket_t *socket = NULL; + isc_http2_error_responses_t code = ISC_HTTP_ERROR_SUCCESS; + const char path[] = ":path"; + const char method[] = ":method"; + const char scheme[] = ":scheme"; + const char accept[] = "accept"; + const char content_length[] = "Content-Length"; + const char content_type[] = "Content-Type"; + + UNUSED(flags); + UNUSED(user_data); + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + break; + } + + socket = nghttp2_session_get_stream_user_data( + session, frame->hd.stream_id); + if (socket == NULL) { + break; + } + + if (HEADER_MATCH(path, name, namelen)) { + code = server_handle_path_header(socket, value, + valuelen); + } else if (HEADER_MATCH(method, name, namelen)) { + code = server_handle_method_header(socket, value, + valuelen); + } else if (HEADER_MATCH(scheme, name, namelen)) { + code = server_handle_scheme_header(socket, value, + valuelen); + } else if (HEADER_MATCH(content_length, name, namelen)) { + code = server_handle_content_length_header( + socket, value, valuelen); + } else if (HEADER_MATCH(content_type, name, namelen)) { + code = server_handle_content_type_header(socket, value, + valuelen); + } else if (HEADER_MATCH(accept, (const char *)name, namelen)) { + code = server_handle_accept_header(socket, value, + valuelen); + } + break; + } + + if (code == ISC_HTTP_ERROR_SUCCESS) { + return (0); + } + + INSIST(socket != NULL); + if (server_send_error_response(code, session, socket) != 0) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + }; + failed_httpstream_read_cb(socket, ISC_R_CANCELED, socket->h2.session); + return (0); +} + +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_nmsocket_t *socket = (isc_nmsocket_t *)source->ptr; + size_t buflen; + + REQUIRE(socket->h2.stream_id == stream_id); + + UNUSED(ngsession); + UNUSED(session); + + buflen = socket->h2.bufsize - socket->h2.bufpos; + if (buflen > length) { + buflen = length; + } + + memmove(buf, socket->h2.buf + socket->h2.bufpos, buflen); + socket->h2.bufpos += buflen; + if (socket->h2.bufpos == socket->h2.bufsize) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + + return (buflen); +} + +static int +server_send_response(nghttp2_session *ngsession, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen, + isc_nmsocket_t *socket) { + nghttp2_data_provider data_prd; + int rv; + + data_prd.source.ptr = socket; + data_prd.read_callback = server_read_callback; + + rv = nghttp2_submit_response(ngsession, stream_id, nva, nvlen, + &data_prd); + if (rv != 0) { + return (-1); + } + return (0); +} + +#define MAKE_ERROR_REPLY(tag, code) \ + { \ + tag, MAKE_NV2(":status", #code) \ + } + +static struct http2_error_responses { + const isc_http2_error_responses_t type; + const nghttp2_nv header; +} error_responses[] = { + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_SUCCESS, 200), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_NOT_FOUND, 404), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE, 413), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_URI_TOO_LONG, 414), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE, 415), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_BAD_REQUEST, 400), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_NOT_IMPLEMENTED, 501), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_GENERIC, 500), +}; + +static int +server_send_error_response(const isc_http2_error_responses_t error, + nghttp2_session *ngsession, isc_nmsocket_t *socket) { + int rv; + + socket->h2.bufsize = 0; + socket->h2.bufpos = 0; + + for (size_t i = 0; + i < sizeof(error_responses) / sizeof(error_responses[0]); i++) + { + if (error_responses[i].type == error) { + rv = server_send_response( + ngsession, socket->h2.stream_id, + &error_responses[i].header, + sizeof(error_responses[i].header), socket); + return (rv); + } + } + + rv = server_send_error_response(ISC_HTTP_ERROR_GENERIC, ngsession, + socket); + return (rv); +} + +static int +server_on_request_recv(nghttp2_session *ngsession, + isc_nm_http2_session_t *session, + isc_nmsocket_t *socket) { + isc_nmhandle_t *handle = NULL; + isc_sockaddr_t addr; + isc_http2_error_responses_t code = ISC_HTTP_ERROR_SUCCESS; + isc_region_t data; + + /* + * Sanity checks. Here we use the same error codes that + * Unbound uses. + * (https://blog.nlnetlabs.nl/dns-over-https-in-unbound/) + */ + if (!socket->h2.request_path || !socket->h2.handler_cb) { + code = ISC_HTTP_ERROR_NOT_FOUND; + } else if ((socket->h2.request_type == ISC_HTTP_REQ_POST && + !socket->h2.content_type_verified) || + !socket->h2.accept_type_verified) + { + code = ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE; + } else if (socket->h2.request_type == ISC_HTTP_REQ_UNSUPPORTED) { + code = ISC_HTTP_ERROR_NOT_IMPLEMENTED; + } else if (socket->h2.request_scheme == ISC_HTTP_SCHEME_UNSUPPORTED) { + /* + * TODO: additional checks if we have enabled encryption + * on the socket or not + */ + code = ISC_HTTP_ERROR_BAD_REQUEST; + } else if (socket->h2.content_length > MAX_DNS_MESSAGE_SIZE || + socket->h2.query_too_large) + { + code = ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE; + } else if (socket->h2.request_type == ISC_HTTP_REQ_GET && + (socket->h2.content_length > 0 || + socket->h2.query_data_len == 0)) + { + code = ISC_HTTP_ERROR_BAD_REQUEST; + } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && + socket->h2.content_length == 0) + { + code = ISC_HTTP_ERROR_BAD_REQUEST; + } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && + socket->h2.bufsize > socket->h2.content_length) + { + code = ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE; + } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && + socket->h2.bufsize != socket->h2.content_length) + { + code = ISC_HTTP_ERROR_BAD_REQUEST; + } + + if (code != ISC_HTTP_ERROR_SUCCESS) { + goto error; + } + + if (socket->h2.request_type == ISC_HTTP_REQ_GET) { + isc_buffer_t decoded_buf; + isc__buffer_init(&decoded_buf, socket->h2.buf, + MAX_DNS_MESSAGE_SIZE); + if (isc_base64_decodestring(socket->h2.query_data, + &decoded_buf) != ISC_R_SUCCESS) + { + code = ISC_HTTP_ERROR_GENERIC; + goto error; + } + isc__buffer_usedregion(&decoded_buf, &data); + } else if (socket->h2.request_type == ISC_HTTP_REQ_POST) { + INSIST(socket->h2.content_length > 0); + data = (isc_region_t){ socket->h2.buf, socket->h2.bufsize }; + } else { + INSIST(0); + ISC_UNREACHABLE(); + } + + 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); + isc_nmhandle_detach(&handle); + return (0); + +error: + if (server_send_error_response(code, ngsession, socket) != 0) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + return (0); +} + +void +isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + isc_nmsocket_t *sock = handle->sock; + isc__nm_uvreq_t *uvreq = NULL; + isc__netievent_httpsend_t *ievent = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(handle->httpsession != NULL); + REQUIRE(VALID_NMSOCK(sock)); + + /* TODO: should be called from within the context of an NM thread? */ + if (inactive(sock) || handle->httpsession->closed) { + cb(handle, ISC_R_CANCELED, cbarg); + return; + } + + INSIST(VALID_NMHANDLE(handle->httpsession->handle)); + INSIST(VALID_NMSOCK(handle->httpsession->handle->sock)); + INSIST(sock->tid == handle->httpsession->handle->sock->tid); + + uvreq = isc__nm_uvreq_get(sock->mgr, sock); + isc_nmhandle_attach(handle, &uvreq->handle); + uvreq->cb.send = cb; + uvreq->cbarg = cbarg; + + uvreq->uvbuf.base = (char *)region->base; + uvreq->uvbuf.len = region->length; + + ievent = isc__nm_get_netievent_httpsend(sock->mgr, sock, uvreq); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_httpsend_t *ievent = (isc__netievent_httpsend_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *req = ievent->req; + isc_nmhandle_t *handle = NULL; + isc_nm_cb_t cb = NULL; + void *cbarg = NULL; + isc_result_t res; + size_t content_length_str_len; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + + ievent->req = NULL; + handle = req->handle; + cb = req->cb.send; + cbarg = req->cbarg; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMHANDLE(handle->httpsession->handle)); + REQUIRE(VALID_NMSOCK(handle->httpsession->handle->sock)); + REQUIRE(handle->httpsession->handle->sock->tid == isc_nm_tid()); + + UNUSED(worker); + + memmove(sock->h2.buf, req->uvbuf.base, req->uvbuf.len); + sock->h2.bufsize = req->uvbuf.len; + + content_length_str_len = + snprintf(sock->h2.response_content_length_str, + sizeof(sock->h2.response_content_length_str), "%lu", + (unsigned long)req->uvbuf.len); + const nghttp2_nv hdrs[] = { + MAKE_NV2(":status", "200"), + MAKE_NV2("Content-Type", "application/dns-message"), + MAKE_NV("Content-Length", sock->h2.response_content_length_str, + content_length_str_len) + }; + if (server_send_response(handle->httpsession->ngsession, + sock->h2.stream_id, hdrs, + sizeof(hdrs) / sizeof(nghttp2_nv), sock) != 0) + { + res = ISC_R_FAILURE; + } else { + res = ISC_R_SUCCESS; + } + + http2_do_bio(handle->httpsession); /*TODO: Should we call it only + * on success? */ + cb(handle, res, cbarg); + + isc__nm_uvreq_put(&req, sock); +} + +static int +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_nmsocket_t *socket = NULL; + + switch (frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: + /* Check that the client request has finished */ + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + socket = nghttp2_session_get_stream_user_data( + ngsession, frame->hd.stream_id); + + /* + * For DATA and HEADERS frame, this callback may be + * called after on_stream_close_callback. Check that + * the stream is still alive. + */ + if (socket == NULL) { + return (0); + } + + return (server_on_request_recv(ngsession, session, + socket)); + } + break; + default: + break; + } + return (0); +} + +static void +initialize_nghttp2_server_session(isc_nm_http2_session_t *session) { + nghttp2_session_callbacks *callbacks = NULL; + + RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback( + callbacks, server_on_header_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, server_on_begin_headers_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback( + callbacks, server_on_frame_recv_callback); + + nghttp2_session_server_new(&session->ngsession, callbacks, session); + + nghttp2_session_callbacks_del(callbacks); +} + +static int +server_send_connection_header(isc_nm_http2_session_t *session) { + nghttp2_settings_entry iv[1] = { + { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 } + }; + int rv; + + rv = nghttp2_submit_settings(session->ngsession, NGHTTP2_FLAG_NONE, iv, + 1); + if (rv != 0) { + return (-1); + } + return (0); +} + +static isc_result_t +httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + isc_nmsocket_t *httplistensock = (isc_nmsocket_t *)cbarg; + isc_nm_http2_session_t *session = NULL; + isc_nmsocket_t *listener = NULL, *httpserver = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + if (handle->sock->type == isc_nm_tlssocket) { + REQUIRE(VALID_NMSOCK(handle->sock->listener)); + listener = handle->sock->listener; + httpserver = listener->h2.httpserver; + } else { + REQUIRE(VALID_NMSOCK(handle->sock->server)); + listener = handle->sock->server; + REQUIRE(VALID_NMSOCK(listener->parent)); + httpserver = listener->parent->h2.httpserver; + } + + /* + * NOTE: HTTP listener socket might be destroyed by the time this + * function gets invoked, so we need to do extra sanity checks to + * detect this case. + */ + if (inactive(handle->sock) || httpserver == NULL) { + return (ISC_R_CANCELED); + } + + if (result != ISC_R_SUCCESS) { + /* XXXWPK do nothing? */ + return (result); + } + + REQUIRE(VALID_NMSOCK(httplistensock)); + INSIST(httplistensock == httpserver); + + if (inactive(httplistensock) || + !atomic_load(&httplistensock->listening)) { + return (ISC_R_CANCELED); + } + + session = isc_mem_get(httplistensock->mgr->mctx, + sizeof(isc_nm_http2_session_t)); + *session = (isc_nm_http2_session_t){ .magic = HTTP2_SESSION_MAGIC, + .ssl_ctx_created = false, + .client = false }; + initialize_nghttp2_server_session(session); + handle->sock->h2.session = session; + + isc_mem_attach(httplistensock->mgr->mctx, &session->mctx); + isc_nmhandle_attach(handle, &session->handle); + isc__nmsocket_attach(httplistensock, &session->serversocket); + session->serversocket = httplistensock; + server_send_connection_header(session); + + /* TODO H2 */ + http2_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_nmsocket_t *sock = NULL; + isc_result_t result; + + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_httplistener, iface); + + if (ctx != NULL) { +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_protos_advertised_cb(ctx, next_proto_cb, NULL); +#endif // OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + result = isc_nm_listentls(mgr, iface, httplisten_acceptcb, sock, + sizeof(isc_nm_http2_session_t), + backlog, quota, ctx, &sock->outer); + } else { + result = isc_nm_listentcp(mgr, iface, httplisten_acceptcb, sock, + sizeof(isc_nm_http2_session_t), + backlog, quota, &sock->outer); + } + + if (result != ISC_R_SUCCESS) { + atomic_store(&sock->closed, true); + isc__nmsocket_detach(&sock); + return (result); + } + + sock->outer->h2.httpserver = sock; + + sock->nchildren = sock->outer->nchildren; + sock->result = ISC_R_DEFAULT; + sock->tid = isc_random_uniform(sock->nchildren); + sock->fd = (uv_os_sock_t)-1; + + atomic_store(&sock->listening, true); + *sockp = sock; + return (ISC_R_SUCCESS); +} + +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. + */ +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; + + REQUIRE(VALID_NMHANDLE(handle)); + + if (result != ISC_R_SUCCESS) { + /* Shut down the client, then ourselves */ + dohcbarg->cb(handle, result, NULL, dohcbarg->cbarg); + /* XXXWPK FREE */ + return; + } + dohcbarg->cb(handle, result, data, dohcbarg->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; + + 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); + + 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)); + } + + RWLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write); + ISC_LIST_APPEND(sock->h2.handlers_cbargs, dohcbarg, link); + RWUNLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write); + + return (result); +} + +void +isc__nm_http_stoplistening(isc_nmsocket_t *sock) { + isc__netievent_httpstop_t *ievent = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_httplistener); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) { + INSIST(0); + ISC_UNREACHABLE(); + } + + ievent = isc__nm_get_netievent_httpstop(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_http_clear_handlers(isc_nmsocket_t *sock) { + isc_nm_http2_server_handler_t *handler = NULL; + isc_nm_http_doh_cbarg_t *dohcbarg = NULL; + + /* delete all handlers */ + RWLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write); + handler = ISC_LIST_HEAD(sock->h2.handlers); + while (handler != NULL) { + isc_nm_http2_server_handler_t *next; + + next = ISC_LIST_NEXT(handler, link); + ISC_LIST_DEQUEUE(sock->h2.handlers, handler, link); + isc_mem_free(sock->mgr->mctx, handler->path); + isc_mem_put(sock->mgr->mctx, handler, sizeof(*handler)); + handler = next; + } + + dohcbarg = ISC_LIST_HEAD(sock->h2.handlers_cbargs); + while (dohcbarg != NULL) { + isc_nm_http_doh_cbarg_t *next; + + next = ISC_LIST_NEXT(dohcbarg, link); + ISC_LIST_DEQUEUE(sock->h2.handlers_cbargs, dohcbarg, link); + isc_mem_put(sock->mgr->mctx, dohcbarg, + sizeof(isc_nm_http_doh_cbarg_t)); + dohcbarg = next; + } + RWUNLOCK(&sock->h2.handlers_lock, isc_rwlocktype_write); +} + +void +isc__nm_http_clear_session(isc_nmsocket_t *sock) { + if (sock->type != isc_nm_httpstream && sock->h2.session != NULL) { + isc_nm_http2_session_t *session = sock->h2.session; + INSIST(ISC_LIST_EMPTY(session->sstreams)); + session->magic = 0; + if (session->ssl_ctx_created) { + SSL_CTX_free(session->ssl_ctx); + } + isc_mem_putanddetach(&sock->h2.session->mctx, session, + sizeof(isc_nm_http2_session_t)); + sock->h2.session = NULL; + } +} + +void +isc__nm_async_httpstop(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_httpstop_t *ievent = (isc__netievent_httpstop_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + atomic_store(&sock->listening, false); + atomic_store(&sock->closing, false); + atomic_store(&sock->closed, true); + if (sock->outer != NULL) { + sock->outer->h2.httpserver = NULL; + isc_nm_stoplistening(sock->outer); + isc_nmsocket_close(&sock->outer); + } +} + +static void +http_close_direct(isc_nmsocket_t *sock) { + bool sessions_empty; + isc_nm_http2_session_t *session; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_HTTP2_SESSION(sock->h2.session)); + + atomic_store(&sock->closed, true); + session = sock->h2.session; + + if (ISC_LINK_LINKED(&sock->h2, link)) { + ISC_LIST_UNLINK(session->sstreams, &sock->h2, link); +#ifdef NETMGR_TRACE + session->sstreams_count--; +#endif /* NETMGR_TRACE */ + } + + sessions_empty = ISC_LIST_EMPTY(session->sstreams); + if (!sessions_empty) { + http2_do_bio(session); + } else if (session->reading) { + session->reading = false; + if (session->handle != NULL) { + isc_nm_cancelread(session->handle); + } + } + /* If session is closed then the only reference to the socket is + * the one, created when handling the netievent. */ + if (!session->closed) { + INSIST(session->handle != NULL); + isc__nmsocket_detach(&sock); + } else { + INSIST(isc_refcount_current(&sock->references) == 1); + } +} + +void +isc__nm_http_close(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_httpstream); + REQUIRE(!isc__nmsocket_active(sock)); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) { + return; + } + + isc__netievent_httpclose_t *ievent = + isc__nm_get_netievent_httpclose(sock->mgr, sock); + + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_httpclose_t *ievent = (isc__netievent_httpclose_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + UNUSED(worker); + + http_close_direct(sock); +} + +static void +failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result, + isc_nm_http2_session_t *session) { + isc_nmhandle_t *handle = NULL; + isc_sockaddr_t addr; + + REQUIRE(VALID_NMSOCK(sock)); + INSIST(sock->type == isc_nm_httpstream); + + if (!sock->h2.request_path) { + return; + } + + INSIST(sock->h2.handler_cbarg != NULL); + + (void)nghttp2_submit_rst_stream( + session->ngsession, NGHTTP2_FLAG_END_STREAM, sock->h2.stream_id, + NGHTTP2_REFUSED_STREAM); + addr = isc_nmhandle_peeraddr(session->handle); + handle = isc__nmhandle_get(sock, &addr, NULL); + sock->h2.handler_cb(handle, result, + &(isc_region_t){ sock->h2.buf, sock->h2.bufsize }, + sock->h2.handler_cbarg); + isc_nmhandle_detach(&handle); +} + +static void +failed_read_cb(isc_result_t result, isc_nm_http2_session_t *session) { + REQUIRE(VALID_HTTP2_SESSION(session)); + + if (session->client) { + http2_client_stream_t *cstream = NULL; + cstream = ISC_LIST_HEAD(session->cstreams); + while (cstream != NULL) { + http2_client_stream_t *next = ISC_LIST_NEXT(cstream, + link); + ISC_LIST_DEQUEUE(session->cstreams, cstream, link); + + cstream->read_cb(session->handle, result, + &(isc_region_t){ cstream->rbuf, + cstream->rbufsize }, + cstream->read_cbarg); + + put_http2_client_stream(session->mctx, cstream); + cstream = next; + } + } else { + isc_nmsocket_h2_t *h2data = NULL; /* stream socket */ + session->closed = true; + for (h2data = ISC_LIST_HEAD(session->sstreams); h2data != NULL; + h2data = ISC_LIST_NEXT(h2data, link)) + { + failed_httpstream_read_cb(h2data->psock, result, + session); + } + + h2data = ISC_LIST_HEAD(session->sstreams); + while (h2data != NULL) { + isc_nmsocket_h2_t *next = ISC_LIST_NEXT(h2data, link); + ISC_LIST_DEQUEUE(session->sstreams, h2data, link); + /* Cleanup socket in place */ + atomic_store(&h2data->psock->active, false); + atomic_store(&h2data->psock->closed, true); + isc__nmsocket_detach(&h2data->psock); + + h2data = next; + } + } + finish_http2_session(session); +} + +static const bool base64url_validation_table[256] = { + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, true, false, false, true, true, + true, true, true, true, true, true, true, true, false, false, + false, false, false, false, false, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, false, false, false, false, true, false, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false +}; + +char * +isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url, + const size_t base64url_len, size_t *res_len) { + char *res = NULL; + size_t i, k, len; + + if (mem == NULL || base64url == NULL || base64url_len == 0) { + return (NULL); + } + + len = base64url_len % 4 ? base64url_len + (4 - base64url_len % 4) + : base64url_len; + res = isc_mem_allocate(mem, len + 1); /* '\0' */ + + for (i = 0; i < base64url_len; i++) { + switch (base64url[i]) { + case '-': + res[i] = '+'; + break; + case '_': + res[i] = '/'; + break; + default: + if (base64url_validation_table[(size_t)base64url[i]]) { + res[i] = base64url[i]; + } else { + isc_mem_free(mem, res); + return (NULL); + } + break; + } + } + + if (base64url_len % 4 != 0) { + for (k = 0; k < (4 - base64url_len % 4); k++, i++) { + res[i] = '='; + } + } + + INSIST(i == len); + + if (res_len) { + *res_len = len; + } + + res[len] = '\0'; + + return (res); +} + +char * +isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64, + const size_t base64_len, size_t *res_len) { + char *res = NULL; + size_t i; + + if (mem == NULL || base64 == NULL || base64_len == 0) { + return (NULL); + } + + res = isc_mem_allocate(mem, base64_len + 1); /* '\0' */ + + for (i = 0; i < base64_len; i++) { + switch (base64[i]) { + case '+': + res[i] = '-'; + break; + case '/': + res[i] = '_'; + break; + case '=': + goto end; + break; + default: + /* All other characters from the alphabet are the same + * for both base64 and base64url, so we can reuse the + * validation table for the rest of the characters. */ + if (base64[i] != '-' && base64[i] != '_' && + base64url_validation_table[(size_t)base64[i]]) + { + res[i] = base64[i]; + } else { + isc_mem_free(mem, res); + return (NULL); + } + break; + } + } +end: + if (res_len) { + *res_len = i; + } + + res[i] = '\0'; + + return (res); +} + +/* DoH GET Query String Scanner-less Recursive Descent Parser/Verifier + +It is based on the following grammar (using WSN/EBNF): + +S = query-string. +query-string = ['?'] { key-value-pair } EOF. +key-value-pair = key '=' value [ '&' ]. +key = ('_' | alpha) { '_' | alnum}. +value = value-char {value-char}. +value-char = unreserved-char | percent-charcode. +unreserved-char = alnum |'_' | '.' | '-' | '~'. (* RFC3986, Section 2.3 *) +percent-charcode = '%' hexdigit hexdigit. +... + +Should be good enough. +*/ + +typedef struct isc_doh_query_parser_state { + const char *str; + + const char *last_key; + size_t last_key_len; + + const char *last_value; + size_t last_value_len; + + bool doh_query_found; + const char *doh_query; + size_t doh_query_length; +} isc_doh_query_parser_state_t; + +#define MATCH(ch) (st->str[0] == (ch)) +#define MATCH_ALPHA() isalpha(st->str[0]) +#define MATCH_ALNUM() isalnum(st->str[0]) +#define MATCH_XDIGIT() isxdigit(st->str[0]) +#define ADVANCE() st->str++ +#define GETP() (st->str) + +static bool +rule_query_string(isc_doh_query_parser_state_t *st); + +bool +isc__nm_parse_doh_query_string(const char *query_string, const char **start, + size_t *len) { + isc_doh_query_parser_state_t state; + + REQUIRE(start != NULL); + REQUIRE(len != 0); + + if (query_string == NULL || *query_string == '\0' || start == NULL || + len == 0) { + return (false); + } + + memset(&state, 0, sizeof(state)); + state.str = query_string; + if (!rule_query_string(&state)) { + return (false); + } + + if (!state.doh_query_found) { + return (false); + } + + *start = state.doh_query; + *len = state.doh_query_length; + + return (true); +} + +static bool +rule_key_value_pair(isc_doh_query_parser_state_t *st); + +static bool +rule_key(isc_doh_query_parser_state_t *st); + +static bool +rule_value(isc_doh_query_parser_state_t *st); + +static bool +rule_value_char(isc_doh_query_parser_state_t *st); + +static bool +rule_percent_charcode(isc_doh_query_parser_state_t *st); + +static bool +rule_unreserved_char(isc_doh_query_parser_state_t *st); + +static bool +rule_query_string(isc_doh_query_parser_state_t *st) { + if (MATCH('?')) { + ADVANCE(); + } + + while (rule_key_value_pair(st)) { + /* skip */; + } + + if (!MATCH('\0')) { + return (false); + } + + ADVANCE(); + return (true); +} + +static bool +rule_key_value_pair(isc_doh_query_parser_state_t *st) { + if (!rule_key(st)) { + return (false); + } + + if (MATCH('=')) { + ADVANCE(); + } else { + return (false); + } + + if (rule_value(st)) { + const char dns[] = "dns"; + if (st->last_key_len == sizeof(dns) - 1 && + memcmp(st->last_key, dns, sizeof(dns) - 1) == 0) + { + st->doh_query_found = true; + st->doh_query = st->last_value; + st->doh_query_length = st->last_value_len; + } + } else { + return (false); + } + + if (MATCH('&')) { + ADVANCE(); + } + + return (true); +} + +static bool +rule_key(isc_doh_query_parser_state_t *st) { + if (MATCH('_') || MATCH_ALPHA()) { + st->last_key = GETP(); + ADVANCE(); + } else { + return (false); + } + + while (MATCH('_') || MATCH_ALNUM()) { + ADVANCE(); + } + + st->last_key_len = GETP() - st->last_key; + return (true); +} + +static bool +rule_value(isc_doh_query_parser_state_t *st) { + const char *s = GETP(); + if (!rule_value_char(st)) { + return (false); + } + + st->last_value = s; + while (rule_value_char(st)) { + /* skip */; + } + st->last_value_len = GETP() - st->last_value; + return (true); +} + +static bool +rule_value_char(isc_doh_query_parser_state_t *st) { + if (rule_unreserved_char(st)) { + return (true); + } + + return (rule_percent_charcode(st)); +} + +static bool +rule_unreserved_char(isc_doh_query_parser_state_t *st) { + if (MATCH_ALNUM() || MATCH('_') || MATCH('.') || MATCH('-') || + MATCH('~')) { + ADVANCE(); + return (true); + } + return (false); +} + +static bool +rule_percent_charcode(isc_doh_query_parser_state_t *st) { + if (MATCH('%')) { + ADVANCE(); + } else { + return (false); + } + + if (!MATCH_XDIGIT()) { + return (false); + } + ADVANCE(); + + if (!MATCH_XDIGIT()) { + return (false); + } + ADVANCE(); + + return (true); +} diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index 0d7bdae422..105ea4d0bb 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -155,6 +156,8 @@ 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. */ @@ -207,6 +210,8 @@ 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_sockaddr_t peer; isc_sockaddr_t local; isc_nm_opaquecb_t doreset; /* reset extra callback, external */ @@ -252,6 +257,13 @@ typedef enum isc__netievent_type { netievent_tcpdnsclose, netievent_tcpdnsstop, + netievent_tlsclose, + netievent_tlssend, + netievent_tlsstartread, + netievent_tlsconnect, + netievent_tlsdobio, + netievent_tlscancel, + netievent_tlsdnsaccept, netievent_tlsdnsconnect, netievent_tlsdnssend, @@ -262,6 +274,10 @@ typedef enum isc__netievent_type { netievent_tlsdnscycle, netievent_tlsdnsshutdown, + netievent_httpstop, + netievent_httpsend, + netievent_httpclose, + netievent_close, netievent_shutdown, netievent_stop, @@ -286,11 +302,22 @@ 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 { + char *path; + isc_nm_http_cb_t cb; + void *cbarg; + size_t extrahandlesize; + LINK(isc_nm_http2_server_handler_t) link; +}; + /* * Wrapper around uv_req_t with 'our' fields in it. req->data should * always point to its parent. Note that we always allocate more than @@ -642,8 +669,12 @@ typedef enum isc_nmsocket_type { isc_nm_tcplistener, isc_nm_tcpdnslistener, isc_nm_tcpdnssocket, + isc_nm_tlslistener, + isc_nm_tlssocket, isc_nm_tlsdnslistener, - isc_nm_tlsdnssocket + isc_nm_tlsdnssocket, + isc_nm_httplistener, + isc_nm_httpstream } isc_nmsocket_type; /*% @@ -670,12 +701,74 @@ enum { STATID_ACTIVE = 10 }; +typedef struct isc_nmsocket_tls_send_req { + isc_nmsocket_t *tlssock; + isc_region_t data; +} isc_nmsocket_tls_send_req_t; + +typedef enum isc_doh_request_type { + ISC_HTTP_REQ_GET, + ISC_HTTP_REQ_POST, + ISC_HTTP_REQ_UNSUPPORTED +} isc_http2_request_type_t; + +typedef enum isc_http2_scheme_type { + ISC_HTTP_SCHEME_HTTP, + ISC_HTTP_SCHEME_HTTP_SECURE, + ISC_HTTP_SCHEME_UNSUPPORTED +} isc_http2_scheme_type_t; + +typedef struct isc_nm_http_doh_cbarg { + isc_nm_recv_cb_t cb; + void *cbarg; + LINK(struct isc_nm_http_doh_cbarg) link; +} isc_nm_http_doh_cbarg_t; + +typedef struct isc_nmsocket_h2 { + isc_nmsocket_t *psock; /* owner of the structure */ + char *request_path; + char *query_data; + size_t query_data_len; + bool query_too_large; + isc_nm_http2_server_handler_t *handler; + + uint8_t *buf; + size_t bufsize; + size_t bufpos; + + int32_t stream_id; + isc_nm_http2_session_t *session; + + isc_nmsocket_t *httpserver; + + isc_http2_request_type_t request_type; + isc_http2_scheme_type_t request_scheme; + size_t content_length; + bool content_type_verified; + bool accept_type_verified; + + isc_nm_http_cb_t handler_cb; + void *handler_cbarg; + LINK(struct isc_nmsocket_h2) link; + + ISC_LIST(isc_nm_http2_server_handler_t) handlers; + ISC_LIST(isc_nm_http_doh_cbarg_t) handlers_cbargs; + isc_rwlock_t handlers_lock; + + char response_content_length_str[128]; + + struct isc_nmsocket_h2_connect_data { + char *uri; + bool post; + } connect; +} isc_nmsocket_h2_t; struct isc_nmsocket { /*% Unlocked, RO */ int magic; int tid; isc_nmsocket_type type; isc_nm_t *mgr; + /*% Parent socket for multithreaded listeners */ isc_nmsocket_t *parent; /*% Listener socket this connection was accepted on */ @@ -705,6 +798,28 @@ struct isc_nmsocket { isc__nm_uvreq_t *pending_req; } tls; + /*% TLS stuff */ + struct tlsstream { + bool server; + BIO *app_bio; + SSL *ssl; + SSL_CTX *ctx; + BIO *ssl_bio; + isc_nmsocket_t *tlslistener; + enum { + TLS_INIT, + TLS_HANDSHAKE, + TLS_IO, + TLS_ERROR, + TLS_CLOSING, + TLS_CLOSED + } state; + size_t nsending; + /* List of active send requests. */ + ISC_LIST(isc__nm_uvreq_t) sends; + } tlsstream; + + isc_nmsocket_h2_t h2; /*% * quota is the TCP client, attached when a TCP connection * is established. pquota is a non-attached pointer to the @@ -906,6 +1021,7 @@ struct isc_nmsocket { void *accept_cbarg; atomic_int_fast32_t active_child_connections; + #ifdef NETMGR_TRACE void *backtrace[TRACE_SIZE]; int backtrace_size; @@ -1070,8 +1186,8 @@ isc__nm_async_shutdown(isc__networker_t *worker, isc__netievent_t *ev0); */ void -isc__nm_udp_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, - void *cbarg); +isc__nm_udp_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); /*%< * Back-end implementation of isc_nm_send() for UDP handles. */ @@ -1132,8 +1248,8 @@ isc__nm_async_udpclose(isc__networker_t *worker, isc__netievent_t *ev0); */ void -isc__nm_tcp_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, - void *cbarg); +isc__nm_tcp_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); /*%< * Back-end implementation of isc_nm_send() for TCP handles. */ @@ -1220,6 +1336,28 @@ isc__nm_async_tcpclose(isc__networker_t *worker, isc__netievent_t *ev0); * stoplisten, send, read, pause, close). */ +void +isc__nm_async_tlsclose(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_tlssend(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_tlsconnect(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_tlsstartread(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_tlsdobio(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_tlscancel(isc__networker_t *worker, isc__netievent_t *ev0); + +/*%< + * Callback handlers for asynchronouse TLS events. + */ + void isc__nm_async_tcpdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0); void @@ -1291,6 +1429,14 @@ isc__nm_async_tlsdnslisten(isc__networker_t *worker, isc__netievent_t *ev0); void isc__nm_tlsdns_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, void *cbarg); + +void +isc__nm_tls_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); + +void +isc__nm_tls_cancelread(isc_nmhandle_t *handle); + /*%< * Back-end implementation of isc_nm_send() for TLSDNS handles. */ @@ -1344,6 +1490,71 @@ isc__nm_tlsdns_cancelread(isc_nmhandle_t *handle); * Stop reading on a connected TLSDNS handle. */ +void +isc__nm_tls_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg); + +void +isc__nm_tls_close(isc_nmsocket_t *sock); +/*%< + * Close a TLS socket. + */ + +void +isc__nm_tls_pauseread(isc_nmhandle_t *handle); +/*%< + * Pause reading on this handle, while still remembering the callback. + */ + +void +isc__nm_tls_resumeread(isc_nmhandle_t *handle); +/*%< + * Resume reading from the handle. + * + */ + +void +isc__nm_tls_cleanup_data(isc_nmsocket_t *sock); + +void +isc__nm_tls_stoplistening(isc_nmsocket_t *sock); + +void +isc__nm_http_stoplistening(isc_nmsocket_t *sock); + +void +isc__nm_http_clear_handlers(isc_nmsocket_t *sock); + +void +isc__nm_http_clear_session(isc_nmsocket_t *sock); + +void +isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); + +void +isc__nm_http_close(isc_nmsocket_t *sock); + +void +isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_httpstop(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0); + +bool +isc__nm_parse_doh_query_string(const char *query_string, const char **start, + size_t *len); + +char * +isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url, + const size_t base64url_len, size_t *res_len); + +char * +isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64, + const size_t base64_len, size_t *res_len); + #define isc__nm_uverr2result(x) \ isc___nm_uverr2result(x, true, __FILE__, __LINE__, __func__) isc_result_t @@ -1444,6 +1655,12 @@ NETIEVENT_SOCKET_TYPE(tcpclose); NETIEVENT_SOCKET_TYPE(tcplisten); NETIEVENT_SOCKET_TYPE(tcppauseread); NETIEVENT_SOCKET_TYPE(tcpstop); +NETIEVENT_SOCKET_TYPE(tlsclose); +/* NETIEVENT_SOCKET_TYPE(tlsconnect); */ /* unique type, defined independently + */ +NETIEVENT_SOCKET_TYPE(tlsdobio); +NETIEVENT_SOCKET_TYPE(tlsstartread); +NETIEVENT_SOCKET_HANDLE_TYPE(tlscancel); NETIEVENT_SOCKET_TYPE(udpclose); NETIEVENT_SOCKET_TYPE(udplisten); NETIEVENT_SOCKET_TYPE(udpread); @@ -1470,9 +1687,14 @@ NETIEVENT_SOCKET_HANDLE_TYPE(tlsdnscancel); NETIEVENT_SOCKET_QUOTA_TYPE(tlsdnsaccept); NETIEVENT_SOCKET_TYPE(tlsdnscycle); +NETIEVENT_SOCKET_TYPE(httpstop); +NETIEVENT_SOCKET_REQ_TYPE(httpsend); +NETIEVENT_SOCKET_TYPE(httpclose); + NETIEVENT_SOCKET_REQ_TYPE(tcpconnect); NETIEVENT_SOCKET_REQ_TYPE(tcpsend); NETIEVENT_SOCKET_TYPE(tcpstartread); +NETIEVENT_SOCKET_REQ_TYPE(tlssend); NETIEVENT_SOCKET_REQ_TYPE(udpconnect); NETIEVENT_SOCKET_REQ_RESULT_TYPE(connectcb); @@ -1498,6 +1720,11 @@ NETIEVENT_SOCKET_DECL(tcplisten); NETIEVENT_SOCKET_DECL(tcppauseread); NETIEVENT_SOCKET_DECL(tcpstartread); NETIEVENT_SOCKET_DECL(tcpstop); +NETIEVENT_SOCKET_DECL(tlsclose); +NETIEVENT_SOCKET_DECL(tlsconnect); +NETIEVENT_SOCKET_DECL(tlsdobio); +NETIEVENT_SOCKET_DECL(tlsstartread); +NETIEVENT_SOCKET_HANDLE_DECL(tlscancel); NETIEVENT_SOCKET_DECL(udpclose); NETIEVENT_SOCKET_DECL(udplisten); NETIEVENT_SOCKET_DECL(udpread); @@ -1524,8 +1751,13 @@ NETIEVENT_SOCKET_HANDLE_DECL(tlsdnscancel); NETIEVENT_SOCKET_QUOTA_DECL(tlsdnsaccept); NETIEVENT_SOCKET_DECL(tlsdnscycle); +NETIEVENT_SOCKET_DECL(httpstop); +NETIEVENT_SOCKET_REQ_DECL(httpsend); +NETIEVENT_SOCKET_DECL(httpclose); + NETIEVENT_SOCKET_REQ_DECL(tcpconnect); NETIEVENT_SOCKET_REQ_DECL(tcpsend); +NETIEVENT_SOCKET_REQ_DECL(tlssend); NETIEVENT_SOCKET_REQ_DECL(udpconnect); NETIEVENT_SOCKET_REQ_RESULT_DECL(connectcb); diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c index 584142a52c..fb8b1fa701 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -716,6 +716,13 @@ process_netievent(isc__networker_t *worker, isc__netievent_t *ievent) { NETIEVENT_CASE(tcpdnsread); NETIEVENT_CASE(tcpdnsstop); + NETIEVENT_CASE(tlsstartread); + NETIEVENT_CASE(tlssend); + NETIEVENT_CASE(tlsclose); + NETIEVENT_CASE(tlsconnect); + NETIEVENT_CASE(tlsdobio); + NETIEVENT_CASE(tlscancel); + NETIEVENT_CASE(tlsdnscycle); NETIEVENT_CASE(tlsdnsaccept); NETIEVENT_CASE(tlsdnslisten); @@ -727,6 +734,10 @@ process_netievent(isc__networker_t *worker, isc__netievent_t *ievent) { NETIEVENT_CASE(tlsdnsstop); NETIEVENT_CASE(tlsdnsshutdown); + NETIEVENT_CASE(httpstop); + NETIEVENT_CASE(httpsend); + NETIEVENT_CASE(httpclose); + NETIEVENT_CASE(connectcb); NETIEVENT_CASE(readcb); NETIEVENT_CASE(sendcb); @@ -776,6 +787,11 @@ NETIEVENT_SOCKET_DEF(tcplisten); NETIEVENT_SOCKET_DEF(tcppauseread); NETIEVENT_SOCKET_DEF(tcpstartread); NETIEVENT_SOCKET_DEF(tcpstop); +NETIEVENT_SOCKET_DEF(tlsclose); +NETIEVENT_SOCKET_DEF(tlsconnect); +NETIEVENT_SOCKET_DEF(tlsdobio); +NETIEVENT_SOCKET_DEF(tlsstartread); +NETIEVENT_SOCKET_HANDLE_DEF(tlscancel); NETIEVENT_SOCKET_DEF(udpclose); NETIEVENT_SOCKET_DEF(udplisten); NETIEVENT_SOCKET_DEF(udpread); @@ -802,8 +818,13 @@ NETIEVENT_SOCKET_QUOTA_DEF(tlsdnsaccept); NETIEVENT_SOCKET_DEF(tlsdnscycle); NETIEVENT_SOCKET_DEF(tlsdnsshutdown); +NETIEVENT_SOCKET_DEF(httpstop); +NETIEVENT_SOCKET_REQ_DEF(httpsend); +NETIEVENT_SOCKET_DEF(httpclose); + NETIEVENT_SOCKET_REQ_DEF(tcpconnect); NETIEVENT_SOCKET_REQ_DEF(tcpsend); +NETIEVENT_SOCKET_REQ_DEF(tlssend); NETIEVENT_SOCKET_REQ_DEF(udpconnect); NETIEVENT_SOCKET_REQ_RESULT_DEF(connectcb); @@ -986,6 +1007,34 @@ nmsocket_cleanup(isc_nmsocket_t *sock, bool dofree FLARG) { isc_mutex_destroy(&sock->lock); 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); #ifdef NETMGR_TRACE LOCK(&sock->mgr->lock); ISC_LIST_UNLINK(sock->mgr->active_sockets, sock, active_link); @@ -1094,9 +1143,15 @@ isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG) { case isc_nm_tcpdnssocket: isc__nm_tcpdns_close(sock); return; + case isc_nm_tlssocket: + isc__nm_tls_close(sock); + break; case isc_nm_tlsdnssocket: isc__nm_tlsdns_close(sock); return; + case isc_nm_httpstream: + isc__nm_http_close(sock); + return; default: break; } @@ -1139,7 +1194,9 @@ isc_nmsocket_close(isc_nmsocket_t **sockp) { REQUIRE((*sockp)->type == isc_nm_udplistener || (*sockp)->type == isc_nm_tcplistener || (*sockp)->type == isc_nm_tcpdnslistener || - (*sockp)->type == isc_nm_tlsdnslistener); + (*sockp)->type == isc_nm_tlsdnslistener || + (*sockp)->type == isc_nm_tlslistener || + (*sockp)->type == isc_nm_httplistener); isc__nmsocket_detach(sockp); } @@ -1202,6 +1259,8 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type, case isc_nm_tcpdnslistener: case isc_nm_tlsdnssocket: case isc_nm_tlsdnslistener: + case isc_nm_httpstream: + case isc_nm_httplistener: if (family == AF_INET) { sock->statsindex = tcp4statsindex; } else { @@ -1218,6 +1277,9 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type, isc_condition_init(&sock->scond); 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)); @@ -1228,6 +1290,28 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type, atomic_store(&sock->active_child_connections, 0); + if (type == isc_nm_httplistener) { + ISC_LIST_INIT(sock->h2.handlers); + ISC_LIST_INIT(sock->h2.handlers_cbargs); + isc_rwlock_init(&sock->h2.handlers_lock, 0, 1); + } + + sock->h2.session = NULL; + sock->h2.httpserver = NULL; + sock->h2.query_data = NULL; + sock->h2.query_data_len = 0; + sock->h2.query_too_large = false; + sock->h2.request_path = NULL; + sock->h2.request_type = ISC_HTTP_REQ_UNSUPPORTED; + sock->h2.request_scheme = ISC_HTTP_SCHEME_UNSUPPORTED; + sock->h2.content_length = 0; + sock->h2.content_type_verified = false; + sock->h2.accept_type_verified = false; + sock->h2.handler_cb = NULL; + sock->h2.handler_cbarg = NULL; + sock->h2.connect.uri = NULL; + sock->h2.buf = NULL; + sock->magic = NMSOCK_MAGIC; } @@ -1353,7 +1437,7 @@ isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t *peer, #endif UNLOCK(&sock->lock); - if (sock->type == isc_nm_tcpsocket || + if (sock->type == isc_nm_tcpsocket || sock->type == isc_nm_tlssocket || (sock->type == isc_nm_udpsocket && atomic_load(&sock->client)) || (sock->type == isc_nm_tcpdnssocket && atomic_load(&sock->client)) || (sock->type == isc_nm_tlsdnssocket && atomic_load(&sock->client))) @@ -1369,6 +1453,10 @@ isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t *peer, sock->statichandle = handle; } + if (sock->type == isc_nm_httpstream) { + handle->httpsession = sock->h2.session; + } + return (handle); } @@ -1390,6 +1478,7 @@ isc_nmhandle_is_stream(isc_nmhandle_t *handle) { return (handle->sock->type == isc_nm_tcpsocket || handle->sock->type == isc_nm_tcpdnssocket || + handle->sock->type == isc_nm_tlssocket || handle->sock->type == isc_nm_tlsdnssocket); } @@ -1667,9 +1756,15 @@ isc_nm_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, case isc_nm_tcpdnssocket: isc__nm_tcpdns_send(handle, region, cb, cbarg); break; + case isc_nm_tlssocket: + isc__nm_tls_send(handle, region, cb, cbarg); + break; case isc_nm_tlsdnssocket: isc__nm_tlsdns_send(handle, region, cb, cbarg); break; + case isc_nm_httpstream: + isc__nm_http_send(handle, region, cb, cbarg); + break; default: INSIST(0); ISC_UNREACHABLE(); @@ -1697,6 +1792,9 @@ isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { case isc_nm_tcpdnssocket: isc__nm_tcpdns_read(handle, cb, cbarg); break; + case isc_nm_tlssocket: + isc__nm_tls_read(handle, cb, cbarg); + break; case isc_nm_tlsdnssocket: isc__nm_tlsdns_read(handle, cb, cbarg); break; @@ -1723,6 +1821,9 @@ isc_nm_cancelread(isc_nmhandle_t *handle) { case isc_nm_tlsdnssocket: isc__nm_tlsdns_cancelread(handle); break; + case isc_nm_tlssocket: + isc__nm_tls_cancelread(handle); + break; default: INSIST(0); ISC_UNREACHABLE(); @@ -1739,6 +1840,9 @@ isc_nm_pauseread(isc_nmhandle_t *handle) { case isc_nm_tcpsocket: isc__nm_tcp_pauseread(handle); break; + case isc_nm_tlssocket: + isc__nm_tls_pauseread(handle); + break; default: INSIST(0); ISC_UNREACHABLE(); @@ -1755,6 +1859,9 @@ isc_nm_resumeread(isc_nmhandle_t *handle) { case isc_nm_tcpsocket: isc__nm_tcp_resumeread(handle); break; + case isc_nm_tlssocket: + isc__nm_tls_resumeread(handle); + break; default: INSIST(0); ISC_UNREACHABLE(); @@ -1775,9 +1882,15 @@ isc_nm_stoplistening(isc_nmsocket_t *sock) { case isc_nm_tcplistener: isc__nm_tcp_stoplistening(sock); break; + case isc_nm_tlslistener: + isc__nm_tls_stoplistening(sock); + break; case isc_nm_tlsdnslistener: isc__nm_tlsdns_stoplistening(sock); break; + case isc_nm_httplistener: + isc__nm_http_stoplistening(sock); + break; default: INSIST(0); ISC_UNREACHABLE(); @@ -2322,10 +2435,18 @@ nmsocket_type_totext(isc_nmsocket_type type) { return ("isc_nm_tcpdnslistener"); case isc_nm_tcpdnssocket: return ("isc_nm_tcpdnssocket"); + case isc_nm_tlssocket: + return ("isc_nm_tlssocket"); + case isc_nm_tlslistener: + return ("isc_nm_tlslistener"); case isc_nm_tlsdnslistener: return ("isc_nm_tlsdnslistener"); case isc_nm_tlsdnssocket: return ("isc_nm_tlsdnssocket"); + case isc_nm_httplistener: + return ("isc_nm_httplistener"); + case isc_nm_httpstream: + return ("isc_nm_httpstream"); default: INSIST(0); ISC_UNREACHABLE(); diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c index 24f43c9856..e7ad6cd634 100644 --- a/lib/isc/netmgr/tcp.c +++ b/lib/isc/netmgr/tcp.c @@ -167,6 +167,27 @@ tcp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { REQUIRE(isc__nm_in_netthread()); REQUIRE(sock->tid == isc_nm_tid()); + result = isc__nm_socket(req->peer.type.sa.sa_family, SOCK_STREAM, 0, + &sock->fd); + /* + * The socket() call can fail spuriously on FreeBSD 12, so we need to + * handle the failure early and gracefully. + */ + if (result != ISC_R_SUCCESS) { + atomic_store(&sock->closed, true); + isc__nm_uvreq_t *cbreq = NULL; + cbreq = isc__nm_uvreq_get(sock->mgr, sock); + cbreq->cb.connect = req->cb.connect; + cbreq->cbarg = req->cbarg; + isc_nmhandle_attach(req->handle, &cbreq->handle); + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, cbreq, result); + goto error; + } + result = isc__nm_socket_connectiontimeout(sock->fd, + sock->connect_timeout); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + worker = &sock->mgr->workers[sock->tid]; atomic_store(&sock->connecting, true); @@ -210,7 +231,7 @@ tcp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { done: result = isc__nm_uverr2result(r); - +error: LOCK(&sock->lock); sock->result = result; SIGNAL(&sock->cond); @@ -239,10 +260,13 @@ isc__nm_async_tcpconnect(isc__networker_t *worker, isc__netievent_t *ev0) { REQUIRE(sock->parent == NULL); REQUIRE(sock->tid == isc_nm_tid()); + sock->fd = (uv_os_sock_t)(-1); result = tcp_connect_direct(sock, req); if (result != ISC_R_SUCCESS) { atomic_store(&sock->active, false); - isc__nm_tcp_close(sock); + if (sock->fd != (uv_os_sock_t)(-1)) { + isc__nm_tcp_close(sock); + } isc__nm_uvreq_put(&req, sock); } @@ -309,36 +333,19 @@ isc_nm_tcpconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, isc_nmsocket_t *sock = NULL; isc__netievent_tcpconnect_t *ievent = NULL; isc__nm_uvreq_t *req = NULL; - sa_family_t sa_family; - uv_os_sock_t fd; REQUIRE(VALID_NM(mgr)); REQUIRE(local != NULL); REQUIRE(peer != NULL); - sa_family = peer->addr.type.sa.sa_family; - - /* - * The socket() call can fail spuriously on FreeBSD 12, so we need to - * handle the failure early and gracefully. - */ - result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &fd); - if (result != ISC_R_SUCCESS) { - return (result); - } - sock = isc_mem_get(mgr->mctx, sizeof(*sock)); isc__nmsocket_init(sock, mgr, isc_nm_tcpsocket, local); sock->extrahandlesize = extrahandlesize; sock->connect_timeout = timeout; sock->result = ISC_R_DEFAULT; - sock->fd = fd; atomic_init(&sock->client, true); - result = isc__nm_socket_connectiontimeout(fd, timeout); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - req = isc__nm_uvreq_get(mgr, sock); req->cb.connect = cb; req->cbarg = cbarg; @@ -1155,8 +1162,8 @@ failure: } void -isc__nm_tcp_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, - void *cbarg) { +isc__nm_tcp_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { REQUIRE(VALID_NMHANDLE(handle)); REQUIRE(VALID_NMSOCK(handle->sock)); diff --git a/lib/isc/netmgr/tlsstream.c b/lib/isc/netmgr/tlsstream.c new file mode 100644 index 0000000000..87242e0a0b --- /dev/null +++ b/lib/isc/netmgr/tlsstream.c @@ -0,0 +1,936 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netmgr-int.h" +#include "uv-compat.h" + +#define TLS_BUF_SIZE 65536 + +static isc_result_t +tls_error_to_result(int tls_err) { + switch (tls_err) { + case SSL_ERROR_ZERO_RETURN: + return (ISC_R_EOF); + default: + return (ISC_R_UNEXPECTED); + } +} + +static void +tls_do_bio(isc_nmsocket_t *sock); + +static void +tls_close_direct(isc_nmsocket_t *sock); + +static void +async_tls_do_bio(isc_nmsocket_t *sock); + +/* + * The socket is closing, outerhandle has been detached, listener is + * inactive, or the netmgr is closing: any operation on it should abort + * with ISC_R_CANCELED. + */ +static bool +inactive(isc_nmsocket_t *sock) { + return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) || + sock->outerhandle == NULL || + (sock->listener != NULL && + !isc__nmsocket_active(sock->listener)) || + atomic_load(&sock->mgr->closing)); +} + +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); + } + UNLOCK(&sock->lock); + if (sock->parent) { + LOCK(&sock->parent->lock); + sock->parent->result = result; + UNLOCK(&sock->parent->lock); + } +} + +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; + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(VALID_NMSOCK(sock)); + + /* XXXWPK TODO */ + UNUSED(eresult); + + 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)); + + sock->tlsstream.nsending--; + async_tls_do_bio(sock); + isc__nmsocket_detach(&sock); +} + +static void +tls_failed_read_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle, + const isc_result_t result, const bool close) { + REQUIRE(VALID_NMSOCK(sock)); + + if (!sock->tlsstream.server && + (sock->tlsstream.state == TLS_INIT || + sock->tlsstream.state == TLS_HANDSHAKE) && + sock->connect_cb != NULL) + { + 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); + isc_nmhandle_detach(&handle); + } else if (sock->recv_cb != NULL) { + isc__nm_uvreq_t *req = NULL; + req = isc__nm_uvreq_get(sock->mgr, sock); + req->cb.recv = sock->recv_cb; + req->cbarg = sock->recv_cbarg; + req->handle = NULL; + if (handle) { + REQUIRE(VALID_NMHANDLE(handle)); + isc_nmhandle_attach(handle, &req->handle); + } else { + req->handle = isc__nmhandle_get(sock, NULL, NULL); + } + isc__nmsocket_clearcb(sock); + isc__nm_readcb(sock, req, result); + } + sock->tlsstream.state = TLS_ERROR; + + if (close) { + isc__nmsocket_prep_destroy(sock); + } +} + +static void +async_tls_do_bio(isc_nmsocket_t *sock) { + isc__netievent_tlsdobio_t *ievent = + isc__nm_get_netievent_tlsdobio(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +static void +tls_do_bio(isc_nmsocket_t *sock) { + isc_result_t result = ISC_R_SUCCESS; + int pending, tls_err = 0; + int rv; + isc__nm_uvreq_t *req; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + /* We will resume read if TLS layer wants us to */ + if (sock->outerhandle != NULL) { + REQUIRE(VALID_NMHANDLE(sock->outerhandle)); + isc_nm_pauseread(sock->outerhandle); + } + + if (sock->tlsstream.state == TLS_INIT) { + (void)SSL_do_handshake(sock->tlsstream.ssl); + 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; + } + + /* Data from TLS to client */ + char buf[1]; + 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) { + 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.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); + } + } + + /* Peek to move the session forward */ + (void)SSL_peek(sock->tlsstream.ssl, 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; + } + 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); + /* We'll continue in tls_senddone */ + return; + } + + /* Get the potential error code */ + rv = SSL_peek(sock->tlsstream.ssl, buf, 1); + + if (rv < 0) { + tls_err = SSL_get_error(sock->tlsstream.ssl, 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) + { + isc_nmhandle_t *tlshandle = isc__nmhandle_get(sock, NULL, NULL); + if (sock->tlsstream.server) { + sock->listener->accept_cb(sock->statichandle, + 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); + } + isc_nmhandle_detach(&tlshandle); + sock->tlsstream.state = TLS_IO; + async_tls_do_bio(sock); + return; + } + + switch (tls_err) { + case 0: + return; + case SSL_ERROR_WANT_WRITE: + if (sock->tlsstream.nsending == 0) { + /* + * Launch tls_do_bio asynchronously. If we're sending + * already the send callback will call it. + */ + async_tls_do_bio(sock); + } else { + return; + } + break; + case SSL_ERROR_WANT_READ: + INSIST(VALID_NMHANDLE(sock->outerhandle)); + isc_nm_resumeread(sock->outerhandle); + 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; +} + +static void +tls_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, + void *cbarg) { + isc_nmsocket_t *tlssock = (isc_nmsocket_t *)cbarg; + int rv; + + 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, + true); + 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_do_bio(tlssock); +} + +static isc_result_t +initialize_tls(isc_nmsocket_t *sock, bool server) { + REQUIRE(sock->tid == isc_nm_tid()); + + 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); + return (ISC_R_TLSERROR); + } + + SSL_set_bio(sock->tlsstream.ssl, sock->tlsstream.ssl_bio, + sock->tlsstream.ssl_bio); + if (server) { + SSL_set_accept_state(sock->tlsstream.ssl); + } else { + SSL_set_connect_state(sock->tlsstream.ssl); + } + sock->tlsstream.nsending = 0; + isc_nm_read(sock->outerhandle, tls_readcb, sock); + tls_do_bio(sock); + return (ISC_R_SUCCESS); +} + +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) { + return (result); + } + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(VALID_NMSOCK(tlslistensock)); + REQUIRE(tlslistensock->type == isc_nm_tlslistener); + + /* + * We need to create a 'wrapper' tlssocket for this connection. + */ + tlssock = isc_mem_get(handle->sock->mgr->mctx, sizeof(*tlssock)); + isc__nmsocket_init(tlssock, handle->sock->mgr, isc_nm_tlssocket, + handle->sock->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); + atomic_store(&tlssock->closed, true); + isc__nmsocket_detach(&tlssock); + return (ISC_R_TLSERROR); + } + + tlssock->extrahandlesize = tlslistensock->extrahandlesize; + isc__nmsocket_attach(tlslistensock, &tlssock->listener); + isc_nmhandle_attach(handle, &tlssock->outerhandle); + tlssock->peer = handle->sock->peer; + tlssock->read_timeout = atomic_load(&handle->sock->mgr->init); + tlssock->tid = isc_nm_tid(); + 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); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + /* TODO: catch failure code, detach tlssock, and log the error */ + + return (result); +} + +isc_result_t +isc_nm_listentls(isc_nm_t *mgr, isc_nmiface_t *iface, + isc_nm_accept_cb_t accept_cb, void *accept_cbarg, + size_t extrahandlesize, int backlog, isc_quota_t *quota, + SSL_CTX *sslctx, isc_nmsocket_t **sockp) { + isc_result_t result; + isc_nmsocket_t *tlssock = isc_mem_get(mgr->mctx, sizeof(*tlssock)); + isc_nmsocket_t *tsock = NULL; + + REQUIRE(VALID_NM(mgr)); + + isc__nmsocket_init(tlssock, mgr, isc_nm_tlslistener, 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 will be a TLS 'wrapper' around an unencrypted stream. + * We set tlssock->outer to a socket listening for a TCP connection. + */ + result = isc_nm_listentcp(mgr, iface, tlslisten_acceptcb, tlssock, + extrahandlesize, backlog, quota, + &tlssock->outer); + if (result != ISC_R_SUCCESS) { + atomic_store(&tlssock->closed, true); + isc__nmsocket_detach(&tlssock); + return (result); + } + + /* wait for listen result */ + isc__nmsocket_attach(tlssock->outer, &tsock); + LOCK(&tlssock->outer->lock); + while (tlssock->outer->rchildren != tlssock->outer->nchildren) { + WAIT(&tlssock->outer->cond, &tlssock->outer->lock); + } + result = tlssock->outer->result; + tlssock->result = result; + atomic_store(&tlssock->active, true); + INSIST(tlssock->outer->tlsstream.tlslistener == NULL); + isc__nmsocket_attach(tlssock, &tlssock->outer->tlsstream.tlslistener); + BROADCAST(&tlssock->outer->scond); + UNLOCK(&tlssock->outer->lock); + isc__nmsocket_detach(&tsock); + INSIST(result != ISC_R_DEFAULT); + + if (result == ISC_R_SUCCESS) { + atomic_store(&tlssock->listening, true); + *sockp = tlssock; + } + + return (result); +} + +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); + + 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); + isc__nm_uvreq_put(&req, sock); + tls_do_bio(sock); + return; +} + +void +isc__nm_tls_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + isc__netievent_tlssend_t *ievent = NULL; + isc__nm_uvreq_t *uvreq = NULL; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + + REQUIRE(sock->type == isc_nm_tlssocket); + + if (inactive(sock)) { + cb(handle, ISC_R_CANCELED, cbarg); + return; + } + + uvreq = isc__nm_uvreq_get(sock->mgr, sock); + isc_nmhandle_attach(handle, &uvreq->handle); + uvreq->cb.send = cb; + uvreq->cbarg = cbarg; + + uvreq->uvbuf.base = (char *)region->base; + uvreq->uvbuf.len = region->length; + + /* + * We need to create an event and pass it using async channel + */ + ievent = isc__nm_get_netievent_tlssend(sock->mgr, sock, uvreq); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_tlsstartread(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsstartread_t *ievent = + (isc__netievent_tlsstartread_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(sock->tid == isc_nm_tid()); + UNUSED(worker); + + tls_do_bio(sock); +} + +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; + + if (inactive(sock)) { + cb(handle, ISC_R_NOTCONNECTED, NULL, cbarg); + return; + } + + sock->recv_cb = cb; + sock->recv_cbarg = cbarg; + + ievent = isc__nm_get_netievent_tlsstartread(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +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); +} + +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); +} + +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 + */ + 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); + } +} + +void +isc__nm_tls_close(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tlssocket); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) { + 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); + } +} + +void +isc__nm_async_tlsclose(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsclose_t *ievent = (isc__netievent_tlsclose_t *)ev0; + + REQUIRE(ievent->sock->tid == isc_nm_tid()); + UNUSED(worker); + + tls_close_direct(ievent->sock); +} + +void +isc__nm_tls_stoplistening(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tlslistener); + + atomic_store(&sock->listening, false); + 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; + sock->tlsstream.ctx = NULL; + } + + if (sock->outer != NULL) { + isc_nm_stoplistening(sock->outer); + isc__nmsocket_detach(&sock->outer); + } +} + +isc_result_t +isc_nm_tlsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, + isc_nm_cb_t cb, void *cbarg, SSL_CTX *ctx, + unsigned int timeout, size_t extrahandlesize) { + isc_nmsocket_t *nsock = NULL, *tsock = NULL; + isc__netievent_tlsconnect_t *ievent = NULL; + isc_result_t result = ISC_R_DEFAULT; + + REQUIRE(VALID_NM(mgr)); + + nsock = isc_mem_get(mgr->mctx, sizeof(*nsock)); + isc__nmsocket_init(nsock, mgr, isc_nm_tlssocket, local); + nsock->extrahandlesize = extrahandlesize; + nsock->result = ISC_R_DEFAULT; + nsock->connect_cb = cb; + nsock->connect_cbarg = cbarg; + nsock->connect_timeout = timeout; + nsock->tlsstream.ctx = ctx; + + ievent = isc__nm_get_netievent_tlsconnect(mgr, nsock); + ievent->local = local->addr; + ievent->peer = peer->addr; + ievent->ctx = ctx; + + isc__nmsocket_attach(nsock, &tsock); + if (isc__nm_in_netthread()) { + nsock->tid = isc_nm_tid(); + isc__nm_async_tlsconnect(&mgr->workers[nsock->tid], + (isc__netievent_t *)ievent); + isc__nm_put_netievent_tlsconnect(mgr, ievent); + } else { + nsock->tid = isc_random_uniform(mgr->nworkers); + isc__nm_enqueue_ievent(&mgr->workers[nsock->tid], + (isc__netievent_t *)ievent); + } + + LOCK(&nsock->lock); + result = nsock->result; + while (result == ISC_R_DEFAULT) { + WAIT(&nsock->cond, &nsock->lock); + result = nsock->result; + } + atomic_store(&nsock->active, true); + BROADCAST(&nsock->scond); + UNLOCK(&nsock->lock); + INSIST(VALID_NMSOCK(nsock)); + isc__nmsocket_detach(&tsock); + + INSIST(result != ISC_R_DEFAULT); + + return (result); +} + +static void +tls_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + isc_nmsocket_t *tlssock = (isc_nmsocket_t *)cbarg; + + REQUIRE(VALID_NMSOCK(tlssock)); + + if (result != ISC_R_SUCCESS) { + tlssock->connect_cb(handle, result, tlssock->connect_cbarg); + update_result(tlssock, result); + tls_close_direct(tlssock); + return; + } + + 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; + } +} + +void +isc__nm_async_tlsconnect(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsconnect_t *ievent = + (isc__netievent_tlsconnect_t *)ev0; + isc_nmsocket_t *tlssock = ievent->sock; + isc_result_t result; + int r; + isc_nmhandle_t *tlshandle = NULL; + + UNUSED(worker); + + /* + * We need to initialize SSL now to reference SSL_CTX properly. + */ + tlssock->tlsstream.ssl = SSL_new(tlssock->tlsstream.ctx); + if (tlssock->tlsstream.ssl == NULL) { + result = ISC_R_TLSERROR; + 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; + } + return; +error: + tlshandle = isc__nmhandle_get(tlssock, NULL, NULL); + atomic_store(&tlssock->closed, true); + tlssock->connect_cb(tlshandle, result, tlssock->connect_cbarg); + isc_nmhandle_detach(&tlshandle); + update_result(tlssock, result); + tls_close_direct(tlssock); +} + +void +isc__nm_tls_cancelread(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + isc__netievent_tlscancel_t *ievent = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + 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); +} + +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); + } +} + +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); +} + +void +isc__nm_tls_cleanup_data(isc_nmsocket_t *sock) { + if (sock->tlsstream.tlslistener) { + REQUIRE(VALID_NMSOCK(sock->tlsstream.tlslistener)); + isc__nmsocket_detach(&sock->tlsstream.tlslistener); + } +} diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c index 411b8deacf..8651fd2c00 100644 --- a/lib/isc/netmgr/udp.c +++ b/lib/isc/netmgr/udp.c @@ -478,8 +478,8 @@ free: * another thread. */ void -isc__nm_udp_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, - void *cbarg) { +isc__nm_udp_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { isc_nmsocket_t *sock = handle->sock; isc_nmsocket_t *psock = NULL, *rsock = sock; isc_sockaddr_t *peer = &handle->peer; diff --git a/lib/isc/tests/Makefile.am b/lib/isc/tests/Makefile.am index c8d07aa288..05b97487b7 100644 --- a/lib/isc/tests/Makefile.am +++ b/lib/isc/tests/Makefile.am @@ -20,6 +20,7 @@ TESTS = \ buffer_test \ counter_test \ crc64_test \ + doh_test \ errno_test \ file_test \ hash_test \ @@ -44,17 +45,27 @@ TESTS = \ symtab_test \ task_test \ taskpool_test \ - tcp_test \ tcp_quota_test \ + tcp_test \ tcpdns_test \ - tlsdns_test \ time_test \ timer_test \ + tlsdns_test \ udp_test check_PROGRAMS = \ $(TESTS) +doh_test_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(LIBUV_CFLAGS) \ + $(OPENSSL_CFLAGS) + +doh_test_LDADD = \ + $(LDADD) \ + $(LIBUV_LIBS) \ + $(OPENSSL_LIBS) + hmac_test_CPPFLAGS = \ $(AM_CPPFLAGS) \ $(OPENSSL_CFLAGS) @@ -77,8 +88,8 @@ random_test_LDADD = \ tcp_test_CPPFLAGS = \ $(AM_CPPFLAGS) \ - $(OPENSSL_CFLAGS) \ - $(LIBUV_CFLAGS) + $(LIBUV_CFLAGS) \ + $(OPENSSL_CFLAGS) tcp_test_LDADD = \ $(LDADD) \ @@ -86,8 +97,8 @@ tcp_test_LDADD = \ tcp_quota_test_CPPFLAGS = \ $(AM_CPPFLAGS) \ - $(OPENSSL_CFLAGS) \ - $(LIBUV_CFLAGS) + $(LIBUV_CFLAGS) \ + $(OPENSSL_CFLAGS) tcp_quota_test_LDADD = \ $(LDADD) \ @@ -95,8 +106,8 @@ tcp_quota_test_LDADD = \ tcpdns_test_CPPFLAGS = \ $(AM_CPPFLAGS) \ - $(OPENSSL_CFLAGS) \ - $(LIBUV_CFLAGS) + $(LIBUV_CFLAGS) \ + $(OPENSSL_CFLAGS) tcpdns_test_LDADD = \ $(LDADD) \ @@ -104,8 +115,8 @@ tcpdns_test_LDADD = \ tlsdns_test_CPPFLAGS = \ $(AM_CPPFLAGS) \ - $(OPENSSL_CFLAGS) \ - $(LIBUV_CFLAGS) + $(LIBUV_CFLAGS) \ + $(OPENSSL_CFLAGS) tlsdns_test_LDADD = \ $(LDADD) \ @@ -113,8 +124,8 @@ tlsdns_test_LDADD = \ udp_test_CPPFLAGS = \ $(AM_CPPFLAGS) \ - $(OPENSSL_CFLAGS) \ - $(LIBUV_CFLAGS) + $(LIBUV_CFLAGS) \ + $(OPENSSL_CFLAGS) udp_test_LDADD = \ $(LDADD) \ diff --git a/lib/isc/tests/doh_test.c b/lib/isc/tests/doh_test.c new file mode 100644 index 0000000000..879e42a431 --- /dev/null +++ b/lib/isc/tests/doh_test.c @@ -0,0 +1,1771 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uv_wrap.h" +#define KEEP_BEFORE + +#include "../netmgr/http.c" +#include "../netmgr/netmgr-int.h" +#include "../netmgr/uv-compat.c" +#include "../netmgr/uv-compat.h" +#include "isctest.h" + +#define MAX_NM 2 + +static isc_sockaddr_t tcp_listen_addr; + +static uint64_t send_magic = 0; +static uint64_t stop_magic = 0; + +static uv_buf_t send_msg = { .base = (char *)&send_magic, + .len = sizeof(send_magic) }; + +static atomic_uint_fast64_t nsends; + +static atomic_uint_fast64_t ssends; +static atomic_uint_fast64_t sreads; + +static atomic_uint_fast64_t csends; +static atomic_uint_fast64_t creads; + +static atomic_bool was_error; + +static unsigned int workers = 1; + +static bool reuse_supported = true; + +static atomic_bool POST = ATOMIC_VAR_INIT(true); + +static atomic_bool use_TLS = ATOMIC_VAR_INIT(false); +static SSL_CTX *server_ssl_ctx = NULL; + +#define NSENDS 100 +#define NWRITES 10 + +#define DOH_PATH "/dns-query" + +#define CHECK_RANGE_FULL(v) \ + { \ + int __v = atomic_load(&v); \ + assert_true(__v > NSENDS * NWRITES * 10 / 100); \ + assert_true(__v <= NSENDS * NWRITES * 110 / 100); \ + } + +#define CHECK_RANGE_HALF(v) \ + { \ + int __v = atomic_load(&v); \ + assert_true(__v > NSENDS * NWRITES * 5 / 100); \ + assert_true(__v <= NSENDS * NWRITES * 110 / 100); \ + } + +/* Enable this to print values while running tests */ +#undef PRINT_DEBUG +#ifdef PRINT_DEBUG +#define X(v) fprintf(stderr, #v " = %" PRIu64 "\n", atomic_load(&v)) +#else +#define X(v) +#endif + +static int +setup_ephemeral_port(isc_sockaddr_t *addr, sa_family_t family) { + isc_result_t result; + socklen_t addrlen = sizeof(*addr); + int fd; + int r; + + isc_sockaddr_fromin6(addr, &in6addr_loopback, 0); + + fd = socket(AF_INET6, family, 0); + if (fd < 0) { + perror("setup_ephemeral_port: socket()"); + return (-1); + } + + r = bind(fd, (const struct sockaddr *)&addr->type.sa, + sizeof(addr->type.sin6)); + if (r != 0) { + perror("setup_ephemeral_port: bind()"); + isc__nm_closesocket(fd); + return (r); + } + + r = getsockname(fd, (struct sockaddr *)&addr->type.sa, &addrlen); + if (r != 0) { + perror("setup_ephemeral_port: getsockname()"); + isc__nm_closesocket(fd); + return (r); + } + + result = isc__nm_socket_reuse(fd); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) { + fprintf(stderr, + "setup_ephemeral_port: isc__nm_socket_reuse(): %s", + isc_result_totext(result)); + close(fd); + return (-1); + } + + result = isc__nm_socket_reuse_lb(fd); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) { + fprintf(stderr, + "setup_ephemeral_port: isc__nm_socket_reuse_lb(): %s", + isc_result_totext(result)); + close(fd); + return (-1); + } + if (result == ISC_R_NOTIMPLEMENTED) { + reuse_supported = false; + } + +#if IPV6_RECVERR +#define setsockopt_on(socket, level, name) \ + setsockopt(socket, level, name, &(int){ 1 }, sizeof(int)) + + r = setsockopt_on(fd, IPPROTO_IPV6, IPV6_RECVERR); + if (r != 0) { + perror("setup_ephemeral_port"); + close(fd); + return (r); + } +#endif + + return (fd); +} + +static int +_setup(void **state) { + UNUSED(state); + + /*workers = isc_os_ncpus();*/ + + if (isc_test_begin(NULL, false, workers) != ISC_R_SUCCESS) { + return (-1); + } + + signal(SIGPIPE, SIG_IGN); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +/* Generic */ + +static void +noop_read_cb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, + void *cbarg) { + UNUSED(handle); + UNUSED(result); + UNUSED(region); + UNUSED(cbarg); +} + +thread_local uint8_t tcp_buffer_storage[4096]; +thread_local size_t tcp_buffer_length = 0; + +static int +nm_setup(void **state) { + size_t nworkers = ISC_MAX(ISC_MIN(workers, 32), 1); + int tcp_listen_sock = -1; + isc_nm_t **nm = NULL; + + tcp_listen_addr = (isc_sockaddr_t){ .length = 0 }; + tcp_listen_sock = setup_ephemeral_port(&tcp_listen_addr, SOCK_STREAM); + if (tcp_listen_sock < 0) { + return (-1); + } + close(tcp_listen_sock); + tcp_listen_sock = -1; + + atomic_store(&nsends, NSENDS * NWRITES); + + atomic_store(&csends, 0); + atomic_store(&creads, 0); + atomic_store(&sreads, 0); + atomic_store(&ssends, 0); + + atomic_store(&was_error, false); + + atomic_store(&POST, false); + atomic_store(&use_TLS, false); + + isc_nonce_buf(&send_magic, sizeof(send_magic)); + isc_nonce_buf(&stop_magic, sizeof(stop_magic)); + if (send_magic == stop_magic) { + return (-1); + } + + nm = isc_mem_get(test_mctx, MAX_NM * sizeof(nm[0])); + for (size_t i = 0; i < MAX_NM; i++) { + nm[i] = isc_nm_start(test_mctx, nworkers); + assert_non_null(nm[i]); + } + + server_ssl_ctx = NULL; + isc_tlsctx_createserver(NULL, NULL, &server_ssl_ctx); + + *state = nm; + + return (0); +} + +static int +nm_teardown(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + + for (size_t i = 0; i < MAX_NM; i++) { + isc_nm_destroy(&nm[i]); + assert_null(nm[i]); + } + isc_mem_put(test_mctx, nm, MAX_NM * sizeof(nm[0])); + + if (server_ssl_ctx) { + isc_tlsctx_free(&server_ssl_ctx); + } + + return (0); +} + +thread_local size_t nwrites = NWRITES; + +static void +sockaddr_to_url(isc_sockaddr_t *sa, const bool https, char *outbuf, + size_t outbuf_len, const char *append) { + uint16_t port; + char saddr[INET6_ADDRSTRLEN] = { 0 }; + int family; + if (sa == NULL || outbuf == NULL || outbuf_len == 0) { + return; + } + + family = ((struct sockaddr *)&sa->type.sa)->sa_family; + + port = ntohs(family == AF_INET ? sa->type.sin.sin_port + : sa->type.sin6.sin6_port); + inet_ntop(family, + family == AF_INET + ? (struct sockaddr *)&sa->type.sin.sin_addr + : (struct sockaddr *)&sa->type.sin6.sin6_addr, + saddr, sizeof(saddr)); + + snprintf(outbuf, outbuf_len, "%s://%s%s%s:%u%s", + https ? "https" : "http", family == AF_INET ? "" : "[", saddr, + family == AF_INET ? "" : "]", port, append ? append : ""); +} + +static void +doh_receive_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + uint_fast64_t sends = atomic_load(&nsends); + assert_non_null(handle); + UNUSED(cbarg); + UNUSED(region); + + if (eresult == ISC_R_SUCCESS) { + atomic_fetch_add(&csends, 1); + atomic_fetch_add(&creads, 1); + if (sends > 0) { + atomic_fetch_sub(&nsends, 1); + } + isc_nm_resumeread(handle); + } else { + /* We failed to connect; try again */ + while (sends > 0) { + /* Continue until we subtract or we are done */ + if (atomic_compare_exchange_weak(&nsends, &sends, + sends - 1)) { + sends--; + break; + } + } + atomic_store(&was_error, true); + /* Send failed, we need to stop reading too */ + isc_nm_cancelread(handle); + } +} + +static void +doh_reply_sent_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) { + UNUSED(eresult); + UNUSED(cbarg); + + assert_non_null(handle); + + if (eresult == ISC_R_SUCCESS) { + atomic_fetch_add(&ssends, 1); + } +} + +static void +doh_receive_request_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + uint64_t magic = 0; + + UNUSED(cbarg); + assert_non_null(handle); + + if (eresult != ISC_R_SUCCESS) { + atomic_store(&was_error, true); + return; + } + + atomic_fetch_add(&sreads, 1); + + memmove(tcp_buffer_storage + tcp_buffer_length, region->base, + region->length); + tcp_buffer_length += region->length; + + while (tcp_buffer_length >= sizeof(magic)) { + magic = *(uint64_t *)tcp_buffer_storage; + assert_true(magic == stop_magic || magic == send_magic); + + tcp_buffer_length -= sizeof(magic); + memmove(tcp_buffer_storage, tcp_buffer_storage + sizeof(magic), + tcp_buffer_length); + + if (magic == send_magic) { + isc_nm_send(handle, region, doh_reply_sent_cb, NULL); + return; + } else if (magic == stop_magic) { + /* We are done, so we don't send anything back */ + /* There should be no more packets in the buffer */ + assert_int_equal(tcp_buffer_length, 0); + } + } +} + +static void +mock_doh_uv_tcp_bind(void **state) { + isc_nm_t **nm = (isc_nm_t **)*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); + + result = isc_nm_listenhttp(listen_nm, (isc_nmiface_t *)&tcp_listen_addr, + 0, NULL, NULL, &listen_sock); + assert_int_not_equal(result, ISC_R_SUCCESS); + assert_null(listen_sock); + + RESET_RETURN; +} + +static void +doh_noop(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp(listen_nm, (isc_nmiface_t *)&tcp_listen_addr, + 0, NULL, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + noop_read_cb, NULL, 0); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + sockaddr_to_url(&tcp_listen_addr, false, req_url, sizeof(req_url), + DOH_PATH); + (void)isc_nm_http_connect_send_request( + connect_nm, req_url, atomic_load(&POST), + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + noop_read_cb, NULL, NULL, 30000); + + isc_nm_closedown(connect_nm); + + assert_int_equal(0, atomic_load(&csends)); + assert_int_equal(0, atomic_load(&creads)); + assert_int_equal(0, atomic_load(&sreads)); + assert_int_equal(0, atomic_load(&ssends)); +} + +static void +doh_noop_POST(void **state) { + atomic_store(&POST, true); + doh_noop(state); +} + +static void +doh_noop_GET(void **state) { + atomic_store(&POST, false); + doh_noop(state); +} + +static void +doh_noresponse(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp(listen_nm, (isc_nmiface_t *)&tcp_listen_addr, + 0, NULL, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + noop_read_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + sockaddr_to_url(&tcp_listen_addr, false, req_url, sizeof(req_url), + DOH_PATH); + (void)isc_nm_http_connect_send_request( + connect_nm, req_url, atomic_load(&POST), + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + noop_read_cb, NULL, NULL, 30000); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc_nm_closedown(connect_nm); +} + +static void +doh_noresponse_POST(void **state) { + atomic_store(&POST, true); + doh_noresponse(state); +} + +static void +doh_noresponse_GET(void **state) { + atomic_store(&POST, false); + doh_noresponse(state); +} + +static void +doh_receive_send_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + uint_fast64_t sends = atomic_load(&nsends); + assert_non_null(handle); + UNUSED(region); + + if (eresult == ISC_R_SUCCESS) { + atomic_fetch_add(&csends, 1); + atomic_fetch_add(&creads, 1); + if (sends > 0) { + size_t i; + atomic_fetch_sub(&nsends, 1); + for (i = 0; i < NWRITES / 2; i++) { + eresult = isc_nm_httprequest( + handle, + &(isc_region_t){ + .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_send_reply_cb, cbarg); + assert_true(eresult == ISC_R_SUCCESS); + } + } + } else { + /* We failed to connect; try again */ + while (sends > 0) { + /* Continue until we subtract or we are done */ + if (atomic_compare_exchange_weak(&nsends, &sends, + sends - 1)) { + sends--; + break; + } + } + atomic_store(&was_error, true); + } +} + +static isc_threadresult_t +doh_connect_thread(isc_threadarg_t arg) { + isc_nm_t *connect_nm = (isc_nm_t *)arg; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + sockaddr_to_url(&tcp_listen_addr, atomic_load(&use_TLS), req_url, + sizeof(req_url), DOH_PATH); + + while (atomic_load(&nsends) > 0) { + (void)isc_nm_http_connect_send_request( + connect_nm, req_url, atomic_load(&POST), + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_send_reply_cb, NULL, NULL, 5000); + } + + return ((isc_threadresult_t)0); +} + +static void +doh_recv_one(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + atomic_store(&nsends, 1); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + sockaddr_to_url(&tcp_listen_addr, atomic_load(&use_TLS), req_url, + sizeof(req_url), DOH_PATH); + result = isc_nm_http_connect_send_request( + connect_nm, req_url, atomic_load(&POST), + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_reply_cb, NULL, NULL, 5000); + + assert_int_equal(result, ISC_R_SUCCESS); + + while (atomic_load(&nsends) > 0) { + if (atomic_load(&was_error)) { + break; + } + isc_thread_yield(); + } + + while (atomic_load(&ssends) != 1 || atomic_load(&sreads) != 1 || + atomic_load(&csends) != 1) + { + if (atomic_load(&was_error)) { + break; + } + isc_thread_yield(); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc_nm_closedown(connect_nm); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + assert_int_equal(atomic_load(&csends), 1); + assert_int_equal(atomic_load(&creads), 1); + assert_int_equal(atomic_load(&sreads), 1); + assert_int_equal(atomic_load(&ssends), 1); +} + +static void +doh_recv_one_POST(void **state) { + atomic_store(&POST, true); + doh_recv_one(state); +} + +static void +doh_recv_one_GET(void **state) { + atomic_store(&POST, false); + doh_recv_one(state); +} + +static void +doh_recv_one_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_recv_one(state); +} + +static void +doh_recv_one_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_recv_one(state); +} + +static void +doh_connect_send_two_requests_cb(isc_nmhandle_t *handle, isc_result_t result, + void *arg) { + REQUIRE(VALID_NMHANDLE(handle)); + if (result != ISC_R_SUCCESS) { + goto error; + } + + result = isc_nm_httprequest( + handle, + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_reply_cb, arg); + if (result != ISC_R_SUCCESS) { + goto error; + } + + result = isc_nm_httprequest( + handle, + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_reply_cb, arg); + if (result != ISC_R_SUCCESS) { + goto error; + } + + isc_nm_resumeread(handle); + return; +error: + atomic_store(&was_error, true); +} + +static void +doh_recv_two(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + atomic_store(&nsends, 2); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + sockaddr_to_url(&tcp_listen_addr, atomic_load(&use_TLS), req_url, + sizeof(req_url), DOH_PATH); + result = isc_nm_httpconnect( + connect_nm, NULL, NULL, req_url, atomic_load(&POST), + doh_connect_send_two_requests_cb, NULL, NULL, 5000, 0); + + assert_int_equal(result, ISC_R_SUCCESS); + + while (atomic_load(&nsends) > 0) { + if (atomic_load(&was_error)) { + break; + } + isc_thread_yield(); + } + + while (atomic_load(&ssends) != 2 || atomic_load(&sreads) != 2 || + atomic_load(&csends) != 2) + { + if (atomic_load(&was_error)) { + break; + } + isc_thread_yield(); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc_nm_closedown(connect_nm); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + assert_int_equal(atomic_load(&csends), 2); + assert_int_equal(atomic_load(&creads), 2); + assert_int_equal(atomic_load(&sreads), 2); + assert_int_equal(atomic_load(&ssends), 2); +} + +static void +doh_recv_two_POST(void **state) { + atomic_store(&POST, true); + doh_recv_two(state); +} + +static void +doh_recv_two_GET(void **state) { + atomic_store(&POST, false); + doh_recv_two(state); +} + +static void +doh_recv_two_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_recv_two(state); +} + +static void +doh_recv_two_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_recv_two(state); +} + +static void +doh_recv_send(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1); + isc_thread_t threads[32] = { 0 }; + isc_sockaddr_t tcp_connect_addr; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_create(doh_connect_thread, connect_nm, &threads[i]); + } + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + isc_nm_closedown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_FULL(csends); + CHECK_RANGE_FULL(creads); + CHECK_RANGE_FULL(sreads); + CHECK_RANGE_FULL(ssends); +} + +static void +doh_recv_send_POST(void **state) { + atomic_store(&POST, true); + doh_recv_send(state); +} + +static void +doh_recv_send_GET(void **state) { + atomic_store(&POST, false); + doh_recv_send(state); +} + +static void +doh_recv_send_POST_TLS(void **state) { + atomic_store(&POST, true); + atomic_store(&use_TLS, true); + doh_recv_send(state); +} + +static void +doh_recv_send_GET_TLS(void **state) { + atomic_store(&POST, false); + atomic_store(&use_TLS, true); + doh_recv_send(state); +} + +static void +doh_recv_half_send(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1); + isc_thread_t threads[32] = { 0 }; + isc_sockaddr_t tcp_connect_addr; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_create(doh_connect_thread, connect_nm, &threads[i]); + } + + while (atomic_load(&nsends) >= (NSENDS * NWRITES) / 2) { + isc_thread_yield(); + } + + isc_nm_closedown(connect_nm); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +doh_recv_half_send_POST(void **state) { + atomic_store(&POST, true); + doh_recv_half_send(state); +} + +static void +doh_recv_half_send_GET(void **state) { + atomic_store(&POST, false); + doh_recv_half_send(state); +} + +static void +doh_recv_half_send_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_recv_half_send(state); +} + +static void +doh_recv_half_send_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_recv_half_send(state); +} + +static void +doh_half_recv_send(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1); + isc_thread_t threads[32] = { 0 }; + isc_sockaddr_t tcp_connect_addr; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_create(doh_connect_thread, connect_nm, &threads[i]); + } + + while (atomic_load(&nsends) >= (NSENDS * NWRITES) / 2) { + isc_thread_yield(); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + isc_nm_closedown(connect_nm); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +doh_half_recv_send_POST(void **state) { + atomic_store(&POST, true); + doh_half_recv_send(state); +} + +static void +doh_half_recv_send_GET(void **state) { + atomic_store(&POST, false); + doh_half_recv_send(state); +} + +static void +doh_half_recv_send_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_half_recv_send(state); +} + +static void +doh_half_recv_send_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_half_recv_send(state); +} + +static void +doh_half_recv_half_send(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1); + isc_thread_t threads[32] = { 0 }; + isc_sockaddr_t tcp_connect_addr; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_create(doh_connect_thread, connect_nm, &threads[i]); + } + + while (atomic_load(&nsends) >= (NSENDS * NWRITES) / 2) { + isc_thread_yield(); + } + + isc_nm_closedown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +doh_half_recv_half_send_POST(void **state) { + atomic_store(&POST, true); + doh_half_recv_half_send(state); +} + +static void +doh_half_recv_half_send_GET(void **state) { + atomic_store(&POST, false); + doh_half_recv_half_send(state); +} + +static void +doh_half_recv_half_send_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_half_recv_half_send(state); +} + +static void +doh_half_recv_half_send_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_half_recv_half_send(state); +} + +static void +doh_parse_GET_query_string(void **state) { + UNUSED(state); + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = + "dns=AAABAAABAAAAAAAAAWE-" + "NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3" + "QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == strlen(str) - 4); + assert_true(memcmp(queryp, str + 4, len) == 0); + } + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = + "?dns=AAABAAABAAAAAAAAAWE-" + "NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3" + "QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == strlen(str) - 6); + assert_true(memcmp(queryp, str + 5, len) == 0); + } + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns=123&dns=567"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == 3); + assert_true(memcmp(queryp, "567", 3) == 0); + } + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?name1=123&dns=567&name2=123&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == 3); + assert_true(memcmp(queryp, "567", 3) == 0); + } + /* complex, but still valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = + "?title=%D0%92%D1%96%D0%B4%D1%81%D0%BE%D1%82%D0%BA%D0%" + "BE%D0%B2%D0%B5_%D0%BA%D0%BE%D0%B4%D1%83%D0%B2%D0%B0%" + "D0%BD%D0%BD%D1%8F&dns=123&veaction=edit§ion=0"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == 3); + assert_true(memcmp(queryp, "123", 3) == 0); + } + /* invalid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = + "?title=%D0%92%D1%96%D0%B4%D1%81%D0%BE%D1%82%D0%BA%D0%" + "BE%D0%B2%D0%B5_%D0%BA%D0%BE%D0%B4%D1%83%D0%B2%D0%B0%" + "D0%BD%D0%BD%D1%8F&veaction=edit§ion=0"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_false(ret); + assert_null(queryp); + assert_true(len == 0); + } + /* invalid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = ""; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_false(ret); + assert_null(queryp); + assert_true(len == 0); + } + /* invalid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_false(ret); + assert_null(queryp); + assert_true(len == 0); + } + /* invalid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_false(ret); + assert_null(queryp); + assert_true(len == 0); + } + /* invalid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns=&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_false(ret); + assert_null(queryp); + assert_true(len == 0); + } + /* invalid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns=123&&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_false(ret); + assert_null(queryp); + assert_true(len == 0); + } + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns=123%12&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == 6); + assert_true(memcmp(queryp, "123%12", 6) == 0); + } + /* invalid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns=123%ZZ&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_false(ret); + assert_null(queryp); + assert_true(len == 0); + } + /* invalid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns=123%%&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_false(ret); + assert_null(queryp); + assert_true(len == 0); + } + /* invalid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns=123%AZ&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_false(ret); + assert_null(queryp); + assert_true(len == 0); + } + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns=123%0AZ&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == 7); + assert_true(memcmp(queryp, "123%0AZ", 7) == 0); + } +} + +static void +doh_base64url_to_base64(void **state) { + UNUSED(state); + char *res; + size_t res_len = 0; + /* valid */ + { + char test[] = "YW55IGNhcm5hbCBwbGVhc3VyZS4"; + char res_test[] = "YW55IGNhcm5hbCBwbGVhc3VyZS4="; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char test[] = "YW55IGNhcm5hbCBwbGVhcw"; + char res_test[] = "YW55IGNhcm5hbCBwbGVhcw=="; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char test[] = "YW55IGNhcm5hbCBwbGVhc3Vy"; + char res_test[] = "YW55IGNhcm5hbCBwbGVhc3Vy"; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char test[] = "YW55IGNhcm5hbCBwbGVhc3U"; + char res_test[] = "YW55IGNhcm5hbCBwbGVhc3U="; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char test[] = "YW55IGNhcm5hbCBwbGVhcw"; + char res_test[] = "YW55IGNhcm5hbCBwbGVhcw=="; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char test[] = "PDw_Pz8-Pg"; + char res_test[] = "PDw/Pz8+Pg=="; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char test[] = "PDw_Pz8-Pg"; + char res_test[] = "PDw/Pz8+Pg=="; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + NULL); + assert_non_null(res); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* invalid */ + { + char test[] = "YW55IGNhcm5hbCBwbGVhcw"; + res_len = 0; + + res = isc__nm_base64url_to_base64(test_mctx, test, 0, &res_len); + assert_null(res); + assert_true(res_len == 0); + } + /* invalid */ + { + char test[] = ""; + res_len = 0; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + &res_len); + assert_null(res); + assert_true(res_len == 0); + } + /* invalid */ + { + char test[] = "PDw_Pz8-Pg=="; + res_len = 0; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + &res_len); + assert_null(res); + assert_true(res_len == 0); + } + /* invalid */ + { + char test[] = "PDw_Pz8-Pg%3D%3D"; /* percent encoded "==" at the + end */ + res_len = 0; + + res = isc__nm_base64url_to_base64(test_mctx, test, strlen(test), + &res_len); + assert_null(res); + assert_true(res_len == 0); + } + /* invalid */ + { + res_len = 0; + + res = isc__nm_base64url_to_base64(test_mctx, NULL, 31231, + &res_len); + assert_null(res); + assert_true(res_len == 0); + } +} + +static void +doh_base64_to_base64url(void **state) { + char *res; + size_t res_len = 0; + UNUSED(state); + /* valid */ + { + char res_test[] = "YW55IGNhcm5hbCBwbGVhc3VyZS4"; + char test[] = "YW55IGNhcm5hbCBwbGVhc3VyZS4="; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char res_test[] = "YW55IGNhcm5hbCBwbGVhcw"; + char test[] = "YW55IGNhcm5hbCBwbGVhcw=="; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char res_test[] = "YW55IGNhcm5hbCBwbGVhc3Vy"; + char test[] = "YW55IGNhcm5hbCBwbGVhc3Vy"; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char res_test[] = "YW55IGNhcm5hbCBwbGVhc3U"; + char test[] = "YW55IGNhcm5hbCBwbGVhc3U="; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char res_test[] = "YW55IGNhcm5hbCBwbGVhcw"; + char test[] = "YW55IGNhcm5hbCBwbGVhcw=="; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char res_test[] = "PDw_Pz8-Pg"; + char test[] = "PDw/Pz8+Pg=="; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + &res_len); + assert_non_null(res); + assert_true(res_len == strlen(res_test)); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* valid */ + { + char res_test[] = "PDw_Pz8-Pg"; + char test[] = "PDw/Pz8+Pg=="; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + NULL); + assert_non_null(res); + assert_true(strcmp(res, res_test) == 0); + isc_mem_free(test_mctx, res); + } + /* invalid */ + { + char test[] = "YW55IGNhcm5hbCBwbGVhcw"; + res_len = 0; + + res = isc__nm_base64_to_base64url(test_mctx, test, 0, &res_len); + assert_null(res); + assert_true(res_len == 0); + } + /* invalid */ + { + char test[] = ""; + res_len = 0; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + &res_len); + assert_null(res); + assert_true(res_len == 0); + } + /* invalid */ + { + char test[] = "PDw_Pz8-Pg=="; + res_len = 0; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + &res_len); + assert_null(res); + assert_true(res_len == 0); + } + /* invalid */ + { + char test[] = "PDw_Pz8-Pg%3D%3D"; /* percent encoded "==" at the + end */ + res_len = 0; + + res = isc__nm_base64_to_base64url(test_mctx, test, strlen(test), + &res_len); + assert_null(res); + assert_true(res_len == 0); + } + /* invalid */ + { + res_len = 0; + + res = isc__nm_base64_to_base64url(test_mctx, NULL, 31231, + &res_len); + assert_null(res); + assert_true(res_len == 0); + } +} + +/* +static char wikipedia_org_A[] = { 0xae, 0x35, 0x01, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x77, + 0x69, 0x6b, 0x69, 0x70, 0x65, 0x64, 0x69, + 0x61, 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, + 0x01, 0x00, 0x01 }; + +static void +doh_print_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + assert_non_null(handle); + UNUSED(cbarg); + UNUSED(region); + + puts("Cloudflare DNS query result:"); + if (eresult == ISC_R_SUCCESS) { + puts("success!"); + printf("Response size: %lu\n", region->length); + atomic_fetch_add(&creads, 1); + isc_nm_resumeread(handle); + } else { + puts("failure!"); + atomic_store(&was_error, true); + isc_nm_cancelread(handle); + } +} + +static void +doh_cloudflare(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_result_t result = ISC_R_SUCCESS; + + result = isc_nm_http_connect_send_request( + nm[0], "https://cloudflare-dns.com/dns-query", + atomic_load(&POST), + &(isc_region_t){ .base = (uint8_t *)wikipedia_org_A, + .length = sizeof(wikipedia_org_A) }, + doh_print_reply_cb, NULL, NULL, 5000); + + assert_int_equal(result, ISC_R_SUCCESS); + + while (atomic_load(&creads) != 1 && atomic_load(&was_error) == false) { + isc_thread_yield(); + } + + isc_nm_closedown(nm[0]); +} + +static void +doh_cloudflare_POST(void **state) { + atomic_store(&POST, true); + doh_cloudflare(state); +} + +static void +doh_cloudflare_GET(void **state) { + atomic_store(&POST, false); + doh_cloudflare(state); +} +*/ +int +main(void) { + const struct CMUnitTest tests_short[] = { + cmocka_unit_test_setup_teardown(mock_doh_uv_tcp_bind, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_parse_GET_query_string, + NULL, NULL), + cmocka_unit_test_setup_teardown(doh_base64url_to_base64, NULL, + NULL), + cmocka_unit_test_setup_teardown(doh_base64_to_base64url, NULL, + NULL), + cmocka_unit_test_setup_teardown(doh_noop_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_noop_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_noresponse_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_noresponse_GET, nm_setup, + nm_teardown), + }; + + const struct CMUnitTest tests_long[] = { + cmocka_unit_test_setup_teardown(mock_doh_uv_tcp_bind, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_parse_GET_query_string, + NULL, NULL), + cmocka_unit_test_setup_teardown(doh_base64url_to_base64, NULL, + NULL), + cmocka_unit_test_setup_teardown(doh_base64_to_base64url, NULL, + NULL), + cmocka_unit_test_setup_teardown(doh_noop_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_noop_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_noresponse_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_noresponse_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_one_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_one_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_one_POST_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_one_GET_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_two_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_two_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_two_POST_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_two_GET_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_send_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_send_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_send_GET_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_send_POST_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_half_send_GET, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_half_send_POST, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_half_send_GET_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_half_send_POST_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_send_GET, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_send_POST, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_send_GET_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_send_POST_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_half_send_GET, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_half_send_POST, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_half_send_GET_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown( + doh_half_recv_half_send_POST_TLS, nm_setup, + nm_teardown), + /*cmocka_unit_test_setup_teardown(doh_cloudflare_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_cloudflare_POST, nm_setup, + nm_teardown)*/ + }; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + UNUSED(tests_long); + return (cmocka_run_group_tests(tests_short, _setup, _teardown)); +#else + if (getenv("CI") != NULL || !reuse_supported) { + return (cmocka_run_group_tests(tests_short, _setup, _teardown)); + } else { + return (cmocka_run_group_tests(tests_long, _setup, _teardown)); + } +#endif +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (0); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/isc/tls.c b/lib/isc/tls.c index e45678a355..ea5de5dbf9 100644 --- a/lib/isc/tls.c +++ b/lib/isc/tls.c @@ -127,8 +127,10 @@ isc_tlsctx_createclient(isc_tlsctx_t **ctxp) { #if HAVE_SSL_CTX_SET_MIN_PROTO_VERSION SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); #else - SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); + SSL_CTX_set_options( + ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | + SSL_OP_NO_TLSv1_1 | SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); #endif *ctxp = ctx; diff --git a/lib/isc/url.c b/lib/isc/url.c new file mode 100644 index 0000000000..d4e13ee70a --- /dev/null +++ b/lib/isc/url.c @@ -0,0 +1,667 @@ +/* + * 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. + */ + +/* + * Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include + +#ifndef BIT_AT +#define BIT_AT(a, i) \ + (!!((unsigned int)(a)[(unsigned int)(i) >> 3] & \ + (1 << ((unsigned int)(i)&7)))) +#endif + +#if HTTP_PARSER_STRICT +#define T(v) 0 +#else +#define T(v) v +#endif + +static const uint8_t normal_url_char[32] = { + /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, + /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, + /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, + /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +}; + +#undef T + +typedef enum { + s_dead = 1, /* important that this is > 0 */ + + s_start_req_or_res, + s_res_or_resp_H, + s_start_res, + s_res_H, + s_res_HT, + s_res_HTT, + s_res_HTTP, + s_res_http_major, + s_res_http_dot, + s_res_http_minor, + s_res_http_end, + s_res_first_status_code, + s_res_status_code, + s_res_status_start, + s_res_status, + s_res_line_almost_done, + + s_start_req, + + s_req_method, + s_req_spaces_before_url, + s_req_schema, + s_req_schema_slash, + s_req_schema_slash_slash, + s_req_server_start, + s_req_server, + s_req_server_with_at, + s_req_path, + s_req_query_string_start, + s_req_query_string, + s_req_fragment_start, + s_req_fragment, + s_req_http_start, + s_req_http_H, + s_req_http_HT, + s_req_http_HTT, + s_req_http_HTTP, + s_req_http_I, + s_req_http_IC, + s_req_http_major, + s_req_http_dot, + s_req_http_minor, + s_req_http_end, + s_req_line_almost_done, + + s_header_field_start, + s_header_field, + s_header_value_discard_ws, + s_header_value_discard_ws_almost_done, + s_header_value_discard_lws, + s_header_value_start, + s_header_value, + s_header_value_lws, + + s_header_almost_done, + + s_chunk_size_start, + s_chunk_size, + s_chunk_parameters, + s_chunk_size_almost_done, + + s_headers_almost_done, + s_headers_done, + + /* + * Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + s_chunk_data, + s_chunk_data_almost_done, + s_chunk_data_done, + + s_body_identity, + s_body_identity_eof, + + s_message_done +} state_t; + +typedef enum { + s_http_host_dead = 1, + s_http_userinfo_start, + s_http_userinfo, + s_http_host_start, + s_http_host_v6_start, + s_http_host, + s_http_host_v6, + s_http_host_v6_end, + s_http_host_v6_zone_start, + s_http_host_v6_zone, + s_http_host_port_start, + s_http_host_port +} host_state_t; + +/* Macros for character classes; depends on strict-mode */ +#define IS_MARK(c) \ + ((c) == '-' || (c) == '_' || (c) == '.' || (c) == '!' || (c) == '~' || \ + (c) == '*' || (c) == '\'' || (c) == '(' || (c) == ')') +#define IS_USERINFO_CHAR(c) \ + (isalnum(c) || IS_MARK(c) || (c) == '%' || (c) == ';' || (c) == ':' || \ + (c) == '&' || (c) == '=' || (c) == '+' || (c) == '$' || (c) == ',') + +#if HTTP_PARSER_STRICT +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (isalnum(c) || (c) == '.' || (c) == '-') +#else +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c) || ((c)&0x80)) +#define IS_HOST_CHAR(c) (isalnum(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + +/* + * Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static state_t +parse_url_char(state_t s, const char ch) { + if (ch == ' ' || ch == '\r' || ch == '\n') { + return (s_dead); + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return (s_dead); + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI + * (alpha). All methods except CONNECT are followed by '/' or + * '*'. + */ + + if (ch == '/' || ch == '*') { + return (s_req_path); + } + + if (isalpha(ch)) { + return (s_req_schema); + } + + break; + + case s_req_schema: + if (isalpha(ch)) { + return (s); + } + + if (ch == ':') { + return (s_req_schema_slash); + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return (s_req_schema_slash_slash); + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return (s_req_server_start); + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return (s_dead); + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return (s_req_path); + } + + if (ch == '?') { + return (s_req_query_string_start); + } + + if (ch == '@') { + return (s_req_server_with_at); + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return (s_req_server); + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return (s); + } + + switch (ch) { + case '?': + return (s_req_query_string_start); + + case '#': + return (s_req_fragment_start); + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return (s_req_query_string); + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return (s_req_query_string); + + case '#': + return (s_req_fragment_start); + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return (s_req_fragment); + } + + switch (ch) { + case '?': + return (s_req_fragment); + + case '#': + return (s); + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return (s); + } + + switch (ch) { + case '?': + case '#': + return (s); + } + + break; + + default: + break; + } + + /* + * We should never fall out of the switch above unless there's an + * error. + */ + return (s_dead); +} + +static host_state_t +http_parse_host_char(host_state_t s, const char ch) { + switch (s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return (s_http_host_start); + } + + if (IS_USERINFO_CHAR(ch)) { + return (s_http_userinfo); + } + break; + + case s_http_host_start: + if (ch == '[') { + return (s_http_host_v6_start); + } + + if (IS_HOST_CHAR(ch)) { + return (s_http_host); + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return (s_http_host); + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return (s_http_host_port_start); + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return (s_http_host_v6_end); + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (isxdigit(ch) || ch == ':' || ch == '.') { + return (s_http_host_v6); + } + + if (s == s_http_host_v6 && ch == '%') { + return (s_http_host_v6_zone_start); + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return (s_http_host_v6_end); + } + + /* FALLTHROUGH */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (isalnum(ch) || ch == '%' || ch == '.' || ch == '-' || + ch == '_' || ch == '~') + { + return (s_http_host_v6_zone); + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (isdigit(ch)) { + return (s_http_host_port); + } + + break; + + default: + break; + } + + return (s_http_host_dead); +} + +static isc_result_t +http_parse_host(const char *buf, isc_url_parser_t *up, int found_at) { + host_state_t s; + const char *p = NULL; + size_t buflen = up->field_data[ISC_UF_HOST].off + + up->field_data[ISC_UF_HOST].len; + + REQUIRE((up->field_set & (1 << ISC_UF_HOST)) != 0); + + up->field_data[ISC_UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + up->field_data[ISC_UF_HOST].off; p < buf + buflen; p++) { + host_state_t new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return (ISC_R_FAILURE); + } + + switch (new_s) { + case s_http_host: + if (s != s_http_host) { + up->field_data[ISC_UF_HOST].off = + (uint16_t)(p - buf); + } + up->field_data[ISC_UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + up->field_data[ISC_UF_HOST].off = + (uint16_t)(p - buf); + } + up->field_data[ISC_UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + up->field_data[ISC_UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + up->field_data[ISC_UF_PORT].off = + (uint16_t)(p - buf); + up->field_data[ISC_UF_PORT].len = 0; + up->field_set |= (1 << ISC_UF_PORT); + } + up->field_data[ISC_UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + up->field_data[ISC_UF_USERINFO].off = + (uint16_t)(p - buf); + up->field_data[ISC_UF_USERINFO].len = 0; + up->field_set |= (1 << ISC_UF_USERINFO); + } + up->field_data[ISC_UF_USERINFO].len++; + break; + + default: + break; + } + + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return (ISC_R_FAILURE); + default: + break; + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_url_parse(const char *buf, size_t buflen, bool is_connect, + isc_url_parser_t *up) { + state_t s; + isc_url_field_t uf, old_uf; + int found_at = 0; + const char *p = NULL; + + if (buflen == 0) { + return (ISC_R_FAILURE); + } + + up->port = up->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = ISC_UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return (ISC_R_FAILURE); + + /* Skip delimiters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = ISC_UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + /* FALLTHROUGH */ + case s_req_server: + uf = ISC_UF_HOST; + break; + + case s_req_path: + uf = ISC_UF_PATH; + break; + + case s_req_query_string: + uf = ISC_UF_QUERY; + break; + + case s_req_fragment: + uf = ISC_UF_FRAGMENT; + break; + + default: + INSIST(0); + ISC_UNREACHABLE(); + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + up->field_data[uf].len++; + continue; + } + + up->field_data[uf].off = (uint16_t)(p - buf); + up->field_data[uf].len = 1; + + up->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((up->field_set & (1 << ISC_UF_SCHEMA)) && + (up->field_set & (1 << ISC_UF_HOST)) == 0) + { + return (ISC_R_FAILURE); + } + + if (up->field_set & (1 << ISC_UF_HOST)) { + isc_result_t result; + + result = http_parse_host(buf, up, found_at); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && + up->field_set != ((1 << ISC_UF_HOST) | (1 << ISC_UF_PORT))) { + return (ISC_R_FAILURE); + } + + if (up->field_set & (1 << ISC_UF_PORT)) { + uint16_t off; + uint16_t len; + const char *pp = NULL; + const char *end = NULL; + unsigned long v; + + off = up->field_data[ISC_UF_PORT].off; + len = up->field_data[ISC_UF_PORT].len; + end = buf + off + len; + + /* + * NOTE: The characters are already validated and are in the + * [0-9] range + */ + INSIST(off + len <= buflen); + + v = 0; + for (pp = buf + off; pp < end; pp++) { + v *= 10; + v += *pp - '0'; + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return (ISC_R_RANGE); + } + } + + up->port = (uint16_t)v; + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/win32/libisc.def.in b/lib/isc/win32/libisc.def.in index ce691b6a78..e460d5757e 100644 --- a/lib/isc/win32/libisc.def.in +++ b/lib/isc/win32/libisc.def.in @@ -448,7 +448,14 @@ 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_httpconnect +isc_nm_httprequest +isc_nm_listenhttp isc_nm_listentcpdns +isc_nm_listentls isc_nm_listentlsdns isc_nm_listentcp isc_nm_listenudp @@ -706,6 +713,7 @@ isc_tlsctx_createserver isc_tlsctx_free isc_tm_timegm isc_tm_strptime +isc_url_parse isc_utf8_bom isc_utf8_valid isc_win32os_versioncheck diff --git a/lib/isc/win32/libisc.vcxproj.filters.in b/lib/isc/win32/libisc.vcxproj.filters.in index fea6c87ab4..aab1a899ae 100644 --- a/lib/isc/win32/libisc.vcxproj.filters.in +++ b/lib/isc/win32/libisc.vcxproj.filters.in @@ -260,6 +260,9 @@ Library Header Files + + Library Header Files + Library Header Files @@ -608,12 +611,15 @@ Library Source Files - + Library Source Files Library Source Files + + Library Source Files + Library Source Files diff --git a/lib/isc/win32/libisc.vcxproj.in b/lib/isc/win32/libisc.vcxproj.in index 72e7e0cb50..7d989c5591 100644 --- a/lib/isc/win32/libisc.vcxproj.in +++ b/lib/isc/win32/libisc.vcxproj.in @@ -62,11 +62,11 @@ @IF PKCS11 BIND9;@PK11_LIB_LOCATION@WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions) ..\..\..\config.h - .\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;..\..\dns\win32\include;..\..\dns\include;%(AdditionalIncludeDirectories) + .\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@NGHTTP2_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;..\..\dns\win32\include;..\..\dns\include;%(AdditionalIncludeDirectories) @ELSE PKCS11 BIND9;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions) ..\..\..\config.h - .\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;%(AdditionalIncludeDirectories) + .\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@NGHTTP2_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;%(AdditionalIncludeDirectories) @END PKCS11 true .\$(Configuration)\$(TargetName).pch @@ -80,7 +80,7 @@ Console true ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) - @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@@LIBUV_LIB@@LIBXML2_LIB@@ZLIB_LIB@ws2_32.lib;%(AdditionalDependencies) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@@LIBUV_LIB@@NGHTTP2_LIB@@LIBXML2_LIB@@ZLIB_LIB@ws2_32.lib;%(AdditionalDependencies) $(ProjectName).def .\$(Configuration)\$(ProjectName).lib @@ -116,6 +116,9 @@ echo Copying the libxml DLL. copy @LIBXML2_DLL@ ..\Build\Debug\ @END LIBXML2 +echo Copying nghttp2 DLL. +copy @NGHTTP2_DLL@ ..\Build\Debug\ + @IF GSSAPI echo Copying the GSSAPI and KRB5 DLLs. @@ -163,11 +166,11 @@ copy InstallFiles ..\Build\Debug\ @IF PKCS11 BIND9;@PK11_LIB_LOCATION@WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions) ..\..\..\config.h - .\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;..\..\dns\win32\include;..\..\dns\include;%(AdditionalIncludeDirectories) + .\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@NGHTTP2_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;..\..\dns\win32\include;..\..\dns\include;%(AdditionalIncludeDirectories) @ELSE PKCS11 BIND9;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions) ..\..\..\config.h - .\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;%(AdditionalIncludeDirectories) + .\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@NGHTTP2_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;%(AdditionalIncludeDirectories) @END PKCS11 OnlyExplicitInline false @@ -184,7 +187,7 @@ copy InstallFiles ..\Build\Debug\ true true ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) - @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@@LIBUV_LIB@@LIBXML2_LIB@@ZLIB_LIB@ws2_32.lib;%(AdditionalDependencies) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@@LIBUV_LIB@@NGHTTP2_LIB@@LIBXML2_LIB@@ZLIB_LIB@ws2_32.lib;%(AdditionalDependencies) $(ProjectName).def .\$(Configuration)\$(ProjectName).lib Default @@ -211,6 +214,9 @@ copy @OPENSSL_PATH@\LICENSE ..\Build\Release\OpenSSL-LICENSE echo Copying libuv DLL. copy @LIBUV_DLL@ ..\Build\Release\ +echo Copying nghttp2 DLL. +copy @NGHTTP2_DLL@ ..\Build\Release\ + @IF LIBXML2 echo Copying the libxml DLL. @@ -337,6 +343,7 @@ copy InstallFiles ..\Build\Release\ + @IF PKCS11 @@ -408,13 +415,15 @@ copy InstallFiles ..\Build\Release\ + - - - + + + + @@ -442,6 +451,7 @@ copy InstallFiles ..\Build\Release\ + @IF PKCS11 diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index c18196ea5b..e90ab215f8 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -81,6 +81,7 @@ static cfg_type_t cfg_type_bracketed_dscpsockaddrlist; static cfg_type_t cfg_type_bracketed_namesockaddrkeylist; static cfg_type_t cfg_type_bracketed_netaddrlist; static cfg_type_t cfg_type_bracketed_sockaddrnameportlist; +static cfg_type_t cfg_type_bracketed_qstring_list; static cfg_type_t cfg_type_controls; static cfg_type_t cfg_type_controls_sockaddr; static cfg_type_t cfg_type_destinationlist; @@ -91,6 +92,7 @@ static cfg_type_t cfg_type_dnstap; static cfg_type_t cfg_type_dnstapoutput; static cfg_type_t cfg_type_dyndb; static cfg_type_t cfg_type_plugin; +static cfg_type_t cfg_type_http_description; static cfg_type_t cfg_type_ixfrdifftype; static cfg_type_t cfg_type_ixfrratio; static cfg_type_t cfg_type_key; @@ -108,6 +110,7 @@ static cfg_type_t cfg_type_optional_allow; static cfg_type_t cfg_type_optional_class; static cfg_type_t cfg_type_optional_dscp; static cfg_type_t cfg_type_optional_facility; +static cfg_type_t cfg_type_optional_http; static cfg_type_t cfg_type_optional_keyref; static cfg_type_t cfg_type_optional_port; static cfg_type_t cfg_type_optional_uint32; @@ -151,6 +154,7 @@ static cfg_tuplefielddef_t listenon_fields[] = { { "port", &cfg_type_optional_port, 0 }, { "dscp", &cfg_type_optional_dscp, 0 }, { "tls", &cfg_type_optional_tls, 0 }, + { "http", &cfg_type_optional_http, 0 }, { "acl", &cfg_type_bracketed_aml, 0 }, { NULL, NULL, 0 } }; @@ -1088,6 +1092,7 @@ static cfg_clausedef_t namedconf_clauses[] = { { "acl", &cfg_type_acl, CFG_CLAUSEFLAG_MULTI }, { "controls", &cfg_type_controls, CFG_CLAUSEFLAG_MULTI }, { "dnssec-policy", &cfg_type_dnssecpolicy, CFG_CLAUSEFLAG_MULTI }, + { "http", &cfg_type_http_description, CFG_CLAUSEFLAG_MULTI }, { "logging", &cfg_type_logging, 0 }, { "lwres", NULL, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_ANCIENT }, { "masters", &cfg_type_primaries, CFG_CLAUSEFLAG_MULTI }, @@ -1221,6 +1226,8 @@ static cfg_clausedef_t options_clauses[] = { { "pid-file", &cfg_type_qstringornone, 0 }, { "port", &cfg_type_uint32, 0 }, { "tls-port", &cfg_type_uint32, 0 }, + { "http-port", &cfg_type_uint32, 0 }, + { "https-port", &cfg_type_uint32, 0 }, { "querylog", &cfg_type_boolean, 0 }, { "random-device", &cfg_type_qstringornone, 0 }, { "recursing-file", &cfg_type_qstring, 0 }, @@ -3853,3 +3860,31 @@ static cfg_type_t cfg_type_optional_tls = { "tlsoptional", parse_optional_keyvalue, print_keyvalue, doc_optional_keyvalue, &cfg_rep_string, &tls_kw }; + +/* http and https */ + +static cfg_type_t cfg_type_bracketed_qstring_list = { "bracketed_qstring_list", + cfg_parse_bracketed_list, + cfg_print_bracketed_list, + cfg_doc_bracketed_list, + &cfg_rep_list, + &cfg_type_qstring }; + +static cfg_clausedef_t cfg_http_description_clauses[] = { + { "endpoints", &cfg_type_bracketed_qstring_list, 0 }, { NULL, NULL, 0 } +}; + +static cfg_clausedef_t *http_description_clausesets[] = { + cfg_http_description_clauses, NULL +}; + +static cfg_type_t cfg_type_http_description = { + "http_desc", cfg_parse_named_map, cfg_print_map, + cfg_doc_map, &cfg_rep_map, http_description_clausesets +}; + +static keyword_type_t http_kw = { "http", &cfg_type_astring }; +static cfg_type_t cfg_type_optional_http = { + "http_optional", parse_optional_keyvalue, print_keyvalue, + doc_optional_keyvalue, &cfg_rep_string, &http_kw +}; diff --git a/lib/ns/include/ns/interfacemgr.h b/lib/ns/include/ns/interfacemgr.h index e210ee3bc0..152ce4b76c 100644 --- a/lib/ns/include/ns/interfacemgr.h +++ b/lib/ns/include/ns/interfacemgr.h @@ -80,6 +80,8 @@ struct ns_interface { isc_socket_t * tcpsocket; /*%< TCP socket. */ isc_nmsocket_t *udplistensocket; isc_nmsocket_t *tcplistensocket; + isc_nmsocket_t *http_listensocket; + isc_nmsocket_t *http_secure_listensocket; isc_dscp_t dscp; /*%< "listen-on" DSCP value */ isc_refcount_t ntcpaccepting; /*%< Number of clients * ready to accept new diff --git a/lib/ns/include/ns/listenlist.h b/lib/ns/include/ns/listenlist.h index fe6dffde7e..8393d67980 100644 --- a/lib/ns/include/ns/listenlist.h +++ b/lib/ns/include/ns/listenlist.h @@ -42,9 +42,12 @@ typedef struct ns_listenlist ns_listenlist_t; struct ns_listenelt { isc_mem_t * mctx; in_port_t port; + bool is_http; isc_dscp_t dscp; /* -1 = not set, 0..63 */ dns_acl_t * acl; isc_tlsctx_t *sslctx; + char ** http_endpoints; + size_t http_endpoints_number; ISC_LINK(ns_listenelt_t) link; }; @@ -66,6 +69,15 @@ ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, * Create a listen-on list element. */ +isc_result_t +ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, + dns_acl_t *acl, bool tls, const char *key, + const char *cert, char **endpoints, size_t nendpoints, + ns_listenelt_t **target); +/*%< + * Create a listen-on list element for HTTP(S). + */ + void ns_listenelt_destroy(ns_listenelt_t *elt); /*%< diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c index 8f73279263..a1443410a7 100644 --- a/lib/ns/interfacemgr.c +++ b/lib/ns/interfacemgr.c @@ -437,6 +437,10 @@ ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, goto failure; } + ifp->tcplistensocket = NULL; + ifp->http_listensocket = NULL; + ifp->http_secure_listensocket = NULL; + *ifpret = ifp; return (ISC_R_SUCCESS); @@ -539,6 +543,54 @@ ns_interface_listentls(ns_interface_t *ifp, isc_tlsctx_t *sslctx) { return (result); } +static isc_result_t +ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps, + size_t neps) { + isc_result_t result; + isc_nmsocket_t *sock = NULL; + size_t i = 0; + + result = isc_nm_listenhttp(ifp->mgr->nm, (isc_nmiface_t *)&ifp->addr, + ifp->mgr->backlog, &ifp->mgr->sctx->tcpquota, + sslctx, &sock); + + if (result == ISC_R_SUCCESS) { + for (i = 0; i < neps; i++) { + result = isc_nm_http_add_doh_endpoint( + sock, eps[i], ns__client_request, ifp, + sizeof(ns_client_t)); + } + } + + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "creating %s socket: %s", + sslctx ? "HTTPS" : "HTTP", + isc_result_totext(result)); + return (result); + } + + if (sslctx) { + ifp->http_secure_listensocket = sock; + } else { + ifp->http_listensocket = sock; + } + + /* + * We call this now to update the tcp-highwater statistic: + * this is necessary because we are adding to the TCP quota just + * by listening. + */ + result = ns__client_tcpconn(NULL, ISC_R_SUCCESS, ifp); + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "updating TCP stats: %s", + isc_result_totext(result)); + } + + return (result); +} + static isc_result_t ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name, ns_interface_t **ifpret, bool accept_tcp, @@ -555,6 +607,17 @@ ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, ifp->dscp = elt->dscp; + if (elt->is_http) { + result = ns_interface_listenhttp(ifp, elt->sslctx, + elt->http_endpoints, + elt->http_endpoints_number); + if (result != ISC_R_SUCCESS) { + goto cleanup_interface; + } + *ifpret = ifp; + return (result); + } + if (elt->sslctx != NULL) { result = ns_interface_listentls(ifp, elt->sslctx); if (result != ISC_R_SUCCESS) { @@ -611,6 +674,14 @@ ns_interface_shutdown(ns_interface_t *ifp) { isc_nm_stoplistening(ifp->tcplistensocket); isc_nmsocket_close(&ifp->tcplistensocket); } + if (ifp->http_listensocket != NULL) { + isc_nm_stoplistening(ifp->http_listensocket); + isc_nmsocket_close(&ifp->http_listensocket); + } + if (ifp->http_secure_listensocket != NULL) { + isc_nm_stoplistening(ifp->http_secure_listensocket); + isc_nmsocket_close(&ifp->http_secure_listensocket); + } if (ifp->clientmgr != NULL) { ns_clientmgr_destroy(&ifp->clientmgr); } diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c index c5153ca60c..2d88c8654f 100644 --- a/lib/ns/listenlist.c +++ b/lib/ns/listenlist.c @@ -30,24 +30,59 @@ ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, ns_listenelt_t **target) { ns_listenelt_t *elt = NULL; isc_result_t result = ISC_R_SUCCESS; + isc_tlsctx_t *sslctx = NULL; + REQUIRE(target != NULL && *target == NULL); - elt = isc_mem_get(mctx, sizeof(*elt)); - elt->mctx = mctx; - ISC_LINK_INIT(elt, link); - elt->port = port; - elt->dscp = dscp; - elt->acl = acl; - elt->sslctx = NULL; + if (tls) { - result = isc_tlsctx_createserver(key, cert, &elt->sslctx); + result = isc_tlsctx_createserver(key, cert, &sslctx); if (result != ISC_R_SUCCESS) { return (result); } } + + elt = isc_mem_get(mctx, sizeof(*elt)); + elt->mctx = mctx; + ISC_LINK_INIT(elt, link); + elt->port = port; + elt->is_http = false; + elt->dscp = dscp; + elt->acl = acl; + elt->sslctx = sslctx; + elt->http_endpoints = NULL; + elt->http_endpoints_number = 0; + *target = elt; return (ISC_R_SUCCESS); } +isc_result_t +ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, + dns_acl_t *acl, bool tls, const char *key, + const char *cert, char **endpoints, size_t nendpoints, + ns_listenelt_t **target) { + isc_result_t result; + + REQUIRE(target != NULL && *target == NULL); + REQUIRE(endpoints != NULL && *endpoints != NULL); + REQUIRE(nendpoints > 0); + + result = ns_listenelt_create(mctx, http_port, dscp, acl, tls, key, cert, + target); + if (result == ISC_R_SUCCESS) { + (*target)->is_http = true; + (*target)->http_endpoints = endpoints; + (*target)->http_endpoints_number = nendpoints; + } else { + size_t i; + for (i = 0; i < nendpoints; i++) { + isc_mem_free(mctx, endpoints[i]); + } + isc_mem_free(mctx, endpoints); + } + return (result); +} + void ns_listenelt_destroy(ns_listenelt_t *elt) { if (elt->acl != NULL) { @@ -56,6 +91,14 @@ ns_listenelt_destroy(ns_listenelt_t *elt) { if (elt->sslctx != NULL) { isc_tlsctx_free(&elt->sslctx); } + if (elt->http_endpoints != NULL) { + size_t i; + INSIST(elt->http_endpoints_number > 0); + for (i = 0; i < elt->http_endpoints_number; i++) { + isc_mem_free(elt->mctx, elt->http_endpoints[i]); + } + isc_mem_free(elt->mctx, elt->http_endpoints); + } isc_mem_put(elt->mctx, elt, sizeof(*elt)); } diff --git a/lib/ns/win32/libns.def b/lib/ns/win32/libns.def index 66d706af4f..a08ba68d1d 100644 --- a/lib/ns/win32/libns.def +++ b/lib/ns/win32/libns.def @@ -66,6 +66,7 @@ ns_interfacemgr_shutdown ns_lib_init ns_lib_shutdown ns_listenelt_create +ns_listenelt_create_http ns_listenelt_destroy ns_listenlist_attach ns_listenlist_create diff --git a/util/copyrights b/util/copyrights index 639c1bb3b4..137caa0fba 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1887,6 +1887,7 @@ ./lib/isc/include/isc/tls.h C 2021 ./lib/isc/include/isc/tm.h C 2014,2016,2018,2019,2020,2021 ./lib/isc/include/isc/types.h C 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2012,2013,2014,2016,2017,2018,2019,2020,2021 +./lib/isc/include/isc/url.h C 2021 ./lib/isc/include/isc/utf8.h C 2020,2021 ./lib/isc/include/isc/util.h C 1998,1999,2000,2001,2004,2005,2006,2007,2010,2011,2012,2015,2016,2017,2018,2019,2020,2021 ./lib/isc/include/pk11/constants.h C 2014,2016,2017,2018,2019,2020,2021 @@ -1904,11 +1905,13 @@ ./lib/isc/mem_p.h C 2018,2019,2020,2021 ./lib/isc/mutexblock.c C 1999,2000,2001,2004,2005,2007,2011,2012,2016,2018,2019,2020,2021 ./lib/isc/netaddr.c C 1999,2000,2001,2002,2004,2005,2007,2010,2011,2012,2014,2015,2016,2017,2018,2019,2020,2021 +./lib/isc/netmgr/http.c C 2021 ./lib/isc/netmgr/netmgr-int.h C 2019,2020,2021 ./lib/isc/netmgr/netmgr.c C 2019,2020,2021 ./lib/isc/netmgr/tcp.c C 2019,2020,2021 ./lib/isc/netmgr/tcpdns.c C 2019,2020,2021 ./lib/isc/netmgr/tlsdns.c C 2020,2021 +./lib/isc/netmgr/tlsstream.c C 2019,2020,2021 ./lib/isc/netmgr/udp.c C 2019,2020,2021 ./lib/isc/netmgr/uv-compat.c C 2020,2021 ./lib/isc/netmgr/uv-compat.h C 2019,2020,2021 @@ -1952,6 +1955,7 @@ ./lib/isc/tests/buffer_test.c C 2014,2015,2016,2017,2018,2019,2020,2021 ./lib/isc/tests/counter_test.c C 2014,2016,2018,2019,2020,2021 ./lib/isc/tests/crc64_test.c C 2018,2019,2020,2021 +./lib/isc/tests/doh_test.c C 2020,2021 ./lib/isc/tests/errno_test.c C 2016,2018,2019,2020,2021 ./lib/isc/tests/file_test.c C 2014,2016,2017,2018,2019,2020,2021 ./lib/isc/tests/hash_test.c C 2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021 @@ -2018,6 +2022,7 @@ ./lib/isc/unix/stdtime.c C 1999,2000,2001,2004,2005,2007,2016,2018,2019,2020,2021 ./lib/isc/unix/syslog.c C 2001,2004,2005,2007,2016,2018,2019,2020,2021 ./lib/isc/unix/time.c C 1998,1999,2000,2001,2003,2004,2005,2006,2007,2008,2011,2012,2014,2015,2016,2017,2018,2019,2020,2021 +./lib/isc/url.c C 2021 ./lib/isc/utf8.c C 2020,2021 ./lib/isc/win32/DLLMain.c C 2001,2004,2007,2016,2018,2019,2020,2021 ./lib/isc/win32/condition.c C 1998,1999,2000,2001,2004,2006,2007,2016,2018,2019,2020,2021 diff --git a/win32utils/Configure b/win32utils/Configure index 7ae633d40f..57e94d70a4 100644 --- a/win32utils/Configure +++ b/win32utils/Configure @@ -221,6 +221,7 @@ my @substinc = ("GSSAPI_INC", "IDN_INC", "LIBXML2_INC", "LIBUV_INC", + "NGHTTP2_INC", "OPENSSL_INC", "READLINE_INC", "ZLIB_INC"); @@ -235,6 +236,7 @@ my @substlib = ("GSSAPI_LIB", "KRB5_LIB", "LIBXML2_LIB", "LIBUV_LIB", + "NGHTTP2_LIB", "OPENSSL_LIBCRYPTO", "OPENSSL_LIBSSL", "READLINE_LIB", @@ -253,6 +255,7 @@ my @substdll = ("COMERR_DLL", "K5SPRT_DLL", "LIBXML2_DLL", "LIBUV_DLL", + "NGHTTP2_DLL", "OPENSSL_DLLCRYPTO", "OPENSSL_DLLSSL", "WSHELP_DLL", @@ -341,6 +344,7 @@ my @withlist = ("aes", "idn", "openssl", "libxml2", + "nghttp2", "pkcs11", "pssuspend", "python", @@ -389,6 +393,7 @@ my @help = ( " with-samples build with sample programs\n", " with-openssl[=PATH] build with OpenSSL yes|path (mandatory)\n", " with-libuv[=PATH] build with libuv yes|path (mandatory)\n", +" with-nghttp2[=PATH] build with nghttp2 yes|path (mandatory)\n", " with-pkcs11[=PATH] build with PKCS#11 support yes|no|provider-path\n", " with-gssapi[=PATH] build with MIT KfW GSSAPI yes|no|path\n", " with-libxml2[=PATH] build with libxml2 library yes|no|path\n", @@ -431,6 +436,8 @@ my $use_stests = "no"; my $use_samples = "no"; my $use_libuv = "auto"; my $libuv_path = "../../"; +my $nghttp2_path = "../../"; +my $use_nghttp2 = "auto"; my $use_openssl = "auto"; my $openssl_path = "../../"; my $use_pkcs11 = "no"; @@ -758,6 +765,13 @@ sub mywith { $use_libuv = "yes"; $libuv_path = $val; } + } elsif ($key =~ /^nghttp2$/i) { + if ($val =~ /^no$/i) { + die "nghttp2 is required\n"; + } elsif ($val !~ /^yes$/i) { + $use_nghttp2 = "yes"; + $nghttp2_path = $val; + } } elsif ($key =~ /^pkcs11$/i) { if ($val =~ /^yes$/i) { $use_pkcs11 = "yes"; @@ -949,6 +963,7 @@ if ($verbose) { print "querytrace: disabled\n"; } print "libuv-path: $libuv_path\n"; + print "nghttp2-path: $nghttp2_path\n"; print "openssl-path: $openssl_path\n"; if ($use_tests eq "yes") { print "tests: enabled\n"; @@ -1327,6 +1342,63 @@ if ($use_libuv eq "yes") { # $configdefh{"HAVE_UV_IMPORT"} = 1; } +# with-nghttp2 +if ($use_nghttp2 eq "auto") { + if ($verbose) { + print "checking for an nghttp2 built directory at sibling root\n"; + } + opendir DIR, $nghttp2_path || die "No Directory: $!\n"; + my @dirlist = grep (/^nghttp2-[0-9]+\.[0-9]+\.[0-9]+$/i, readdir(DIR)); + closedir(DIR); + + # Make sure we have something + if (scalar(@dirlist) == 0) { + die "can't find an nghttp2 at sibling root\n"; + } + # Now see if we have a directory or just a file. + # Make sure we are case insensitive + my $file; + foreach $file (sort {uc($b) cmp uc($a)} @dirlist) { + if (-f File::Spec->catfile($nghttp2_path, + $file, + "include", "nghttp2", "nghttp2.h")) { + $nghttp2_path = File::Spec->catdir($nghttp2_path, $file); + $use_nghttp2 = "yes"; + last; + } + } + + # If we have one use it otherwise report the error + if ($use_nghttp2 eq "auto") { + die "can't find an nghttp2 built directory at sibling root\n"; + } +} + +if ($use_nghttp2 eq "yes") { + $nghttp2_path = File::Spec->rel2abs($nghttp2_path); + if ($verbose) { + print "checking for nghttp2 directory at \"$nghttp2_path\"\n"; + } + if (!-f File::Spec->catfile($nghttp2_path, + "include", "nghttp2", "nghttp2.h")) { + die "can't find nghttp2 nghttp2.h include\n"; + } + my $nghttp2_inc = File::Spec->catdir($nghttp2_path, "include"); + my $nghttp2_bindir = File::Spec->catdir($nghttp2_path, "bin"); + my $nghttp2_libdir = File::Spec->catdir($nghttp2_path, "lib"); + my $nghttp2_dll = File::Spec->catfile($nghttp2_bindir, "nghttp2.dll"); + my $nghttp2_lib = File::Spec->catfile($nghttp2_libdir, "nghttp2.lib"); + if (!-f $nghttp2_lib) { + die "can't find nghttp2.lib library\n"; + } + if (!-f $nghttp2_dll) { + die "can't find nghttp2.dll library\n"; + } + $configinc{"NGHTTP2_INC"} = "$nghttp2_inc"; + $configlib{"NGHTTP2_LIB"} = "$nghttp2_lib"; + $configdll{"NGHTTP2_DLL"} = "$nghttp2_dll"; +} + # with-openssl if ($use_openssl eq "auto") { if ($verbose) { @@ -2433,6 +2505,7 @@ sub makeinstallfile { print LOUT "libdns.dll-BCFT\n"; print LOUT "libirs.dll-BCFT\n"; print LOUT "libns.dll-BCFT\n"; + print LOUT "nghttp2.dll-BCFT\n"; print LOUT "uv.dll-BCFT\n"; if ($use_openssl eq "yes") { my $v;