diff options
author | Stefan Eissing <stefan@eissing.org> | 2023-02-01 17:13:12 +0100 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2023-02-02 09:57:34 +0100 |
commit | 671158242db32032a41e8da89da33d773b481ef8 (patch) | |
tree | effae55989c06297fc6ad316c0019f32c296dac3 /lib/vtls | |
parent | b7aaf074e56c4c6ef066da0d4d683628dcc2b225 (diff) | |
download | curl-671158242db32032a41e8da89da33d773b481ef8.tar.gz |
connections: introduce http/3 happy eyeballs
New cfilter HTTP-CONNECT for h3/h2/http1.1 eyeballing.
- filter is installed when `--http3` in the tool is used (or
the equivalent CURLOPT_ done in the library)
- starts a QUIC/HTTP/3 connect right away. Should that not
succeed after 100ms (subject to change), a parallel attempt
is started for HTTP/2 and HTTP/1.1 via TCP
- both attempts are subject to IPv6/IPv4 eyeballing, same
as happens for other connections
- tie timeout to the ip-version HAPPY_EYEBALLS_TIMEOUT
- use a `soft` timeout at half the value. When the soft timeout
expires, the HTTPS-CONNECT filter checks if the QUIC filter
has received any data from the server. If not, it will start
the HTTP/2 attempt.
HTTP/3(ngtcp2) improvements.
- setting call_data in all cfilter calls similar to http/2 and vtls filters
for use in callback where no stream data is available.
- returning CURLE_PARTIAL_FILE for prematurely terminated transfers
- enabling pytest test_05 for h3
- shifting functionality to "connect" UDP sockets from ngtcp2
implementation into the udp socket cfilter. Because unconnected
UDP sockets are weird. For example they error when adding to a
pollset.
HTTP/3(quiche) improvements.
- fixed upload bug in quiche implementation, now passes 251 and pytest
- error codes on stream RESET
- improved debug logs
- handling of DRAIN during connect
- limiting pending event queue
HTTP/2 cfilter improvements.
- use LOG_CF macros for dynamic logging in debug build
- fix CURLcode on RST streams to be CURLE_PARTIAL_FILE
- enable pytest test_05 for h2
- fix upload pytests and improve parallel transfer performance.
GOAWAY handling for ngtcp2/quiche
- during connect, when the remote server refuses to accept new connections
and closes immediately (so the local conn goes into DRAIN phase), the
connection is torn down and a another attempt is made after a short grace
period.
This is the behaviour observed with nghttpx when we tell it to shut
down gracefully. Tested in pytest test_03_02.
TLS improvements
- ALPN selection for SSL/SSL-PROXY filters in one vtls set of functions, replaces
copy of logic in all tls backends.
- standardized the infof logging of offered ALPNs
- ALPN negotiated: have common function for all backends that sets alpn proprty
and connection related things based on the negotiated protocol (or lack thereof).
- new tests/tests-httpd/scorecard.py for testing h3/h2 protocol implementation.
Invoke:
python3 tests/tests-httpd/scorecard.py --help
for usage.
Improvements on gathering connect statistics and socket access.
- new CF_CTRL_CONN_REPORT_STATS cfilter control for having cfilters
report connection statistics. This is triggered when the connection
has completely connected.
- new void Curl_pgrsTimeWas(..) method to report a timer update with
a timestamp of when it happend. This allows for updating timers
"later", e.g. a connect statistic after full connectivity has been
reached.
- in case of HTTP eyeballing, the previous changes will update
statistics only from the filter chain that "won" the eyeballing.
- new cfilter query CF_QUERY_SOCKET for retrieving the socket used
by a filter chain.
Added methods Curl_conn_cf_get_socket() and Curl_conn_get_socket()
for convenient use of this query.
- Change VTLS backend to query their sub-filters for the socket when
checks during the handshake are made.
HTTP/3 documentation on how https eyeballing works.
TLS improvements
- ALPN selection for SSL/SSL-PROXY filters in one vtls set of functions, replaces
copy of logic in all tls backends.
- standardized the infof logging of offered ALPNs
- ALPN negotiated: have common function for all backends that sets alpn proprty
and connection related things based on the negotiated protocol (or lack thereof).
Scorecard with Caddy.
- configure can be run with `--with-test-caddy=path` to specify which caddy to use for testing
- tests/tests-httpd/scorecard.py now measures download speeds with caddy
pytest improvements
- adding Makfile to clean gen dir
- adding nghttpx rundir creation on start
- checking httpd version 2.4.55 for test_05 cases where it is needed. Skipping with message if too old.
- catch exception when checking for caddy existance on system.
Closes #10349
Diffstat (limited to 'lib/vtls')
-rw-r--r-- | lib/vtls/bearssl.c | 63 | ||||
-rw-r--r-- | lib/vtls/gskit.c | 12 | ||||
-rw-r--r-- | lib/vtls/gtls.c | 65 | ||||
-rw-r--r-- | lib/vtls/mbedtls.c | 49 | ||||
-rw-r--r-- | lib/vtls/nss.c | 64 | ||||
-rw-r--r-- | lib/vtls/openssl.c | 66 | ||||
-rw-r--r-- | lib/vtls/rustls.c | 67 | ||||
-rw-r--r-- | lib/vtls/schannel.c | 69 | ||||
-rw-r--r-- | lib/vtls/sectransp.c | 44 | ||||
-rw-r--r-- | lib/vtls/vtls.c | 188 | ||||
-rw-r--r-- | lib/vtls/vtls.h | 44 | ||||
-rw-r--r-- | lib/vtls/vtls_int.h | 2 | ||||
-rw-r--r-- | lib/vtls/wolfssl.c | 54 |
13 files changed, 347 insertions, 440 deletions
diff --git a/lib/vtls/bearssl.c b/lib/vtls/bearssl.c index 933e3c3d0..7e3eb79ce 100644 --- a/lib/vtls/bearssl.c +++ b/lib/vtls/bearssl.c @@ -58,7 +58,7 @@ struct ssl_backend_data { unsigned char buf[BR_SSL_BUFSIZE_BIDI]; br_x509_trust_anchor *anchors; size_t anchors_len; - const char *protocols[2]; + const char *protocols[ALPN_ENTRIES_MAX]; /* SSL client context is active */ bool active; /* size of pending write, yet to be flushed */ @@ -691,35 +691,17 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf, Curl_ssl_sessionid_unlock(data); } - if(cf->conn->bits.tls_enable_alpn) { - int cur = 0; - - /* NOTE: when adding more protocols here, increase the size of the - * protocols array in `struct ssl_backend_data`. - */ + if(connssl->alpn) { + struct alpn_proto_buf proto; + size_t i; - if(data->state.httpwant == CURL_HTTP_VERSION_1_0) { - backend->protocols[cur++] = ALPN_HTTP_1_0; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0); + for(i = 0; i < connssl->alpn->count; ++i) { + backend->protocols[i] = connssl->alpn->entries[i]; } - else { -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - backend->protocols[cur++] = ALPN_H2; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - backend->protocols[cur++] = ALPN_HTTP_1_1; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - } - - br_ssl_engine_set_protocol_names(&backend->ctx.eng, - backend->protocols, cur); + br_ssl_engine_set_protocol_names(&backend->ctx.eng, backend->protocols, + connssl->alpn->count); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } if((1 == Curl_inet_pton(AF_INET, hostname, &addr)) @@ -868,26 +850,11 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, DEBUGASSERT(backend); if(cf->conn->bits.tls_enable_alpn) { - const char *protocol; + const char *proto; - protocol = br_ssl_engine_get_selected_protocol(&backend->ctx.eng); - if(protocol) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, protocol); - -#ifdef USE_HTTP2 - if(!strcmp(protocol, ALPN_H2)) - cf->conn->alpn = CURL_HTTP_VERSION_2; - else -#endif - if(!strcmp(protocol, ALPN_HTTP_1_1)) - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - else - infof(data, "ALPN, unrecognized protocol %s", protocol); - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - } - else - infof(data, VTLS_INFOF_NO_ALPN); + proto = br_ssl_engine_get_selected_protocol(&backend->ctx.eng); + Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, + proto? strlen(proto) : 0); } if(ssl_config->primary.sessionid) { @@ -983,7 +950,7 @@ static CURLcode bearssl_connect_common(struct Curl_cfilter *cf, { CURLcode ret; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); timediff_t timeout_ms; int what; diff --git a/lib/vtls/gskit.c b/lib/vtls/gskit.c index 802b48895..59fd27ce4 100644 --- a/lib/vtls/gskit.c +++ b/lib/vtls/gskit.c @@ -499,7 +499,7 @@ static void cancel_async_handshake(struct Curl_cfilter *cf, (void)data; DEBUGASSERT(BACKEND); - if(QsoCancelOperation(cf->conn->sock[cf->sockindex], 0) > 0) + if(QsoCancelOperation(Curl_conn_cf_get_socket(cf, data), 0) > 0) QsoWaitForIOCompletion(BACKEND->iocport, &cstat, (struct timeval *) NULL); } @@ -532,7 +532,7 @@ static int pipe_ssloverssl(struct Curl_cfilter *cf, int directions) DEBUGASSERT(connssl_next->backend); n = 1; fds[0].fd = BACKEND->remotefd; - fds[1].fd = cf->conn->sock[cf->sockindex]; + fds[1].fd = Curl_conn_cf_get_socket(cf, data); if(directions & SOS_READ) { fds[0].events |= POLLOUT; @@ -847,7 +847,7 @@ static CURLcode gskit_connect_step1(struct Curl_cfilter *cf, result = set_numeric(data, BACKEND->handle, GSK_OS400_READ_TIMEOUT, 1); if(!result) result = set_numeric(data, BACKEND->handle, GSK_FD, BACKEND->localfd >= 0? - BACKEND->localfd: cf->conn->sock[cf->sockindex]); + BACKEND->localfd: Curl_conn_cf_get_socket(cf, data)); if(!result) result = set_ciphers(cf, data, BACKEND->handle, &protoflags); if(!protoflags) { @@ -1208,7 +1208,7 @@ static int gskit_shutdown(struct Curl_cfilter *cf, close_one(cf, data); rc = 0; - what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), SSL_SHUTDOWN_TIMEOUT); while(loop--) { @@ -1230,7 +1230,7 @@ static int gskit_shutdown(struct Curl_cfilter *cf, notify alert from the server. No way to gsk_secure_soc_read() now, so use read(). */ - nread = read(cf->conn->sock[cf->sockindex], buf, sizeof(buf)); + nread = read(Curl_conn_cf_get_socket(cf, data), buf, sizeof(buf)); if(nread < 0) { char buffer[STRERROR_LEN]; @@ -1241,7 +1241,7 @@ static int gskit_shutdown(struct Curl_cfilter *cf, if(nread <= 0) break; - what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0); + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); } return rc; diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 286bce3b3..07dfaa437 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -214,7 +214,7 @@ static CURLcode handshake(struct Curl_cfilter *cf, struct ssl_connect_data *connssl = cf->ctx; struct ssl_backend_data *backend = connssl->backend; gnutls_session_t session; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); DEBUGASSERT(backend); session = backend->gtls.session; @@ -698,37 +698,22 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) if(result) return result; - if(cf->conn->bits.tls_enable_alpn) { - int cur = 0; - gnutls_datum_t protocols[2]; - - if(data->state.httpwant == CURL_HTTP_VERSION_1_0) { - protocols[cur].data = (unsigned char *)ALPN_HTTP_1_0; - protocols[cur++].size = ALPN_HTTP_1_0_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0); - } - else { -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur].data = (unsigned char *)ALPN_H2; - protocols[cur++].size = ALPN_H2_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif + if(connssl->alpn) { + struct alpn_proto_buf proto; + gnutls_datum_t alpn[ALPN_ENTRIES_MAX]; + size_t i; - protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1; - protocols[cur++].size = ALPN_HTTP_1_1_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); + for(i = 0; i < connssl->alpn->count; ++i) { + alpn[i].data = (unsigned char *)connssl->alpn->entries[i]; + alpn[i].size = (unsigned)strlen(connssl->alpn->entries[i]); } - - if(gnutls_alpn_set_protocols(backend->gtls.session, protocols, cur, 0)) { + if(gnutls_alpn_set_protocols(backend->gtls.session, alpn, + (unsigned)connssl->alpn->count, 0)) { failf(data, "failed setting ALPN"); return CURLE_SSL_CONNECT_ERROR; } + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } /* This might be a reconnect, so we check for a session ID in the cache @@ -1272,28 +1257,10 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, int rc; rc = gnutls_alpn_get_selected_protocol(session, &proto); - if(rc == 0) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, proto.size, - proto.data); - -#ifdef USE_HTTP2 - if(proto.size == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, proto.data, - ALPN_H2_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(proto.size == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, proto.data, ALPN_HTTP_1_1_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - } + if(rc == 0) + Curl_alpn_set_negotiated(cf, data, proto.data, proto.size); else - infof(data, VTLS_INFOF_NO_ALPN); - - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, NULL, 0); } if(ssl_config->primary.sessionid) { @@ -1517,7 +1484,7 @@ static int gtls_shutdown(struct Curl_cfilter *cf, char buf[120]; while(!done) { - int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], + int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), SSL_SHUTDOWN_TIMEOUT); if(what > 0) { /* Something to read, let's do it and hope that it is the close diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c index 32d0fa981..7f0f4e366 100644 --- a/lib/vtls/mbedtls.c +++ b/lib/vtls/mbedtls.c @@ -646,19 +646,13 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #ifdef HAS_ALPN - if(cf->conn->bits.tls_enable_alpn) { - const char **p = &backend->protocols[0]; - if(data->state.httpwant == CURL_HTTP_VERSION_1_0) { - *p++ = ALPN_HTTP_1_0; - } - else { -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2) - *p++ = ALPN_H2; -#endif - *p++ = ALPN_HTTP_1_1; + if(connssl->alpn) { + struct alpn_proto_buf proto; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + backend->protocols[i] = connssl->alpn->entries[i]; } - *p = NULL; /* this function doesn't clone the protocols array, which is why we need to keep it around */ if(mbedtls_ssl_conf_alpn_protocols(&backend->config, @@ -666,8 +660,8 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) failf(data, "Failed setting ALPN protocols"); return CURLE_SSL_CONNECT_ERROR; } - for(p = &backend->protocols[0]; *p; ++p) - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, *p); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif @@ -847,28 +841,11 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) } #ifdef HAS_ALPN - if(cf->conn->bits.tls_enable_alpn) { - const char *next_protocol = mbedtls_ssl_get_alpn_protocol(&backend->ssl); + if(connssl->alpn) { + const char *proto = mbedtls_ssl_get_alpn_protocol(&backend->ssl); - if(next_protocol) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, next_protocol); -#ifdef USE_HTTP2 - if(!strncmp(next_protocol, ALPN_H2, ALPN_H2_LENGTH) && - !next_protocol[ALPN_H2_LENGTH]) { - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH) && - !next_protocol[ALPN_HTTP_1_1_LENGTH]) { - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - } - else { - infof(data, VTLS_INFOF_NO_ALPN); - } - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto, + proto? strlen(proto) : 0); } #endif @@ -1084,7 +1061,7 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, { CURLcode retcode; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); timediff_t timeout_ms; int what; diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c index 6cae7b8f3..774cbdd7b 100644 --- a/lib/vtls/nss.c +++ b/lib/vtls/nss.c @@ -873,11 +873,11 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg) #endif case SSL_NEXT_PROTO_NO_SUPPORT: case SSL_NEXT_PROTO_NO_OVERLAP: - infof(data, VTLS_INFOF_NO_ALPN); + Curl_alpn_set_negotiated(cf, data, NULL, 0); return; #ifdef SSL_ENABLE_ALPN case SSL_NEXT_PROTO_SELECTED: - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, buflen, buf); + Curl_alpn_set_negotiated(cf, data, buf, buflen); break; #endif default: @@ -885,29 +885,6 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg) break; } -#ifdef USE_HTTP2 - if(buflen == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, buf, ALPN_H2_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(buflen == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - else if(buflen == ALPN_HTTP_1_0_LENGTH && - !memcmp(ALPN_HTTP_1_0, buf, ALPN_HTTP_1_0_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_1_0; - } - - /* This callback might get called when PR_Recv() is used within - * close_one() during a connection shutdown. At that point there might not - * be any "bundle" associated with the connection anymore. - */ - if(conn->bundle) - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); } } @@ -1901,7 +1878,7 @@ static CURLcode nss_setup_connect(struct Curl_cfilter *cf, PRFileDesc *nspr_io_stub = NULL; PRBool ssl_no_cache; PRBool ssl_cbc_random_iv; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); struct ssl_connect_data *connssl = cf->ctx; struct ssl_backend_data *backend = connssl->backend; struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); @@ -2167,34 +2144,17 @@ static CURLcode nss_setup_connect(struct Curl_cfilter *cf, #endif #if defined(SSL_ENABLE_ALPN) - if(cf->conn->bits.tls_enable_alpn) { - int cur = 0; - unsigned char protocols[128]; - - if(data->state.httpwant == CURL_HTTP_VERSION_1_0) { - protocols[cur++] = ALPN_HTTP_1_0_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_0, ALPN_HTTP_1_0_LENGTH); - cur += ALPN_HTTP_1_0_LENGTH; - } - else { -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur++] = ALPN_H2_LENGTH; - memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH); - cur += ALPN_H2_LENGTH; - } -#endif - protocols[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - } + if(connssl->alpn) { + struct alpn_proto_buf proto; - if(SSL_SetNextProtoNego(backend->handle, protocols, cur) != SECSuccess) + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result || SSL_SetNextProtoNego(backend->handle, proto.data, proto.len) + != SECSuccess) { + failf(data, "Error setting ALPN"); goto error; + } + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index ec90d94a1..b56fe30d4 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -1814,7 +1814,7 @@ static int ossl_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef MSG_PEEK char buf; ssize_t nread; - nread = recv((RECV_TYPE_ARG1)cf->conn->sock[cf->sockindex], + nread = recv((RECV_TYPE_ARG1)Curl_conn_cf_get_socket(cf, data), (RECV_TYPE_ARG2)&buf, (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK); if(nread == 0) @@ -2008,7 +2008,7 @@ static int ossl_shutdown(struct Curl_cfilter *cf, if(backend->handle) { buffsize = (int)sizeof(buf); while(!done && loop--) { - int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], + int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), SSL_SHUTDOWN_TIMEOUT); if(what > 0) { ERR_clear_error(); @@ -3651,43 +3651,17 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, SSL_CTX_set_options(backend->ctx, ctx_options); #ifdef HAS_ALPN - if(cf->conn->bits.tls_enable_alpn) { - int cur = 0; - unsigned char protocols[128]; - - if(data->state.httpwant == CURL_HTTP_VERSION_1_0) { - protocols[cur++] = ALPN_HTTP_1_0_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_0, ALPN_HTTP_1_0_LENGTH); - cur += ALPN_HTTP_1_0_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0); - } - else { -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur++] = ALPN_H2_LENGTH; + if(connssl->alpn) { + struct alpn_proto_buf proto; - memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH); - cur += ALPN_H2_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - protocols[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - } - /* expects length prefixed preference ordered list of protocols in wire - * format - */ - if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) { + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result || + SSL_CTX_set_alpn_protos(backend->ctx, proto.data, proto.len)) { failf(data, "Error setting ALPN"); return CURLE_SSL_CONNECT_ERROR; } + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif @@ -4038,26 +4012,8 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, const unsigned char *neg_protocol; unsigned int len; SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len); - if(len) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, len, neg_protocol); - -#ifdef USE_HTTP2 - if(len == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, neg_protocol, len)) { - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(len == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) { - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - } - else - infof(data, VTLS_INFOF_NO_ALPN); - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + return Curl_alpn_set_negotiated(cf, data, neg_protocol, len); } #endif @@ -4374,7 +4330,7 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf, { CURLcode result = CURLE_OK; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); int what; /* check if the connection has already been established */ diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c index 5a33899df..8bc137b49 100644 --- a/lib/vtls/rustls.c +++ b/lib/vtls/rustls.c @@ -354,34 +354,19 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data, rconn = backend->conn; config_builder = rustls_client_config_builder_new(); - if(data->state.httpwant == CURL_HTTP_VERSION_1_0) { - rustls_slice_bytes alpn[] = { - { (const uint8_t *)ALPN_HTTP_1_0, ALPN_HTTP_1_0_LENGTH } - }; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0); - rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 1); - } - else { - rustls_slice_bytes alpn[2] = { - { (const uint8_t *)ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH }, - { (const uint8_t *)ALPN_H2, ALPN_H2_LENGTH }, - }; -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy) -#endif - ) { - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 2); - } - else -#endif - { - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 1); + if(connssl->alpn) { + struct alpn_proto_buf proto; + rustls_slice_bytes alpn[ALPN_ENTRIES_MAX]; + size_t i; + + for(i = 0; i < connssl->alpn->count; ++i) { + alpn[i].data = (const uint8_t *)connssl->alpn->entries[i]; + alpn[i].len = strlen(connssl->alpn->entries[i]); } + rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, + connssl->alpn->count); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } if(!verifypeer) { rustls_client_config_builder_dangerous_set_certificate_verifier( @@ -457,29 +442,7 @@ cr_set_negotiated_alpn(struct Curl_cfilter *cf, struct Curl_easy *data, size_t len = 0; rustls_connection_get_alpn_protocol(rconn, &protocol, &len); - if(!protocol) { - infof(data, VTLS_INFOF_NO_ALPN); - return; - } - -#ifdef USE_HTTP2 - if(len == ALPN_H2_LENGTH && 0 == memcmp(ALPN_H2, protocol, len)) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_H2); - cf->conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(len == ALPN_HTTP_1_1_LENGTH && - 0 == memcmp(ALPN_HTTP_1_1, protocol, len)) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_HTTP_1_1); - cf->conn->alpn = CURL_HTTP_VERSION_1_1; - } - else { - infof(data, "ALPN, negotiated an unrecognized protocol"); - } - - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, protocol, len); } static CURLcode @@ -487,7 +450,7 @@ cr_connect_nonblocking(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { struct ssl_connect_data *const connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); struct ssl_backend_data *const backend = connssl->backend; struct rustls_connection *rconn = NULL; CURLcode tmperr = CURLE_OK; @@ -591,7 +554,7 @@ cr_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *socks) { struct ssl_connect_data *const connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); struct ssl_backend_data *const backend = connssl->backend; struct rustls_connection *rconn = NULL; diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 27a843286..452fa409a 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -1105,7 +1105,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #ifdef HAS_ALPN /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above. Also it doesn't seem to be supported for Wine, see curl bug #983. */ - backend->use_alpn = cf->conn->bits.tls_enable_alpn && + backend->use_alpn = connssl->alpn && !GetProcAddress(GetModuleHandle(TEXT("ntdll")), "wine_get_version") && curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, @@ -1196,6 +1196,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) int list_start_index = 0; unsigned int *extension_len = NULL; unsigned short* list_len = NULL; + struct alpn_proto_buf proto; /* The first four bytes will be an unsigned int indicating number of bytes of data in the rest of the buffer. */ @@ -1215,33 +1216,22 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) list_start_index = cur; - if(data->state.httpwant == CURL_HTTP_VERSION_1_0) { - alpn_buffer[cur++] = ALPN_HTTP_1_0_LENGTH; - memcpy(&alpn_buffer[cur], ALPN_HTTP_1_0, ALPN_HTTP_1_0_LENGTH); - cur += ALPN_HTTP_1_0_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0); - } - else { -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2) { - alpn_buffer[cur++] = ALPN_H2_LENGTH; - memcpy(&alpn_buffer[cur], ALPN_H2, ALPN_H2_LENGTH); - cur += ALPN_H2_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; } + memcpy(&alpn_buffer[cur], proto.data, proto.len); + cur += proto.len; *list_len = curlx_uitous(cur - list_start_index); *extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short); InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); + + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } else { InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); @@ -1735,40 +1725,23 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) if(alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { - unsigned char alpn = 0; + unsigned char prev_alpn = cf->conn->alpn; - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, - alpn_result.ProtocolIdSize, alpn_result.ProtocolId); - -#ifdef USE_HTTP2 - if(alpn_result.ProtocolIdSize == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, alpn_result.ProtocolId, ALPN_H2_LENGTH)) { - alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId, - ALPN_HTTP_1_1_LENGTH)) { - alpn = CURL_HTTP_VERSION_1_1; - } + Curl_alpn_set_negotiated(cf, data, alpn_result.ProtocolId, + alpn_result.ProtocolIdSize); if(backend->recv_renegotiating) { - if(alpn != cf->conn->alpn) { + if(prev_alpn != cf->conn->alpn && + prev_alpn != CURL_HTTP_VERSION_NONE) { + /* Renegotiation selected a different protocol now, we cannot + * deal with this */ failf(data, "schannel: server selected an ALPN protocol too late"); return CURLE_SSL_CONNECT_ERROR; } } - else - cf->conn->alpn = alpn; } else { if(!backend->recv_renegotiating) - infof(data, VTLS_INFOF_NO_ALPN); - } - - if(!backend->recv_renegotiating) { - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, NULL, 0); } } #endif @@ -1849,7 +1822,7 @@ schannel_connect_common(struct Curl_cfilter *cf, { CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); timediff_t timeout_ms; int what; @@ -2064,7 +2037,7 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data, } else if(!timeout_ms) timeout_ms = TIMEDIFF_T_MAX; - what = SOCKET_WRITABLE(cf->conn->sock[cf->sockindex], timeout_ms); + what = SOCKET_WRITABLE(Curl_conn_cf_get_socket(cf, data), timeout_ms); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); diff --git a/lib/vtls/sectransp.c b/lib/vtls/sectransp.c index 7f311c643..2e98169e2 100644 --- a/lib/vtls/sectransp.c +++ b/lib/vtls/sectransp.c @@ -1636,7 +1636,6 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, const bool verifypeer = conn_config->verifypeer; char * const ssl_cert = ssl_config->primary.clientcert; const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob; - bool isproxy = Curl_ssl_cf_is_proxy(cf); #ifdef ENABLE_IPV6 struct in6_addr addr; #else @@ -1797,38 +1796,28 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ #if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(cf->conn->bits.tls_enable_alpn) { + if(connssl->alpn) { if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { + struct alpn_proto_buf proto; + size_t i; + CFStringRef cstr; CFMutableArrayRef alpnArr = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); - - if(data->state.httpwant == CURL_HTTP_VERSION_1_0) { - CFArrayAppendValue(alpnArr, CFSTR(ALPN_HTTP_1_0)); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0); - } - else { -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!isproxy || !cf->conn->bits.tunnel_proxy) -#endif - ) { - CFArrayAppendValue(alpnArr, CFSTR(ALPN_H2)); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - CFArrayAppendValue(alpnArr, CFSTR(ALPN_HTTP_1_1)); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); + for(i = 0; i < connssl->alpn->count; ++i) { + cstr = CFStringCreateWithCString(NULL, connssl->alpn->entries[i], + kCFStringEncodingUTF8); + if(!cstr) + return CURLE_OUT_OF_MEMORY; + CFArrayAppendValue(alpnArr, cstr); + CFRelease(cstr); } - /* expects length prefixed preference ordered list of protocols in wire - * format - */ err = SSLSetALPNProtocols(backend->ssl_ctx, alpnArr); if(err != noErr) infof(data, "WARNING: failed to set ALPN protocols; OSStatus %d", err); CFRelease(alpnArr); + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } } #endif @@ -3018,7 +3007,7 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, { CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); int what; /* check if the connection has already been established */ @@ -3196,7 +3185,8 @@ static int sectransp_shutdown(struct Curl_cfilter *cf, rc = 0; - what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], SSL_SHUTDOWN_TIMEOUT); + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), + SSL_SHUTDOWN_TIMEOUT); DEBUGF(LOG_CF(data, cf, "shutdown")); while(loop--) { @@ -3225,7 +3215,7 @@ static int sectransp_shutdown(struct Curl_cfilter *cf, if(nread <= 0) break; - what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0); + what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); } return rc; diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 541df5b0c..604caccf7 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -290,7 +290,8 @@ static bool ssl_prefs_check(struct Curl_easy *data) return TRUE; } -static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data) +static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data, + const struct alpn_spec *alpn) { struct ssl_connect_data *ctx; @@ -299,6 +300,7 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data) if(!ctx) return NULL; + ctx->alpn = alpn; ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data); if(!ctx->backend) { free(ctx); @@ -329,7 +331,6 @@ static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data) result = Curl_ssl->connect_blocking(cf, data); if(!result) { - Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */ DEBUGASSERT(connssl->state == ssl_connection_complete); } @@ -605,19 +606,20 @@ int Curl_ssl_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data, curl_socket_t *socks) { struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); - (void)data; - if(connssl->connecting_state == ssl_connect_2_writing) { - /* write mode */ - socks[0] = cf->conn->sock[FIRSTSOCKET]; - return GETSOCK_WRITESOCK(0); - } - if(connssl->connecting_state == ssl_connect_2_reading) { - /* read mode */ - socks[0] = cf->conn->sock[FIRSTSOCKET]; - return GETSOCK_READSOCK(0); + if(sock != CURL_SOCKET_BAD) { + if(connssl->connecting_state == ssl_connect_2_writing) { + /* write mode */ + socks[0] = sock; + return GETSOCK_WRITESOCK(0); + } + if(connssl->connecting_state == ssl_connect_2_reading) { + /* read mode */ + socks[0] = sock; + return GETSOCK_READSOCK(0); + } } - return GETSOCK_BLANK; } @@ -1534,8 +1536,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf, if(!result && *done) { cf->connected = TRUE; - if(cf->sockindex == FIRSTSOCKET && !Curl_ssl_cf_is_proxy(cf)) - Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */ + connssl->handshake_done = Curl_now(); DEBUGASSERT(connssl->state == ssl_connection_complete); } out: @@ -1603,11 +1604,16 @@ static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf, struct Curl_easy *data, int event, int arg1, void *arg2) { + struct ssl_connect_data *connssl = cf->ctx; struct cf_call_data save; (void)arg1; (void)arg2; switch(event) { + case CF_CTRL_CONN_REPORT_STATS: + if(cf->sockindex == FIRSTSOCKET && !Curl_ssl_cf_is_proxy(cf)) + Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done); + break; case CF_CTRL_DATA_ATTACH: if(Curl_ssl->attach_data) { CF_DATA_SAVE(save, cf, data); @@ -1683,14 +1689,16 @@ struct Curl_cftype Curl_cft_ssl_proxy = { }; static CURLcode cf_ssl_create(struct Curl_cfilter **pcf, - struct Curl_easy *data) + struct Curl_easy *data, + struct connectdata *conn) { struct Curl_cfilter *cf = NULL; struct ssl_connect_data *ctx; CURLcode result; DEBUGASSERT(data->conn); - ctx = cf_ctx_new(data); + + ctx = cf_ctx_new(data, Curl_alpn_get_spec(data, conn)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; @@ -1712,7 +1720,7 @@ CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data, struct Curl_cfilter *cf; CURLcode result; - result = cf_ssl_create(&cf, data); + result = cf_ssl_create(&cf, data, conn); if(!result) Curl_conn_cf_add(data, conn, sockindex, cf); return result; @@ -1724,7 +1732,7 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, struct Curl_cfilter *cf; CURLcode result; - result = cf_ssl_create(&cf, data); + result = cf_ssl_create(&cf, data, cf_at->conn); if(!result) Curl_conn_cf_insert_after(cf_at, cf); return result; @@ -1732,18 +1740,18 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at, #ifndef CURL_DISABLE_PROXY static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf, - struct Curl_easy *data) + struct Curl_easy *data, + struct connectdata *conn) { struct Curl_cfilter *cf = NULL; struct ssl_connect_data *ctx; CURLcode result; - ctx = cf_ctx_new(data); + ctx = cf_ctx_new(data, Curl_alpn_get_proxy_spec(data, conn)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; } - result = Curl_cf_create(&cf, &Curl_cft_ssl_proxy, ctx); out: @@ -1760,7 +1768,7 @@ CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data, struct Curl_cfilter *cf; CURLcode result; - result = cf_ssl_proxy_create(&cf, data); + result = cf_ssl_proxy_create(&cf, data, conn); if(!result) Curl_conn_cf_add(data, conn, sockindex, cf); return result; @@ -1772,7 +1780,7 @@ CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at, struct Curl_cfilter *cf; CURLcode result; - result = cf_ssl_proxy_create(&cf, data); + result = cf_ssl_proxy_create(&cf, data, cf_at->conn); if(!result) Curl_conn_cf_insert_after(cf_at, cf); return result; @@ -1900,4 +1908,136 @@ struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf) return NULL; } +static const struct alpn_spec ALPN_SPEC_H10 = { + { ALPN_HTTP_1_0 }, 1 +}; +static const struct alpn_spec ALPN_SPEC_H11 = { + { ALPN_HTTP_1_1 }, 1 +}; +#ifdef USE_HTTP2 +static const struct alpn_spec ALPN_SPEC_H2_H11 = { + { ALPN_H2, ALPN_HTTP_1_1 }, 2 +}; +#endif + +const struct alpn_spec * +Curl_alpn_get_spec(struct Curl_easy *data, struct connectdata *conn) +{ + if(!conn->bits.tls_enable_alpn) + return NULL; + if(data->state.httpwant == CURL_HTTP_VERSION_1_0) + return &ALPN_SPEC_H10; +#ifdef USE_HTTP2 + if(data->state.httpwant >= CURL_HTTP_VERSION_2) + return &ALPN_SPEC_H2_H11; +#endif + return &ALPN_SPEC_H11; +} + +const struct alpn_spec * +Curl_alpn_get_proxy_spec(struct Curl_easy *data, struct connectdata *conn) +{ + if(!conn->bits.tls_enable_alpn) + return NULL; + if(data->state.httpwant == CURL_HTTP_VERSION_1_0) + return &ALPN_SPEC_H10; + return &ALPN_SPEC_H11; +} + +CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, + const struct alpn_spec *spec) +{ + size_t i, len; + int off = 0; + unsigned char blen; + + memset(buf, 0, sizeof(*buf)); + for(i = 0; spec && i < spec->count; ++i) { + len = strlen(spec->entries[i]); + if(len > 255) + return CURLE_FAILED_INIT; + blen = (unsigned char)len; + if(off + blen + 1 >= (int)sizeof(buf->data)) + return CURLE_FAILED_INIT; + buf->data[off++] = blen; + memcpy(buf->data + off, spec->entries[i], blen); + off += blen; + } + buf->len = off; + return CURLE_OK; +} + +CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, + const struct alpn_spec *spec) +{ + size_t i, len; + size_t off = 0; + + memset(buf, 0, sizeof(*buf)); + for(i = 0; spec && i < spec->count; ++i) { + len = strlen(spec->entries[i]); + if(len > 255) + return CURLE_FAILED_INIT; + if(off + len + 2 >= (int)sizeof(buf->data)) + return CURLE_FAILED_INIT; + if(off) + buf->data[off++] = ','; + memcpy(buf->data + off, spec->entries[i], len); + off += len; + } + buf->data[off] = '\0'; + buf->len = (int)off; + return CURLE_OK; +} + +CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, + struct Curl_easy *data, + const unsigned char *proto, + size_t proto_len) +{ + int can_multi = 0; + + if(proto && proto_len) { + if(proto_len == ALPN_HTTP_1_1_LENGTH && + !memcmp(ALPN_HTTP_1_1, proto, ALPN_HTTP_1_1_LENGTH)) { + cf->conn->alpn = CURL_HTTP_VERSION_1_1; + } + else if(proto_len == ALPN_HTTP_1_0_LENGTH && + !memcmp(ALPN_HTTP_1_0, proto, ALPN_HTTP_1_0_LENGTH)) { + cf->conn->alpn = CURL_HTTP_VERSION_1_0; + } +#ifdef USE_HTTP2 + else if(proto_len == ALPN_H2_LENGTH && + !memcmp(ALPN_H2, proto, ALPN_H2_LENGTH)) { + cf->conn->alpn = CURL_HTTP_VERSION_2; + can_multi = 1; + } +#endif +#ifdef USE_HTTP3 + else if(proto_len == ALPN_H3_LENGTH && + !memcmp(ALPN_H3, proto, ALPN_H3_LENGTH)) { + cf->conn->alpn = CURL_HTTP_VERSION_3; + can_multi = 1; + } +#endif + else { + cf->conn->alpn = CURL_HTTP_VERSION_NONE; + failf(data, "unsupported ALPN protocol: '%.*s'", proto_len, proto); + /* TODO: do we want to fail this? Previous code just ignored it and + * some vtls backends even ignore the return code of this function. */ + /* return CURLE_NOT_BUILT_IN; */ + goto out; + } + infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, proto_len, proto); + } + else { + cf->conn->alpn = CURL_HTTP_VERSION_NONE; + infof(data, VTLS_INFOF_NO_ALPN); + } + +out: + Curl_multiuse_state(data, can_multi? BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + return CURLE_OK; +} + #endif /* USE_SSL */ diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h index d349d1f61..0f8801c29 100644 --- a/lib/vtls/vtls.h +++ b/lib/vtls/vtls.h @@ -27,7 +27,6 @@ struct connectdata; struct ssl_config_data; -struct ssl_connect_data; struct ssl_primary_config; struct Curl_ssl_session; @@ -73,6 +72,49 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, #define ALPN_HTTP_1_0 "http/1.0" #define ALPN_H2_LENGTH 2 #define ALPN_H2 "h2" +#define ALPN_H3_LENGTH 2 +#define ALPN_H3 "h3" + +/* conservative sizes on the ALPN entries and count we are handling, + * we can increase these if we ever feel the need or have to accomodate + * ALPN strings from the "outside". */ +#define ALPN_NAME_MAX 10 +#define ALPN_ENTRIES_MAX 3 +#define ALPN_PROTO_BUF_MAX (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1)) + +struct alpn_spec { + const char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX]; + size_t count; /* number of entries */ +}; + +struct alpn_proto_buf { + unsigned char data[ALPN_PROTO_BUF_MAX]; + int len; +}; + +CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf, + const struct alpn_spec *spec); +CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf, + const struct alpn_spec *spec); + +CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf, + struct Curl_easy *data, + const unsigned char *proto, + size_t proto_len); + +/** + * Get the ALPN specification to use for talking to remote host. + * May return NULL if ALPN is disabled on the connection. + */ +const struct alpn_spec * +Curl_alpn_get_spec(struct Curl_easy *data, struct connectdata *conn); + +/** + * Get the ALPN specification to use for talking to the proxy. + * May return NULL if ALPN is disabled on the connection. + */ +const struct alpn_spec * +Curl_alpn_get_proxy_spec(struct Curl_easy *data, struct connectdata *conn); char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen); diff --git a/lib/vtls/vtls_int.h b/lib/vtls/vtls_int.h index 9403173af..a20ca7db7 100644 --- a/lib/vtls/vtls_int.h +++ b/lib/vtls/vtls_int.h @@ -36,8 +36,10 @@ struct ssl_connect_data { char *hostname; /* hostname for verification */ char *dispname; /* display version of hostname */ int port; /* remote port at origin */ + const struct alpn_spec *alpn; /* ALPN to use or NULL for none */ struct ssl_backend_data *backend; /* vtls backend specific props */ struct cf_call_data call_data; /* data handle used in current call */ + struct curltime handshake_done; /* time when handshake finished */ }; diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index 93f3e2c94..478c4bddd 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -631,34 +631,18 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) #endif #ifdef HAVE_ALPN - if(cf->conn->bits.tls_enable_alpn) { - char protocols[128]; - *protocols = '\0'; - - /* wolfSSL's ALPN protocol name list format is a comma separated string of - protocols in descending order of preference, eg: "h2,http/1.1" */ - - if(data->state.httpwant == CURL_HTTP_VERSION_1_0) { - strcpy(protocols, ALPN_HTTP_1_0); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0); - } - else { -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2) { - strcpy(protocols + strlen(protocols), ALPN_H2 ","); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif + if(connssl->alpn) { + struct alpn_proto_buf proto; + CURLcode result; - strcpy(protocols + strlen(protocols), ALPN_HTTP_1_1); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - } - if(wolfSSL_UseALPN(backend->handle, protocols, - (unsigned)strlen(protocols), + result = Curl_alpn_to_proto_str(&proto, connssl->alpn); + if(result || + wolfSSL_UseALPN(backend->handle, (char *)proto.data, proto.len, WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) { failf(data, "SSL: failed setting ALPN protocols"); return CURLE_SSL_CONNECT_ERROR; } + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); } #endif /* HAVE_ALPN */ @@ -710,7 +694,7 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } #else /* USE_BIO_CHAIN */ /* pass the raw socket into the SSL layer */ - if(!SSL_set_fd(backend->handle, (int)cf->conn->sock[cf->sockindex])) { + if(!SSL_set_fd(backend->handle, (int)Curl_conn_cf_get_socket(cf, data))) { failf(data, "SSL: SSL_set_fd failed"); return CURLE_SSL_CONNECT_ERROR; } @@ -886,25 +870,11 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len); if(rc == SSL_SUCCESS) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, protocol_len, protocol); - - if(protocol_len == ALPN_HTTP_1_1_LENGTH && - !memcmp(protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH)) - cf->conn->alpn = CURL_HTTP_VERSION_1_1; -#ifdef USE_HTTP2 - else if(data->state.httpwant >= CURL_HTTP_VERSION_2 && - protocol_len == ALPN_H2_LENGTH && - !memcmp(protocol, ALPN_H2, ALPN_H2_LENGTH)) - cf->conn->alpn = CURL_HTTP_VERSION_2; -#endif - else - infof(data, "ALPN, unrecognized protocol %.*s", protocol_len, - protocol); - Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); + Curl_alpn_set_negotiated(cf, data, (const unsigned char *)protocol, + protocol_len); } else if(rc == SSL_ALPN_NOT_FOUND) - infof(data, VTLS_INFOF_NO_ALPN); + Curl_alpn_set_negotiated(cf, data, NULL, 0); else { failf(data, "ALPN, failure getting protocol, error %d", rc); return CURLE_SSL_CONNECT_ERROR; @@ -1169,7 +1139,7 @@ wolfssl_connect_common(struct Curl_cfilter *cf, { CURLcode result; struct ssl_connect_data *connssl = cf->ctx; - curl_socket_t sockfd = cf->conn->sock[cf->sockindex]; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); int what; /* check if the connection has already been established */ |