]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: mux-quic: release BE idle conn after GOAWAY reception
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 17 Apr 2026 10:02:41 +0000 (12:02 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 17 Apr 2026 11:28:17 +0000 (13:28 +0200)
An idle backend connection is useless if a HTTP/3 GOAWAY frame has been
received. Indeed, it is forbid to open new stream on such connection.

Thus, this patch ensures such connections are removed as soon as
possible. This is performed via a new check in qcc_is_dead() on
QC_CF_CONN_SHUT flag for backend connections. This ensures that a shut
connection is released instead of being inserted in idle list on detach
operation.

This commits also completes qcc_recv() with a new call to qcc_is_dead()
on its ending. This is necessary if GOAWAY is received on an idle
connection. For now, this is only checked for backend connections as a
GOAWAY is without any real effect for frontend connections. Thus, this
extra protection ensures that we do not break by incident QUIC frontend
support.

qcc_io_recv() also performs qcc_decode_qcs(). However, an extra
qcc_is_dead() is not necessary in this case as the following
qcc_io_process() already performs it.

src/mux_quic.c

index 6066a7b16036401c9ffcf0417bd656f2ede10618..68d974ad123f0300ee4463ecbb0c3f669b2babd7 100644 (file)
@@ -44,6 +44,7 @@ DECLARE_STATIC_TYPED_POOL(pool_head_qc_stream_rxbuf, "qc_stream_rxbuf", struct q
 static void qmux_ctrl_send(struct qc_stream_desc *, uint64_t data, uint64_t offset);
 static void qmux_ctrl_room(struct qc_stream_desc *, uint64_t room);
 
+static void qcc_release(struct qcc *qcc);
 static int qcc_app_init(struct qcc *qcc);
 static void qcc_app_shutdown(struct qcc *qcc);
 
@@ -288,10 +289,12 @@ static inline int qcc_is_dead(const struct qcc *qcc)
         * - error detected locally
         * - MUX timeout expired
         * - app layer shut and all transfers done (FE side only - used for stream.max-total)
+        * - new stream initiating definitely blocked (BE side only - used for H3 GOAWAY reception)
         */
        if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL_DONE) ||
            !qcc->task ||
-           (!conn_is_back(qcc->conn) && !qcc->nb_hreq && qcc->app_st == QCC_APP_ST_SHUT)) {
+           (!conn_is_back(qcc->conn) && !qcc->nb_hreq && qcc->app_st == QCC_APP_ST_SHUT) ||
+           (conn_is_back(qcc->conn) && !qcc->nb_hreq && (qcc->flags & QC_CF_CONN_SHUT))) {
                return 1;
        }
 
@@ -2033,6 +2036,13 @@ int qcc_recv(struct qcc *qcc, uint64_t id, uint64_t len, uint64_t offset,
                BUG_ON_HOT(fin_standalone); /* On fin_standalone <ret> should be NULL, which ensures no infinite loop. */
        }
 
+       /* Ensure that an idle backend conn is freed if it cannot open new stream. */
+       if (conn_is_back(qcc->conn) && qcc_is_dead(qcc)) {
+               TRACE_STATE("releasing dead connection after STREAM decoding", QMUX_EV_QCC_RECV, qcc->conn);
+               qcc_release(qcc);
+               return 0;
+       }
+
  out:
        TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
        return 0;