summaryrefslogtreecommitdiffstats
path: root/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c
diff options
context:
space:
mode:
Diffstat (limited to 'Utilities/cmcurl/lib/vquic/curl_ngtcp2.c')
-rw-r--r--Utilities/cmcurl/lib/vquic/curl_ngtcp2.c360
1 files changed, 219 insertions, 141 deletions
diff --git a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c
index 7d681e5..f09b10b 100644
--- a/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c
+++ b/Utilities/cmcurl/lib/vquic/curl_ngtcp2.c
@@ -78,7 +78,6 @@
#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
@@ -134,6 +133,7 @@ void Curl_ngtcp2_ver(char *p, size_t len)
struct cf_ngtcp2_ctx {
struct cf_quic_ctx q;
+ struct ssl_peer peer;
ngtcp2_path connected_path;
ngtcp2_conn *qconn;
ngtcp2_cid dcid;
@@ -161,6 +161,7 @@ struct cf_ngtcp2_ctx {
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
@@ -191,6 +192,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 +238,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 +261,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.dselect_bits != bits) {
+ data->state.dselect_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 +310,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 +326,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,7 +407,7 @@ 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;
}
@@ -398,14 +451,19 @@ 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;
+ struct ssl_primary_config *conn_config;
CURLcode result = CURLE_FAILED_INIT;
- SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
+ SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
if(!ssl_ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
+ conn_config = Curl_ssl_cf_get_primary_config(cf);
+ if(!conn_config) {
+ result = CURLE_FAILED_INIT;
+ goto out;
+ }
#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) {
@@ -422,8 +480,8 @@ static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx,
SSL_CTX_set_default_verify_paths(ssl_ctx);
{
- const char *curves = conn->ssl_config.curves ?
- conn->ssl_config.curves : QUIC_GROUPS;
+ const char *curves = conn_config->curves ?
+ conn_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;
@@ -432,8 +490,8 @@ static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx,
#ifndef OPENSSL_IS_BORINGSSL
{
- const char *ciphers13 = conn->ssl_config.cipher_list13 ?
- conn->ssl_config.cipher_list13 : QUIC_CIPHERS;
+ const char *ciphers13 = conn_config->cipher_list13 ?
+ conn_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;
@@ -452,7 +510,7 @@ static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx,
* 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_CTX_set_verify(ssl_ctx, conn_config->verifypeer ?
SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
/* give application a chance to interfere with SSL set up. */
@@ -491,7 +549,7 @@ static CURLcode quic_set_client_cert(struct Curl_cfilter *cf,
SSL_CTX *ssl_ctx = ctx->sslctx;
const struct ssl_config_data *ssl_config;
- ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET);
+ ssl_config = Curl_ssl_cf_get_config(cf, data);
DEBUGASSERT(ssl_config);
if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob
@@ -514,7 +572,6 @@ static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
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);
@@ -529,13 +586,8 @@ static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
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)) {
+ if(ctx->peer.sni) {
+ if(!SSL_set_tlsext_host_name(ctx->ssl, ctx->peer.sni)) {
failf(data, "Failed set SNI");
SSL_free(ctx->ssl);
ctx->ssl = NULL;
@@ -549,20 +601,24 @@ static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ struct ssl_primary_config *conn_config;
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;
+ conn_config = Curl_ssl_cf_get_primary_config(cf);
+ if(!conn_config)
+ return CURLE_FAILED_INIT;
+
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);
+ result = gtls_client_init(data, conn_config, &data->set.ssl,
+ &ctx->peer, ctx->gtls, pverifyresult);
if(result)
return result;
@@ -602,10 +658,17 @@ static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
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());
+ struct ssl_primary_config *conn_config;
+ WOLFSSL_CTX *ssl_ctx = NULL;
+ conn_config = Curl_ssl_cf_get_primary_config(cf);
+ if(!conn_config) {
+ result = CURLE_FAILED_INIT;
+ goto out;
+ }
+
+ ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
if(!ssl_ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
@@ -613,13 +676,14 @@ static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx,
if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
+ result = CURLE_FAILED_INIT;
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 :
+ if(wolfSSL_CTX_set_cipher_list(ssl_ctx, conn_config->cipher_list13 ?
+ conn_config->cipher_list13 :
QUIC_CIPHERS) != 1) {
char error_buffer[256];
ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
@@ -627,8 +691,8 @@ static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx,
goto out;
}
- if(wolfSSL_CTX_set1_groups_list(ssl_ctx, conn->ssl_config.curves ?
- conn->ssl_config.curves :
+ if(wolfSSL_CTX_set1_groups_list(ssl_ctx, conn_config->curves ?
+ conn_config->curves :
(char *)QUIC_GROUPS) != 1) {
failf(data, "wolfSSL failed to set curves");
goto out;
@@ -645,9 +709,9 @@ static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx,
#endif
}
- if(conn->ssl_config.verifypeer) {
- const char * const ssl_cafile = conn->ssl_config.CAfile;
- const char * const ssl_capath = conn->ssl_config.CApath;
+ if(conn_config->verifypeer) {
+ const char * const ssl_cafile = conn_config->CAfile;
+ const char * const ssl_capath = conn_config->CApath;
wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
if(ssl_cafile || ssl_capath) {
@@ -786,6 +850,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 +886,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 +914,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 +938,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 +957,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 +981,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 +1117,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 +1152,29 @@ 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;
+ bool want_recv = CURL_WANT_RECV(data);
+ bool want_send = CURL_WANT_SEND(data);
- /* in HTTP/3 we can always get a frame, so check read */
- rv |= GETSOCK_READSOCK(0);
-
- /* 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;
-}
-
-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;
+ if(ctx->qconn && (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 = !ngtcp2_conn_get_cwnd_left(ctx->qconn) ||
+ !ngtcp2_conn_get_max_data_left(ctx->qconn);
+ s_exhaust = 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 +1203,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 +1631,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 +1731,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 +1770,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;
@@ -1860,15 +1919,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,40 +1944,37 @@ static CURLcode qng_verify_peer(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ struct ssl_primary_config *conn_config;
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;
+ conn_config = Curl_ssl_cf_get_primary_config(cf);
+ if(!conn_config)
+ return CURLE_FAILED_INIT;
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) {
+ if(conn_config->verifyhost) {
#ifdef USE_OPENSSL
X509 *server_cert;
- server_cert = SSL_get_peer_certificate(ctx->ssl);
+ server_cert = SSL_get1_peer_certificate(ctx->ssl);
if(!server_cert) {
return CURLE_PEER_FAILED_VERIFICATION;
}
- result = Curl_ossl_verifyhost(data, cf->conn, server_cert);
+ result = Curl_ossl_verifyhost(data, cf->conn, &ctx->peer, 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,
+ conn_config, &data->set.ssl, &ctx->peer,
data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
if(result)
return result;
#elif defined(USE_WOLFSSL)
- if(wolfSSL_check_domain_name(ctx->ssl, snihost) == SSL_FAILURE)
+ if(!ctx->peer.sni ||
+ wolfSSL_check_domain_name(ctx->ssl, ctx->peer.sni) == SSL_FAILURE)
return CURLE_PEER_FAILED_VERIFICATION;
#endif
infof(data, "Verified certificate just fine");
@@ -1955,8 +2009,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,7 +2047,7 @@ static CURLcode cf_progress_ingress(struct Curl_cfilter *cf,
pktx = &local_pktx;
}
else {
- pktx->ts = timestamp();
+ pktx_update_time(pktx, cf);
}
#ifdef USE_OPENSSL
@@ -2081,11 +2135,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 +2206,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 +2343,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) {
@@ -2344,6 +2407,7 @@ static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx)
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 +2422,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);
@@ -2411,9 +2475,14 @@ 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);
+ result = Curl_ssl_peer_init(&ctx->peer, cf);
+ if(result)
+ return result;
+
#ifdef USE_OPENSSL
result = quic_ssl_ctx(&ctx->sslctx, cf, data);
if(result)
@@ -2559,27 +2628,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
@@ -2657,24 +2708,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 +2764,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 +2784,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;