MINOR: mux-quic: release BE idle conn after GOAWAY reception

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.
This commit is contained in:
Amaury Denoyelle 2026-04-17 12:02:41 +02:00
parent 220b1bf6d9
commit 1acf147e2a

View 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;