diff options
Diffstat (limited to 'lib/vquic/curl_ngtcp2.c')
-rw-r--r-- | lib/vquic/curl_ngtcp2.c | 811 |
1 files changed, 220 insertions, 591 deletions
diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c index 7d681e585..a26b3e429 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/curl_ngtcp2.c @@ -41,7 +41,6 @@ #include "vtls/gtls.h" #elif defined(USE_WOLFSSL) #include <ngtcp2/ngtcp2_crypto_wolfssl.h> -#include "vtls/wolfssl.h" #endif #include "urldata.h" @@ -61,6 +60,7 @@ #include "inet_pton.h" #include "vquic.h" #include "vquic_int.h" +#include "vquic-tls.h" #include "vtls/keylog.h" #include "vtls/vtls.h" #include "curl_ngtcp2.h" @@ -73,12 +73,8 @@ #include "memdebug.h" -#define H3_ALPN_H3_29 "\x5h3-29" -#define H3_ALPN_H3 "\x2h3" - #define QUIC_MAX_STREAMS (256*1024) #define QUIC_MAX_DATA (1*1024*1024) -#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS) #define QUIC_HANDSHAKE_TIMEOUT (10*NGTCP2_SECONDS) /* A stream window is the maximum amount we need to buffer for @@ -102,25 +98,6 @@ (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) -#ifdef USE_OPENSSL -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:X25519:P-384:P-521" -#elif defined(USE_GNUTLS) -#define QUIC_PRIORITY \ - "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ - "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ - "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ - "%DISABLE_TLS13_COMPAT_MODE" -#elif defined(USE_WOLFSSL) -#define QUIC_CIPHERS \ - "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ - "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" -#define QUIC_GROUPS "P-256:P-384:P-521" -#endif - - /* * Store ngtcp2 version info in this buffer. */ @@ -134,6 +111,8 @@ void Curl_ngtcp2_ver(char *p, size_t len) struct cf_ngtcp2_ctx { struct cf_quic_ctx q; + struct ssl_peer peer; + struct quic_tls_ctx tls; ngtcp2_path connected_path; ngtcp2_conn *qconn; ngtcp2_cid dcid; @@ -143,29 +122,16 @@ struct cf_ngtcp2_ctx { ngtcp2_transport_params transport_params; ngtcp2_ccerr last_error; ngtcp2_crypto_conn_ref conn_ref; -#ifdef USE_OPENSSL - SSL_CTX *sslctx; - SSL *ssl; -#elif defined(USE_GNUTLS) - struct gtls_instance *gtls; -#elif defined(USE_WOLFSSL) - WOLFSSL_CTX *sslctx; - WOLFSSL *ssl; -#endif struct cf_call_data call_data; nghttp3_conn *h3conn; nghttp3_settings h3settings; struct curltime started_at; /* time the current attempt started */ struct curltime handshake_at; /* time connect handshake finished */ - struct curltime first_byte_at; /* when first byte was recvd */ struct curltime reconnect_at; /* time the next attempt should start */ struct bufc_pool stream_bufcp; /* chunk pool for streams */ size_t max_stream_window; /* max flow window for one stream */ + uint64_t max_idle_ms; /* max idle time for QUIC connection */ int qlogfd; - BIT(got_first_byte); /* if first byte was received */ -#ifdef USE_OPENSSL - BIT(x509_store_setup); /* if x509 store has been set up */ -#endif }; /* How to access `call_data` from a cf_ngtcp2 filter */ @@ -191,6 +157,7 @@ struct h3_stream_ctx { bool closed; /* TRUE on stream close */ bool reset; /* TRUE on stream reset */ bool send_closed; /* stream is local closed */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ }; #define H3_STREAM_CTX(d) ((struct h3_stream_ctx *)(((d) && (d)->req.p.http)? \ @@ -236,11 +203,21 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) { + struct cf_ngtcp2_ctx *ctx = cf->ctx; struct h3_stream_ctx *stream = H3_STREAM_CTX(data); (void)cf; if(stream) { CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id); + if(ctx->h3conn && !stream->closed) { + nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream->id); + nghttp3_conn_close_stream(ctx->h3conn, stream->id, + NGHTTP3_H3_REQUEST_CANCELLED); + nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); + ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); + stream->closed = TRUE; + } + Curl_bufq_free(&stream->sendbuf); Curl_bufq_free(&stream->recvbuf); Curl_h1_req_parse_free(&stream->h1); @@ -249,6 +226,43 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) } } +static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream_id) +{ + struct Curl_easy *sdata; + + (void)cf; + if(H3_STREAM_ID(data) == stream_id) { + return data; + } + else { + DEBUGASSERT(data->multi); + for(sdata = data->multi->easyp; sdata; sdata = sdata->next) { + if((sdata->conn == data->conn) && H3_STREAM_ID(sdata) == stream_id) { + return sdata; + } + } + } + return NULL; +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && stream->upload_left && !stream->send_closed) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + /* ngtcp2 default congestion controller does not perform pacing. Limit the maximum packet burst to MAX_PKT_BURST packets. */ #define MAX_PKT_BURST 10 @@ -261,10 +275,14 @@ struct pkt_io_ctx { ngtcp2_path_storage ps; }; -static ngtcp2_tstamp timestamp(void) +static void pktx_update_time(struct pkt_io_ctx *pktx, + struct Curl_cfilter *cf) { - struct curltime ct = Curl_now(); - return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + + vquic_ctx_update_time(&ctx->q); + pktx->ts = ctx->q.last_op.tv_sec * NGTCP2_SECONDS + + ctx->q.last_op.tv_usec * NGTCP2_MICROSECONDS; } static void pktx_init(struct pkt_io_ctx *pktx, @@ -273,9 +291,9 @@ static void pktx_init(struct pkt_io_ctx *pktx, { pktx->cf = cf; pktx->data = data; - pktx->ts = timestamp(); pktx->pkt_count = 0; ngtcp2_path_storage_zero(&pktx->ps); + pktx_update_time(pktx, cf); } static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, @@ -354,383 +372,14 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx, t->initial_max_stream_data_uni = ctx->max_stream_window; t->initial_max_streams_bidi = QUIC_MAX_STREAMS; t->initial_max_streams_uni = QUIC_MAX_STREAMS; - t->max_idle_timeout = QUIC_IDLE_TIMEOUT; + t->max_idle_timeout = (ctx->max_idle_ms * NGTCP2_MILLISECONDS); if(ctx->qlogfd != -1) { s->qlog_write = qlog_callback; } } -#ifdef USE_OPENSSL -static void keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#elif defined(USE_GNUTLS) -static int keylog_callback(gnutls_session_t session, const char *label, - const gnutls_datum_t *secret) -{ - gnutls_datum_t crandom; - gnutls_datum_t srandom; - - gnutls_session_get_random(session, &crandom, &srandom); - if(crandom.size != 32) { - return -1; - } - - Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); - return 0; -} -#elif defined(USE_WOLFSSL) -#if defined(HAVE_SECRET_CALLBACK) -static void keylog_callback(const WOLFSSL *ssl, const char *line) -{ - (void)ssl; - Curl_tls_keylog_write_line(line); -} -#endif -#endif - static int init_ngh3_conn(struct Curl_cfilter *cf); -#ifdef USE_OPENSSL -static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx, - struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct connectdata *conn = cf->conn; - CURLcode result = CURLE_FAILED_INIT; - SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); - - if(!ssl_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - -#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) - if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); - goto out; - } -#else - if(ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); - goto out; - } -#endif - - SSL_CTX_set_default_verify_paths(ssl_ctx); - - { - const char *curves = conn->ssl_config.curves ? - conn->ssl_config.curves : QUIC_GROUPS; - if(!SSL_CTX_set1_curves_list(ssl_ctx, curves)) { - failf(data, "failed setting curves list for QUIC: '%s'", curves); - return CURLE_SSL_CIPHER; - } - } - -#ifndef OPENSSL_IS_BORINGSSL - { - const char *ciphers13 = conn->ssl_config.cipher_list13 ? - conn->ssl_config.cipher_list13 : QUIC_CIPHERS; - if(SSL_CTX_set_ciphersuites(ssl_ctx, ciphers13) != 1) { - failf(data, "failed setting QUIC cipher suite: %s", ciphers13); - return CURLE_SSL_CIPHER; - } - infof(data, "QUIC cipher selection: %s", ciphers13); - } -#endif - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); - } - - /* OpenSSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(ssl_ctx, conn->ssl_config.verifypeer ? - SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - /* When a user callback is installed to modify the SSL_CTX, - * we need to do the full initialization before calling it. - * See: #11800 */ - if(!ctx->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, ssl_ctx); - if(result) - goto out; - ctx->x509_store_setup = TRUE; - } - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, ssl_ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - goto out; - } - } - result = CURLE_OK; - -out: - *pssl_ctx = result? NULL : ssl_ctx; - if(result && ssl_ctx) - SSL_CTX_free(ssl_ctx); - return result; -} - -static CURLcode quic_set_client_cert(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - SSL_CTX *ssl_ctx = ctx->sslctx; - const struct ssl_config_data *ssl_config; - - ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET); - DEBUGASSERT(ssl_config); - - if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob - || ssl_config->cert_type) { - return Curl_ossl_set_client_cert( - data, ssl_ctx, ssl_config->primary.clientcert, - ssl_config->primary.cert_blob, ssl_config->cert_type, - ssl_config->key, ssl_config->key_blob, - ssl_config->key_type, ssl_config->key_passwd); - } - - return CURLE_OK; -} - -/** SSL callbacks ***/ - -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - unsigned char checkip[16]; - - DEBUGASSERT(!ctx->ssl); - ctx->ssl = SSL_new(ctx->sslctx); - - SSL_set_app_data(ctx->ssl, &ctx->conn_ref); - SSL_set_connect_state(ctx->ssl); - SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - SSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); - - /* set SNI */ - if((0 == Curl_inet_pton(AF_INET, cf->conn->host.name, checkip)) -#ifdef ENABLE_IPV6 - && (0 == Curl_inet_pton(AF_INET6, cf->conn->host.name, checkip)) -#endif - ) { - char *snihost = Curl_ssl_snihost(data, cf->conn->host.name, NULL); - if(!snihost || !SSL_set_tlsext_host_name(ctx->ssl, snihost)) { - failf(data, "Failed set SNI"); - SSL_free(ctx->ssl); - ctx->ssl = NULL; - return CURLE_QUIC_CONNECT_ERROR; - } - } - return CURLE_OK; -} -#elif defined(USE_GNUTLS) -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result; - gnutls_datum_t alpn[2]; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = cf->conn->host.name; - long * const pverifyresult = &data->set.ssl.certverifyresult; - int rc; - - DEBUGASSERT(ctx->gtls == NULL); - ctx->gtls = calloc(1, sizeof(*(ctx->gtls))); - if(!ctx->gtls) - return CURLE_OUT_OF_MEMORY; - - result = gtls_client_init(data, &cf->conn->ssl_config, &data->set.ssl, - hostname, ctx->gtls, pverifyresult); - if(result) - return result; - - gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref); - - if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) { - CURL_TRC_CF(data, cf, - "ngtcp2_crypto_gnutls_configure_client_session failed\n"); - return CURLE_QUIC_CONNECT_ERROR; - } - - rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL); - if(rc < 0) { - CURL_TRC_CF(data, cf, "gnutls_priority_set_direct failed: %s\n", - gnutls_strerror(rc)); - return CURLE_QUIC_CONNECT_ERROR; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { - gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback); - } - - /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */ - alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1; - alpn[0].size = sizeof(H3_ALPN_H3_29) - 2; - alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1; - alpn[1].size = sizeof(H3_ALPN_H3) - 2; - - gnutls_alpn_set_protocols(ctx->gtls->session, - alpn, 2, GNUTLS_ALPN_MANDATORY); - return CURLE_OK; -} -#elif defined(USE_WOLFSSL) - -static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx, - struct Curl_cfilter *cf, struct Curl_easy *data) -{ - struct connectdata *conn = cf->conn; - CURLcode result = CURLE_FAILED_INIT; - WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); - - if(!ssl_ctx) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) { - failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); - goto out; - } - - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - - if(wolfSSL_CTX_set_cipher_list(ssl_ctx, conn->ssl_config.cipher_list13 ? - conn->ssl_config.cipher_list13 : - QUIC_CIPHERS) != 1) { - char error_buffer[256]; - ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer)); - failf(data, "wolfSSL failed to set ciphers: %s", error_buffer); - goto out; - } - - if(wolfSSL_CTX_set1_groups_list(ssl_ctx, conn->ssl_config.curves ? - conn->ssl_config.curves : - (char *)QUIC_GROUPS) != 1) { - failf(data, "wolfSSL failed to set curves"); - goto out; - } - - /* Open the file if a TLS or QUIC backend has not done this before. */ - Curl_tls_keylog_open(); - if(Curl_tls_keylog_enabled()) { -#if defined(HAVE_SECRET_CALLBACK) - wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback); -#else - failf(data, "wolfSSL was built without keylog callback"); - goto out; -#endif - } - - if(conn->ssl_config.verifypeer) { - const char * const ssl_cafile = conn->ssl_config.CAfile; - const char * const ssl_capath = conn->ssl_config.CApath; - - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); - if(ssl_cafile || ssl_capath) { - /* tell wolfSSL where to find CA certificates that are used to verify - the server's certificate. */ - int rc = - wolfSSL_CTX_load_verify_locations_ex(ssl_ctx, ssl_cafile, ssl_capath, - WOLFSSL_LOAD_FLAG_IGNORE_ERR); - if(SSL_SUCCESS != rc) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - goto out; - } - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); - } -#ifdef CURL_CA_FALLBACK - else { - /* verifying the peer without any CA certificates won't work so - use wolfssl's built-in default as fallback */ - wolfSSL_CTX_set_default_verify_paths(ssl_ctx); - } -#endif - } - else { - wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); - } - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, ssl_ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - goto out; - } - } - result = CURLE_OK; - -out: - *pssl_ctx = result? NULL : ssl_ctx; - if(result && ssl_ctx) - SSL_CTX_free(ssl_ctx); - return result; -} - -/** SSL callbacks ***/ - -static CURLcode quic_init_ssl(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_ngtcp2_ctx *ctx = cf->ctx; - const uint8_t *alpn = NULL; - size_t alpnlen = 0; - /* this will need some attention when HTTPS proxy over QUIC get fixed */ - const char * const hostname = cf->conn->host.name; - - (void)data; - DEBUGASSERT(!ctx->ssl); - ctx->ssl = wolfSSL_new(ctx->sslctx); - - wolfSSL_set_app_data(ctx->ssl, &ctx->conn_ref); - wolfSSL_set_connect_state(ctx->ssl); - wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0); - - alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3; - alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1; - if(alpn) - wolfSSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen); - - /* set SNI */ - wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME, - hostname, (unsigned short)strlen(hostname)); - - return CURLE_OK; -} -#endif /* defined(USE_WOLFSSL) */ - static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) { (void)user_data; @@ -786,6 +435,12 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, CURL_TRC_CF(data, cf, "[%" PRId64 "] read_stream(len=%zu) -> %zd", stream_id, buflen, nconsumed); if(nconsumed < 0) { + if(!data) { + struct Curl_easy *cdata = CF_DATA_CURRENT(cf); + CURL_TRC_CF(cdata, cf, "[%" PRId64 "] nghttp3 error on stream not " + "used by us, ignored", stream_id); + return 0; + } ngtcp2_ccerr_set_application_error( &ctx->last_error, nghttp3_err_infer_quic_app_error_code((int)nconsumed), NULL, 0); @@ -816,7 +471,7 @@ cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id, (void)stream_user_data; rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -844,7 +499,7 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags, app_error_code); CURL_TRC_CF(data, cf, "[%" PRId64 "] quic close(err=%" PRIu64 ") -> %d", stream3_id, app_error_code, rv); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { ngtcp2_ccerr_set_application_error( &ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0); return NGTCP2_ERR_CALLBACK_FAILURE; @@ -868,7 +523,7 @@ static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id, rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); CURL_TRC_CF(data, cf, "[%" PRId64 "] reset -> %d", stream_id, rv); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -887,7 +542,7 @@ static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id, (void)stream_user_data; rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } @@ -911,16 +566,25 @@ static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id, { struct Curl_cfilter *cf = user_data; struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct Curl_easy *s_data; + struct h3_stream_ctx *stream; int rv; (void)tconn; (void)max_data; (void)stream_user_data; rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } - + s_data = get_stream_easy(cf, data, stream_id); + stream = H3_STREAM_CTX(s_data); + if(stream && stream->quic_flow_blocked) { + CURL_TRC_CF(data, cf, "[%" PRId64 "] unblock quic flow", stream_id); + stream->quic_flow_blocked = FALSE; + h3_drain_stream(cf, data); + } return 0; } @@ -1038,7 +702,7 @@ static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, pktx = &local_pktx; } else { - pktx->ts = timestamp(); + pktx_update_time(pktx, cf); } expiry = ngtcp2_conn_get_expiry(ctx->qconn); @@ -1073,46 +737,33 @@ static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, return CURLE_OK; } -static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf, +static void cf_ngtcp2_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, - curl_socket_t *socks) + struct easy_pollset *ps) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct SingleRequest *k = &data->req; - int rv = GETSOCK_BLANK; - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); - struct cf_call_data save; - - CF_DATA_SAVE(save, cf, data); - socks[0] = ctx->q.sockfd; - - /* in HTTP/3 we can always get a frame, so check read */ - rv |= GETSOCK_READSOCK(0); + bool want_recv, want_send; - /* we're still uploading or the HTTP/2 layer wants to send data */ - if((k->keepon & KEEP_SENDBITS) == KEEP_SEND && - ngtcp2_conn_get_cwnd_left(ctx->qconn) && - ngtcp2_conn_get_max_data_left(ctx->qconn) && - stream && nghttp3_conn_is_stream_writable(ctx->h3conn, stream->id)) - rv |= GETSOCK_WRITESOCK(0); - - CF_DATA_RESTORE(cf, save); - return rv; -} + if(!ctx->qconn) + return; -static void h3_drain_stream(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct h3_stream_ctx *stream = H3_STREAM_CTX(data); - unsigned char bits; + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { + struct h3_stream_ctx *stream = H3_STREAM_CTX(data); + struct cf_call_data save; + bool c_exhaust, s_exhaust; - (void)cf; - bits = CURL_CSELECT_IN; - if(stream && stream->upload_left && !stream->send_closed) - bits |= CURL_CSELECT_OUT; - if(data->state.dselect_bits != bits) { - data->state.dselect_bits = bits; - Curl_expire(data, 0, EXPIRE_RUN_NOW); + CF_DATA_SAVE(save, cf, data); + c_exhaust = want_send && (!ngtcp2_conn_get_cwnd_left(ctx->qconn) || + !ngtcp2_conn_get_max_data_left(ctx->qconn)); + s_exhaust = want_send && stream && stream->id >= 0 && + stream->quic_flow_blocked; + want_recv = (want_recv || c_exhaust || s_exhaust); + want_send = (!s_exhaust && want_send) || + !Curl_bufq_is_empty(&ctx->q.sendbuf); + + Curl_pollset_set(data, ps, ctx->q.sockfd, want_recv, want_send); + CF_DATA_RESTORE(cf, save); } } @@ -1141,7 +792,6 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id, else { CURL_TRC_CF(data, cf, "[%" PRId64 "] CLOSED", stream->id); } - data->req.keepon &= ~KEEP_SEND_HOLD; h3_drain_stream(cf, data); return 0; } @@ -1570,15 +1220,9 @@ static int cb_h3_acked_req_body(nghttp3_conn *conn, int64_t stream_id, /* Everything ACKed, we resume upload processing */ if(!stream->sendbuf_len_in_flight) { int rv = nghttp3_conn_resume_stream(conn, stream_id); - if(rv) { + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { return NGTCP2_ERR_CALLBACK_FAILURE; } - if((data->req.keepon & KEEP_SEND_HOLD) && - (data->req.keepon & KEEP_SEND)) { - data->req.keepon &= ~KEEP_SEND_HOLD; - h3_drain_stream(cf, data); - CURL_TRC_CF(data, cf, "[%" PRId64 "] unpausing acks", stream_id); - } } return 0; } @@ -1676,6 +1320,10 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, goto out; stream = H3_STREAM_CTX(data); DEBUGASSERT(stream); + if(!stream) { + *err = CURLE_FAILED_INIT; + goto out; + } nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); if(nwritten < 0) @@ -1711,7 +1359,7 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, nva[i].flags = NGHTTP3_NV_FLAG_NONE; } - rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream->id, NULL); + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream->id, data); if(rc) { failf(data, "can get bidi streams"); *err = CURLE_SEND_ERROR; @@ -1835,6 +1483,8 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, sent = (ssize_t)len; goto out; } + CURL_TRC_CF(data, cf, "[%" PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); *err = CURLE_HTTP3; sent = -1; goto out; @@ -1860,15 +1510,13 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, if(stream && sent > 0 && stream->sendbuf_len_in_flight) { /* We have unacknowledged DATA and cannot report success to our * caller. Instead we EAGAIN and remember how much we have already - * "written" into our various internal connection buffers. - * We put the stream upload on HOLD, until this gets ACKed. */ + * "written" into our various internal connection buffers. */ stream->upload_blocked_len = sent; CURL_TRC_CF(data, cf, "[%" PRId64 "] cf_send(len=%zu), " "%zu bytes in flight -> EGAIN", stream->id, len, stream->sendbuf_len_in_flight); *err = CURLE_AGAIN; sent = -1; - data->req.keepon |= KEEP_SEND_HOLD; } out: @@ -1887,52 +1535,12 @@ static CURLcode qng_verify_peer(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - CURLcode result = CURLE_OK; - const char *hostname, *disp_hostname; - int port; - char *snihost; - - Curl_conn_get_host(data, cf->sockindex, &hostname, &disp_hostname, &port); - snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) - return CURLE_PEER_FAILED_VERIFICATION; cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ cf->conn->httpversion = 30; cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; - if(cf->conn->ssl_config.verifyhost) { -#ifdef USE_OPENSSL - X509 *server_cert; - server_cert = SSL_get_peer_certificate(ctx->ssl); - if(!server_cert) { - return CURLE_PEER_FAILED_VERIFICATION; - } - result = Curl_ossl_verifyhost(data, cf->conn, server_cert); - X509_free(server_cert); - if(result) - return result; -#elif defined(USE_GNUTLS) - result = Curl_gtls_verifyserver(data, ctx->gtls->session, - &cf->conn->ssl_config, &data->set.ssl, - hostname, disp_hostname, - data->set.str[STRING_SSL_PINNEDPUBLICKEY]); - if(result) - return result; -#elif defined(USE_WOLFSSL) - if(wolfSSL_check_domain_name(ctx->ssl, snihost) == SSL_FAILURE) - return CURLE_PEER_FAILED_VERIFICATION; -#endif - infof(data, "Verified certificate just fine"); - } - else - infof(data, "Skipped certificate verification"); -#ifdef USE_OPENSSL - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, ctx->ssl); -#endif - return result; + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); } static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, @@ -1955,8 +1563,8 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen, rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, pkt, pktlen, pktx->ts); if(rv) { - CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s", - ngtcp2_strerror(rv)); + CURL_TRC_CF(pktx->data, pktx->cf, "ingress, read_pkt -> %s (%d)", + ngtcp2_strerror(rv), rv); if(!ctx->last_error.error_code) { if(rv == NGTCP2_ERR_CRYPTO) { ngtcp2_ccerr_set_tls_alert(&ctx->last_error, @@ -1993,17 +1601,12 @@ static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, pktx = &local_pktx; } else { - pktx->ts = timestamp(); + pktx_update_time(pktx, cf); } -#ifdef USE_OPENSSL - if(!ctx->x509_store_setup) { - result = Curl_ssl_setup_x509_store(cf, data, ctx->sslctx); - if(result) - return result; - ctx->x509_store_setup = TRUE; - } -#endif + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + return result; for(i = 0; i < pkts_max; i += pkts_chunk) { pktx->pkt_count = 0; @@ -2081,11 +1684,18 @@ static ssize_t read_pkt_to_send(void *userp, } else if(n < 0) { switch(n) { - case NGTCP2_ERR_STREAM_DATA_BLOCKED: + case NGTCP2_ERR_STREAM_DATA_BLOCKED: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(x->data); DEBUGASSERT(ndatalen == -1); nghttp3_conn_block_stream(ctx->h3conn, stream_id); + CURL_TRC_CF(x->data, x->cf, "[%" PRId64 "] block quic flow", + stream_id); + DEBUGASSERT(stream); + if(stream) + stream->quic_flow_blocked = TRUE; n = 0; break; + } case NGTCP2_ERR_STREAM_SHUT_WR: DEBUGASSERT(ndatalen == -1); nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id); @@ -2145,7 +1755,7 @@ static CURLcode cf_progress_egress(struct Curl_cfilter *cf, pktx = &local_pktx; } else { - pktx->ts = timestamp(); + pktx_update_time(pktx, cf); ngtcp2_path_storage_zero(&pktx->ps); } @@ -2282,10 +1892,12 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf, case CF_CTRL_DATA_PAUSE: result = h3_data_pause(cf, data, (arg1 != 0)); break; - case CF_CTRL_DATA_DONE: { + case CF_CTRL_DATA_DETACH: + h3_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE: h3_data_done(cf, data); break; - } case CF_CTRL_DATA_DONE_SEND: { struct h3_stream_ctx *stream = H3_STREAM_CTX(data); if(stream && !stream->send_closed) { @@ -2319,31 +1931,14 @@ static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx) if(ctx->qlogfd != -1) { close(ctx->qlogfd); } -#ifdef USE_OPENSSL - if(ctx->ssl) - SSL_free(ctx->ssl); - if(ctx->sslctx) - SSL_CTX_free(ctx->sslctx); -#elif defined(USE_GNUTLS) - if(ctx->gtls) { - if(ctx->gtls->cred) - gnutls_certificate_free_credentials(ctx->gtls->cred); - if(ctx->gtls->session) - gnutls_deinit(ctx->gtls->session); - free(ctx->gtls); - } -#elif defined(USE_WOLFSSL) - if(ctx->ssl) - wolfSSL_free(ctx->ssl); - if(ctx->sslctx) - wolfSSL_CTX_free(ctx->sslctx); -#endif + Curl_vquic_tls_cleanup(&ctx->tls); vquic_ctx_free(&ctx->q); if(ctx->h3conn) nghttp3_conn_del(ctx->h3conn); if(ctx->qconn) ngtcp2_conn_del(ctx->qconn); Curl_bufcp_free(&ctx->stream_bufcp); + Curl_ssl_peer_cleanup(&ctx->peer); memset(ctx, 0, sizeof(*ctx)); ctx->qlogfd = -1; @@ -2358,15 +1953,15 @@ static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data) CF_DATA_SAVE(save, cf, data); if(ctx && ctx->qconn) { char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; - ngtcp2_tstamp ts; + struct pkt_io_ctx pktx; ngtcp2_ssize rc; CURL_TRC_CF(data, cf, "close"); - ts = timestamp(); + pktx_init(&pktx, cf, data); rc = ngtcp2_conn_write_connection_close(ctx->qconn, NULL, /* path */ NULL, /* pkt_info */ (uint8_t *)buffer, sizeof(buffer), - &ctx->last_error, ts); + &ctx->last_error, pktx.ts); if(rc > 0) { while((send(ctx->q.sockfd, buffer, (SEND_TYPE_ARG3)rc, 0) == -1) && SOCKERRNO == EINTR); @@ -2395,6 +1990,37 @@ static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) (void)save; } +static CURLcode tls_ctx_setup(struct quic_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)cf; +#ifdef USE_OPENSSL +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#else + if(ngtcp2_crypto_quictls_configure_client_context(ctx->ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */ +#elif defined(USE_GNUTLS) + if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) { + failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed"); + return CURLE_FAILED_INIT; + } +#elif defined(USE_WOLFSSL) + if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + /* * Might be called twice for happy eyeballs. */ @@ -2411,24 +2037,18 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, ctx->version = NGTCP2_PROTO_VER_MAX; ctx->max_stream_window = H3_STREAM_WINDOW_SIZE; + ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS; Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, H3_STREAM_POOL_SPARES); -#ifdef USE_OPENSSL - result = quic_ssl_ctx(&ctx->sslctx, cf, data); + result = Curl_ssl_peer_init(&ctx->peer, cf); if(result) return result; - result = quic_set_client_cert(cf, data); - if(result) - return result; -#elif defined(USE_WOLFSSL) - result = quic_ssl_ctx(&ctx->sslctx, cf, data); - if(result) - return result; -#endif - - result = quic_init_ssl(cf, data); +#define H3_ALPN "\x2h3\x5h3-29" + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + H3_ALPN, sizeof(H3_ALPN) - 1, + tls_ctx_setup, &ctx->conn_ref); if(result) return result; @@ -2475,9 +2095,9 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, return CURLE_QUIC_CONNECT_ERROR; #ifdef USE_GNUTLS - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session); + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls->session); #else - ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl); + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ssl); #endif ngtcp2_ccerr_default(&ctx->last_error); @@ -2559,27 +2179,9 @@ out: ngtcp2_conn_in_draining_period(ctx->qconn)) { /* When a QUIC server instance is shutting down, it may send us a * CONNECTION_CLOSE right away. Our connection then enters the DRAINING - * state. - * This may be a stopping of the service or it may be that the server - * is reloading and a new instance will start serving soon. - * In any case, we tear down our socket and start over with a new one. - * We re-open the underlying UDP cf right now, but do not start - * connecting until called again. - */ - int reconn_delay_ms = 200; - - CURL_TRC_CF(data, cf, "connect, remote closed, reconnect after %dms", - reconn_delay_ms); - Curl_conn_cf_close(cf->next, data); - cf_ngtcp2_ctx_clear(ctx); - result = Curl_conn_cf_connect(cf->next, data, FALSE, done); - if(!result && *done) { - *done = FALSE; - ctx->reconnect_at = now; - ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000; - Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC); - result = CURLE_OK; - } + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; } #ifndef CURL_DISABLE_VERBOSE_STRINGS @@ -2626,8 +2228,8 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, return CURLE_OK; } case CF_QUERY_CONNECT_REPLY_MS: - if(ctx->got_first_byte) { - timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at); + if(ctx->q.got_first_byte) { + timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at); *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX; } else @@ -2635,8 +2237,8 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf, return CURLE_OK; case CF_QUERY_TIMER_CONNECT: { struct curltime *when = pres2; - if(ctx->got_first_byte) - *when = ctx->first_byte_at; + if(ctx->q.got_first_byte) + *when = ctx->q.first_byte_at; return CURLE_OK; } case CF_QUERY_TIMER_APPCONNECT: { @@ -2657,24 +2259,51 @@ static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data, bool *input_pending) { - bool alive = TRUE; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + bool alive = FALSE; + const ngtcp2_transport_params *rp; + struct cf_call_data save; + CF_DATA_SAVE(save, cf, data); *input_pending = FALSE; + if(!ctx->qconn) + goto out; + + /* Both sides of the QUIC connection announce they max idle times in + * the transport parameters. Look at the minimum of both and if + * we exceed this, regard the connection as dead. The other side + * may have completely purged it and will no longer respond + * to any packets from us. */ + rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn); + if(rp) { + timediff_t idletime; + uint64_t idle_ms = ctx->max_idle_ms; + + if(rp->max_idle_timeout && + (rp->max_idle_timeout / NGTCP2_MILLISECONDS) < idle_ms) + idle_ms = (rp->max_idle_timeout / NGTCP2_MILLISECONDS); + idletime = Curl_timediff(Curl_now(), ctx->q.last_io); + if(idletime > 0 && (uint64_t)idletime > idle_ms) + goto out; + } + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) - return FALSE; + goto out; + alive = TRUE; if(*input_pending) { + CURLcode result; /* This happens before we've sent off a request and the connection is not in use by any other transfer, there shouldn't be any data here, only "protocol frames" */ *input_pending = FALSE; - if(cf_progress_ingress(cf, data, NULL)) - alive = FALSE; - else { - alive = TRUE; - } + result = cf_progress_ingress(cf, data, NULL); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + alive = result? FALSE : TRUE; } +out: + CF_DATA_RESTORE(cf, save); return alive; } @@ -2686,7 +2315,7 @@ struct Curl_cftype Curl_cft_http3 = { cf_ngtcp2_connect, cf_ngtcp2_close, Curl_cf_def_get_host, - cf_ngtcp2_get_select_socks, + cf_ngtcp2_adjust_pollset, cf_ngtcp2_data_pending, cf_ngtcp2_send, cf_ngtcp2_recv, @@ -2706,7 +2335,7 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf, CURLcode result; (void)data; - ctx = calloc(sizeof(*ctx), 1); + ctx = calloc(1, sizeof(*ctx)); if(!ctx) { result = CURLE_OUT_OF_MEMORY; goto out; |