]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: quic: Add support for "handshake" timeout setting.
authorFrédéric Lécaille <flecaille@haproxy.com>
Tue, 14 Nov 2023 17:34:03 +0000 (18:34 +0100)
committerFrédéric Lécaille <flecaille@haproxy.com>
Fri, 17 Nov 2023 16:31:42 +0000 (17:31 +0100)
The idle timer task may be used to trigger the client handshake timeout.
The hanshake timeout expiration date (qc->hs_expire) is initialized when the
connection is allocated. Obviously, this timeout is taken into an account only
during the handshake by qc_idle_timer_do_rearm() whose job is to rearm the idle timer.

The idle timer expiration date could be initialized only one time, then
never updated until the hanshake completes. But this only works if the
handshake timeout is smaller than the idle timer task timeout. If the handshake
timeout is set greater than the idle timeout, this latter may expire before the
handshake timeout.

This patch may have an impact on the L1/C1 interop tests (with heavy packet loss
or corruption). This is why I guess some implementations with a hanshake timeout
support set a big timeout during this test. This is at least the case for ngtcp2
which sets a 180s hanshake timeout! haproxy will certainly have to proceed the
same way if it wants to have a chance to pass this test as before this handshake
timeout.

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

index 2ead20de01c1413abf1ed757055449a770078209..86d80a7b54d2d1467bb21033017ed10d0a6ba87d 100644 (file)
@@ -535,6 +535,8 @@ struct quic_conn {
        struct task *timer_task;
        unsigned int timer;
        unsigned int ack_expire;
+       /* Handshake expiration date */
+       unsigned int hs_expire;
 
        const struct qcc_app_ops *app_ops;
        /* Proxy counters */
index 4b8beb794b9628167bdfb7139df63e5caaa45259..8792310c7b82dac0fc1d3592500849d39d48e0cc 100644 (file)
@@ -143,7 +143,7 @@ DECLARE_STATIC_POOL(pool_head_quic_cstream, "quic_cstream", sizeof(struct quic_c
 
 struct task *quic_conn_app_io_cb(struct task *t, void *context, unsigned int state);
 static int quic_conn_init_timer(struct quic_conn *qc);
-static int quic_conn_init_idle_timer_task(struct quic_conn *qc);
+static int quic_conn_init_idle_timer_task(struct quic_conn *qc, struct proxy *px);
 
 /* Returns 1 if the peer has validated <qc> QUIC connection address, 0 if not. */
 int quic_peer_validated_addr(struct quic_conn *qc)
@@ -1177,6 +1177,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
        int i;
        struct quic_conn *qc = NULL;
        struct listener *l = server ? owner : NULL;
+       struct proxy *prx = l ? l->bind_conf->frontend : NULL;
        struct quic_cc_algo *cc_algo = NULL;
        unsigned int next_actconn = 0, next_sslconn = 0, next_handshake = 0;
 
@@ -1264,9 +1265,6 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
 
        /* QUIC Server (or listener). */
        if (server) {
-               struct proxy *prx;
-
-               prx = l->bind_conf->frontend;
                cc_algo = l->bind_conf->quic_cc_algo;
 
                qc->prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe,
@@ -1399,7 +1397,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
 
        if (qc_alloc_ssl_sock_ctx(qc) ||
            !quic_conn_init_timer(qc) ||
-           !quic_conn_init_idle_timer_task(qc))
+           !quic_conn_init_idle_timer_task(qc, prx))
                goto err;
 
        if (!qc_new_isecs(qc, &qc->iel->tls_ctx, qc->original_version, dcid->data, dcid->len, 1))
@@ -1655,6 +1653,10 @@ void qc_idle_timer_do_rearm(struct quic_conn *qc, int arm_ack)
        }
 
        qc->idle_expire = tick_add(now_ms, MS_TO_TICKS(expire));
+       /* Note that the ACK timer is not armed during the handshake. So,
+        * the handshake expiration date is taken into an account only
+        * when <arm_ack> is false.
+        */
        if (arm_ack) {
                /* Arm the ack timer only if not already armed. */
                if (!tick_isset(qc->ack_expire)) {
@@ -1666,6 +1668,8 @@ void qc_idle_timer_do_rearm(struct quic_conn *qc, int arm_ack)
        }
        else {
                qc->idle_timer_task->expire = tick_first(qc->ack_expire, qc->idle_expire);
+               if (qc->state < QUIC_HS_ST_COMPLETE)
+                       qc->idle_timer_task->expire = tick_first(qc->hs_expire, qc->idle_expire);
                task_queue(qc->idle_timer_task);
                TRACE_PROTO("idle timer armed", QUIC_EV_CONN_IDLE_TIMER, qc);
        }
@@ -1688,6 +1692,7 @@ void qc_idle_timer_rearm(struct quic_conn *qc, int read, int arm_ack)
        }
        qc_idle_timer_do_rearm(qc, arm_ack);
 
+ leave:
        TRACE_LEAVE(QUIC_EV_CONN_IDLE_TIMER, qc);
 }
 
@@ -1750,12 +1755,16 @@ struct task *qc_idle_timer_task(struct task *t, void *ctx, unsigned int state)
 /* 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)
+static int quic_conn_init_idle_timer_task(struct quic_conn *qc,
+                                          struct proxy *px)
 {
        int ret = 0;
+       int timeout;
 
        TRACE_ENTER(QUIC_EV_CONN_NEW, qc);
 
+
+       timeout = px->timeout.handshake ? px->timeout.handshake : px->timeout.client;
        qc->idle_timer_task = task_new_here();
        if (!qc->idle_timer_task) {
                TRACE_ERROR("Idle timer task allocation failed", QUIC_EV_CONN_NEW, qc);
@@ -1765,6 +1774,7 @@ static int quic_conn_init_idle_timer_task(struct quic_conn *qc)
        qc->idle_timer_task->process = qc_idle_timer_task;
        qc->idle_timer_task->context = qc;
        qc->ack_expire = TICK_ETERNITY;
+       qc->hs_expire = tick_add_ifset(now_ms, MS_TO_TICKS(timeout));
        qc_idle_timer_rearm(qc, 1, 0);
        task_queue(qc->idle_timer_task);