From 08da09bc76c83e82c7aa19641ccda54eb24fa155 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Mon, 7 Dec 2020 14:19:10 +0200 Subject: [PATCH] Initial support for DNS-over-HTTP(S) This commit completes the support for DNS-over-HTTP(S) built on top of nghttp2 and plugs it into the BIND. Support for both GET and POST requests is present, as required by RFC8484. Both encrypted (via TLS) and unencrypted HTTP/2 connections are supported. The latter are mostly there for debugging/troubleshooting purposes and for the means of encryption offloading to third-party software (as might be desirable in some environments to simplify TLS certificates management). --- CHANGES | 5 + bin/named/include/named/globals.h | 2 +- bin/named/named.rst | 2 + bin/named/server.c | 206 ++- bin/tests/system/conf.sh.common | 1 + bin/tests/system/get_ports.sh | 1 + bin/tests/system/run.sh.in | 2 +- lib/isc/include/isc/netmgr.h | 30 +- lib/isc/netmgr/http.c | 2160 ++++++++++++++++++++++++----- lib/isc/netmgr/netmgr-int.h | 93 +- lib/isc/netmgr/netmgr.c | 75 +- lib/isc/netmgr/tcp.c | 45 +- lib/isc/netmgr/tlsstream.c | 2 +- lib/isc/tests/Makefile.am | 31 +- lib/isc/tests/doh_test.c | 1875 +++++++++++++++++++++++++ lib/isc/tests/tls_test_cert_key.h | 465 +++++++ lib/isc/tls.c | 6 +- lib/isc/url.c | 2 +- lib/isc/win32/libisc.def.in | 8 +- lib/ns/include/ns/interfacemgr.h | 2 + lib/ns/include/ns/listenlist.h | 12 + lib/ns/interfacemgr.c | 71 + lib/ns/listenlist.c | 59 +- lib/ns/win32/libns.def | 1 + util/copyrights | 6 + 25 files changed, 4734 insertions(+), 428 deletions(-) create mode 100644 lib/isc/tests/doh_test.c create mode 100644 lib/isc/tests/tls_test_cert_key.h diff --git a/CHANGES b/CHANGES index 4372665812..cd946939a1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +5576. [func] Initial support for DNS-over-HTTP(S). BIND now + includes DNS-over-HTTP(S) layer built on top of nghttp2. + Both encrypted and unencrypted HTTP/2 connections + are supported. [GL !4566] + 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/bin/named/include/named/globals.h b/bin/named/include/named/globals.h index 302eb10cd3..b8ea5946a4 100644 --- a/bin/named/include/named/globals.h +++ b/bin/named/include/named/globals.h @@ -73,8 +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_httpport 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/named.rst b/bin/named/named.rst index c2b19f6dd6..3fa96e038c 100644 --- a/bin/named/named.rst +++ b/bin/named/named.rst @@ -117,6 +117,8 @@ Options 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..f46202633a 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -101,8 +101,10 @@ #include #include +#include #include #include +#include #include #include @@ -397,14 +399,24 @@ fatal(named_server_t *server, const char *msg, isc_result_t result); static void named_server_reload(isc_task_t *task, isc_event_t *event); +static isc_result_t +ns_listenelt_from_http(isc_cfg_http_obj_t *http, isc_cfg_tls_obj_t *tls, + in_port_t port, isc_mem_t *mctx, + ns_listenelt_t **target); + static isc_result_t ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_mem_t *mctx, - uint16_t family, ns_listenelt_t **target); + uint16_t family, isc_cfg_http_storage_t *http_servers, + isc_cfg_tls_data_storage_t *tls_storage, + ns_listenelt_t **target); + static isc_result_t ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_mem_t *mctx, - uint16_t family, ns_listenlist_t **target); + uint16_t family, isc_cfg_http_storage_t *http_servers, + isc_cfg_tls_data_storage_t *tls_storage, + ns_listenlist_t **target); static isc_result_t configure_forward(const cfg_obj_t *config, dns_view_t *view, @@ -8505,6 +8517,8 @@ load_configuration(const char *filename, named_server_t *server, unsigned int initial, idle, keepalive, advertised; dns_aclenv_t *env = ns_interfacemgr_getaclenv(named_g_server->interfacemgr); + isc_cfg_tls_data_storage_t tls_storage; + isc_cfg_http_storage_t http_storage; ISC_LIST_INIT(kasplist); ISC_LIST_INIT(viewlist); @@ -8512,6 +8526,9 @@ load_configuration(const char *filename, named_server_t *server, ISC_LIST_INIT(cachelist); ISC_LIST_INIT(altsecrets); + cfg_tls_storage_init(named_g_mctx, &tls_storage); + cfg_http_storage_init(named_g_mctx, &http_storage); + /* Create the ACL configuration context */ if (named_g_aclconfctx != NULL) { cfg_aclconfctx_detach(&named_g_aclconfctx); @@ -8573,6 +8590,19 @@ load_configuration(const char *filename, named_server_t *server, maps[i++] = named_g_defaults; maps[i] = NULL; + obj = NULL; + result = named_config_get(maps, "http-port", &obj); + INSIST(result == ISC_R_SUCCESS); + named_g_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); + + CHECK(cfg_tls_storage_load(config, &tls_storage)); + CHECK(cfg_http_storage_load(config, &http_storage)); + /* * If bind.keys exists, load it. If "dnssec-validation auto" * is turned on, the root key found there will be used as a @@ -8991,7 +9021,8 @@ load_configuration(const char *filename, named_server_t *server, /* check return code? */ (void)ns_listenlist_fromconfig( clistenon, config, named_g_aclconfctx, - named_g_mctx, AF_INET, &listenon); + named_g_mctx, AF_INET, &http_storage, + &tls_storage, &listenon); } else { /* * Not specified, use default. @@ -9019,7 +9050,8 @@ load_configuration(const char *filename, named_server_t *server, /* check return code? */ (void)ns_listenlist_fromconfig( clistenon, config, named_g_aclconfctx, - named_g_mctx, AF_INET6, &listenon); + named_g_mctx, AF_INET6, &http_storage, + &tls_storage, &listenon); } else { /* * Not specified, use default. @@ -9780,6 +9812,9 @@ cleanup: isc_task_endexclusive(server->task); } + cfg_http_storage_uninit(&http_storage); + cfg_tls_storage_uninit(&tls_storage); + isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_DEBUG(1), "load_configuration: %s", isc_result_totext(result)); @@ -10987,7 +11022,9 @@ named_server_togglequerylog(named_server_t *server, isc_lex_t *lex) { static isc_result_t ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_mem_t *mctx, - uint16_t family, ns_listenlist_t **target) { + uint16_t family, isc_cfg_http_storage_t *http_servers, + isc_cfg_tls_data_storage_t *tls_storage, + ns_listenlist_t **target) { isc_result_t result; const cfg_listelt_t *element; ns_listenlist_t *dlist = NULL; @@ -11005,7 +11042,8 @@ ns_listenlist_fromconfig(const cfg_obj_t *listenlist, const cfg_obj_t *config, ns_listenelt_t *delt = NULL; const cfg_obj_t *listener = cfg_listelt_value(element); result = ns_listenelt_fromconfig(listener, config, actx, mctx, - family, &delt); + family, http_servers, + tls_storage, &delt); if (result != ISC_R_SUCCESS) { goto cleanup; } @@ -11026,14 +11064,18 @@ cleanup: static isc_result_t ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_mem_t *mctx, - uint16_t family, ns_listenelt_t **target) { + uint16_t family, isc_cfg_http_storage_t *http_servers, + isc_cfg_tls_data_storage_t *tls_storage, + ns_listenelt_t **target) { isc_result_t result; - const cfg_obj_t *tlsobj, *portobj, *dscpobj; - in_port_t port; + const cfg_obj_t *tlsobj, *portobj, *dscpobj, *httpobj; + in_port_t port = 0; isc_dscp_t dscp = -1; const char *key = NULL, *cert = NULL; - bool tls = false; + bool tls = false, http = false; ns_listenelt_t *delt = NULL; + isc_cfg_http_obj_t *http_server = NULL; + isc_cfg_tls_obj_t *tls_cert = NULL; REQUIRE(target != NULL && *target == NULL); /* XXXWPK TODO be more verbose on failures. */ @@ -11042,43 +11084,60 @@ ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, if (!strcmp(cfg_obj_asstring(tlsobj), "ephemeral")) { tls = true; } else { - const cfg_obj_t *tlsconfigs = NULL; - const cfg_listelt_t *element; - (void)cfg_map_get(config, "tls", &tlsconfigs); - for (element = cfg_list_first(tlsconfigs); - element != NULL; element = cfg_list_next(element)) - { - cfg_obj_t *tconfig = cfg_listelt_value(element); - const cfg_obj_t *name = - cfg_map_getname(tconfig); - if (!strcmp(cfg_obj_asstring(name), - cfg_obj_asstring(tlsobj))) { - tls = true; - const cfg_obj_t *keyo = NULL, - *certo = NULL; - (void)cfg_map_get(tconfig, "key-file", - &keyo); - if (keyo == NULL) { - return (ISC_R_FAILURE); - } - (void)cfg_map_get(tconfig, "cert-file", - &certo); - if (certo == NULL) { - return (ISC_R_FAILURE); - } - key = cfg_obj_asstring(keyo); - cert = cfg_obj_asstring(certo); - break; - } + tls_cert = cfg_tls_storage_find( + cfg_obj_asstring(tlsobj), tls_storage); + if (tls_cert != NULL) { + tls = true; + key = tls_cert->key_file; + cert = tls_cert->cert_file; + INSIST(key != NULL); + INSIST(cert != NULL); } } if (!tls) { return (ISC_R_FAILURE); } } + httpobj = cfg_tuple_get(listener, "http"); + if (httpobj != NULL && cfg_obj_isstring(httpobj)) { + if (tls && tls_cert == NULL) { + return (ISC_R_FAILURE); + } + http = true; + http_server = cfg_http_find(cfg_obj_asstring(httpobj), + http_servers); + if (http_server == NULL) { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, + "HTTP(S) server \"%s\" is nowhere to be found", + cfg_obj_asstring(httpobj)); + return (ISC_R_FAILURE); + } + } portobj = cfg_tuple_get(listener, "port"); if (!cfg_obj_isuint32(portobj)) { - if (tls) { + if (http && tls) { + if (named_g_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 && !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 (tls) { if (named_g_tlsport != 0) { port = named_g_tlsport; } else { @@ -11122,8 +11181,14 @@ ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, dscp = (isc_dscp_t)cfg_obj_asuint32(dscpobj); } - result = ns_listenelt_create(mctx, port, dscp, NULL, tls, key, cert, - &delt); + if (http) { + INSIST(http_server != NULL); + result = ns_listenelt_from_http(http_server, tls_cert, port, + mctx, &delt); + } else { + result = ns_listenelt_create(mctx, port, dscp, NULL, tls, key, + cert, &delt); + } if (result != ISC_R_SUCCESS) { return (result); } @@ -11139,6 +11204,65 @@ ns_listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, return (ISC_R_SUCCESS); } +/* + * Create a listen list for HTTP/HTTPS + */ +static isc_result_t +ns_listenelt_from_http(isc_cfg_http_obj_t *http, isc_cfg_tls_obj_t *tls, + in_port_t port, isc_mem_t *mctx, + ns_listenelt_t **target) { + isc_result_t result = ISC_R_SUCCESS; + ns_listenelt_t *delt = NULL; + const char *key = NULL, *cert = NULL; + char **http_endpoints = NULL; + size_t http_endpoints_number; + isc_cfg_http_endpoint_t *ep; + size_t i = 0; + REQUIRE(target != NULL && *target == NULL); + + if (tls) { + INSIST(tls->key_file != NULL); + INSIST(tls->cert_file != NULL); + key = tls->key_file; + cert = tls->cert_file; + } + + if (port == 0) { + port = tls != NULL ? named_g_httpsport : named_g_httpport; + } + + for (ep = ISC_LIST_HEAD(http->endpoints), i = 0; ep != NULL; + ep = ISC_LIST_NEXT(ep, link), i++) + ; + + INSIST(i > 0); + + http_endpoints_number = i; + http_endpoints = isc_mem_allocate(mctx, sizeof(http_endpoints[0]) * + http_endpoints_number); + for (ep = ISC_LIST_HEAD(http->endpoints), i = 0; ep != NULL; + ep = ISC_LIST_NEXT(ep, link), i++) + { + http_endpoints[i] = isc_mem_strdup(mctx, ep->path); + } + + INSIST(i == http_endpoints_number); + + result = ns_listenelt_create_http(mctx, port, named_g_dscp, NULL, key, + cert, http_endpoints, + http_endpoints_number, &delt); + + if (result != ISC_R_SUCCESS) { + if (delt != NULL) { + ns_listenelt_destroy(delt); + } + return result; + } + + *target = delt; + return (result); +} + isc_result_t named_server_dumpstats(named_server_t *server) { isc_result_t result; diff --git a/bin/tests/system/conf.sh.common b/bin/tests/system/conf.sh.common index df88dd1d10..76f8c7cdf4 100644 --- a/bin/tests/system/conf.sh.common +++ b/bin/tests/system/conf.sh.common @@ -668,6 +668,7 @@ copy_setports() { atsign="@" sed -e "s/${atsign}PORT${atsign}/${PORT}/g" \ -e "s/${atsign}TLSPORT${atsign}/${TLSPORT}/g" \ + -e "s/${atsign}HTTPPORT${atsign}/${HTTPSPORT}/g" \ -e "s/${atsign}HTTPSPORT${atsign}/${HTTPSPORT}/g" \ -e "s/${atsign}EXTRAPORT1${atsign}/${EXTRAPORT1}/g" \ -e "s/${atsign}EXTRAPORT2${atsign}/${EXTRAPORT2}/g" \ diff --git a/bin/tests/system/get_ports.sh b/bin/tests/system/get_ports.sh index f63ba47253..a8bb7ae6a2 100755 --- a/bin/tests/system/get_ports.sh +++ b/bin/tests/system/get_ports.sh @@ -82,6 +82,7 @@ done echo "export PORT=$(get_port "$baseport")" echo "export TLSPORT=$(get_port)" +echo "export HTTPPORT=$(get_port)" echo "export HTTPSPORT=$(get_port)" echo "export EXTRAPORT1=$(get_port)" echo "export EXTRAPORT2=$(get_port)" diff --git a/bin/tests/system/run.sh.in b/bin/tests/system/run.sh.in index 3802181762..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},${HTTPSPORT},${EXTRAPORT1},${EXTRAPORT2},${EXTRAPORT3},${EXTRAPORT4},${EXTRAPORT5},${EXTRAPORT6},${EXTRAPORT7},${EXTRAPORT8},${CONTROLPORT}" +echoinfo "I:$systest:PORTS:${PORT},${TLSPORT},${HTTPPORT},${HTTPSPORT},${EXTRAPORT1},${EXTRAPORT2},${EXTRAPORT3},${EXTRAPORT4},${EXTRAPORT5},${EXTRAPORT6},${EXTRAPORT7},${EXTRAPORT8},${CONTROLPORT}" $PERL ${srcdir}/testsock.pl -p "$PORT" || { echowarn "I:$systest:Network interface aliases not set up. Skipping test." diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h index d2e4ef4c0a..cb505f9237 100644 --- a/lib/isc/include/isc/netmgr.h +++ b/lib/isc/include/isc/netmgr.h @@ -507,33 +507,37 @@ isc_nm_tlsdnsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, */ typedef void (*isc_nm_http_cb_t)(isc_nmhandle_t *handle, isc_result_t eresult, - isc_region_t *postdata, isc_region_t *getdata, - void *cbarg); + isc_region_t *data, void *cbarg); /*%< * Callback function to be used when receiving an HTTP request. * * 'handle' the handle that can be used to send back the answer. * 'eresult' the result of the event. - * 'postdata' contains the received POST data, if any. It will be freed + * 'data' contains the received data, if any. It will be freed * after return by caller. - * 'getdata' contains the received GET data (past '?'), if any. It will be - * freed after return by caller. * 'cbarg' the callback argument passed to listen function. */ isc_result_t -isc_nm_doh_request(isc_nm_t *mgr, const char *uri, isc_region_t *message, - isc_nm_recv_cb_t cb, void *cbarg, isc_ssl_ctx_t *ctx); +isc_nm_http_connect_send_request(isc_nm_t *mgr, const char *uri, bool POST, + isc_region_t *message, isc_nm_recv_cb_t cb, + void *cbarg, isc_tlsctx_t *ctx, + unsigned int timeout); isc_result_t -isc_nm_httpsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, - const char *uri, isc_nm_cb_t cb, void *cbarg, - unsigned int timeout, size_t extrahandlesize); +isc_nm_httpconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, + const char *uri, bool POST, isc_nm_cb_t cb, void *cbarg, + isc_tlsctx_t *ctx, unsigned int timeout, + size_t extrahandlesize); isc_result_t -isc_nm_listenhttps(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog, - isc_quota_t *quota, isc_ssl_ctx_t *ctx, - isc_nmsocket_t **sockp); +isc_nm_httprequest(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_recv_cb_t reply_cb, void *cbarg); + +isc_result_t +isc_nm_listenhttp(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog, + isc_quota_t *quota, isc_tlsctx_t *ctx, + isc_nmsocket_t **sockp); isc_result_t isc_nm_http_add_endpoint(isc_nmsocket_t *sock, const char *uri, diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c index f11f4dfea9..f6b5a778d4 100644 --- a/lib/isc/netmgr/http.c +++ b/lib/isc/netmgr/http.c @@ -9,6 +9,9 @@ * information regarding copyright ownership. */ +#include +#include +#include #include #include #include @@ -20,15 +23,31 @@ #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 cb; - void *cbarg; + isc_nm_recv_cb_t read_cb; + void *read_cbarg; + + isc_nm_cb_t connect_cb; + void *connect_cbarg; char *uri; isc_url_parser_t up; @@ -37,13 +56,21 @@ typedef struct http2_client_stream { size_t authoritylen; char *path; - uint8_t rbuf[65535]; + uint8_t rbuf[MAX_DNS_MESSAGE_SIZE]; size_t rbufsize; size_t pathlen; int32_t stream_id; - isc_region_t *postdata; + + bool post; /* POST or GET */ + isc_region_t postdata; size_t postdata_pos; + char *GET_path; + size_t GET_path_len; + + isc_nm_http2_response_status_t response_status; + + LINK(struct http2_client_stream) link; } http2_client_stream_t; #define HTTP2_SESSION_MAGIC ISC_MAGIC('H', '2', 'S', 'S') @@ -57,42 +84,94 @@ struct isc_nm_http2_session { bool reading; nghttp2_session *ngsession; - http2_client_stream_t *cstream; + bool client; + + ISC_LIST(http2_client_stream_t) cstreams; ISC_LIST(isc_nmsocket_h2_t) sstreams; +#ifdef NETMGR_TRACE + size_t sstreams_count; +#endif /* NETMGR_TRACE */ isc_nmhandle_t *handle; isc_nmsocket_t *serversocket; isc_region_t r; - uint8_t buf[65535]; + uint8_t buf[MAX_DNS_MESSAGE_SIZE]; size_t bufsize; - SSL_CTX *ctx; + bool ssl_ctx_created; + SSL_CTX *ssl_ctx; }; -static bool +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, uint16_t *port) { + const char *uri) { http2_client_stream_t *stream = NULL; - int rv; + isc_result_t result; REQUIRE(streamp != NULL && *streamp == NULL); REQUIRE(uri != NULL); - REQUIRE(port != NULL); stream = isc_mem_get(mctx, sizeof(http2_client_stream_t)); - *stream = (http2_client_stream_t){ .stream_id = -1 }; + *stream = (http2_client_stream_t){ .stream_id = -1, .rbufsize = 0 }; stream->uri = isc_mem_strdup(mctx, uri); - rv = isc_url_parse(stream->uri, strlen(stream->uri), 0, &stream->up); - if (rv != 0) { - isc_mem_put(mctx, stream, sizeof(http2_client_stream_t)); + result = isc_url_parse(stream->uri, strlen(stream->uri), 0, + &stream->up); + if (result != ISC_R_SUCCESS) { isc_mem_free(mctx, stream->uri); - return (ISC_R_FAILURE); + isc_mem_put(mctx, stream, sizeof(http2_client_stream_t)); + return (result); } stream->authoritylen = stream->up.field_data[ISC_UF_HOST].len; @@ -136,10 +215,10 @@ get_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t **streamp, stream->up.field_data[ISC_UF_QUERY].len); } - *port = 443; - if ((stream->up.field_set & (1 << ISC_UF_PORT)) != 0) { - *port = stream->up.port; - } + stream->GET_path = NULL; + stream->GET_path_len = 0; + stream->postdata = (isc_region_t){ .base = NULL, .length = 0 }; + memset(&stream->response_status, 0, sizeof(stream->response_status)); *streamp = stream; @@ -149,12 +228,36 @@ get_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t **streamp, static void put_http2_client_stream(isc_mem_t *mctx, http2_client_stream_t *stream) { isc_mem_put(mctx, stream->path, stream->pathlen); - isc_mem_put(mctx, stream->authority, stream->authoritylen + AUTHEXTRA); + isc_mem_put(mctx, stream->authority, + stream->up.field_data[ISC_UF_HOST].len + AUTHEXTRA); + isc_mem_free(mctx, stream->uri); + if (stream->GET_path != 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); @@ -163,9 +266,23 @@ delete_http2_session(isc_nm_http2_session_t *session) { nghttp2_session_del(session->ngsession); session->ngsession = NULL; } - if (session->cstream != NULL) { - put_http2_client_stream(session->mctx, session->cstream); - session->cstream = NULL; + if (!ISC_LIST_EMPTY(session->cstreams)) { + http2_client_stream_t *cstream = + ISC_LIST_HEAD(session->cstreams); + while (cstream != NULL) { + http2_client_stream_t *next = ISC_LIST_NEXT(cstream, + link); + ISC_LIST_DEQUEUE(session->cstreams, cstream, link); + put_http2_client_stream(session->mctx, cstream); + + cstream = next; + } + } + INSIST(ISC_LIST_EMPTY(session->cstreams)); + + /* detach from server socket */ + if (session->serversocket != NULL) { + isc__nmsocket_detach(&session->serversocket); } /* @@ -173,10 +290,6 @@ delete_http2_session(isc_nm_http2_session_t *session) { */ if (session->sending) { session->closed = true; - } else if (!session->reading) { - session->magic = 0; - isc_mem_putanddetach(&session->mctx, session, - sizeof(isc_nm_http2_session_t)); } } @@ -189,25 +302,45 @@ on_data_chunk_recv_callback(nghttp2_session *ngsession, uint8_t flags, UNUSED(ngsession); UNUSED(flags); - if (session->cstream != NULL) { - if (session->cstream->stream_id == stream_id) { - /* TODO buffer overrun! */ - memmove(session->cstream->rbuf + - session->cstream->rbufsize, - data, len); - session->cstream->rbufsize += len; + if (session->client) { + http2_client_stream_t *cstream = + find_http2_client_stream(stream_id, session); + if (cstream) { + size_t new_rbufsize = cstream->rbufsize + len; + if (new_rbufsize <= MAX_DNS_MESSAGE_SIZE && + new_rbufsize <= + cstream->response_status.content_length) + { + memmove(cstream->rbuf + cstream->rbufsize, data, + len); + cstream->rbufsize = new_rbufsize; + } else { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + } else { + return (NGHTTP2_ERR_CALLBACK_FAILURE); } } else { isc_nmsocket_h2_t *sock_h2 = ISC_LIST_HEAD(session->sstreams); while (sock_h2 != NULL) { if (stream_id == sock_h2->stream_id) { - memmove(sock_h2->buf + sock_h2->bufsize, data, - len); - sock_h2->bufsize += len; + size_t new_bufsize = sock_h2->bufsize + len; + if (new_bufsize <= MAX_DNS_MESSAGE_SIZE && + new_bufsize <= sock_h2->content_length) + { + memmove(sock_h2->buf + sock_h2->bufsize, + data, len); + sock_h2->bufsize = new_bufsize; + } else { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } break; } sock_h2 = ISC_LIST_NEXT(sock_h2, link); } + if (sock_h2 == NULL) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } } return (0); @@ -217,40 +350,68 @@ static int on_stream_close_callback(nghttp2_session *ngsession, int32_t stream_id, uint32_t error_code, void *user_data) { isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data; + int rv = 0; REQUIRE(VALID_HTTP2_SESSION(session)); UNUSED(error_code); - if (session->cstream != NULL) { - if (session->cstream->stream_id == stream_id) { - int rv; - - session->cstream->cb( - NULL, ISC_R_SUCCESS, - &(isc_region_t){ session->cstream->rbuf, - session->cstream->rbufsize }, - session->cstream->cbarg); - rv = nghttp2_session_terminate_session( - ngsession, NGHTTP2_NO_ERROR); - if (rv != 0) { - return (NGHTTP2_ERR_CALLBACK_FAILURE); + /* NOTE: calling isc_nm_cancelread() or + * isc__nmsocket_prep_destroy() on a socket will lead to an + * indirect call to the delete_http2_session() which will, in + * turn, perform required stream session cleanup.*/ + if (session->client) { + http2_client_stream_t *cstream = + find_http2_client_stream(stream_id, session); + if (cstream) { + isc_result_t result = + cstream->response_status.code >= 200 && + cstream->response_status.code < + 300 + ? ISC_R_SUCCESS + : ISC_R_FAILURE; + cstream->read_cb(session->handle, result, + &(isc_region_t){ cstream->rbuf, + cstream->rbufsize }, + cstream->read_cbarg); + ISC_LIST_UNLINK(session->cstreams, cstream, link); + put_http2_client_stream(session->mctx, cstream); + if (ISC_LIST_EMPTY(session->cstreams)) { + rv = nghttp2_session_terminate_session( + ngsession, NGHTTP2_NO_ERROR); + if (rv != 0) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + if (session->handle->sock->h2.session->reading) + { + isc_nm_cancelread( + session->handle->sock->h2 + .session->handle); + } } + } else { + return (NGHTTP2_ERR_CALLBACK_FAILURE); } } else { - /* XXX */ + isc_nmsocket_t *sock = nghttp2_session_get_stream_user_data( + ngsession, stream_id); + if (ISC_LIST_EMPTY(session->sstreams)) { + rv = nghttp2_session_terminate_session( + ngsession, NGHTTP2_NO_ERROR); + } + isc__nmsocket_prep_destroy(sock); + if (rv != 0) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } } - /* XXXWPK TODO we need to close the session */ - return (0); } #ifndef OPENSSL_NO_NEXTPROTONEG /* * NPN TLS extension client callback. We check that server advertised - * the HTTP/2 protocol the nghttp2 library supports. If not, exit the - * program. + * the HTTP/2 protocol the nghttp2 library supports. */ static int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, @@ -259,7 +420,7 @@ select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, UNUSED(arg); if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { - /* TODO */ + return (SSL_TLSEXT_ERR_NOACK); } return (SSL_TLSEXT_ERR_OK); } @@ -267,32 +428,109 @@ select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, /* Create SSL_CTX. */ static SSL_CTX * -create_ssl_ctx(void) { +create_client_ssl_ctx(void) { SSL_CTX *ssl_ctx = NULL; - ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + RUNTIME_CHECK(isc_tlsctx_createclient(&ssl_ctx) == ISC_R_SUCCESS); RUNTIME_CHECK(ssl_ctx != NULL); - SSL_CTX_set_options( - ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_COMPRESSION | - SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); #ifndef OPENSSL_NO_NEXTPROTONEG SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL); #endif /* !OPENSSL_NO_NEXTPROTONEG */ #if OPENSSL_VERSION_NUMBER >= 0x10002000L - SSL_CTX_set_alpn_protos(ssl_ctx, (const unsigned char *)"\x02h2", 3); + SSL_CTX_set_alpn_protos(ssl_ctx, + (const unsigned char *)NGHTTP2_PROTO_ALPN, + NGHTTP2_PROTO_ALPN_LEN); #endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + 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; - nghttp2_session_callbacks_new(&callbacks); + RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0); nghttp2_session_callbacks_set_on_data_chunk_recv_callback( callbacks, on_data_chunk_recv_callback); @@ -300,25 +538,26 @@ initialize_nghttp2_client_session(isc_nm_http2_session_t *session) { nghttp2_session_callbacks_set_on_stream_close_callback( callbacks, on_stream_close_callback); + nghttp2_session_callbacks_set_on_header_callback( + callbacks, client_on_header_callback); + nghttp2_session_client_new(&session->ngsession, callbacks, session); nghttp2_session_callbacks_del(callbacks); } -static void +static bool send_client_connection_header(isc_nm_http2_session_t *session) { - nghttp2_settings_entry iv[1] = { - { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 } - }; + nghttp2_settings_entry iv[] = { { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 } }; int rv; rv = nghttp2_submit_settings(session->ngsession, NGHTTP2_FLAG_NONE, iv, - 1); + sizeof(iv) / sizeof(iv[0])); if (rv != 0) { - /* TODO */ + return (false); } - http2_do_bio(session); + return (true); } #define MAKE_NV(NAME, VALUE, VALUELEN) \ @@ -335,36 +574,44 @@ send_client_connection_header(isc_nm_http2_session_t *session) { } static ssize_t -client_post_read_callback(nghttp2_session *ngsession, int32_t stream_id, - uint8_t *buf, size_t length, uint32_t *data_flags, - nghttp2_data_source *source, void *user_data) { +client_read_callback(nghttp2_session *ngsession, int32_t stream_id, + uint8_t *buf, size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) { isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)user_data; + http2_client_stream_t *cstream; - REQUIRE(session->cstream != NULL); + REQUIRE(session->client); + REQUIRE(!ISC_LIST_EMPTY(session->cstreams)); UNUSED(ngsession); UNUSED(source); - if (session->cstream->stream_id == stream_id) { - size_t len = session->cstream->postdata->length - - session->cstream->postdata_pos; + cstream = find_http2_client_stream(stream_id, session); + if (!cstream || cstream->stream_id != stream_id) { + /* We haven't found the stream, so we are not reading anything + */ + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + + if (cstream->post) { + size_t len = cstream->postdata.length - cstream->postdata_pos; if (len > length) { len = length; } - memmove(buf, - session->cstream->postdata->base + - session->cstream->postdata_pos, + memmove(buf, cstream->postdata.base + cstream->postdata_pos, len); - session->cstream->postdata_pos += len; + cstream->postdata_pos += len; - if (session->cstream->postdata_pos == - session->cstream->postdata->length) { + if (cstream->postdata_pos == cstream->postdata.length) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; } return (len); + } else { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return (0); } return (0); @@ -372,31 +619,55 @@ client_post_read_callback(nghttp2_session *ngsession, int32_t stream_id, /* Send HTTP request to the remote peer */ static isc_result_t -client_submit_request(isc_nm_http2_session_t *session) { +client_submit_request(isc_nm_http2_session_t *session, + http2_client_stream_t *stream) { int32_t stream_id; - http2_client_stream_t *stream = session->cstream; char *uri = stream->uri; isc_url_parser_t *up = &stream->up; nghttp2_data_provider dp; - char p[64]; - snprintf(p, 64, "%u", stream->postdata->length); + 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)), + }; - 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_post_read_callback }; - stream_id = nghttp2_submit_request(session->ngsession, NULL, hdrs, 7, - &dp, stream); + dp = (nghttp2_data_provider){ .read_callback = + client_read_callback }; + stream_id = nghttp2_submit_request( + session->ngsession, NULL, hdrs, + sizeof(hdrs) / sizeof(hdrs[0]), &dp, stream); + } if (stream_id < 0) { return (ISC_R_FAILURE); } @@ -419,26 +690,23 @@ https_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, REQUIRE(VALID_HTTP2_SESSION(session)); UNUSED(handle); - UNUSED(result); if (result != ISC_R_SUCCESS) { session->reading = false; - delete_http2_session(session); - /* TODO callback! */ + failed_read_cb(result, session); return; } readlen = nghttp2_session_mem_recv(session->ngsession, region->base, region->length); if (readlen < 0) { - delete_http2_session(session); - /* TODO callback! */ + failed_read_cb(ISC_R_CANCELED, session); return; } if (readlen < region->length) { INSIST(session->bufsize == 0); - INSIST(region->length - readlen < 65535); + INSIST(region->length - readlen < MAX_DNS_MESSAGE_SIZE); memmove(session->buf, region->base, region->length - readlen); session->bufsize = region->length - readlen; isc_nm_pauseread(session->handle); @@ -455,15 +723,16 @@ https_writecb(isc_nmhandle_t *handle, isc_result_t result, void *arg) { REQUIRE(VALID_HTTP2_SESSION(session)); UNUSED(handle); - UNUSED(result); session->sending = false; isc_mem_put(session->mctx, session->r.base, session->r.length); session->r.base = NULL; - http2_do_bio(session); + if (result == ISC_R_SUCCESS) { + http2_do_bio(session); + } } -static bool +static void http2_do_bio(isc_nm_http2_session_t *session) { REQUIRE(VALID_HTTP2_SESSION(session)); @@ -471,8 +740,8 @@ http2_do_bio(isc_nm_http2_session_t *session) { (nghttp2_session_want_read(session->ngsession) == 0 && nghttp2_session_want_write(session->ngsession) == 0)) { - delete_http2_session(session); - return (false); + finish_http2_session(session); + return; } if (nghttp2_session_want_read(session->ngsession) != 0) { @@ -495,7 +764,7 @@ http2_do_bio(isc_nm_http2_session_t *session) { } http2_do_bio(session); - return (false); + return; } else { /* Resume reading, it's idempotent, wait for more */ isc_nm_resumeread(session->handle); @@ -528,128 +797,404 @@ http2_do_bio(isc_nm_http2_session_t *session) { session->sending = true; isc_nm_send(session->handle, &session->r, https_writecb, session); - return (true); + return; } - return (false); + return; } +typedef struct http_connect_data { + char *uri; + isc_nm_cb_t connect_cb; + void *connect_cbarg; + bool post; + bool ssl_ctx_created; + SSL_CTX *ssl_ctx; +} http_connect_data_t; + static void -https_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *arg) { - isc_nm_http2_session_t *session = (isc_nm_http2_session_t *)arg; +transport_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + char *uri = NULL; + http_connect_data_t *pconn_data = (http_connect_data_t *)cbarg; + http_connect_data_t conn_data; + isc_nm_http2_session_t *session = NULL; + isc_mem_t *mctx; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(cbarg != NULL); + + handle->sock->h2.session = NULL; + mctx = handle->sock->mgr->mctx; + + conn_data = *pconn_data; + uri = conn_data.uri; + isc_mem_put(mctx, pconn_data, sizeof(*pconn_data)); + + INSIST(conn_data.connect_cb != NULL); + INSIST(conn_data.uri != NULL); if (result != ISC_R_SUCCESS) { - delete_http2_session(session); - return; + goto error; + } + + 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); - -#if 0 -/* TODO H2 */ -#ifndef OPENSSL_NO_NEXTPROTONEG - SSL_get0_next_proto_negotiated(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("h2", alpn, 2) != 0) { - delete_http2_session(session); - return; - } -#endif + handle->sock->h2.session = session; initialize_nghttp2_client_session(session); - send_client_connection_header(session); - client_submit_request(session); - http2_do_bio(session); -} - -isc_result_t -isc_nm_httpsconnect(isc_nm_t *mgr, isc_nmiface_t *local, isc_nmiface_t *peer, - const char *uri, isc_nm_cb_t cb, void *cbarg, - unsigned int timeout, size_t extrahandlesize) { - REQUIRE(VALID_NM(mgr)); - - UNUSED(local); - UNUSED(peer); - UNUSED(uri); - UNUSED(cb); - UNUSED(cbarg); - UNUSED(timeout); - UNUSED(extrahandlesize); - - return (ISC_R_NOTIMPLEMENTED); -} - -isc_result_t -isc_nm_doh_request(isc_nm_t *mgr, const char *uri, isc_region_t *region, - isc_nm_recv_cb_t cb, void *cbarg, SSL_CTX *ctx) { - uint16_t port; - char *host = NULL; - isc_nm_http2_session_t *session = NULL; - http2_client_stream_t *cstream = NULL; - struct addrinfo hints; - struct addrinfo *res = NULL; - isc_sockaddr_t local, peer; - isc_result_t result; - int s; - - if (ctx == NULL) { - ctx = create_ssl_ctx(); + 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); } - session = isc_mem_get(mgr->mctx, sizeof(isc_nm_http2_session_t)); - *session = (isc_nm_http2_session_t){ .magic = HTTP2_SESSION_MAGIC, - .ctx = ctx }; - isc_mem_attach(mgr->mctx, &session->mctx); + 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); + } - result = get_http2_client_stream(mgr->mctx, &cstream, uri, &port); - if (result != ISC_R_SUCCESS) { + 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); } - cstream->postdata = region; - cstream->postdata_pos = 0; - cstream->cb = cb; - cstream->cbarg = cbarg; + schema_len = url_parser.field_data[ISC_UF_SCHEMA].len; + INSIST(schema_len > 0); + schema = &uri[url_parser.field_data[ISC_UF_SCHEMA].off]; - session->cstream = cstream; + 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 */ - hints = (struct addrinfo){ .ai_family = PF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_CANONNAME }; - host = isc_mem_strndup(mgr->mctx, cstream->authority, - cstream->authoritylen + 1); + /* 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'; - s = getaddrinfo(host, NULL, &hints, &res); - isc_mem_free(mgr->mctx, host); - if (s != 0) { - delete_http2_session(session); - return (ISC_R_FAILURE); - } + 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 */ - isc_sockaddr_fromsockaddr(&peer, res->ai_addr); - isc_sockaddr_setport(&peer, port); - isc_sockaddr_anyofpf(&local, res->ai_family); + if ((url_parser.field_set & (1 << ISC_UF_PORT)) != 0) { + port = url_parser.port; + } - freeaddrinfo(res); + 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; + } - result = isc_nm_tlsconnect(mgr, (isc_nmiface_t *)&local, - (isc_nmiface_t *)&peer, https_connect_cb, - session, ctx, 30000, 0); - /* XXX: timeout is hard-coded to 30 seconds - make it a parameter */ - if (result != ISC_R_SUCCESS) { - return (result); + 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 @@ -669,29 +1214,214 @@ server_on_begin_headers_callback(nghttp2_session *ngsession, iface = isc_nmhandle_localaddr(session->handle); isc__nmsocket_init(socket, session->serversocket->mgr, isc_nm_httpstream, (isc_nmiface_t *)&iface); - socket->h2 = (isc_nmsocket_h2_t){ .bufpos = 0, - .bufsize = 0, - .psock = socket, - .handler = NULL, - .request_path = NULL, - .query_data = NULL, - .stream_id = frame->hd.stream_id, - .session = session }; - + socket->h2 = (isc_nmsocket_h2_t){ + .bufpos = 0, + .bufsize = 0, + .buf = isc_mem_allocate(session->mctx, MAX_DNS_MESSAGE_SIZE), + .psock = socket, + .handler = NULL, + .request_path = NULL, + .query_data = NULL, + .stream_id = frame->hd.stream_id, + .session = session + }; + socket->tid = session->handle->sock->tid; ISC_LINK_INIT(&socket->h2, link); ISC_LIST_APPEND(session->sstreams, &socket->h2, link); + +#ifdef NETMGR_TRACE + session->sstreams_count++; + if (session->sstreams_count > 1) { + fprintf(stderr, "HTTP/2 session %p (active streams: %lu)\n", + session, session->sstreams_count); + } +#endif /* NETMGR_TRACE */ + nghttp2_session_set_stream_user_data(ngsession, frame->hd.stream_id, socket); return (0); } +static 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); @@ -704,26 +1434,41 @@ server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, socket = nghttp2_session_get_stream_user_data( session, frame->hd.stream_id); - if (socket == NULL || socket->h2.request_path != NULL) { + if (socket == NULL) { break; } - if (namelen == sizeof(path) - 1 && - memcmp(path, name, namelen) == 0) { - size_t j; - for (j = 0; j < valuelen && value[j] != '?'; ++j) - ; - socket->h2.request_path = isc_mem_strndup( - socket->mgr->mctx, (const char *)value, j + 1); - if (j < valuelen) { - socket->h2.query_data = isc_mem_strndup( - socket->mgr->mctx, (char *)value + j, - valuelen - j); - } + if (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); } @@ -758,8 +1503,9 @@ static int server_send_response(nghttp2_session *ngsession, int32_t stream_id, const nghttp2_nv *nva, size_t nvlen, isc_nmsocket_t *socket) { - int rv; nghttp2_data_provider data_prd; + int rv; + data_prd.source.ptr = socket; data_prd.read_callback = server_read_callback; @@ -771,80 +1517,226 @@ server_send_response(nghttp2_session *ngsession, int32_t stream_id, return (0); } -static const char ERROR_HTML[] = "404" - "

404 Not Found

"; +#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 -error_reply(nghttp2_session *ngsession, isc_nmsocket_t *socket) { - const nghttp2_nv hdrs[] = { MAKE_NV2(":status", "404") }; +server_send_error_response(const isc_http2_error_responses_t error, + nghttp2_session *ngsession, isc_nmsocket_t *socket) { + int rv; - memmove(socket->h2.buf, ERROR_HTML, sizeof(ERROR_HTML)); - socket->h2.bufsize = sizeof(ERROR_HTML); + socket->h2.bufsize = 0; socket->h2.bufpos = 0; - server_send_response(ngsession, socket->h2.stream_id, hdrs, - sizeof(hdrs) / sizeof(nghttp2_nv), socket); - return (0); + for (size_t i = 0; + i < sizeof(error_responses) / sizeof(error_responses[0]); i++) + { + if (error_responses[i].type == error) { + 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_nm_http2_server_handler_t *handler = NULL; isc_nmhandle_t *handle = NULL; isc_sockaddr_t addr; + isc_http2_error_responses_t code = ISC_HTTP_ERROR_SUCCESS; + isc_region_t data; - if (!socket->h2.request_path) { - if (error_reply(ngsession, socket) != 0) { - return (NGHTTP2_ERR_CALLBACK_FAILURE); - } - return (0); - } - - for (handler = ISC_LIST_HEAD(session->serversocket->handlers); - handler != NULL; handler = ISC_LIST_NEXT(handler, link)) + /* + * 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) { - if (!strcmp(socket->h2.request_path, handler->path)) { - break; - } + 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 (handler == NULL) { - if (error_reply(ngsession, socket) != 0) { - return (NGHTTP2_ERR_CALLBACK_FAILURE); - } - return (0); + 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(); } - socket->extrahandlesize = handler->extrahandlesize; addr = isc_nmhandle_peeraddr(session->handle); handle = isc__nmhandle_get(socket, &addr, NULL); - handler->cb(handle, ISC_R_SUCCESS, - &(isc_region_t){ socket->h2.buf, socket->h2.bufsize }, - &(isc_region_t){ (unsigned char *)socket->h2.query_data, - strlen(socket->h2.query_data) + 1 }, - handler->cbarg); + socket->h2.handler_cb(handle, ISC_R_SUCCESS, &data, + socket->h2.handler_cbarg); + isc_nmhandle_detach(&handle); + return (0); + +error: + if (server_send_error_response(code, ngsession, socket) != 0) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } return (0); } void isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region, isc_nm_cb_t cb, void *cbarg) { - const nghttp2_nv hdrs[] = { MAKE_NV2(":status", "200") }; isc_nmsocket_t *sock = handle->sock; + isc__nm_uvreq_t *uvreq = NULL; + isc__netievent_httpsend_t *ievent = NULL; - /* TODO FIXME do it asynchronously!!! */ - memcpy(sock->h2.buf, region->base, region->length); - sock->h2.bufsize = region->length; + 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) { - cb(handle, ISC_R_FAILURE, cbarg); + res = ISC_R_FAILURE; } else { - cb(handle, ISC_R_SUCCESS, cbarg); + res = ISC_R_SUCCESS; } + + http2_do_bio(handle->httpsession); /*TODO: Should we call it only + * on success? */ + cb(handle, res, cbarg); + + isc__nm_uvreq_put(&req, sock); } static int @@ -884,7 +1776,7 @@ static void initialize_nghttp2_server_session(isc_nm_http2_session_t *session) { nghttp2_session_callbacks *callbacks = NULL; - nghttp2_session_callbacks_new(&callbacks); + RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0); nghttp2_session_callbacks_set_on_data_chunk_recv_callback( callbacks, on_data_chunk_recv_callback); @@ -925,20 +1817,55 @@ 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 }; + *session = (isc_nm_http2_session_t){ .magic = HTTP2_SESSION_MAGIC, + .ssl_ctx_created = false, + .client = false }; initialize_nghttp2_server_session(session); + handle->sock->h2.session = session; isc_mem_attach(httplistensock->mgr->mctx, &session->mctx); isc_nmhandle_attach(handle, &session->handle); isc__nmsocket_attach(httplistensock, &session->serversocket); + session->serversocket = httplistensock; server_send_connection_header(session); /* TODO H2 */ @@ -946,16 +1873,55 @@ httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { return (ISC_R_SUCCESS); } +#ifndef OPENSSL_NO_NEXTPROTONEG +static int +next_proto_cb(SSL *ssl, const unsigned char **data, unsigned int *len, + void *arg) { + 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_listenhttps(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog, - isc_quota_t *quota, SSL_CTX *ctx, isc_nmsocket_t **sockp) { +isc_nm_listenhttp(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog, + isc_quota_t *quota, SSL_CTX *ctx, isc_nmsocket_t **sockp) { isc_nmsocket_t *sock = NULL; isc_result_t result; - isc_mem_get(mgr->mctx, sizeof(*sock)); + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); isc__nmsocket_init(sock, mgr, isc_nm_httplistener, iface); if (ctx != NULL) { +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_protos_advertised_cb(ctx, next_proto_cb, NULL); +#endif // OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, NULL); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L result = isc_nm_listentls(mgr, iface, httplisten_acceptcb, sock, sizeof(isc_nm_http2_session_t), backlog, quota, ctx, &sock->outer); @@ -971,6 +1937,13 @@ isc_nm_listenhttps(isc_nm_t *mgr, isc_nmiface_t *iface, int backlog, return (result); } + sock->outer->h2.httpserver = sock; + + sock->nchildren = sock->outer->nchildren; + sock->result = ISC_R_DEFAULT; + sock->tid = isc_random_uniform(sock->nchildren); + sock->fd = (uv_os_sock_t)-1; + atomic_store(&sock->listening, true); *sockp = sock; return (ISC_R_SUCCESS); @@ -985,72 +1958,42 @@ isc_nm_http_add_endpoint(isc_nmsocket_t *sock, const char *uri, REQUIRE(VALID_NMSOCK(sock)); REQUIRE(sock->type == isc_nm_httplistener); - handler = isc_mem_get(sock->mgr->mctx, sizeof(*handler)); - *handler = (isc_nm_http2_server_handler_t){ - .cb = cb, - .cbarg = cbarg, - .extrahandlesize = extrahandlesize, - .path = isc_mem_strdup(sock->mgr->mctx, uri) - }; + 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); - ISC_LINK_INIT(handler, link); - ISC_LIST_APPEND(sock->handlers, 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); } -typedef struct { - isc_nm_recv_cb_t cb; - void *cbarg; -} cbarg_t; - -static unsigned char doh_error[] = - "No request" - "

No request

"; - -static const isc_region_t doh_error_r = { doh_error, sizeof(doh_error) }; - -static void -https_sendcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { - UNUSED(handle); - UNUSED(result); - UNUSED(cbarg); -} - /* * In DoH we just need to intercept the request - the response can be sent * to the client code via the nmhandle directly as it's always just the * http * content. */ static void -doh_callback(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *post, - isc_region_t *get, void *arg) { - cbarg_t *dohcbarg = arg; - isc_region_t *data = NULL; +doh_callback(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *data, + void *arg) { + isc_nm_http_doh_cbarg_t *dohcbarg = arg; REQUIRE(VALID_NMHANDLE(handle)); - UNUSED(result); - UNUSED(get); - if (result != ISC_R_SUCCESS) { /* Shut down the client, then ourselves */ - dohcbarg->cb(NULL, result, NULL, dohcbarg->cbarg); + dohcbarg->cb(handle, result, NULL, dohcbarg->cbarg); /* XXXWPK FREE */ return; } - - if (post != NULL) { - data = post; - } else if (get != NULL) { - /* XXXWPK PARSE */ - data = NULL; /* FIXME */ - } else { - /* Invalid request, just send the error response */ - isc_nm_send(handle, &doh_error_r, https_sendcb, dohcbarg); - return; - } - dohcbarg->cb(handle, result, data, dohcbarg->cbarg); } @@ -1059,19 +2002,586 @@ isc_nm_http_add_doh_endpoint(isc_nmsocket_t *sock, const char *uri, isc_nm_recv_cb_t cb, void *cbarg, size_t extrahandlesize) { isc_result_t result; - cbarg_t *dohcbarg = NULL; + isc_nm_http_doh_cbarg_t *dohcbarg = NULL; REQUIRE(VALID_NMSOCK(sock)); REQUIRE(sock->type == isc_nm_httplistener); - dohcbarg = isc_mem_get(sock->mgr->mctx, sizeof(cbarg_t)); - *dohcbarg = (cbarg_t){ cb, cbarg }; + dohcbarg = isc_mem_get(sock->mgr->mctx, + sizeof(isc_nm_http_doh_cbarg_t)); + *dohcbarg = (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(cbarg_t)); + 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 3ad09823bf..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 @@ -273,6 +274,10 @@ typedef enum isc__netievent_type { netievent_tlsdnscycle, netievent_tlsdnsshutdown, + netievent_httpstop, + netievent_httpsend, + netievent_httpclose, + netievent_close, netievent_shutdown, netievent_stop, @@ -701,18 +706,61 @@ typedef struct isc_nmsocket_tls_send_req { isc_region_t data; } isc_nmsocket_tls_send_req_t; +typedef enum isc_doh_request_type { + ISC_HTTP_REQ_GET, + ISC_HTTP_REQ_POST, + ISC_HTTP_REQ_UNSUPPORTED +} isc_http2_request_type_t; + +typedef enum isc_http2_scheme_type { + ISC_HTTP_SCHEME_HTTP, + ISC_HTTP_SCHEME_HTTP_SECURE, + ISC_HTTP_SCHEME_UNSUPPORTED +} isc_http2_scheme_type_t; + +typedef struct isc_nm_http_doh_cbarg { + isc_nm_recv_cb_t cb; + void *cbarg; + LINK(struct isc_nm_http_doh_cbarg) link; +} isc_nm_http_doh_cbarg_t; + typedef struct isc_nmsocket_h2 { isc_nmsocket_t *psock; /* owner of the structure */ char *request_path; char *query_data; + size_t query_data_len; + bool query_too_large; isc_nm_http2_server_handler_t *handler; - uint8_t buf[65535]; + uint8_t *buf; size_t bufsize; size_t bufpos; int32_t stream_id; + isc_nm_http2_session_t *session; + + isc_nmsocket_t *httpserver; + + isc_http2_request_type_t request_type; + isc_http2_scheme_type_t request_scheme; + size_t content_length; + bool content_type_verified; + bool accept_type_verified; + + isc_nm_http_cb_t handler_cb; + void *handler_cbarg; LINK(struct isc_nmsocket_h2) link; + + ISC_LIST(isc_nm_http2_server_handler_t) handlers; + ISC_LIST(isc_nm_http_doh_cbarg_t) handlers_cbargs; + isc_rwlock_t handlers_lock; + + char response_content_length_str[128]; + + struct isc_nmsocket_h2_connect_data { + char *uri; + bool post; + } connect; } isc_nmsocket_h2_t; struct isc_nmsocket { /*% Unlocked, RO */ @@ -974,8 +1022,6 @@ struct isc_nmsocket { atomic_int_fast32_t active_child_connections; - ISC_LIST(isc_nm_http2_server_handler_t) handlers; - #ifdef NETMGR_TRACE void *backtrace[TRACE_SIZE]; int backtrace_size; @@ -1472,10 +1518,43 @@ isc__nm_tls_cleanup_data(isc_nmsocket_t *sock); void isc__nm_tls_stoplistening(isc_nmsocket_t *sock); +void +isc__nm_http_stoplistening(isc_nmsocket_t *sock); + +void +isc__nm_http_clear_handlers(isc_nmsocket_t *sock); + +void +isc__nm_http_clear_session(isc_nmsocket_t *sock); + void isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region, isc_nm_cb_t cb, void *cbarg); +void +isc__nm_http_close(isc_nmsocket_t *sock); + +void +isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_httpstop(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0); + +bool +isc__nm_parse_doh_query_string(const char *query_string, const char **start, + size_t *len); + +char * +isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url, + const size_t base64url_len, size_t *res_len); + +char * +isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64, + const size_t base64_len, size_t *res_len); + #define isc__nm_uverr2result(x) \ isc___nm_uverr2result(x, true, __FILE__, __LINE__, __func__) isc_result_t @@ -1608,6 +1687,10 @@ NETIEVENT_SOCKET_HANDLE_TYPE(tlsdnscancel); NETIEVENT_SOCKET_QUOTA_TYPE(tlsdnsaccept); NETIEVENT_SOCKET_TYPE(tlsdnscycle); +NETIEVENT_SOCKET_TYPE(httpstop); +NETIEVENT_SOCKET_REQ_TYPE(httpsend); +NETIEVENT_SOCKET_TYPE(httpclose); + NETIEVENT_SOCKET_REQ_TYPE(tcpconnect); NETIEVENT_SOCKET_REQ_TYPE(tcpsend); NETIEVENT_SOCKET_TYPE(tcpstartread); @@ -1668,6 +1751,10 @@ NETIEVENT_SOCKET_HANDLE_DECL(tlsdnscancel); NETIEVENT_SOCKET_QUOTA_DECL(tlsdnsaccept); NETIEVENT_SOCKET_DECL(tlsdnscycle); +NETIEVENT_SOCKET_DECL(httpstop); +NETIEVENT_SOCKET_REQ_DECL(httpsend); +NETIEVENT_SOCKET_DECL(httpclose); + NETIEVENT_SOCKET_REQ_DECL(tcpconnect); NETIEVENT_SOCKET_REQ_DECL(tcpsend); NETIEVENT_SOCKET_REQ_DECL(tlssend); diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c index d1331cea2b..fb8b1fa701 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -734,6 +734,10 @@ process_netievent(isc__networker_t *worker, isc__netievent_t *ievent) { NETIEVENT_CASE(tlsdnsstop); NETIEVENT_CASE(tlsdnsshutdown); + NETIEVENT_CASE(httpstop); + NETIEVENT_CASE(httpsend); + NETIEVENT_CASE(httpclose); + NETIEVENT_CASE(connectcb); NETIEVENT_CASE(readcb); NETIEVENT_CASE(sendcb); @@ -814,6 +818,10 @@ NETIEVENT_SOCKET_QUOTA_DEF(tlsdnsaccept); NETIEVENT_SOCKET_DEF(tlsdnscycle); NETIEVENT_SOCKET_DEF(tlsdnsshutdown); +NETIEVENT_SOCKET_DEF(httpstop); +NETIEVENT_SOCKET_REQ_DEF(httpsend); +NETIEVENT_SOCKET_DEF(httpclose); + NETIEVENT_SOCKET_REQ_DEF(tcpconnect); NETIEVENT_SOCKET_REQ_DEF(tcpsend); NETIEVENT_SOCKET_REQ_DEF(tlssend); @@ -1001,6 +1009,32 @@ nmsocket_cleanup(isc_nmsocket_t *sock, bool dofree FLARG) { isc_condition_destroy(&sock->scond); isc__nm_tls_cleanup_data(sock); + if (sock->type == isc_nm_httplistener) { + isc__nm_http_clear_handlers(sock); + isc_rwlock_destroy(&sock->h2.handlers_lock); + } + + if (sock->h2.request_path != NULL) { + isc_mem_free(sock->mgr->mctx, sock->h2.request_path); + sock->h2.request_path = NULL; + } + + if (sock->h2.query_data != NULL) { + isc_mem_free(sock->mgr->mctx, sock->h2.query_data); + sock->h2.query_data = NULL; + } + + if (sock->h2.connect.uri != NULL) { + isc_mem_free(sock->mgr->mctx, sock->h2.connect.uri); + sock->h2.query_data = NULL; + } + + if (sock->h2.buf != NULL) { + isc_mem_free(sock->mgr->mctx, sock->h2.buf); + sock->h2.buf = NULL; + } + + isc__nm_http_clear_session(sock); #ifdef NETMGR_TRACE LOCK(&sock->mgr->lock); ISC_LIST_UNLINK(sock->mgr->active_sockets, sock, active_link); @@ -1115,6 +1149,9 @@ isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG) { case isc_nm_tlsdnssocket: isc__nm_tlsdns_close(sock); return; + case isc_nm_httpstream: + isc__nm_http_close(sock); + return; default: break; } @@ -1158,7 +1195,8 @@ isc_nmsocket_close(isc_nmsocket_t **sockp) { (*sockp)->type == isc_nm_tcplistener || (*sockp)->type == isc_nm_tcpdnslistener || (*sockp)->type == isc_nm_tlsdnslistener || - (*sockp)->type == isc_nm_tlslistener); + (*sockp)->type == isc_nm_tlslistener || + (*sockp)->type == isc_nm_httplistener); isc__nmsocket_detach(sockp); } @@ -1221,6 +1259,8 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type, case isc_nm_tcpdnslistener: case isc_nm_tlsdnssocket: case isc_nm_tlsdnslistener: + case isc_nm_httpstream: + case isc_nm_httplistener: if (family == AF_INET) { sock->statsindex = tcp4statsindex; } else { @@ -1250,6 +1290,28 @@ isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type, atomic_store(&sock->active_child_connections, 0); + if (type == isc_nm_httplistener) { + ISC_LIST_INIT(sock->h2.handlers); + ISC_LIST_INIT(sock->h2.handlers_cbargs); + isc_rwlock_init(&sock->h2.handlers_lock, 0, 1); + } + + sock->h2.session = NULL; + sock->h2.httpserver = NULL; + sock->h2.query_data = NULL; + sock->h2.query_data_len = 0; + sock->h2.query_too_large = false; + sock->h2.request_path = NULL; + sock->h2.request_type = ISC_HTTP_REQ_UNSUPPORTED; + sock->h2.request_scheme = ISC_HTTP_SCHEME_UNSUPPORTED; + sock->h2.content_length = 0; + sock->h2.content_type_verified = false; + sock->h2.accept_type_verified = false; + sock->h2.handler_cb = NULL; + sock->h2.handler_cbarg = NULL; + sock->h2.connect.uri = NULL; + sock->h2.buf = NULL; + sock->magic = NMSOCK_MAGIC; } @@ -1391,6 +1453,10 @@ isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t *peer, sock->statichandle = handle; } + if (sock->type == isc_nm_httpstream) { + handle->httpsession = sock->h2.session; + } + return (handle); } @@ -1822,6 +1888,9 @@ isc_nm_stoplistening(isc_nmsocket_t *sock) { case isc_nm_tlsdnslistener: isc__nm_tlsdns_stoplistening(sock); break; + case isc_nm_httplistener: + isc__nm_http_stoplistening(sock); + break; default: INSIST(0); ISC_UNREACHABLE(); @@ -2374,6 +2443,10 @@ nmsocket_type_totext(isc_nmsocket_type type) { return ("isc_nm_tlsdnslistener"); case isc_nm_tlsdnssocket: return ("isc_nm_tlsdnssocket"); + case isc_nm_httplistener: + return ("isc_nm_httplistener"); + case isc_nm_httpstream: + return ("isc_nm_httpstream"); default: INSIST(0); ISC_UNREACHABLE(); diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c index 6c52aed62c..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; diff --git a/lib/isc/netmgr/tlsstream.c b/lib/isc/netmgr/tlsstream.c index a2d4f80d62..87242e0a0b 100644 --- a/lib/isc/netmgr/tlsstream.c +++ b/lib/isc/netmgr/tlsstream.c @@ -510,7 +510,7 @@ isc_nm_listentls(isc_nm_t *mgr, isc_nmiface_t *iface, *sockp = tlssock; } - return result; + return (result); } void diff --git a/lib/isc/tests/Makefile.am b/lib/isc/tests/Makefile.am index c8d07aa288..58b43607a2 100644 --- a/lib/isc/tests/Makefile.am +++ b/lib/isc/tests/Makefile.am @@ -48,6 +48,7 @@ TESTS = \ tcp_quota_test \ tcpdns_test \ tlsdns_test \ + doh_test \ time_test \ timer_test \ udp_test @@ -55,6 +56,16 @@ TESTS = \ 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..9e47d10cb6 --- /dev/null +++ b/lib/isc/tests/doh_test.c @@ -0,0 +1,1875 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#if HAVE_CMOCKA +#include /* 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 "uv_wrap.h" +#define KEEP_BEFORE + +#include "../netmgr/http.c" +#include "../netmgr/netmgr-int.h" +#include "../netmgr/uv-compat.c" +#include "../netmgr/uv-compat.h" +#include "isctest.h" +#include "tls_test_cert_key.h" + +#define MAX_NM 2 + +static isc_sockaddr_t tcp_listen_addr; + +static uint64_t send_magic = 0; +static uint64_t stop_magic = 0; + +static uv_buf_t send_msg = { .base = (char *)&send_magic, + .len = sizeof(send_magic) }; + +static atomic_uint_fast64_t nsends; + +static atomic_uint_fast64_t ssends; +static atomic_uint_fast64_t sreads; + +static atomic_uint_fast64_t csends; +static atomic_uint_fast64_t creads; + +static atomic_bool was_error; + +static unsigned int workers = 1; + +static bool reuse_supported = true; + +static atomic_bool POST = true; + +static atomic_bool use_TLS = false; +static SSL_CTX *server_ssl_ctx = NULL; + +static SSL_CTX * +create_server_ssl_ctx(const char *key, const size_t key_size, + const char *key_pass, const char *cert, + const size_t cert_size); + +#define NSENDS 100 +#define NWRITES 10 + +#define DOH_PATH "/dns-query" + +#define CHECK_RANGE_FULL(v) \ + { \ + int __v = atomic_load(&v); \ + assert_true(__v > NSENDS * NWRITES * 10 / 100); \ + assert_true(__v <= NSENDS * NWRITES * 110 / 100); \ + } + +#define CHECK_RANGE_HALF(v) \ + { \ + int __v = atomic_load(&v); \ + assert_true(__v > NSENDS * NWRITES * 5 / 100); \ + assert_true(__v <= NSENDS * NWRITES * 110 / 100); \ + } + +/* Enable this to print values while running tests */ +#undef PRINT_DEBUG +#ifdef PRINT_DEBUG +#define X(v) fprintf(stderr, #v " = %" PRIu64 "\n", atomic_load(&v)) +#else +#define X(v) +#endif + +static int +setup_ephemeral_port(isc_sockaddr_t *addr, sa_family_t family) { + isc_result_t result; + socklen_t addrlen = sizeof(*addr); + int fd; + int r; + + isc_sockaddr_fromin6(addr, &in6addr_loopback, 0); + + fd = socket(AF_INET6, family, 0); + if (fd < 0) { + perror("setup_ephemeral_port: socket()"); + return (-1); + } + + r = bind(fd, (const struct sockaddr *)&addr->type.sa, + sizeof(addr->type.sin6)); + if (r != 0) { + perror("setup_ephemeral_port: bind()"); + isc__nm_closesocket(fd); + return (r); + } + + r = getsockname(fd, (struct sockaddr *)&addr->type.sa, &addrlen); + if (r != 0) { + perror("setup_ephemeral_port: getsockname()"); + isc__nm_closesocket(fd); + return (r); + } + + result = isc__nm_socket_reuse(fd); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) { + fprintf(stderr, + "setup_ephemeral_port: isc__nm_socket_reuse(): %s", + isc_result_totext(result)); + close(fd); + return (-1); + } + + result = isc__nm_socket_reuse_lb(fd); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTIMPLEMENTED) { + fprintf(stderr, + "setup_ephemeral_port: isc__nm_socket_reuse_lb(): %s", + isc_result_totext(result)); + close(fd); + return (-1); + } + if (result == ISC_R_NOTIMPLEMENTED) { + reuse_supported = false; + } + +#if IPV6_RECVERR +#define setsockopt_on(socket, level, name) \ + setsockopt(socket, level, name, &(int){ 1 }, sizeof(int)) + + r = setsockopt_on(fd, IPPROTO_IPV6, IPV6_RECVERR); + if (r != 0) { + perror("setup_ephemeral_port"); + close(fd); + return (r); + } +#endif + + return (fd); +} + +static int +_setup(void **state) { + UNUSED(state); + + /*workers = isc_os_ncpus();*/ + + if (isc_test_begin(NULL, false, workers) != ISC_R_SUCCESS) { + return (-1); + } + + signal(SIGPIPE, SIG_IGN); + + server_ssl_ctx = create_server_ssl_ctx( + (const char *)TLS_test_key, sizeof(TLS_test_key), NULL, + (const char *)TLS_test_cert, sizeof(TLS_test_cert)); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + if (server_ssl_ctx) { + SSL_CTX_free(server_ssl_ctx); + server_ssl_ctx = NULL; + } + + isc_test_end(); + + return (0); +} + +/* Generic */ + +static void +noop_read_cb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, + void *cbarg) { + UNUSED(handle); + UNUSED(result); + UNUSED(region); + UNUSED(cbarg); +} + +thread_local uint8_t tcp_buffer_storage[4096]; +thread_local size_t tcp_buffer_length = 0; + +static int +nm_setup(void **state) { + size_t nworkers = ISC_MAX(ISC_MIN(workers, 32), 1); + int tcp_listen_sock = -1; + isc_nm_t **nm = NULL; + + tcp_listen_addr = (isc_sockaddr_t){ .length = 0 }; + tcp_listen_sock = setup_ephemeral_port(&tcp_listen_addr, SOCK_STREAM); + if (tcp_listen_sock < 0) { + return (-1); + } + close(tcp_listen_sock); + tcp_listen_sock = -1; + + atomic_store(&nsends, NSENDS * NWRITES); + + atomic_store(&csends, 0); + atomic_store(&creads, 0); + atomic_store(&sreads, 0); + atomic_store(&ssends, 0); + + atomic_store(&was_error, false); + + atomic_store(&POST, false); + atomic_store(&use_TLS, false); + + isc_nonce_buf(&send_magic, sizeof(send_magic)); + isc_nonce_buf(&stop_magic, sizeof(stop_magic)); + if (send_magic == stop_magic) { + return (-1); + } + + nm = isc_mem_get(test_mctx, MAX_NM * sizeof(nm[0])); + for (size_t i = 0; i < MAX_NM; i++) { + nm[i] = isc_nm_start(test_mctx, nworkers); + assert_non_null(nm[i]); + } + + *state = nm; + + return (0); +} + +static int +nm_teardown(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + + for (size_t i = 0; i < MAX_NM; i++) { + isc_nm_destroy(&nm[i]); + assert_null(nm[i]); + } + isc_mem_put(test_mctx, nm, MAX_NM * sizeof(nm[0])); + + return (0); +} + +thread_local size_t nwrites = NWRITES; + +static void +sockaddr_to_url(isc_sockaddr_t *sa, const bool https, char *outbuf, + size_t outbuf_len, const char *append) { + uint16_t port; + char saddr[INET6_ADDRSTRLEN] = { 0 }; + int family; + if (sa == NULL || outbuf == NULL || outbuf_len == 0) { + return; + } + + family = ((struct sockaddr *)&sa->type.sa)->sa_family; + + port = ntohs(family == AF_INET ? sa->type.sin.sin_port + : sa->type.sin6.sin6_port); + inet_ntop(family, + family == AF_INET + ? (struct sockaddr *)&sa->type.sin.sin_addr + : (struct sockaddr *)&sa->type.sin6.sin6_addr, + saddr, sizeof(saddr)); + + snprintf(outbuf, outbuf_len, "%s://%s%s%s:%u%s", + https ? "https" : "http", family == AF_INET ? "" : "[", saddr, + family == AF_INET ? "" : "]", port, append ? append : ""); +} + +/* SSL utils */ +static bool +ssl_ctx_add_privatekey(SSL_CTX *ctx, const void *key, + const unsigned int key_size, const char *pass) { + BIO *key_bio = BIO_new_mem_buf(key, key_size); + bool res = false; + if (key_bio) { + RSA *rsa = PEM_read_bio_RSAPrivateKey( + key_bio, 0, 0, (void *)((uintptr_t)pass)); + if (rsa) { + res = (1 == SSL_CTX_use_RSAPrivateKey(ctx, rsa)) + ? true + : false; + } + RSA_free(rsa); + BIO_free_all(key_bio); + } else { + return false; + } + + res = SSL_CTX_check_private_key(ctx) == 1 ? true : false; + + return res; +} + +static bool +ssl_ctx_add_cert_chain(SSL_CTX *ctx, const void *cert, + const unsigned int size) { + BIO *chain_bio = NULL; + STACK_OF(X509_INFO) *chain_stack = NULL; + size_t count = 0; + X509_INFO *ci = NULL; + bool res = true; + + chain_bio = BIO_new_mem_buf(cert, size); + if (chain_bio == NULL) { + res = false; + goto exit; + } + + /* read info into BIO */ + chain_stack = PEM_X509_INFO_read_bio(chain_bio, NULL, NULL, NULL); + if (chain_stack == NULL) { + res = false; + goto exit; + } + + count = sk_X509_INFO_num(chain_stack); + /* add certs */ + for (size_t i = count; i > 0; i--) { + /* get the cert */ + ci = sk_X509_INFO_value(chain_stack, i - 1); + if (ci == NULL) { + res = false; + goto exit; + } + + /* add the cert */ + if (SSL_CTX_add_extra_chain_cert(ctx, ci->x509) != 1) { + res = false; + goto exit; + } + + /* use the first cert in chain by default */ + if (i == 1) { + if (SSL_CTX_use_certificate(ctx, ci->x509) != 1) { + res = false; + goto exit; + } + } + } +exit: + if (chain_stack) { + while ((ci = sk_X509_INFO_pop(chain_stack)) != NULL) { + X509_INFO_free(ci); + } + sk_X509_INFO_free(chain_stack); + } + if (chain_bio) { + BIO_free_all(chain_bio); + } + return res; +} + +static SSL_CTX * +create_server_ssl_ctx(const char *key, const size_t key_size, + const char *key_pass, const char *cert, + const size_t cert_size) { + SSL_CTX *ssl_ctx; + EC_KEY *ecdh; + + ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + if (!ssl_ctx) { + fprintf(stderr, "Could not create SSL/TLS context: %s", + ERR_error_string(ERR_get_error(), NULL)); + SSL_CTX_free(ssl_ctx); + return NULL; + } + /* >= TLSv1.2 */ + SSL_CTX_set_options( + ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (!ecdh) { + fprintf(stderr, "EC_KEY_new_by_curv_name failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + SSL_CTX_free(ssl_ctx); + return NULL; + } + SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); + EC_KEY_free(ecdh); + + if (ssl_ctx_add_cert_chain(ssl_ctx, cert, cert_size) != true) { + fprintf(stderr, "Could not read certificate file\n"); + SSL_CTX_free(ssl_ctx); + return NULL; + } + + if (ssl_ctx_add_privatekey(ssl_ctx, key, key_size, key_pass) != true) { + fprintf(stderr, "Could not read private key\n"); + SSL_CTX_free(ssl_ctx); + return NULL; + } + + return ssl_ctx; +} + +static void +doh_receive_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + uint_fast64_t sends = atomic_load(&nsends); + assert_non_null(handle); + UNUSED(cbarg); + UNUSED(region); + + if (eresult == ISC_R_SUCCESS) { + atomic_fetch_add(&csends, 1); + atomic_fetch_add(&creads, 1); + if (sends > 0) { + atomic_fetch_sub(&nsends, 1); + } + isc_nm_resumeread(handle); + } else { + /* We failed to connect; try again */ + while (sends > 0) { + /* Continue until we subtract or we are done */ + if (atomic_compare_exchange_weak(&nsends, &sends, + sends - 1)) { + sends--; + break; + } + } + atomic_store(&was_error, true); + /* Send failed, we need to stop reading too */ + isc_nm_cancelread(handle); + } +} + +static void +doh_reply_sent_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) { + UNUSED(eresult); + UNUSED(cbarg); + + assert_non_null(handle); + + if (eresult == ISC_R_SUCCESS) { + atomic_fetch_add(&ssends, 1); + } +} + +static void +doh_receive_request_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + uint64_t magic = 0; + + UNUSED(cbarg); + assert_non_null(handle); + + if (eresult != ISC_R_SUCCESS) { + atomic_store(&was_error, true); + return; + } + + atomic_fetch_add(&sreads, 1); + + memmove(tcp_buffer_storage + tcp_buffer_length, region->base, + region->length); + tcp_buffer_length += region->length; + + while (tcp_buffer_length >= sizeof(magic)) { + magic = *(uint64_t *)tcp_buffer_storage; + assert_true(magic == stop_magic || magic == send_magic); + + tcp_buffer_length -= sizeof(magic); + memmove(tcp_buffer_storage, tcp_buffer_storage + sizeof(magic), + tcp_buffer_length); + + if (magic == send_magic) { + isc_nm_send(handle, region, doh_reply_sent_cb, NULL); + return; + } else if (magic == stop_magic) { + /* We are done, so we don't send anything back */ + /* There should be no more packets in the buffer */ + assert_int_equal(tcp_buffer_length, 0); + } + } +} + +static void +doh_noop(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp(listen_nm, (isc_nmiface_t *)&tcp_listen_addr, + 0, NULL, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + noop_read_cb, NULL, 0); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + sockaddr_to_url(&tcp_listen_addr, false, req_url, sizeof(req_url), + DOH_PATH); + (void)isc_nm_http_connect_send_request( + connect_nm, req_url, atomic_load(&POST), + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + noop_read_cb, NULL, NULL, 30000); + + isc_nm_closedown(connect_nm); + + assert_int_equal(0, atomic_load(&csends)); + assert_int_equal(0, atomic_load(&creads)); + assert_int_equal(0, atomic_load(&sreads)); + assert_int_equal(0, atomic_load(&ssends)); +} + +static void +doh_noop_POST(void **state) { + atomic_store(&POST, true); + doh_noop(state); +} + +static void +doh_noop_GET(void **state) { + atomic_store(&POST, false); + doh_noop(state); +} + +static void +doh_noresponse(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp(listen_nm, (isc_nmiface_t *)&tcp_listen_addr, + 0, NULL, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + noop_read_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + sockaddr_to_url(&tcp_listen_addr, false, req_url, sizeof(req_url), + DOH_PATH); + (void)isc_nm_http_connect_send_request( + connect_nm, req_url, atomic_load(&POST), + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + noop_read_cb, NULL, NULL, 30000); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc_nm_closedown(connect_nm); +} + +static void +doh_noresponse_POST(void **state) { + atomic_store(&POST, true); + doh_noresponse(state); +} + +static void +doh_noresponse_GET(void **state) { + atomic_store(&POST, false); + doh_noresponse(state); +} + +static void +doh_receive_send_reply_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + uint_fast64_t sends = atomic_load(&nsends); + assert_non_null(handle); + UNUSED(region); + + if (eresult == ISC_R_SUCCESS) { + atomic_fetch_add(&csends, 1); + atomic_fetch_add(&creads, 1); + if (sends > 0) { + size_t i; + atomic_fetch_sub(&nsends, 1); + for (i = 0; i < NWRITES / 2; i++) { + eresult = isc_nm_httprequest( + handle, + &(isc_region_t){ + .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_send_reply_cb, cbarg); + assert_true(eresult == ISC_R_SUCCESS); + } + } + } else { + /* We failed to connect; try again */ + while (sends > 0) { + /* Continue until we subtract or we are done */ + if (atomic_compare_exchange_weak(&nsends, &sends, + sends - 1)) { + sends--; + break; + } + } + atomic_store(&was_error, true); + } +} + +static isc_threadresult_t +doh_connect_thread(isc_threadarg_t arg) { + isc_nm_t *connect_nm = (isc_nm_t *)arg; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + sockaddr_to_url(&tcp_listen_addr, atomic_load(&use_TLS), req_url, + sizeof(req_url), DOH_PATH); + + while (atomic_load(&nsends) > 0) { + (void)isc_nm_http_connect_send_request( + connect_nm, req_url, atomic_load(&POST), + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_send_reply_cb, NULL, NULL, 5000); + } + + return ((isc_threadresult_t)0); +} + +static void +doh_recv_one(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + atomic_store(&nsends, 1); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + sockaddr_to_url(&tcp_listen_addr, atomic_load(&use_TLS), req_url, + sizeof(req_url), DOH_PATH); + result = isc_nm_http_connect_send_request( + connect_nm, req_url, atomic_load(&POST), + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_reply_cb, NULL, NULL, 5000); + + assert_int_equal(result, ISC_R_SUCCESS); + + while (atomic_load(&nsends) > 0) { + if (atomic_load(&was_error)) { + break; + } + isc_thread_yield(); + } + + while (atomic_load(&ssends) != 1 || atomic_load(&sreads) != 1 || + atomic_load(&csends) != 1) + { + if (atomic_load(&was_error)) { + break; + } + isc_thread_yield(); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc_nm_closedown(connect_nm); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + assert_int_equal(atomic_load(&csends), 1); + assert_int_equal(atomic_load(&creads), 1); + assert_int_equal(atomic_load(&sreads), 1); + assert_int_equal(atomic_load(&ssends), 1); +} + +static void +doh_recv_one_POST(void **state) { + atomic_store(&POST, true); + doh_recv_one(state); +} + +static void +doh_recv_one_GET(void **state) { + atomic_store(&POST, false); + doh_recv_one(state); +} + +static void +doh_recv_one_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_recv_one(state); +} + +static void +doh_recv_one_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_recv_one(state); +} + +static void +doh_connect_send_two_requests_cb(isc_nmhandle_t *handle, isc_result_t result, + void *arg) { + REQUIRE(VALID_NMHANDLE(handle)); + if (result != ISC_R_SUCCESS) { + goto error; + } + + result = isc_nm_httprequest( + handle, + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_reply_cb, arg); + if (result != ISC_R_SUCCESS) { + goto error; + } + + result = isc_nm_httprequest( + handle, + &(isc_region_t){ .base = (uint8_t *)send_msg.base, + .length = send_msg.len }, + doh_receive_reply_cb, arg); + if (result != ISC_R_SUCCESS) { + goto error; + } + + isc_nm_resumeread(handle); + return; +error: + atomic_store(&was_error, true); +} + +static void +doh_recv_two(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_sockaddr_t tcp_connect_addr; + char req_url[256]; + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + atomic_store(&nsends, 2); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + sockaddr_to_url(&tcp_listen_addr, atomic_load(&use_TLS), req_url, + sizeof(req_url), DOH_PATH); + result = isc_nm_httpconnect( + connect_nm, NULL, NULL, req_url, atomic_load(&POST), + doh_connect_send_two_requests_cb, NULL, NULL, 5000, 0); + + assert_int_equal(result, ISC_R_SUCCESS); + + while (atomic_load(&nsends) > 0) { + if (atomic_load(&was_error)) { + break; + } + isc_thread_yield(); + } + + while (atomic_load(&ssends) != 2 || atomic_load(&sreads) != 2 || + atomic_load(&csends) != 2) + { + if (atomic_load(&was_error)) { + break; + } + isc_thread_yield(); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc_nm_closedown(connect_nm); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + assert_int_equal(atomic_load(&csends), 2); + assert_int_equal(atomic_load(&creads), 2); + assert_int_equal(atomic_load(&sreads), 2); + assert_int_equal(atomic_load(&ssends), 2); +} + +static void +doh_recv_two_POST(void **state) { + atomic_store(&POST, true); + doh_recv_two(state); +} + +static void +doh_recv_two_GET(void **state) { + atomic_store(&POST, false); + doh_recv_two(state); +} + +static void +doh_recv_two_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_recv_two(state); +} + +static void +doh_recv_two_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_recv_two(state); +} + +static void +doh_recv_send(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1); + isc_thread_t threads[32] = { 0 }; + isc_sockaddr_t tcp_connect_addr; + + if (!reuse_supported) { + skip(); + return; + } + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_create(doh_connect_thread, connect_nm, &threads[i]); + } + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + isc_nm_closedown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_FULL(csends); + CHECK_RANGE_FULL(creads); + CHECK_RANGE_FULL(sreads); + CHECK_RANGE_FULL(ssends); +} + +static void +doh_recv_send_POST(void **state) { + atomic_store(&POST, true); + doh_recv_send(state); +} + +static void +doh_recv_send_GET(void **state) { + atomic_store(&POST, false); + doh_recv_send(state); +} + +static void +doh_recv_send_POST_TLS(void **state) { + atomic_store(&POST, true); + atomic_store(&use_TLS, true); + doh_recv_send(state); +} + +static void +doh_recv_send_GET_TLS(void **state) { + atomic_store(&POST, false); + atomic_store(&use_TLS, true); + doh_recv_send(state); +} + +static void +doh_recv_half_send(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1); + isc_thread_t threads[32] = { 0 }; + isc_sockaddr_t tcp_connect_addr; + + if (!reuse_supported) { + skip(); + return; + } + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_create(doh_connect_thread, connect_nm, &threads[i]); + } + + while (atomic_load(&nsends) >= (NSENDS * NWRITES) / 2) { + isc_thread_yield(); + } + + isc_nm_closedown(connect_nm); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +doh_recv_half_send_POST(void **state) { + atomic_store(&POST, true); + doh_recv_half_send(state); +} + +static void +doh_recv_half_send_GET(void **state) { + atomic_store(&POST, false); + doh_recv_half_send(state); +} + +static void +doh_recv_half_send_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_recv_half_send(state); +} + +static void +doh_recv_half_send_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_recv_half_send(state); +} + +static void +doh_half_recv_send(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1); + isc_thread_t threads[32] = { 0 }; + isc_sockaddr_t tcp_connect_addr; + + if (!reuse_supported) { + skip(); + return; + } + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_create(doh_connect_thread, connect_nm, &threads[i]); + } + + while (atomic_load(&nsends) >= (NSENDS * NWRITES) / 2) { + isc_thread_yield(); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + isc_nm_closedown(connect_nm); + + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +doh_half_recv_send_POST(void **state) { + atomic_store(&POST, true); + doh_half_recv_send(state); +} + +static void +doh_half_recv_send_GET(void **state) { + atomic_store(&POST, false); + doh_half_recv_send(state); +} + +static void +doh_half_recv_send_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_half_recv_send(state); +} + +static void +doh_half_recv_send_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_half_recv_send(state); +} + +static void +doh_half_recv_half_send(void **state) { + isc_nm_t **nm = (isc_nm_t **)*state; + isc_nm_t *listen_nm = nm[0]; + isc_nm_t *connect_nm = nm[1]; + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + size_t nthreads = ISC_MAX(ISC_MIN(workers, 32), 1); + isc_thread_t threads[32] = { 0 }; + isc_sockaddr_t tcp_connect_addr; + + if (!reuse_supported) { + skip(); + return; + } + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + result = isc_nm_listenhttp( + listen_nm, (isc_nmiface_t *)&tcp_listen_addr, 0, NULL, + atomic_load(&use_TLS) ? server_ssl_ctx : NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_nm_http_add_doh_endpoint(listen_sock, DOH_PATH, + doh_receive_request_cb, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_create(doh_connect_thread, connect_nm, &threads[i]); + } + + while (atomic_load(&nsends) >= (NSENDS * NWRITES) / 2) { + isc_thread_yield(); + } + + isc_nm_closedown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + for (size_t i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +doh_half_recv_half_send_POST(void **state) { + atomic_store(&POST, true); + doh_half_recv_half_send(state); +} + +static void +doh_half_recv_half_send_GET(void **state) { + atomic_store(&POST, false); + doh_half_recv_half_send(state); +} + +static void +doh_half_recv_half_send_POST_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, true); + doh_half_recv_half_send(state); +} + +static void +doh_half_recv_half_send_GET_TLS(void **state) { + atomic_store(&use_TLS, true); + atomic_store(&POST, false); + doh_half_recv_half_send(state); +} + +static void +doh_parse_GET_query_string(void **state) { + UNUSED(state); + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = + "dns=AAABAAABAAAAAAAAAWE-" + "NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3" + "QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == strlen(str) - 4); + assert_true(memcmp(queryp, str + 4, len) == 0); + } + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = + "?dns=AAABAAABAAAAAAAAAWE-" + "NjJjaGFyYWN0ZXJsYWJlbC1tYWtlcy1iYXNlNjR1cmwtZGlzdGluY3" + "QtZnJvbS1zdGFuZGFyZC1iYXNlNjQHZXhhbXBsZQNjb20AAAEAAQ&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == strlen(str) - 6); + assert_true(memcmp(queryp, str + 5, len) == 0); + } + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?dns=123&dns=567"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == 3); + assert_true(memcmp(queryp, "567", 3) == 0); + } + /* valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = "?name1=123&dns=567&name2=123&"; + + ret = isc__nm_parse_doh_query_string(str, &queryp, &len); + assert_true(ret); + assert_non_null(queryp); + assert_true(len > 0); + assert_true(len == 3); + assert_true(memcmp(queryp, "567", 3) == 0); + } + /* complex, but still valid */ + { + bool ret; + const char *queryp = NULL; + size_t len = 0; + char str[] = + "?title=%D0%92%D1%96%D0%B4%D1%81%D0%BE%D1%82%D0%BA%D0%" + "BE%D0%B2%D0%B5_%D0%BA%D0%BE%D0%B4%D1%83%D0%B2%D0%B0%" + "D0%BD%D0%BD%D1%8F&dns=123&veaction=edit§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[] = { + cmocka_unit_test_setup_teardown(doh_parse_GET_query_string, + NULL, NULL), + cmocka_unit_test_setup_teardown(doh_base64url_to_base64, NULL, + NULL), + cmocka_unit_test_setup_teardown(doh_base64_to_base64url, NULL, + NULL), + cmocka_unit_test_setup_teardown(doh_noop_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_noop_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_noresponse_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_noresponse_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_one_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_one_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_one_POST_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_one_GET_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_two_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_two_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_two_POST_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_two_GET_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_send_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_send_POST, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_send_GET_TLS, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_send_POST_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_half_send_GET, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_half_send_POST, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_half_send_GET_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_recv_half_send_POST_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_send_GET, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_send_POST, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_send_GET_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_send_POST_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_half_send_GET, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_half_send_POST, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(doh_half_recv_half_send_GET_TLS, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown( + doh_half_recv_half_send_POST_TLS, nm_setup, + nm_teardown), + /*cmocka_unit_test_setup_teardown(doh_cloudflare_GET, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(doh_cloudflare_POST, nm_setup, + nm_teardown)*/ + }; + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#else /* HAVE_CMOCKA */ + +#include + +int +main(void) { + printf("1..0 # Skipped: cmocka not available\n"); + return (0); +} + +#endif /* if HAVE_CMOCKA */ diff --git a/lib/isc/tests/tls_test_cert_key.h b/lib/isc/tests/tls_test_cert_key.h new file mode 100644 index 0000000000..898b77bd85 --- /dev/null +++ b/lib/isc/tests/tls_test_cert_key.h @@ -0,0 +1,465 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* THIS FILE IS AUTOMATICALLY GENERATED!!! DO NOT EDIT!!! */ +/* Generated on: [ Tue Dec 29 17:58:04 2020 ], from file: test_cert.pem */ + +#ifndef _TLS_TEST_CERT_H +#define _TLS_TEST_CERT_H + +static const unsigned char TLS_test_cert[] = { + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, + 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x46, 0x69, 0x54, 0x43, 0x43, + 0x41, 0x33, 0x47, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x55, + 0x66, 0x67, 0x2f, 0x48, 0x6f, 0x43, 0x6c, 0x42, 0x43, 0x67, 0x6e, 0x64, + 0x50, 0x32, 0x30, 0x56, 0x47, 0x2b, 0x30, 0x4c, 0x63, 0x4b, 0x58, 0x56, + 0x69, 0x66, 0x55, 0x77, 0x44, 0x51, 0x59, 0x4a, 0x4b, 0x6f, 0x5a, 0x49, + 0x68, 0x76, 0x63, 0x4e, 0x41, 0x51, 0x45, 0x4c, 0x0a, 0x42, 0x51, 0x41, + 0x77, 0x55, 0x7a, 0x45, 0x4c, 0x4d, 0x41, 0x6b, 0x47, 0x41, 0x31, 0x55, + 0x45, 0x42, 0x68, 0x4d, 0x43, 0x56, 0x55, 0x45, 0x78, 0x45, 0x44, 0x41, + 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x67, 0x4d, 0x42, 0x31, 0x56, + 0x72, 0x63, 0x6d, 0x46, 0x70, 0x62, 0x6d, 0x55, 0x78, 0x45, 0x44, 0x41, + 0x4f, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x63, 0x4d, 0x42, 0x30, 0x74, + 0x6f, 0x0a, 0x59, 0x58, 0x4a, 0x72, 0x61, 0x58, 0x59, 0x78, 0x44, 0x44, + 0x41, 0x4b, 0x42, 0x67, 0x4e, 0x56, 0x42, 0x41, 0x6f, 0x4d, 0x41, 0x30, + 0x6c, 0x54, 0x51, 0x7a, 0x45, 0x53, 0x4d, 0x42, 0x41, 0x47, 0x41, 0x31, + 0x55, 0x45, 0x41, 0x77, 0x77, 0x4a, 0x62, 0x47, 0x39, 0x6a, 0x59, 0x57, + 0x78, 0x6f, 0x62, 0x33, 0x4e, 0x30, 0x4d, 0x43, 0x41, 0x58, 0x44, 0x54, + 0x49, 0x77, 0x4d, 0x54, 0x49, 0x79, 0x0a, 0x4f, 0x54, 0x45, 0x31, 0x4e, + 0x54, 0x51, 0x79, 0x4d, 0x31, 0x6f, 0x59, 0x44, 0x7a, 0x49, 0x78, 0x4d, + 0x6a, 0x41, 0x78, 0x4d, 0x6a, 0x41, 0x31, 0x4d, 0x54, 0x55, 0x31, 0x4e, + 0x44, 0x49, 0x7a, 0x57, 0x6a, 0x42, 0x54, 0x4d, 0x51, 0x73, 0x77, 0x43, + 0x51, 0x59, 0x44, 0x56, 0x51, 0x51, 0x47, 0x45, 0x77, 0x4a, 0x56, 0x51, + 0x54, 0x45, 0x51, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 0x45, 0x0a, + 0x43, 0x41, 0x77, 0x48, 0x56, 0x57, 0x74, 0x79, 0x59, 0x57, 0x6c, 0x75, + 0x5a, 0x54, 0x45, 0x51, 0x4d, 0x41, 0x34, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x42, 0x77, 0x77, 0x48, 0x53, 0x32, 0x68, 0x68, 0x63, 0x6d, 0x74, 0x70, + 0x64, 0x6a, 0x45, 0x4d, 0x4d, 0x41, 0x6f, 0x47, 0x41, 0x31, 0x55, 0x45, + 0x43, 0x67, 0x77, 0x44, 0x53, 0x56, 0x4e, 0x44, 0x4d, 0x52, 0x49, 0x77, + 0x45, 0x41, 0x59, 0x44, 0x0a, 0x56, 0x51, 0x51, 0x44, 0x44, 0x41, 0x6c, + 0x73, 0x62, 0x32, 0x4e, 0x68, 0x62, 0x47, 0x68, 0x76, 0x63, 0x33, 0x51, + 0x77, 0x67, 0x67, 0x49, 0x69, 0x4d, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, + 0x47, 0x53, 0x49, 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, + 0x41, 0x41, 0x34, 0x49, 0x43, 0x44, 0x77, 0x41, 0x77, 0x67, 0x67, 0x49, + 0x4b, 0x41, 0x6f, 0x49, 0x43, 0x41, 0x51, 0x43, 0x71, 0x0a, 0x74, 0x49, + 0x42, 0x33, 0x43, 0x4c, 0x75, 0x71, 0x52, 0x54, 0x6d, 0x67, 0x39, 0x4d, + 0x45, 0x56, 0x69, 0x70, 0x30, 0x63, 0x43, 0x45, 0x2f, 0x33, 0x68, 0x47, + 0x64, 0x63, 0x53, 0x61, 0x51, 0x4f, 0x64, 0x6b, 0x39, 0x42, 0x5a, 0x41, + 0x53, 0x79, 0x4f, 0x4a, 0x35, 0x4c, 0x42, 0x38, 0x2f, 0x63, 0x2f, 0x48, + 0x30, 0x38, 0x4c, 0x30, 0x63, 0x4c, 0x79, 0x43, 0x4c, 0x6b, 0x31, 0x70, + 0x33, 0x78, 0x0a, 0x67, 0x56, 0x2b, 0x53, 0x57, 0x2f, 0x6d, 0x79, 0x38, + 0x65, 0x6b, 0x6f, 0x4d, 0x4a, 0x6a, 0x70, 0x4e, 0x54, 0x37, 0x70, 0x68, + 0x74, 0x78, 0x4a, 0x4e, 0x4e, 0x6c, 0x4e, 0x36, 0x39, 0x56, 0x63, 0x6e, + 0x75, 0x71, 0x33, 0x41, 0x45, 0x44, 0x4a, 0x63, 0x41, 0x41, 0x4c, 0x6f, + 0x38, 0x2b, 0x63, 0x65, 0x33, 0x6d, 0x4b, 0x35, 0x77, 0x41, 0x4d, 0x73, + 0x7a, 0x61, 0x52, 0x7a, 0x33, 0x44, 0x34, 0x0a, 0x5a, 0x4e, 0x66, 0x65, + 0x54, 0x33, 0x61, 0x6b, 0x39, 0x45, 0x78, 0x74, 0x47, 0x62, 0x57, 0x42, + 0x4f, 0x6d, 0x32, 0x4f, 0x66, 0x68, 0x49, 0x2b, 0x45, 0x53, 0x6d, 0x32, + 0x55, 0x41, 0x32, 0x34, 0x61, 0x49, 0x6a, 0x46, 0x74, 0x4a, 0x52, 0x72, + 0x6d, 0x56, 0x4d, 0x65, 0x37, 0x68, 0x44, 0x4b, 0x54, 0x78, 0x68, 0x49, + 0x69, 0x54, 0x6a, 0x4c, 0x54, 0x76, 0x56, 0x4e, 0x50, 0x2b, 0x77, 0x53, + 0x0a, 0x43, 0x62, 0x32, 0x47, 0x59, 0x34, 0x74, 0x7a, 0x7a, 0x30, 0x66, + 0x66, 0x79, 0x62, 0x50, 0x6d, 0x65, 0x5a, 0x50, 0x58, 0x61, 0x32, 0x54, + 0x39, 0x77, 0x65, 0x30, 0x68, 0x6a, 0x41, 0x56, 0x53, 0x4b, 0x47, 0x76, + 0x76, 0x30, 0x48, 0x41, 0x48, 0x65, 0x4a, 0x72, 0x4f, 0x75, 0x31, 0x75, + 0x38, 0x79, 0x4a, 0x4c, 0x6e, 0x58, 0x72, 0x64, 0x47, 0x2f, 0x4f, 0x7a, + 0x4b, 0x71, 0x65, 0x4a, 0x38, 0x0a, 0x7a, 0x74, 0x4a, 0x34, 0x47, 0x59, + 0x58, 0x7a, 0x70, 0x4b, 0x39, 0x42, 0x68, 0x74, 0x57, 0x6b, 0x6d, 0x7a, + 0x38, 0x74, 0x41, 0x78, 0x7a, 0x77, 0x6c, 0x5a, 0x47, 0x6d, 0x54, 0x41, + 0x79, 0x58, 0x45, 0x55, 0x50, 0x79, 0x33, 0x42, 0x71, 0x61, 0x4a, 0x66, + 0x2b, 0x6c, 0x46, 0x4e, 0x67, 0x61, 0x68, 0x74, 0x57, 0x78, 0x6c, 0x77, + 0x58, 0x2f, 0x31, 0x59, 0x4c, 0x35, 0x75, 0x69, 0x69, 0x49, 0x0a, 0x77, + 0x4c, 0x69, 0x41, 0x36, 0x67, 0x2f, 0x39, 0x39, 0x79, 0x4e, 0x4c, 0x41, + 0x69, 0x34, 0x38, 0x54, 0x6b, 0x41, 0x63, 0x44, 0x58, 0x42, 0x35, 0x38, + 0x61, 0x5a, 0x78, 0x36, 0x32, 0x43, 0x63, 0x6b, 0x58, 0x75, 0x4d, 0x6d, + 0x32, 0x68, 0x61, 0x37, 0x6e, 0x43, 0x64, 0x77, 0x66, 0x76, 0x31, 0x71, + 0x71, 0x6a, 0x72, 0x79, 0x44, 0x56, 0x34, 0x32, 0x2b, 0x56, 0x37, 0x48, + 0x75, 0x64, 0x4f, 0x0a, 0x57, 0x36, 0x33, 0x39, 0x6f, 0x6b, 0x6a, 0x69, + 0x56, 0x32, 0x33, 0x69, 0x48, 0x79, 0x2f, 0x33, 0x2f, 0x2f, 0x72, 0x56, + 0x31, 0x44, 0x59, 0x6a, 0x76, 0x35, 0x49, 0x4c, 0x48, 0x51, 0x79, 0x6d, + 0x7a, 0x4a, 0x2b, 0x76, 0x6e, 0x4a, 0x38, 0x4a, 0x69, 0x33, 0x59, 0x74, + 0x32, 0x66, 0x44, 0x44, 0x55, 0x58, 0x66, 0x33, 0x41, 0x45, 0x6e, 0x49, + 0x32, 0x74, 0x54, 0x75, 0x4c, 0x34, 0x54, 0x42, 0x0a, 0x4b, 0x54, 0x69, + 0x74, 0x33, 0x79, 0x79, 0x69, 0x54, 0x56, 0x52, 0x4e, 0x45, 0x59, 0x39, + 0x63, 0x4b, 0x47, 0x54, 0x6a, 0x73, 0x41, 0x7a, 0x48, 0x44, 0x7a, 0x35, + 0x33, 0x6b, 0x74, 0x6b, 0x38, 0x2b, 0x77, 0x44, 0x73, 0x49, 0x71, 0x76, + 0x4d, 0x54, 0x6d, 0x79, 0x33, 0x72, 0x65, 0x6d, 0x36, 0x68, 0x4b, 0x30, + 0x71, 0x51, 0x30, 0x48, 0x38, 0x50, 0x4f, 0x77, 0x57, 0x66, 0x31, 0x75, + 0x6c, 0x0a, 0x55, 0x71, 0x46, 0x67, 0x37, 0x34, 0x77, 0x67, 0x62, 0x61, + 0x32, 0x73, 0x71, 0x37, 0x56, 0x78, 0x61, 0x62, 0x63, 0x32, 0x71, 0x65, + 0x4b, 0x71, 0x74, 0x59, 0x4c, 0x59, 0x30, 0x4e, 0x6b, 0x51, 0x71, 0x44, + 0x67, 0x73, 0x42, 0x30, 0x55, 0x56, 0x7a, 0x50, 0x70, 0x68, 0x53, 0x56, + 0x5a, 0x51, 0x65, 0x49, 0x49, 0x41, 0x4b, 0x6c, 0x6f, 0x7a, 0x6a, 0x49, + 0x43, 0x7a, 0x39, 0x75, 0x48, 0x47, 0x0a, 0x39, 0x48, 0x31, 0x36, 0x51, + 0x52, 0x45, 0x74, 0x43, 0x4d, 0x78, 0x37, 0x42, 0x46, 0x77, 0x48, 0x74, + 0x36, 0x49, 0x31, 0x2b, 0x70, 0x4a, 0x69, 0x50, 0x50, 0x47, 0x69, 0x78, + 0x66, 0x42, 0x47, 0x74, 0x72, 0x64, 0x4a, 0x51, 0x78, 0x39, 0x34, 0x62, + 0x77, 0x76, 0x43, 0x6f, 0x38, 0x59, 0x4a, 0x71, 0x57, 0x77, 0x67, 0x66, + 0x41, 0x77, 0x50, 0x30, 0x57, 0x45, 0x6c, 0x6f, 0x6b, 0x69, 0x6b, 0x0a, + 0x53, 0x6a, 0x6a, 0x6b, 0x79, 0x62, 0x33, 0x69, 0x53, 0x48, 0x6c, 0x65, + 0x54, 0x52, 0x36, 0x62, 0x7a, 0x48, 0x47, 0x73, 0x69, 0x78, 0x49, 0x38, + 0x72, 0x76, 0x44, 0x65, 0x33, 0x74, 0x76, 0x63, 0x79, 0x44, 0x4e, 0x46, + 0x2f, 0x78, 0x34, 0x68, 0x70, 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, + 0x6f, 0x31, 0x4d, 0x77, 0x55, 0x54, 0x41, 0x64, 0x42, 0x67, 0x4e, 0x56, + 0x48, 0x51, 0x34, 0x45, 0x0a, 0x46, 0x67, 0x51, 0x55, 0x68, 0x6c, 0x51, + 0x77, 0x37, 0x49, 0x54, 0x2b, 0x38, 0x59, 0x6e, 0x5a, 0x68, 0x6e, 0x62, + 0x6d, 0x38, 0x62, 0x32, 0x64, 0x6e, 0x4d, 0x48, 0x6e, 0x69, 0x76, 0x6b, + 0x77, 0x48, 0x77, 0x59, 0x44, 0x56, 0x52, 0x30, 0x6a, 0x42, 0x42, 0x67, + 0x77, 0x46, 0x6f, 0x41, 0x55, 0x68, 0x6c, 0x51, 0x77, 0x37, 0x49, 0x54, + 0x2b, 0x38, 0x59, 0x6e, 0x5a, 0x68, 0x6e, 0x62, 0x6d, 0x0a, 0x38, 0x62, + 0x32, 0x64, 0x6e, 0x4d, 0x48, 0x6e, 0x69, 0x76, 0x6b, 0x77, 0x44, 0x77, + 0x59, 0x44, 0x56, 0x52, 0x30, 0x54, 0x41, 0x51, 0x48, 0x2f, 0x42, 0x41, + 0x55, 0x77, 0x41, 0x77, 0x45, 0x42, 0x2f, 0x7a, 0x41, 0x4e, 0x42, 0x67, + 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, + 0x73, 0x46, 0x41, 0x41, 0x4f, 0x43, 0x41, 0x67, 0x45, 0x41, 0x49, 0x59, + 0x36, 0x6e, 0x0a, 0x74, 0x54, 0x36, 0x54, 0x5a, 0x59, 0x2b, 0x4b, 0x6b, + 0x56, 0x7a, 0x34, 0x67, 0x31, 0x56, 0x4c, 0x50, 0x6d, 0x44, 0x72, 0x4e, + 0x6e, 0x6a, 0x65, 0x58, 0x59, 0x61, 0x35, 0x59, 0x42, 0x4d, 0x58, 0x4a, + 0x4f, 0x77, 0x61, 0x56, 0x56, 0x44, 0x2b, 0x6e, 0x6d, 0x42, 0x64, 0x51, + 0x51, 0x53, 0x67, 0x41, 0x2f, 0x30, 0x37, 0x41, 0x61, 0x63, 0x68, 0x71, + 0x61, 0x2f, 0x52, 0x77, 0x54, 0x31, 0x61, 0x0a, 0x2b, 0x7a, 0x37, 0x45, + 0x2f, 0x4b, 0x56, 0x34, 0x78, 0x76, 0x43, 0x55, 0x4d, 0x6a, 0x64, 0x64, + 0x4f, 0x64, 0x36, 0x48, 0x77, 0x51, 0x35, 0x58, 0x32, 0x2b, 0x7a, 0x6e, + 0x46, 0x41, 0x71, 0x73, 0x48, 0x75, 0x5a, 0x41, 0x46, 0x63, 0x36, 0x37, + 0x36, 0x47, 0x4d, 0x77, 0x75, 0x73, 0x37, 0x45, 0x69, 0x30, 0x53, 0x4f, + 0x41, 0x41, 0x36, 0x44, 0x30, 0x6f, 0x63, 0x52, 0x72, 0x2f, 0x68, 0x4c, + 0x0a, 0x62, 0x6c, 0x4f, 0x4b, 0x70, 0x48, 0x79, 0x66, 0x72, 0x46, 0x76, + 0x42, 0x70, 0x5a, 0x6a, 0x6e, 0x4f, 0x49, 0x33, 0x47, 0x68, 0x37, 0x45, + 0x32, 0x49, 0x57, 0x71, 0x6f, 0x62, 0x7a, 0x73, 0x35, 0x62, 0x58, 0x5a, + 0x45, 0x6a, 0x34, 0x47, 0x47, 0x58, 0x33, 0x30, 0x39, 0x45, 0x2f, 0x39, + 0x38, 0x6e, 0x33, 0x47, 0x35, 0x46, 0x68, 0x2b, 0x78, 0x31, 0x7a, 0x61, + 0x59, 0x38, 0x79, 0x78, 0x4f, 0x0a, 0x56, 0x62, 0x4a, 0x31, 0x50, 0x61, + 0x4b, 0x64, 0x30, 0x4f, 0x36, 0x4a, 0x45, 0x6b, 0x72, 0x36, 0x6f, 0x36, + 0x6e, 0x61, 0x48, 0x33, 0x44, 0x45, 0x33, 0x53, 0x68, 0x53, 0x68, 0x54, + 0x30, 0x6b, 0x36, 0x63, 0x53, 0x68, 0x5a, 0x38, 0x2b, 0x74, 0x6f, 0x35, + 0x64, 0x47, 0x38, 0x6b, 0x2b, 0x6f, 0x72, 0x69, 0x39, 0x41, 0x44, 0x52, + 0x52, 0x48, 0x4f, 0x62, 0x71, 0x51, 0x54, 0x42, 0x4a, 0x46, 0x0a, 0x6e, + 0x67, 0x35, 0x4f, 0x72, 0x6f, 0x46, 0x2f, 0x31, 0x34, 0x75, 0x73, 0x65, + 0x2b, 0x49, 0x61, 0x42, 0x32, 0x79, 0x41, 0x57, 0x30, 0x68, 0x44, 0x37, + 0x43, 0x2f, 0x79, 0x31, 0x2f, 0x56, 0x38, 0x41, 0x78, 0x35, 0x77, 0x31, + 0x38, 0x59, 0x51, 0x36, 0x6e, 0x4d, 0x44, 0x7a, 0x51, 0x56, 0x64, 0x77, + 0x63, 0x59, 0x69, 0x4c, 0x77, 0x54, 0x42, 0x31, 0x66, 0x4b, 0x30, 0x33, + 0x4f, 0x55, 0x36, 0x0a, 0x31, 0x72, 0x32, 0x59, 0x62, 0x47, 0x4b, 0x49, + 0x62, 0x43, 0x59, 0x63, 0x4e, 0x76, 0x59, 0x72, 0x4a, 0x2b, 0x61, 0x38, + 0x74, 0x59, 0x56, 0x48, 0x58, 0x51, 0x68, 0x2f, 0x78, 0x52, 0x61, 0x74, + 0x79, 0x61, 0x4e, 0x69, 0x41, 0x59, 0x34, 0x77, 0x6b, 0x58, 0x36, 0x49, + 0x32, 0x79, 0x43, 0x66, 0x69, 0x35, 0x61, 0x6d, 0x32, 0x69, 0x4f, 0x42, + 0x50, 0x51, 0x72, 0x36, 0x6f, 0x38, 0x58, 0x58, 0x0a, 0x2b, 0x44, 0x69, + 0x35, 0x4a, 0x4e, 0x58, 0x59, 0x55, 0x2f, 0x76, 0x76, 0x59, 0x53, 0x4d, + 0x35, 0x51, 0x61, 0x79, 0x6f, 0x35, 0x78, 0x7a, 0x4c, 0x63, 0x4d, 0x7a, + 0x76, 0x57, 0x77, 0x4b, 0x70, 0x4d, 0x6c, 0x4c, 0x33, 0x6c, 0x6f, 0x30, + 0x48, 0x34, 0x53, 0x70, 0x54, 0x6e, 0x75, 0x66, 0x55, 0x63, 0x78, 0x39, + 0x6f, 0x72, 0x2f, 0x75, 0x5a, 0x6e, 0x44, 0x51, 0x45, 0x30, 0x43, 0x2f, + 0x2f, 0x0a, 0x6d, 0x6c, 0x44, 0x52, 0x67, 0x2f, 0x73, 0x48, 0x6d, 0x65, + 0x62, 0x37, 0x6f, 0x56, 0x79, 0x65, 0x72, 0x39, 0x30, 0x33, 0x64, 0x45, + 0x74, 0x6b, 0x73, 0x79, 0x32, 0x7a, 0x74, 0x31, 0x6b, 0x45, 0x4b, 0x45, + 0x78, 0x77, 0x41, 0x51, 0x75, 0x73, 0x57, 0x74, 0x63, 0x77, 0x4c, 0x7a, + 0x66, 0x71, 0x45, 0x30, 0x32, 0x6f, 0x51, 0x78, 0x56, 0x74, 0x51, 0x71, + 0x73, 0x66, 0x47, 0x78, 0x30, 0x4c, 0x0a, 0x43, 0x75, 0x6a, 0x51, 0x65, + 0x5a, 0x56, 0x76, 0x42, 0x30, 0x77, 0x6c, 0x63, 0x33, 0x33, 0x33, 0x4d, + 0x31, 0x32, 0x4d, 0x41, 0x69, 0x44, 0x75, 0x6c, 0x71, 0x2b, 0x73, 0x58, + 0x4d, 0x70, 0x4c, 0x53, 0x2b, 0x47, 0x79, 0x39, 0x57, 0x34, 0x34, 0x42, + 0x55, 0x45, 0x74, 0x46, 0x4f, 0x77, 0x51, 0x2f, 0x58, 0x35, 0x57, 0x32, + 0x58, 0x77, 0x4d, 0x53, 0x45, 0x42, 0x57, 0x39, 0x49, 0x66, 0x6f, 0x0a, + 0x58, 0x4d, 0x6d, 0x4f, 0x37, 0x51, 0x69, 0x35, 0x59, 0x65, 0x79, 0x7a, + 0x46, 0x47, 0x78, 0x31, 0x36, 0x6e, 0x59, 0x6f, 0x35, 0x61, 0x45, 0x6e, + 0x4c, 0x2b, 0x7a, 0x6d, 0x72, 0x64, 0x39, 0x43, 0x36, 0x36, 0x61, 0x51, + 0x76, 0x6b, 0x42, 0x66, 0x5a, 0x2f, 0x6e, 0x50, 0x70, 0x76, 0x55, 0x63, + 0x53, 0x57, 0x69, 0x34, 0x73, 0x45, 0x47, 0x44, 0x69, 0x6a, 0x43, 0x4c, + 0x65, 0x4b, 0x43, 0x59, 0x0a, 0x36, 0x4c, 0x34, 0x6e, 0x74, 0x4e, 0x72, + 0x6e, 0x7a, 0x62, 0x79, 0x38, 0x70, 0x70, 0x71, 0x50, 0x64, 0x66, 0x79, + 0x58, 0x4e, 0x67, 0x4e, 0x35, 0x34, 0x6e, 0x4d, 0x4e, 0x77, 0x55, 0x78, + 0x66, 0x47, 0x33, 0x4a, 0x41, 0x69, 0x55, 0x55, 0x3d, 0x0a, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a +}; + +/* THIS FILE IS AUTOMATICALLY GENERATED!!! DO NOT EDIT!!! */ +/* Generated on: [ Tue Dec 29 17:58:52 2020 ], from file: test_key.pem */ + +static const unsigned char TLS_test_key[] = { + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50, + 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x0a, 0x4d, 0x49, 0x49, 0x4a, 0x51, 0x67, 0x49, 0x42, + 0x41, 0x44, 0x41, 0x4e, 0x42, 0x67, 0x6b, 0x71, 0x68, 0x6b, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x53, 0x43, + 0x43, 0x53, 0x77, 0x77, 0x67, 0x67, 0x6b, 0x6f, 0x41, 0x67, 0x45, 0x41, + 0x41, 0x6f, 0x49, 0x43, 0x41, 0x51, 0x43, 0x71, 0x74, 0x49, 0x42, 0x33, + 0x43, 0x4c, 0x75, 0x71, 0x52, 0x54, 0x6d, 0x67, 0x0a, 0x39, 0x4d, 0x45, + 0x56, 0x69, 0x70, 0x30, 0x63, 0x43, 0x45, 0x2f, 0x33, 0x68, 0x47, 0x64, + 0x63, 0x53, 0x61, 0x51, 0x4f, 0x64, 0x6b, 0x39, 0x42, 0x5a, 0x41, 0x53, + 0x79, 0x4f, 0x4a, 0x35, 0x4c, 0x42, 0x38, 0x2f, 0x63, 0x2f, 0x48, 0x30, + 0x38, 0x4c, 0x30, 0x63, 0x4c, 0x79, 0x43, 0x4c, 0x6b, 0x31, 0x70, 0x33, + 0x78, 0x67, 0x56, 0x2b, 0x53, 0x57, 0x2f, 0x6d, 0x79, 0x38, 0x65, 0x6b, + 0x6f, 0x0a, 0x4d, 0x4a, 0x6a, 0x70, 0x4e, 0x54, 0x37, 0x70, 0x68, 0x74, + 0x78, 0x4a, 0x4e, 0x4e, 0x6c, 0x4e, 0x36, 0x39, 0x56, 0x63, 0x6e, 0x75, + 0x71, 0x33, 0x41, 0x45, 0x44, 0x4a, 0x63, 0x41, 0x41, 0x4c, 0x6f, 0x38, + 0x2b, 0x63, 0x65, 0x33, 0x6d, 0x4b, 0x35, 0x77, 0x41, 0x4d, 0x73, 0x7a, + 0x61, 0x52, 0x7a, 0x33, 0x44, 0x34, 0x5a, 0x4e, 0x66, 0x65, 0x54, 0x33, + 0x61, 0x6b, 0x39, 0x45, 0x78, 0x74, 0x0a, 0x47, 0x62, 0x57, 0x42, 0x4f, + 0x6d, 0x32, 0x4f, 0x66, 0x68, 0x49, 0x2b, 0x45, 0x53, 0x6d, 0x32, 0x55, + 0x41, 0x32, 0x34, 0x61, 0x49, 0x6a, 0x46, 0x74, 0x4a, 0x52, 0x72, 0x6d, + 0x56, 0x4d, 0x65, 0x37, 0x68, 0x44, 0x4b, 0x54, 0x78, 0x68, 0x49, 0x69, + 0x54, 0x6a, 0x4c, 0x54, 0x76, 0x56, 0x4e, 0x50, 0x2b, 0x77, 0x53, 0x43, + 0x62, 0x32, 0x47, 0x59, 0x34, 0x74, 0x7a, 0x7a, 0x30, 0x66, 0x66, 0x0a, + 0x79, 0x62, 0x50, 0x6d, 0x65, 0x5a, 0x50, 0x58, 0x61, 0x32, 0x54, 0x39, + 0x77, 0x65, 0x30, 0x68, 0x6a, 0x41, 0x56, 0x53, 0x4b, 0x47, 0x76, 0x76, + 0x30, 0x48, 0x41, 0x48, 0x65, 0x4a, 0x72, 0x4f, 0x75, 0x31, 0x75, 0x38, + 0x79, 0x4a, 0x4c, 0x6e, 0x58, 0x72, 0x64, 0x47, 0x2f, 0x4f, 0x7a, 0x4b, + 0x71, 0x65, 0x4a, 0x38, 0x7a, 0x74, 0x4a, 0x34, 0x47, 0x59, 0x58, 0x7a, + 0x70, 0x4b, 0x39, 0x42, 0x0a, 0x68, 0x74, 0x57, 0x6b, 0x6d, 0x7a, 0x38, + 0x74, 0x41, 0x78, 0x7a, 0x77, 0x6c, 0x5a, 0x47, 0x6d, 0x54, 0x41, 0x79, + 0x58, 0x45, 0x55, 0x50, 0x79, 0x33, 0x42, 0x71, 0x61, 0x4a, 0x66, 0x2b, + 0x6c, 0x46, 0x4e, 0x67, 0x61, 0x68, 0x74, 0x57, 0x78, 0x6c, 0x77, 0x58, + 0x2f, 0x31, 0x59, 0x4c, 0x35, 0x75, 0x69, 0x69, 0x49, 0x77, 0x4c, 0x69, + 0x41, 0x36, 0x67, 0x2f, 0x39, 0x39, 0x79, 0x4e, 0x4c, 0x0a, 0x41, 0x69, + 0x34, 0x38, 0x54, 0x6b, 0x41, 0x63, 0x44, 0x58, 0x42, 0x35, 0x38, 0x61, + 0x5a, 0x78, 0x36, 0x32, 0x43, 0x63, 0x6b, 0x58, 0x75, 0x4d, 0x6d, 0x32, + 0x68, 0x61, 0x37, 0x6e, 0x43, 0x64, 0x77, 0x66, 0x76, 0x31, 0x71, 0x71, + 0x6a, 0x72, 0x79, 0x44, 0x56, 0x34, 0x32, 0x2b, 0x56, 0x37, 0x48, 0x75, + 0x64, 0x4f, 0x57, 0x36, 0x33, 0x39, 0x6f, 0x6b, 0x6a, 0x69, 0x56, 0x32, + 0x33, 0x69, 0x0a, 0x48, 0x79, 0x2f, 0x33, 0x2f, 0x2f, 0x72, 0x56, 0x31, + 0x44, 0x59, 0x6a, 0x76, 0x35, 0x49, 0x4c, 0x48, 0x51, 0x79, 0x6d, 0x7a, + 0x4a, 0x2b, 0x76, 0x6e, 0x4a, 0x38, 0x4a, 0x69, 0x33, 0x59, 0x74, 0x32, + 0x66, 0x44, 0x44, 0x55, 0x58, 0x66, 0x33, 0x41, 0x45, 0x6e, 0x49, 0x32, + 0x74, 0x54, 0x75, 0x4c, 0x34, 0x54, 0x42, 0x4b, 0x54, 0x69, 0x74, 0x33, + 0x79, 0x79, 0x69, 0x54, 0x56, 0x52, 0x4e, 0x0a, 0x45, 0x59, 0x39, 0x63, + 0x4b, 0x47, 0x54, 0x6a, 0x73, 0x41, 0x7a, 0x48, 0x44, 0x7a, 0x35, 0x33, + 0x6b, 0x74, 0x6b, 0x38, 0x2b, 0x77, 0x44, 0x73, 0x49, 0x71, 0x76, 0x4d, + 0x54, 0x6d, 0x79, 0x33, 0x72, 0x65, 0x6d, 0x36, 0x68, 0x4b, 0x30, 0x71, + 0x51, 0x30, 0x48, 0x38, 0x50, 0x4f, 0x77, 0x57, 0x66, 0x31, 0x75, 0x6c, + 0x55, 0x71, 0x46, 0x67, 0x37, 0x34, 0x77, 0x67, 0x62, 0x61, 0x32, 0x73, + 0x0a, 0x71, 0x37, 0x56, 0x78, 0x61, 0x62, 0x63, 0x32, 0x71, 0x65, 0x4b, + 0x71, 0x74, 0x59, 0x4c, 0x59, 0x30, 0x4e, 0x6b, 0x51, 0x71, 0x44, 0x67, + 0x73, 0x42, 0x30, 0x55, 0x56, 0x7a, 0x50, 0x70, 0x68, 0x53, 0x56, 0x5a, + 0x51, 0x65, 0x49, 0x49, 0x41, 0x4b, 0x6c, 0x6f, 0x7a, 0x6a, 0x49, 0x43, + 0x7a, 0x39, 0x75, 0x48, 0x47, 0x39, 0x48, 0x31, 0x36, 0x51, 0x52, 0x45, + 0x74, 0x43, 0x4d, 0x78, 0x37, 0x0a, 0x42, 0x46, 0x77, 0x48, 0x74, 0x36, + 0x49, 0x31, 0x2b, 0x70, 0x4a, 0x69, 0x50, 0x50, 0x47, 0x69, 0x78, 0x66, + 0x42, 0x47, 0x74, 0x72, 0x64, 0x4a, 0x51, 0x78, 0x39, 0x34, 0x62, 0x77, + 0x76, 0x43, 0x6f, 0x38, 0x59, 0x4a, 0x71, 0x57, 0x77, 0x67, 0x66, 0x41, + 0x77, 0x50, 0x30, 0x57, 0x45, 0x6c, 0x6f, 0x6b, 0x69, 0x6b, 0x53, 0x6a, + 0x6a, 0x6b, 0x79, 0x62, 0x33, 0x69, 0x53, 0x48, 0x6c, 0x65, 0x0a, 0x54, + 0x52, 0x36, 0x62, 0x7a, 0x48, 0x47, 0x73, 0x69, 0x78, 0x49, 0x38, 0x72, + 0x76, 0x44, 0x65, 0x33, 0x74, 0x76, 0x63, 0x79, 0x44, 0x4e, 0x46, 0x2f, + 0x78, 0x34, 0x68, 0x70, 0x77, 0x49, 0x44, 0x41, 0x51, 0x41, 0x42, 0x41, + 0x6f, 0x49, 0x43, 0x41, 0x47, 0x2b, 0x31, 0x56, 0x55, 0x67, 0x51, 0x4c, + 0x2f, 0x62, 0x5a, 0x2f, 0x44, 0x39, 0x6e, 0x53, 0x35, 0x2b, 0x55, 0x4b, + 0x51, 0x48, 0x36, 0x0a, 0x4d, 0x70, 0x4a, 0x77, 0x55, 0x38, 0x39, 0x68, + 0x35, 0x58, 0x6b, 0x4e, 0x56, 0x51, 0x6f, 0x65, 0x73, 0x4c, 0x41, 0x4d, + 0x4f, 0x78, 0x49, 0x77, 0x6c, 0x34, 0x63, 0x75, 0x74, 0x36, 0x6d, 0x56, + 0x36, 0x72, 0x45, 0x38, 0x46, 0x42, 0x47, 0x61, 0x6e, 0x4a, 0x73, 0x35, + 0x4a, 0x56, 0x69, 0x36, 0x31, 0x61, 0x6d, 0x54, 0x67, 0x78, 0x65, 0x6f, + 0x7a, 0x62, 0x66, 0x32, 0x2f, 0x79, 0x65, 0x45, 0x0a, 0x2b, 0x45, 0x7a, + 0x7a, 0x78, 0x36, 0x79, 0x6c, 0x51, 0x75, 0x65, 0x73, 0x6d, 0x7a, 0x36, + 0x4d, 0x62, 0x4e, 0x6b, 0x6c, 0x63, 0x50, 0x49, 0x74, 0x44, 0x61, 0x53, + 0x43, 0x62, 0x4f, 0x52, 0x49, 0x44, 0x4a, 0x46, 0x44, 0x43, 0x64, 0x66, + 0x62, 0x58, 0x7a, 0x73, 0x39, 0x69, 0x73, 0x4a, 0x52, 0x54, 0x2f, 0x76, + 0x63, 0x58, 0x74, 0x4d, 0x61, 0x65, 0x74, 0x75, 0x4a, 0x5a, 0x37, 0x35, + 0x53, 0x0a, 0x70, 0x41, 0x39, 0x33, 0x33, 0x63, 0x73, 0x50, 0x6b, 0x68, + 0x72, 0x32, 0x56, 0x57, 0x4c, 0x44, 0x72, 0x45, 0x6a, 0x4a, 0x65, 0x6b, + 0x71, 0x49, 0x55, 0x66, 0x61, 0x43, 0x55, 0x67, 0x72, 0x4e, 0x75, 0x5a, + 0x76, 0x61, 0x48, 0x4d, 0x36, 0x6a, 0x6f, 0x52, 0x47, 0x67, 0x7a, 0x43, + 0x54, 0x51, 0x71, 0x61, 0x5a, 0x73, 0x6f, 0x38, 0x55, 0x2f, 0x30, 0x6e, + 0x6c, 0x6c, 0x59, 0x43, 0x6b, 0x64, 0x0a, 0x66, 0x32, 0x5a, 0x4c, 0x37, + 0x4b, 0x6b, 0x43, 0x58, 0x30, 0x30, 0x48, 0x5a, 0x4c, 0x4c, 0x33, 0x76, + 0x51, 0x67, 0x32, 0x6c, 0x56, 0x2f, 0x70, 0x33, 0x62, 0x75, 0x70, 0x71, + 0x66, 0x43, 0x38, 0x32, 0x38, 0x55, 0x5a, 0x71, 0x4c, 0x39, 0x71, 0x38, + 0x75, 0x72, 0x6e, 0x30, 0x58, 0x57, 0x45, 0x68, 0x6c, 0x4d, 0x4e, 0x6c, + 0x78, 0x36, 0x54, 0x5a, 0x4f, 0x57, 0x6d, 0x4c, 0x6d, 0x35, 0x2f, 0x0a, + 0x56, 0x67, 0x58, 0x65, 0x61, 0x77, 0x54, 0x66, 0x53, 0x58, 0x48, 0x6d, + 0x65, 0x34, 0x66, 0x48, 0x48, 0x36, 0x56, 0x32, 0x62, 0x6c, 0x67, 0x53, + 0x54, 0x75, 0x54, 0x31, 0x44, 0x59, 0x78, 0x38, 0x6e, 0x78, 0x46, 0x76, + 0x49, 0x4a, 0x6e, 0x5a, 0x68, 0x38, 0x4d, 0x4f, 0x45, 0x75, 0x39, 0x52, + 0x76, 0x49, 0x74, 0x65, 0x6d, 0x66, 0x72, 0x77, 0x2f, 0x31, 0x36, 0x35, + 0x65, 0x6c, 0x75, 0x50, 0x0a, 0x56, 0x74, 0x62, 0x6b, 0x2b, 0x79, 0x54, + 0x74, 0x44, 0x69, 0x6c, 0x38, 0x32, 0x6f, 0x4e, 0x69, 0x75, 0x68, 0x4b, + 0x44, 0x39, 0x38, 0x4a, 0x65, 0x51, 0x69, 0x6c, 0x43, 0x75, 0x42, 0x53, + 0x73, 0x4a, 0x4f, 0x78, 0x52, 0x75, 0x64, 0x69, 0x6c, 0x49, 0x4e, 0x53, + 0x76, 0x63, 0x74, 0x72, 0x33, 0x65, 0x53, 0x37, 0x63, 0x63, 0x43, 0x6e, + 0x6e, 0x75, 0x37, 0x5a, 0x61, 0x32, 0x49, 0x56, 0x45, 0x0a, 0x35, 0x31, + 0x41, 0x4c, 0x6b, 0x42, 0x42, 0x61, 0x33, 0x4e, 0x4c, 0x76, 0x49, 0x37, + 0x6c, 0x70, 0x6d, 0x78, 0x47, 0x41, 0x65, 0x6d, 0x33, 0x67, 0x52, 0x6b, + 0x51, 0x30, 0x74, 0x4e, 0x63, 0x76, 0x65, 0x39, 0x65, 0x51, 0x4b, 0x6e, + 0x42, 0x44, 0x71, 0x37, 0x75, 0x45, 0x48, 0x78, 0x35, 0x56, 0x45, 0x79, + 0x54, 0x4d, 0x4c, 0x43, 0x49, 0x6d, 0x6d, 0x6d, 0x7a, 0x32, 0x4d, 0x58, + 0x62, 0x61, 0x0a, 0x73, 0x71, 0x64, 0x52, 0x44, 0x33, 0x35, 0x4d, 0x57, + 0x54, 0x72, 0x77, 0x66, 0x64, 0x36, 0x32, 0x36, 0x72, 0x6b, 0x72, 0x44, + 0x6f, 0x6c, 0x39, 0x61, 0x56, 0x46, 0x50, 0x6d, 0x37, 0x6e, 0x2b, 0x6e, + 0x43, 0x71, 0x34, 0x6c, 0x73, 0x50, 0x63, 0x72, 0x76, 0x7a, 0x4c, 0x52, + 0x4e, 0x63, 0x32, 0x78, 0x31, 0x2b, 0x6a, 0x52, 0x4f, 0x48, 0x64, 0x70, + 0x31, 0x58, 0x48, 0x65, 0x56, 0x75, 0x6e, 0x0a, 0x61, 0x67, 0x78, 0x6d, + 0x44, 0x31, 0x44, 0x75, 0x50, 0x2f, 0x6c, 0x30, 0x42, 0x68, 0x4f, 0x36, + 0x78, 0x55, 0x33, 0x73, 0x74, 0x50, 0x6e, 0x71, 0x33, 0x35, 0x44, 0x4d, + 0x4a, 0x76, 0x79, 0x65, 0x30, 0x46, 0x34, 0x75, 0x2f, 0x6a, 0x6c, 0x46, + 0x4f, 0x4a, 0x36, 0x45, 0x36, 0x2b, 0x34, 0x7a, 0x53, 0x69, 0x71, 0x45, + 0x46, 0x6b, 0x41, 0x4e, 0x57, 0x59, 0x74, 0x67, 0x6c, 0x36, 0x42, 0x47, + 0x0a, 0x32, 0x63, 0x36, 0x58, 0x44, 0x72, 0x42, 0x66, 0x2b, 0x36, 0x53, + 0x34, 0x30, 0x4f, 0x79, 0x76, 0x50, 0x4c, 0x46, 0x48, 0x39, 0x4b, 0x61, + 0x30, 0x63, 0x68, 0x47, 0x58, 0x37, 0x68, 0x59, 0x7a, 0x44, 0x46, 0x2b, + 0x4b, 0x2f, 0x47, 0x64, 0x50, 0x73, 0x56, 0x75, 0x55, 0x74, 0x75, 0x56, + 0x48, 0x4d, 0x4e, 0x63, 0x6e, 0x33, 0x64, 0x5a, 0x2f, 0x6e, 0x4c, 0x33, + 0x5a, 0x4f, 0x4a, 0x6c, 0x47, 0x0a, 0x46, 0x43, 0x33, 0x44, 0x34, 0x41, + 0x2f, 0x36, 0x36, 0x62, 0x41, 0x51, 0x72, 0x4e, 0x69, 0x4d, 0x62, 0x48, + 0x37, 0x35, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x51, 0x44, 0x61, 0x5a, 0x48, + 0x68, 0x4a, 0x49, 0x79, 0x49, 0x4c, 0x34, 0x4d, 0x49, 0x6b, 0x54, 0x4f, + 0x6c, 0x31, 0x63, 0x39, 0x4e, 0x6a, 0x62, 0x48, 0x62, 0x75, 0x32, 0x34, + 0x41, 0x31, 0x4c, 0x76, 0x37, 0x4e, 0x38, 0x47, 0x52, 0x4f, 0x0a, 0x63, + 0x55, 0x6b, 0x34, 0x75, 0x70, 0x6e, 0x68, 0x5a, 0x69, 0x6a, 0x7a, 0x6a, + 0x72, 0x38, 0x53, 0x34, 0x49, 0x6b, 0x71, 0x59, 0x49, 0x49, 0x35, 0x77, + 0x71, 0x45, 0x30, 0x6f, 0x4a, 0x55, 0x41, 0x75, 0x6c, 0x42, 0x7a, 0x66, + 0x6e, 0x39, 0x76, 0x4b, 0x76, 0x32, 0x6e, 0x6d, 0x66, 0x62, 0x39, 0x68, + 0x4e, 0x74, 0x35, 0x32, 0x66, 0x73, 0x64, 0x35, 0x49, 0x4c, 0x54, 0x6e, + 0x6e, 0x58, 0x73, 0x0a, 0x58, 0x39, 0x47, 0x33, 0x7a, 0x56, 0x2f, 0x36, + 0x6c, 0x54, 0x33, 0x4b, 0x6f, 0x53, 0x4d, 0x53, 0x41, 0x46, 0x55, 0x31, + 0x68, 0x37, 0x79, 0x69, 0x64, 0x57, 0x6b, 0x75, 0x6c, 0x38, 0x4e, 0x42, + 0x56, 0x49, 0x57, 0x6a, 0x48, 0x56, 0x65, 0x55, 0x31, 0x4f, 0x4c, 0x31, + 0x34, 0x46, 0x45, 0x54, 0x75, 0x62, 0x62, 0x47, 0x42, 0x6f, 0x2f, 0x2b, + 0x73, 0x48, 0x4a, 0x71, 0x31, 0x65, 0x36, 0x56, 0x0a, 0x39, 0x65, 0x34, + 0x48, 0x4d, 0x35, 0x67, 0x53, 0x70, 0x38, 0x6c, 0x30, 0x68, 0x55, 0x78, + 0x47, 0x45, 0x52, 0x63, 0x65, 0x31, 0x49, 0x74, 0x74, 0x6f, 0x6a, 0x48, + 0x6a, 0x39, 0x61, 0x67, 0x4f, 0x37, 0x58, 0x61, 0x62, 0x75, 0x7a, 0x6b, + 0x4f, 0x4c, 0x63, 0x4d, 0x65, 0x30, 0x62, 0x43, 0x52, 0x66, 0x64, 0x70, + 0x37, 0x52, 0x61, 0x44, 0x62, 0x68, 0x70, 0x4a, 0x6d, 0x49, 0x41, 0x4b, + 0x53, 0x0a, 0x50, 0x58, 0x4c, 0x4c, 0x77, 0x38, 0x57, 0x34, 0x65, 0x34, + 0x43, 0x6c, 0x35, 0x35, 0x6a, 0x37, 0x59, 0x37, 0x5a, 0x71, 0x48, 0x31, + 0x47, 0x32, 0x47, 0x4a, 0x77, 0x4b, 0x58, 0x58, 0x6d, 0x7a, 0x4b, 0x56, + 0x48, 0x5a, 0x77, 0x37, 0x72, 0x4f, 0x49, 0x32, 0x7a, 0x49, 0x2f, 0x46, + 0x4e, 0x70, 0x47, 0x4b, 0x5a, 0x31, 0x4c, 0x6c, 0x54, 0x74, 0x61, 0x6c, + 0x35, 0x62, 0x4b, 0x5a, 0x75, 0x43, 0x0a, 0x59, 0x37, 0x45, 0x47, 0x6f, + 0x5a, 0x37, 0x62, 0x71, 0x64, 0x53, 0x63, 0x2f, 0x51, 0x33, 0x71, 0x64, + 0x6f, 0x58, 0x55, 0x4d, 0x6e, 0x4a, 0x69, 0x52, 0x4f, 0x4d, 0x30, 0x69, + 0x6a, 0x37, 0x51, 0x43, 0x56, 0x56, 0x64, 0x4b, 0x63, 0x36, 0x46, 0x57, + 0x39, 0x48, 0x51, 0x54, 0x72, 0x4a, 0x56, 0x41, 0x6f, 0x49, 0x42, 0x41, + 0x51, 0x44, 0x49, 0x47, 0x63, 0x7a, 0x43, 0x73, 0x41, 0x4a, 0x2b, 0x0a, + 0x55, 0x68, 0x75, 0x7a, 0x42, 0x77, 0x6c, 0x50, 0x4b, 0x32, 0x75, 0x70, + 0x4b, 0x4b, 0x35, 0x57, 0x4f, 0x59, 0x43, 0x63, 0x4b, 0x70, 0x72, 0x65, + 0x79, 0x6b, 0x6b, 0x65, 0x68, 0x41, 0x70, 0x31, 0x2f, 0x38, 0x49, 0x61, + 0x68, 0x70, 0x79, 0x79, 0x46, 0x38, 0x4d, 0x6b, 0x64, 0x37, 0x6d, 0x66, + 0x4b, 0x54, 0x5a, 0x49, 0x5a, 0x75, 0x74, 0x74, 0x52, 0x44, 0x31, 0x6c, + 0x39, 0x34, 0x30, 0x69, 0x0a, 0x79, 0x44, 0x32, 0x7a, 0x73, 0x63, 0x33, + 0x6b, 0x45, 0x49, 0x49, 0x2f, 0x46, 0x41, 0x33, 0x6d, 0x46, 0x41, 0x56, + 0x37, 0x4c, 0x70, 0x4e, 0x4e, 0x51, 0x73, 0x46, 0x7a, 0x59, 0x51, 0x6a, + 0x48, 0x32, 0x4c, 0x64, 0x69, 0x52, 0x36, 0x6f, 0x2b, 0x4a, 0x43, 0x6a, + 0x4f, 0x34, 0x75, 0x77, 0x4b, 0x78, 0x75, 0x50, 0x5a, 0x51, 0x43, 0x74, + 0x6f, 0x50, 0x59, 0x48, 0x6d, 0x48, 0x72, 0x64, 0x6c, 0x0a, 0x30, 0x43, + 0x4c, 0x47, 0x6e, 0x48, 0x4f, 0x37, 0x59, 0x38, 0x31, 0x67, 0x33, 0x69, + 0x35, 0x6f, 0x76, 0x54, 0x63, 0x37, 0x63, 0x61, 0x4f, 0x45, 0x73, 0x6f, + 0x32, 0x6a, 0x67, 0x37, 0x4a, 0x4c, 0x67, 0x49, 0x41, 0x4a, 0x48, 0x55, + 0x31, 0x71, 0x31, 0x62, 0x4d, 0x75, 0x34, 0x6f, 0x33, 0x56, 0x76, 0x6a, + 0x63, 0x43, 0x37, 0x41, 0x57, 0x47, 0x30, 0x4d, 0x56, 0x30, 0x50, 0x30, + 0x64, 0x41, 0x0a, 0x31, 0x7a, 0x46, 0x63, 0x4e, 0x76, 0x6f, 0x6c, 0x47, + 0x4f, 0x32, 0x75, 0x67, 0x63, 0x34, 0x36, 0x48, 0x63, 0x57, 0x37, 0x44, + 0x6c, 0x37, 0x4e, 0x6b, 0x43, 0x76, 0x63, 0x48, 0x7a, 0x59, 0x4c, 0x6b, + 0x68, 0x35, 0x6e, 0x44, 0x48, 0x72, 0x6b, 0x49, 0x69, 0x58, 0x61, 0x6d, + 0x32, 0x52, 0x48, 0x77, 0x65, 0x75, 0x79, 0x71, 0x72, 0x48, 0x35, 0x4b, + 0x2f, 0x2f, 0x74, 0x48, 0x71, 0x7a, 0x77, 0x0a, 0x65, 0x4d, 0x47, 0x35, + 0x54, 0x63, 0x65, 0x33, 0x49, 0x4d, 0x69, 0x67, 0x35, 0x46, 0x55, 0x61, + 0x66, 0x57, 0x47, 0x33, 0x45, 0x6f, 0x42, 0x47, 0x6c, 0x52, 0x30, 0x36, + 0x5a, 0x41, 0x69, 0x7a, 0x34, 0x68, 0x37, 0x52, 0x66, 0x72, 0x7a, 0x50, + 0x44, 0x65, 0x7a, 0x6a, 0x37, 0x66, 0x6d, 0x6c, 0x64, 0x6f, 0x72, 0x62, + 0x46, 0x45, 0x65, 0x79, 0x42, 0x36, 0x33, 0x69, 0x4b, 0x69, 0x58, 0x6b, + 0x0a, 0x70, 0x34, 0x7a, 0x53, 0x78, 0x75, 0x42, 0x49, 0x63, 0x70, 0x67, + 0x4c, 0x41, 0x6f, 0x49, 0x42, 0x41, 0x44, 0x69, 0x75, 0x37, 0x78, 0x46, + 0x38, 0x6a, 0x75, 0x2b, 0x71, 0x54, 0x48, 0x6d, 0x44, 0x68, 0x4f, 0x79, + 0x35, 0x50, 0x56, 0x71, 0x47, 0x34, 0x6d, 0x2b, 0x6f, 0x68, 0x53, 0x52, + 0x49, 0x71, 0x46, 0x58, 0x6e, 0x57, 0x51, 0x47, 0x4c, 0x49, 0x63, 0x67, + 0x5a, 0x6c, 0x71, 0x73, 0x4d, 0x0a, 0x43, 0x77, 0x44, 0x38, 0x51, 0x64, + 0x65, 0x79, 0x63, 0x36, 0x65, 0x4f, 0x47, 0x50, 0x37, 0x49, 0x35, 0x33, + 0x4a, 0x7a, 0x59, 0x33, 0x6b, 0x6d, 0x34, 0x6f, 0x36, 0x33, 0x66, 0x48, + 0x66, 0x73, 0x48, 0x70, 0x34, 0x4c, 0x74, 0x6a, 0x47, 0x69, 0x39, 0x42, + 0x77, 0x79, 0x57, 0x5a, 0x30, 0x75, 0x6e, 0x45, 0x34, 0x30, 0x79, 0x4d, + 0x4b, 0x72, 0x4e, 0x42, 0x47, 0x53, 0x75, 0x71, 0x43, 0x64, 0x0a, 0x62, + 0x38, 0x5a, 0x53, 0x41, 0x48, 0x70, 0x42, 0x6e, 0x39, 0x77, 0x65, 0x2b, + 0x50, 0x54, 0x70, 0x71, 0x48, 0x30, 0x78, 0x59, 0x72, 0x70, 0x6f, 0x4a, + 0x36, 0x39, 0x6f, 0x68, 0x44, 0x7a, 0x61, 0x37, 0x48, 0x57, 0x49, 0x33, + 0x55, 0x4a, 0x54, 0x5a, 0x33, 0x38, 0x4b, 0x59, 0x51, 0x46, 0x6e, 0x35, + 0x71, 0x71, 0x59, 0x45, 0x43, 0x37, 0x59, 0x41, 0x41, 0x6e, 0x61, 0x65, + 0x46, 0x51, 0x50, 0x0a, 0x50, 0x4a, 0x69, 0x44, 0x71, 0x49, 0x4a, 0x66, + 0x47, 0x54, 0x4d, 0x6c, 0x55, 0x33, 0x4d, 0x48, 0x4d, 0x41, 0x2f, 0x4d, + 0x79, 0x4f, 0x76, 0x6d, 0x38, 0x6d, 0x77, 0x46, 0x4d, 0x67, 0x2f, 0x65, + 0x44, 0x4e, 0x44, 0x49, 0x2b, 0x42, 0x30, 0x72, 0x48, 0x7a, 0x50, 0x7a, + 0x70, 0x49, 0x61, 0x56, 0x37, 0x52, 0x57, 0x59, 0x70, 0x56, 0x49, 0x7a, + 0x70, 0x4d, 0x49, 0x43, 0x64, 0x43, 0x55, 0x6e, 0x0a, 0x32, 0x51, 0x49, + 0x32, 0x6c, 0x46, 0x78, 0x62, 0x53, 0x78, 0x4e, 0x4d, 0x51, 0x62, 0x63, + 0x54, 0x75, 0x42, 0x78, 0x77, 0x6d, 0x6f, 0x2f, 0x48, 0x33, 0x37, 0x69, + 0x33, 0x74, 0x70, 0x71, 0x65, 0x55, 0x7a, 0x50, 0x76, 0x57, 0x65, 0x37, + 0x6a, 0x4f, 0x51, 0x45, 0x64, 0x48, 0x32, 0x6e, 0x6e, 0x75, 0x38, 0x6e, + 0x4a, 0x69, 0x4e, 0x56, 0x55, 0x37, 0x72, 0x4f, 0x62, 0x36, 0x31, 0x46, + 0x30, 0x0a, 0x53, 0x50, 0x7a, 0x65, 0x4b, 0x57, 0x68, 0x37, 0x6a, 0x73, + 0x79, 0x2b, 0x73, 0x7a, 0x57, 0x53, 0x54, 0x36, 0x35, 0x70, 0x57, 0x31, + 0x67, 0x2f, 0x73, 0x2b, 0x70, 0x55, 0x57, 0x59, 0x66, 0x2f, 0x68, 0x76, + 0x75, 0x63, 0x45, 0x57, 0x6b, 0x43, 0x67, 0x67, 0x45, 0x41, 0x65, 0x46, + 0x6a, 0x4d, 0x42, 0x65, 0x76, 0x47, 0x46, 0x43, 0x4e, 0x64, 0x39, 0x58, + 0x61, 0x74, 0x36, 0x71, 0x65, 0x36, 0x0a, 0x77, 0x4b, 0x70, 0x75, 0x37, + 0x2f, 0x7a, 0x31, 0x6c, 0x50, 0x63, 0x71, 0x33, 0x67, 0x50, 0x62, 0x70, + 0x6a, 0x62, 0x54, 0x38, 0x39, 0x51, 0x32, 0x38, 0x61, 0x30, 0x30, 0x59, + 0x51, 0x68, 0x5a, 0x6e, 0x58, 0x31, 0x45, 0x62, 0x71, 0x31, 0x69, 0x73, + 0x48, 0x6a, 0x31, 0x37, 0x32, 0x6d, 0x7a, 0x59, 0x37, 0x68, 0x58, 0x63, + 0x69, 0x76, 0x73, 0x73, 0x44, 0x36, 0x6f, 0x44, 0x68, 0x71, 0x2f, 0x0a, + 0x75, 0x79, 0x42, 0x63, 0x6d, 0x35, 0x77, 0x2f, 0x44, 0x36, 0x38, 0x62, + 0x65, 0x4d, 0x52, 0x46, 0x68, 0x52, 0x63, 0x2f, 0x4b, 0x4c, 0x4c, 0x32, + 0x32, 0x47, 0x30, 0x78, 0x76, 0x74, 0x34, 0x51, 0x6a, 0x52, 0x31, 0x39, + 0x79, 0x5a, 0x32, 0x46, 0x50, 0x41, 0x79, 0x55, 0x44, 0x73, 0x57, 0x76, + 0x63, 0x71, 0x2f, 0x57, 0x72, 0x61, 0x31, 0x59, 0x76, 0x51, 0x73, 0x72, + 0x72, 0x2f, 0x42, 0x2b, 0x0a, 0x66, 0x56, 0x77, 0x6a, 0x6e, 0x57, 0x72, + 0x76, 0x35, 0x52, 0x69, 0x62, 0x75, 0x42, 0x75, 0x4c, 0x68, 0x47, 0x53, + 0x59, 0x76, 0x30, 0x41, 0x78, 0x77, 0x55, 0x6d, 0x57, 0x58, 0x6b, 0x4c, + 0x59, 0x32, 0x63, 0x48, 0x34, 0x66, 0x43, 0x31, 0x43, 0x2b, 0x4d, 0x62, + 0x72, 0x4c, 0x41, 0x49, 0x30, 0x50, 0x34, 0x34, 0x76, 0x56, 0x4a, 0x67, + 0x59, 0x58, 0x39, 0x58, 0x51, 0x37, 0x4b, 0x37, 0x70, 0x0a, 0x68, 0x4b, + 0x4d, 0x64, 0x58, 0x57, 0x61, 0x36, 0x6e, 0x5a, 0x34, 0x75, 0x39, 0x6f, + 0x4e, 0x58, 0x58, 0x62, 0x53, 0x48, 0x31, 0x4c, 0x32, 0x30, 0x2b, 0x31, + 0x56, 0x4f, 0x4e, 0x42, 0x63, 0x31, 0x6e, 0x52, 0x30, 0x49, 0x57, 0x77, + 0x41, 0x4f, 0x75, 0x67, 0x35, 0x66, 0x71, 0x2f, 0x55, 0x43, 0x6e, 0x36, + 0x4a, 0x72, 0x63, 0x4e, 0x57, 0x76, 0x37, 0x62, 0x73, 0x52, 0x2b, 0x74, + 0x6f, 0x45, 0x0a, 0x30, 0x48, 0x2b, 0x44, 0x6a, 0x38, 0x4d, 0x4a, 0x47, + 0x67, 0x70, 0x4f, 0x6a, 0x43, 0x54, 0x79, 0x78, 0x30, 0x4f, 0x53, 0x32, + 0x46, 0x32, 0x50, 0x47, 0x34, 0x43, 0x57, 0x48, 0x68, 0x48, 0x4a, 0x2f, + 0x2b, 0x77, 0x49, 0x30, 0x62, 0x4e, 0x53, 0x49, 0x43, 0x45, 0x36, 0x58, + 0x61, 0x43, 0x77, 0x75, 0x2b, 0x32, 0x4e, 0x6a, 0x4e, 0x41, 0x59, 0x71, + 0x6b, 0x78, 0x69, 0x61, 0x44, 0x42, 0x44, 0x0a, 0x73, 0x51, 0x4b, 0x43, + 0x41, 0x51, 0x45, 0x41, 0x72, 0x54, 0x66, 0x56, 0x52, 0x74, 0x43, 0x71, + 0x71, 0x72, 0x72, 0x32, 0x49, 0x45, 0x4b, 0x41, 0x59, 0x63, 0x7a, 0x2b, + 0x4c, 0x4a, 0x4f, 0x71, 0x66, 0x4b, 0x37, 0x77, 0x4f, 0x6f, 0x66, 0x49, + 0x54, 0x7a, 0x73, 0x45, 0x47, 0x76, 0x57, 0x6c, 0x75, 0x47, 0x73, 0x72, + 0x6e, 0x54, 0x33, 0x56, 0x55, 0x71, 0x6c, 0x41, 0x30, 0x2b, 0x45, 0x37, + 0x0a, 0x33, 0x4a, 0x45, 0x41, 0x56, 0x41, 0x33, 0x6c, 0x65, 0x76, 0x32, + 0x51, 0x74, 0x72, 0x61, 0x33, 0x47, 0x30, 0x67, 0x56, 0x77, 0x6f, 0x4f, + 0x6e, 0x63, 0x55, 0x33, 0x50, 0x4f, 0x2b, 0x4c, 0x73, 0x61, 0x56, 0x67, + 0x6a, 0x33, 0x52, 0x43, 0x2b, 0x31, 0x4c, 0x75, 0x68, 0x67, 0x35, 0x68, + 0x68, 0x68, 0x52, 0x4b, 0x34, 0x4b, 0x6a, 0x4d, 0x39, 0x41, 0x38, 0x76, + 0x78, 0x62, 0x2b, 0x6b, 0x76, 0x0a, 0x4c, 0x72, 0x53, 0x50, 0x46, 0x56, + 0x62, 0x4f, 0x35, 0x49, 0x6d, 0x64, 0x4f, 0x6c, 0x33, 0x6f, 0x4c, 0x6f, + 0x67, 0x34, 0x43, 0x2b, 0x69, 0x63, 0x79, 0x44, 0x72, 0x39, 0x79, 0x76, + 0x67, 0x4c, 0x4d, 0x78, 0x67, 0x66, 0x6a, 0x44, 0x55, 0x38, 0x2f, 0x6d, + 0x47, 0x5a, 0x2f, 0x62, 0x42, 0x6d, 0x47, 0x6f, 0x38, 0x79, 0x55, 0x41, + 0x75, 0x61, 0x68, 0x39, 0x4e, 0x79, 0x4f, 0x2f, 0x63, 0x76, 0x0a, 0x65, + 0x6d, 0x4e, 0x49, 0x65, 0x6f, 0x42, 0x6b, 0x33, 0x44, 0x6d, 0x45, 0x34, + 0x7a, 0x55, 0x35, 0x6b, 0x76, 0x38, 0x43, 0x41, 0x41, 0x4e, 0x55, 0x65, + 0x31, 0x75, 0x56, 0x6b, 0x4a, 0x2f, 0x74, 0x78, 0x6d, 0x58, 0x31, 0x70, + 0x56, 0x64, 0x6d, 0x56, 0x61, 0x36, 0x70, 0x38, 0x42, 0x61, 0x41, 0x79, + 0x51, 0x31, 0x4b, 0x61, 0x71, 0x6e, 0x64, 0x46, 0x43, 0x61, 0x53, 0x4d, + 0x79, 0x53, 0x56, 0x0a, 0x66, 0x46, 0x77, 0x33, 0x44, 0x42, 0x61, 0x2b, + 0x39, 0x52, 0x36, 0x69, 0x2f, 0x58, 0x46, 0x4b, 0x36, 0x64, 0x41, 0x67, + 0x70, 0x70, 0x63, 0x47, 0x53, 0x4d, 0x33, 0x36, 0x41, 0x62, 0x42, 0x73, + 0x78, 0x36, 0x6c, 0x6f, 0x70, 0x4f, 0x61, 0x62, 0x61, 0x41, 0x41, 0x6f, + 0x73, 0x71, 0x4f, 0x63, 0x66, 0x73, 0x58, 0x6b, 0x62, 0x6b, 0x68, 0x4c, + 0x76, 0x67, 0x75, 0x69, 0x41, 0x45, 0x77, 0x6f, 0x0a, 0x47, 0x38, 0x33, + 0x66, 0x39, 0x50, 0x62, 0x41, 0x56, 0x38, 0x66, 0x72, 0x6e, 0x6f, 0x54, + 0x62, 0x51, 0x36, 0x51, 0x6c, 0x47, 0x45, 0x6f, 0x4f, 0x67, 0x48, 0x53, + 0x56, 0x73, 0x77, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, + 0x4e, 0x44, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4b, + 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a +}; + +#endif /* _TLS_TEST_CERT_H */ 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 index e64bcd8ae0..d4e13ee70a 100644 --- a/lib/isc/url.c +++ b/lib/isc/url.c @@ -650,7 +650,7 @@ isc_url_parse(const char *buf, size_t buflen, bool is_connect, INSIST(off + len <= buflen); v = 0; - for (pp = buf + off; pp < end; p++) { + for (pp = buf + off; pp < end; pp++) { v *= 10; v += *pp - '0'; diff --git a/lib/isc/win32/libisc.def.in b/lib/isc/win32/libisc.def.in index e08c04753c..e460d5757e 100644 --- a/lib/isc/win32/libisc.def.in +++ b/lib/isc/win32/libisc.def.in @@ -448,7 +448,12 @@ isc_nm_cancelread isc_nm_closedown isc_nm_destroy isc_nm_detach -isc_nm_listenhttps +isc_nm_http_add_doh_endpoint +isc_nm_http_add_endpoint +isc_nm_http_connect_send_request +isc_nm_httpconnect +isc_nm_httprequest +isc_nm_listenhttp isc_nm_listentcpdns isc_nm_listentls isc_nm_listentlsdns @@ -469,7 +474,6 @@ isc_nm_settimeouts isc_nm_tcpdns_keepalive isc_nm_tcpdns_sequential isc_nm_tid -isc_nm_tlsconnect isc_nm_tlsdnsconnect isc_nm_udpconnect isc_nmsocket_close 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..de30a5747b 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, 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..5fbb59db9f 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, const char *key, const char *cert, + char **endpoints, size_t nendpoints, + ns_listenelt_t **target) { + isc_result_t result; + + REQUIRE(target != NULL && *target == NULL); + REQUIRE(endpoints != NULL && *endpoints != NULL); + REQUIRE(nendpoints > 0); + + result = ns_listenelt_create(mctx, http_port, dscp, acl, key != NULL, + key, cert, target); + if (result == ISC_R_SUCCESS) { + (*target)->is_http = true; + (*target)->http_endpoints = endpoints; + (*target)->http_endpoints_number = nendpoints; + } else { + size_t i; + for (i = 0; i < nendpoints; i++) { + isc_mem_free(mctx, endpoints[i]); + } + isc_mem_free(mctx, endpoints); + } + return (result); +} + void ns_listenelt_destroy(ns_listenelt_t *elt) { if (elt->acl != NULL) { @@ -56,6 +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 4bf65ac951..781e928906 100644 --- a/util/copyrights +++ b/util/copyrights @@ -1955,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 @@ -1987,6 +1988,7 @@ ./lib/isc/tests/testdata/file/keep X 2014,2018,2019,2020,2021 ./lib/isc/tests/time_test.c C 2014,2015,2016,2018,2019,2020,2021 ./lib/isc/tests/timer_test.c C 2018,2019,2020,2021 +./lib/isc/tests/tls_test_cert_key.h C 2021 ./lib/isc/tests/tlsdns_test.c C 2021 ./lib/isc/tests/udp_test.c C 2020,2021 ./lib/isc/tests/uv_wrap.h C 2020,2021 @@ -2104,18 +2106,22 @@ ./lib/isccc/win32/libisccc.vcxproj.user X 2013,2018,2019,2020,2021 ./lib/isccfg/aclconf.c C 1999,2000,2001,2002,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021 ./lib/isccfg/dnsconf.c C 2009,2016,2018,2019,2020,2021 +./lib/isccfg/httpconf.c C 2021 ./lib/isccfg/include/isccfg/aclconf.h C 1999,2000,2001,2004,2005,2006,2007,2010,2011,2012,2013,2014,2016,2018,2019,2020,2021 ./lib/isccfg/include/isccfg/cfg.h C 2000,2001,2002,2004,2005,2006,2007,2010,2013,2014,2015,2016,2018,2019,2020,2021 ./lib/isccfg/include/isccfg/grammar.h C 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2013,2014,2015,2016,2017,2018,2019,2020,2021 +./lib/isccfg/include/isccfg/httpconf.h C 2021 ./lib/isccfg/include/isccfg/kaspconf.h C 2019,2020,2021 ./lib/isccfg/include/isccfg/log.h C 2001,2004,2005,2006,2007,2009,2016,2018,2019,2020,2021 ./lib/isccfg/include/isccfg/namedconf.h C 2002,2004,2005,2006,2007,2009,2010,2014,2016,2018,2019,2020,2021 +./lib/isccfg/include/isccfg/tlsconf.h C 2021 ./lib/isccfg/kaspconf.c C 2019,2020,2021 ./lib/isccfg/log.c C 2001,2004,2005,2006,2007,2016,2018,2019,2020,2021 ./lib/isccfg/namedconf.c C 2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021 ./lib/isccfg/parser.c C 2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021 ./lib/isccfg/tests/duration_test.c C 2019,2020,2021 ./lib/isccfg/tests/parser_test.c C 2016,2018,2019,2020,2021 +./lib/isccfg/tlsconf.c C 2021 ./lib/isccfg/win32/DLLMain.c C 2001,2004,2007,2016,2018,2019,2020,2021 ./lib/isccfg/win32/libisccfg.def X 2001,2002,2005,2009,2010,2011,2013,2014,2015,2016,2018,2019,2020,2021 ./lib/isccfg/win32/libisccfg.vcxproj.filters.in X 2013,2014,2015,2016,2018,2019,2020