mirror of
https://github.com/curl/curl.git
synced 2025-04-18 16:30:45 +08:00
quic: manage connection idle timeouts
- configure a 120s idle timeout on our side of the connection - track the timestamp when actual socket IO happens - check IO timestamp to our *and* the peer's idle timeouts in "is this connection alive" checks Reported-by: calvin2021y on github Fixes #12064 Closes #12077
This commit is contained in:
parent
f1e05a6e6e
commit
9cc5787577
@ -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
|
||||
@ -161,6 +160,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
|
||||
@ -261,10 +261,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 +277,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 +358,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;
|
||||
}
|
||||
@ -1038,7 +1042,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);
|
||||
@ -1993,7 +1997,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
|
||||
@ -2145,7 +2149,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);
|
||||
}
|
||||
|
||||
@ -2358,15 +2362,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,6 +2415,7 @@ 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);
|
||||
|
||||
@ -2657,9 +2662,32 @@ static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
struct cf_ngtcp2_ctx *ctx = cf->ctx;
|
||||
bool alive = TRUE;
|
||||
const ngtcp2_transport_params *rp;
|
||||
|
||||
*input_pending = FALSE;
|
||||
if(!ctx->qconn)
|
||||
return FALSE;
|
||||
|
||||
/* 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)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
|
||||
return FALSE;
|
||||
|
||||
|
@ -58,7 +58,6 @@
|
||||
/* #define DEBUG_QUICHE */
|
||||
|
||||
#define QUIC_MAX_STREAMS (100)
|
||||
#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
|
||||
|
||||
#define H3_STREAM_WINDOW_SIZE (128 * 1024)
|
||||
#define H3_STREAM_CHUNK_SIZE (16 * 1024)
|
||||
@ -105,6 +104,7 @@ struct cf_quiche_ctx {
|
||||
struct curltime reconnect_at; /* time the next attempt should start */
|
||||
struct bufc_pool stream_bufcp; /* chunk pool for streams */
|
||||
curl_off_t data_recvd;
|
||||
uint64_t max_idle_ms; /* max idle time for QUIC conn */
|
||||
size_t sends_on_hold; /* # of streams with SEND_HOLD set */
|
||||
BIT(goaway); /* got GOAWAY from server */
|
||||
BIT(got_first_byte); /* if first byte was received */
|
||||
@ -883,6 +883,8 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
ssize_t nread = -1;
|
||||
CURLcode result;
|
||||
|
||||
vquic_ctx_update_time(&ctx->q);
|
||||
|
||||
if(!stream) {
|
||||
*err = CURLE_RECV_ERROR;
|
||||
return -1;
|
||||
@ -1081,6 +1083,8 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
CURLcode result;
|
||||
ssize_t nwritten;
|
||||
|
||||
vquic_ctx_update_time(&ctx->q);
|
||||
|
||||
*err = cf_process_ingress(cf, data);
|
||||
if(*err) {
|
||||
nwritten = -1;
|
||||
@ -1345,6 +1349,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
|
||||
debug_log_init = 1;
|
||||
}
|
||||
#endif
|
||||
ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS;
|
||||
Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
|
||||
H3_STREAM_POOL_SPARES);
|
||||
ctx->data_recvd = 0;
|
||||
@ -1359,7 +1364,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
|
||||
return CURLE_FAILED_INIT;
|
||||
}
|
||||
quiche_config_enable_pacing(ctx->cfg, false);
|
||||
quiche_config_set_max_idle_timeout(ctx->cfg, QUIC_IDLE_TIMEOUT);
|
||||
quiche_config_set_max_idle_timeout(ctx->cfg, ctx->max_idle_ms * 1000);
|
||||
quiche_config_set_initial_max_data(ctx->cfg, (1 * 1024 * 1024)
|
||||
/* (QUIC_MAX_STREAMS/2) * H3_STREAM_WINDOW_SIZE */);
|
||||
quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS);
|
||||
@ -1449,7 +1454,6 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
|
||||
{
|
||||
struct cf_quiche_ctx *ctx = cf->ctx;
|
||||
CURLcode result = CURLE_OK;
|
||||
struct curltime now;
|
||||
|
||||
if(cf->connected) {
|
||||
*done = TRUE;
|
||||
@ -1464,9 +1468,10 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
|
||||
}
|
||||
|
||||
*done = FALSE;
|
||||
now = Curl_now();
|
||||
vquic_ctx_update_time(&ctx->q);
|
||||
|
||||
if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) {
|
||||
if(ctx->reconnect_at.tv_sec &&
|
||||
Curl_timediff(ctx->q.last_op, ctx->reconnect_at) < 0) {
|
||||
/* Not time yet to attempt the next connect */
|
||||
CURL_TRC_CF(data, cf, "waiting for reconnect time");
|
||||
goto out;
|
||||
@ -1476,7 +1481,7 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
|
||||
result = cf_connect_start(cf, data);
|
||||
if(result)
|
||||
goto out;
|
||||
ctx->started_at = now;
|
||||
ctx->started_at = ctx->q.last_op;
|
||||
result = cf_flush_egress(cf, data);
|
||||
/* we do not expect to be able to recv anything yet */
|
||||
goto out;
|
||||
@ -1491,9 +1496,9 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
|
||||
goto out;
|
||||
|
||||
if(quiche_conn_is_established(ctx->qconn)) {
|
||||
ctx->handshake_at = ctx->q.last_op;
|
||||
CURL_TRC_CF(data, cf, "handshake complete after %dms",
|
||||
(int)Curl_timediff(now, ctx->started_at));
|
||||
ctx->handshake_at = now;
|
||||
(int)Curl_timediff(ctx->handshake_at, ctx->started_at));
|
||||
result = cf_verify_peer(cf, data);
|
||||
if(!result) {
|
||||
CURL_TRC_CF(data, cf, "peer verified");
|
||||
@ -1550,6 +1555,7 @@ static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
|
||||
if(ctx) {
|
||||
if(ctx->qconn) {
|
||||
vquic_ctx_update_time(&ctx->q);
|
||||
(void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0);
|
||||
/* flushing the egress is not a failsafe way to deliver all the
|
||||
outstanding packets, but we also don't want to get stuck here... */
|
||||
@ -1617,9 +1623,31 @@ static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
bool *input_pending)
|
||||
{
|
||||
struct cf_quiche_ctx *ctx = cf->ctx;
|
||||
bool alive = TRUE;
|
||||
|
||||
*input_pending = FALSE;
|
||||
if(!ctx->qconn)
|
||||
return FALSE;
|
||||
|
||||
/* 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. */
|
||||
{
|
||||
quiche_stats qstats;
|
||||
timediff_t idletime;
|
||||
uint64_t idle_ms = ctx->max_idle_ms;
|
||||
|
||||
quiche_conn_stats(ctx->qconn, &qstats);
|
||||
if(qstats.peer_max_idle_timeout && qstats.peer_max_idle_timeout < idle_ms)
|
||||
idle_ms = qstats.peer_max_idle_timeout;
|
||||
idletime = Curl_timediff(Curl_now(), cf->conn->lastused);
|
||||
if(idletime > 0 && (uint64_t)idletime > idle_ms)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
|
||||
return FALSE;
|
||||
|
||||
|
@ -100,6 +100,7 @@ CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
vquic_ctx_update_time(qctx);
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
@ -109,6 +110,11 @@ void vquic_ctx_free(struct cf_quic_ctx *qctx)
|
||||
Curl_bufq_free(&qctx->sendbuf);
|
||||
}
|
||||
|
||||
void vquic_ctx_update_time(struct cf_quic_ctx *qctx)
|
||||
{
|
||||
qctx->last_op = Curl_now();
|
||||
}
|
||||
|
||||
static CURLcode send_packet_no_gso(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data,
|
||||
struct cf_quic_ctx *qctx,
|
||||
@ -242,6 +248,7 @@ static CURLcode vquic_send_packets(struct Curl_cfilter *cf,
|
||||
const uint8_t *pkt, size_t pktlen,
|
||||
size_t gsolen, size_t *psent)
|
||||
{
|
||||
CURLcode result;
|
||||
#ifdef DEBUGBUILD
|
||||
/* simulate network blocking/partial writes */
|
||||
if(qctx->wblock_percent > 0) {
|
||||
@ -254,10 +261,14 @@ static CURLcode vquic_send_packets(struct Curl_cfilter *cf,
|
||||
}
|
||||
#endif
|
||||
if(qctx->no_gso && pktlen > gsolen) {
|
||||
return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
|
||||
result = send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
|
||||
}
|
||||
|
||||
return do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent);
|
||||
else {
|
||||
result = do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, psent);
|
||||
}
|
||||
if(!result)
|
||||
qctx->last_io = qctx->last_op;
|
||||
return result;
|
||||
}
|
||||
|
||||
CURLcode vquic_flush(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||
@ -524,13 +535,17 @@ CURLcode vquic_recv_packets(struct Curl_cfilter *cf,
|
||||
size_t max_pkts,
|
||||
vquic_recv_pkt_cb *recv_cb, void *userp)
|
||||
{
|
||||
CURLcode result;
|
||||
#if defined(HAVE_SENDMMSG)
|
||||
return recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
|
||||
result = recvmmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
|
||||
#elif defined(HAVE_SENDMSG)
|
||||
return recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
|
||||
result = recvmsg_packets(cf, data, qctx, max_pkts, recv_cb, userp);
|
||||
#else
|
||||
return recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp);
|
||||
result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp);
|
||||
#endif
|
||||
if(!result)
|
||||
qctx->last_io = qctx->last_op;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -31,6 +31,8 @@
|
||||
|
||||
#define MAX_PKT_BURST 10
|
||||
#define MAX_UDP_PAYLOAD_SIZE 1452
|
||||
/* Default QUIC connection timeout we announce from our side */
|
||||
#define CURL_QUIC_MAX_IDLE_MS (120 * 1000)
|
||||
|
||||
struct cf_quic_ctx {
|
||||
curl_socket_t sockfd; /* connected UDP socket */
|
||||
@ -38,6 +40,8 @@ struct cf_quic_ctx {
|
||||
socklen_t local_addrlen; /* length of local address */
|
||||
|
||||
struct bufq sendbuf; /* buffer for sending one or more packets */
|
||||
struct curltime last_op; /* last (attempted) send/recv operation */
|
||||
struct curltime last_io; /* last successful socket IO */
|
||||
size_t gsolen; /* length of individual packets in send buf */
|
||||
size_t split_len; /* if != 0, buffer length after which GSO differs */
|
||||
size_t split_gsolen; /* length of individual packets after split_len */
|
||||
@ -50,6 +54,8 @@ struct cf_quic_ctx {
|
||||
CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx);
|
||||
void vquic_ctx_free(struct cf_quic_ctx *qctx);
|
||||
|
||||
void vquic_ctx_update_time(struct cf_quic_ctx *qctx);
|
||||
|
||||
void vquic_push_blocked_pkt(struct Curl_cfilter *cf,
|
||||
struct cf_quic_ctx *qctx,
|
||||
const uint8_t *pkt, size_t pktlen, size_t gsolen);
|
||||
|
Loading…
x
Reference in New Issue
Block a user