]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: quic: Implement the idle timeout feature
authorFrédéric Lécaille <flecaille@haproxy.com>
Thu, 10 Mar 2022 14:11:57 +0000 (15:11 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 11 Mar 2022 10:37:30 +0000 (11:37 +0100)
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.

include/haproxy/xprt_quic-t.h
src/xprt_quic.c

index 5b37d7779c1ac515dd5ca0c058c5a242e1571eca..0e2d9d8da17de1d5bf769dc6568d9e49f8401570 100644 (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;
index 15b7ddd2f5803dd56c53cb4847d4bfec21d3fd51..5bf1b2a05ee2d27d1e2bdaf6fc5b3b4f90766958 100644 (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.
                         */