MEDIUM: quic: Implement the idle timeout feature

The aim of the idle timeout is to silently closed the connection after a period
of inactivity depending on the "max_idle_timeout" transport parameters advertised
by the endpoints. We add a new task to implement this timer. Its expiry is
updated each time we received an ack-eliciting packet, and each time we send
an ack-eliciting packet if no other such packet was sent since we received
the last ack-eliciting packet. Such conditions may be implemented thanks
to QUIC_FL_CONN_IDLE_TIMER_RESTARTED_AFTER_READ new flag.
This commit is contained in:
Frédéric Lécaille 2022-03-10 15:11:57 +01:00 committed by Amaury Denoyelle
parent c7a69e2aa5
commit 530601cd84
2 changed files with 63 additions and 9 deletions

View File

@ -659,6 +659,7 @@ enum qc_mux_state {
#define QUIC_FL_CONN_LISTENER (1U << 3)
#define QUIC_FL_ACCEPT_REGISTERED_BIT 4
#define QUIC_FL_ACCEPT_REGISTERED (1U << QUIC_FL_ACCEPT_REGISTERED_BIT)
#define QUIC_FL_CONN_IDLE_TIMER_RESTARTED_AFTER_READ (1U << 6)
#define QUIC_FL_CONN_IMMEDIATE_CLOSE (1U << 31)
struct quic_conn {
uint32_t version;
@ -745,6 +746,8 @@ struct quic_conn {
struct qcc *qcc;
struct task *timer_task;
unsigned int timer;
/* Idle timer task */
struct task *idle_timer_task;
unsigned int flags;
const struct qcc_app_ops *app_ops;

View File

@ -174,6 +174,7 @@ static struct quic_tx_packet *qc_build_pkt(unsigned char **pos, const unsigned c
struct quic_conn *qc, size_t dglen, int pkt_type,
int padding, int ack, int probe, int cc, int *err);
static struct task *quic_conn_app_io_cb(struct task *t, void *context, unsigned int state);
static void qc_idle_timer_rearm(struct quic_conn *qc, int read);
/* Only for debug purpose */
struct enc_debug_info {
@ -2827,6 +2828,8 @@ int qc_send_ppkts(struct qring *qr, struct ssl_sock_ctx *ctx)
if (pkt->flags & QUIC_FL_TX_PACKET_ACK_ELICITING) {
pkt->pktns->tx.time_of_last_eliciting = time_sent;
qc->path->ifae_pkts++;
if (qc->flags & QUIC_FL_CONN_IDLE_TIMER_RESTARTED_AFTER_READ)
qc_idle_timer_rearm(qc, 0);
}
qc->path->in_flight += pkt->in_flight_len;
pkt->pktns->tx.in_flight += pkt->in_flight_len;
@ -3203,9 +3206,11 @@ int qc_treat_rx_pkts(struct quic_enc_level *cur_el, struct quic_enc_level *next_
else {
struct quic_arng ar = { .first = pkt->pn, .last = pkt->pn };
if (pkt->flags & QUIC_FL_RX_PACKET_ACK_ELICITING &&
(!(HA_ATOMIC_ADD_FETCH(&qc->rx.nb_ack_eliciting, 1) & 1) || force_ack))
HA_ATOMIC_BTS(&qel->pktns->flags, QUIC_FL_PKTNS_ACK_REQUIRED_BIT);
if (pkt->flags & QUIC_FL_RX_PACKET_ACK_ELICITING) {
if (!(HA_ATOMIC_ADD_FETCH(&qc->rx.nb_ack_eliciting, 1) & 1) || force_ack)
HA_ATOMIC_BTS(&qel->pktns->flags, QUIC_FL_PKTNS_ACK_REQUIRED_BIT);
qc_idle_timer_rearm(qc, 1);
}
if (pkt->pn > largest_pn)
largest_pn = pkt->pn;
/* Update the list of ranges to acknowledge. */
@ -3525,6 +3530,11 @@ static void quic_conn_release(struct quic_conn *qc)
int i;
struct ssl_sock_ctx *conn_ctx;
if (qc->idle_timer_task) {
task_destroy(qc->idle_timer_task);
qc->idle_timer_task = NULL;
}
if (qc->timer_task) {
task_destroy(qc->timer_task);
qc->timer_task = NULL;
@ -3561,12 +3571,6 @@ void quic_close(struct connection *conn, void *xprt_ctx)
qc->mux_state = QC_MUX_RELEASED;
TRACE_LEAVE(QUIC_EV_CONN_CLOSE, qc);
/* TODO for now release the quic_conn on notification by the upper
* layer. It could be useful to delay it if there is remaining data to
* send or data to be acked.
*/
quic_conn_release(qc);
}
/* Callback called upon loss detection and PTO timer expirations. */
@ -3775,6 +3779,50 @@ static int quic_conn_init_timer(struct quic_conn *qc)
return 1;
}
/* Rearm the idle timer for <qc> QUIC connection depending on <read> boolean
* which is set to 1 when receiving a packet , and 0 when sending packet
*/
static void qc_idle_timer_rearm(struct quic_conn *qc, int read)
{
unsigned int expire;
expire = QUIC_MAX(3 * quic_pto(qc), qc->max_idle_timeout);
if (read) {
qc->flags |= QUIC_FL_CONN_IDLE_TIMER_RESTARTED_AFTER_READ;
}
else {
qc->flags &= ~QUIC_FL_CONN_IDLE_TIMER_RESTARTED_AFTER_READ;
}
qc->idle_timer_task->expire = tick_add(now_ms, MS_TO_TICKS(expire));
}
/* The task handling the idle timeout */
static struct task *qc_idle_timer_task(struct task *t, void *ctx, unsigned int state)
{
struct quic_conn *qc = ctx;
quic_conn_release(qc);
return NULL;
}
/* Initialize the idle timeout task for <qc>.
* Returns 1 if succeeded, 0 if not.
*/
static int quic_conn_init_idle_timer_task(struct quic_conn *qc)
{
qc->idle_timer_task = task_new_here();
if (!qc->idle_timer_task)
return 0;
qc->idle_timer_task->process = qc_idle_timer_task;
qc->idle_timer_task->context = qc;
qc_idle_timer_rearm(qc, 1);
task_queue(qc->idle_timer_task);
return 1;
}
/* Parse into <pkt> a long header located at <*buf> buffer, <end> begin a pointer to the end
* past one byte of this buffer.
*/
@ -4438,6 +4486,9 @@ static ssize_t qc_lstnr_pkt_rcv(unsigned char *buf, const unsigned char *end,
if (!quic_conn_init_timer(qc))
goto err;
if (!quic_conn_init_idle_timer_task(qc))
goto err;
/* NOTE: the socket address has been concatenated to the destination ID
* chosen by the client for Initial packets.
*/