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