]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MINOR: quic: ignore STREAM after MUX closure on BE side
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 17 Jun 2026 16:05:52 +0000 (18:05 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 6 Jul 2026 08:19:12 +0000 (10:19 +0200)
For frontend connections, quic_conn layer is able to reject any new
streams opened after MUX closure. This is necessary as the peer may not
have been notified yet of the closure.

This operation is unnecessary on backend side. This is due to the fact
that only HTTP protocols are currently supported on top of QUIC, with
requests initiated by the client. For requests started before the MUX
closure, either they are already completed or closed early with a
STOP_SENDING emitted during stream shut.

Prior to this patch, spurrious RESET_STREAM could have been emitted on
backend connections after MUX closure as quic_conn stream_max_bidi was
not correctly set. Now reject is only performed for frontend connections
so this should not occured anymore.

This should be backported up to 3.3.

src/mux_quic.c
src/quic_rx.c

index dcf21a726bed8ecdcdde9a620dc31d048d5abf75..a8a552cb6ae0f14819eb704288be5b102bc3c713 100644 (file)
@@ -3643,8 +3643,10 @@ static void qcc_release(struct qcc *qcc)
                }
 
                /* register streams IDs so that quic-conn layer can ignore already closed streams. */
-               qc->rx.stream_max_uni = qcc->largest_uni_r;
-               qc->rx.stream_max_bidi = qcc->largest_bidi_r;
+               if (!conn_is_back(conn)) {
+                       qc->rx.stream_max_uni = qcc->largest_uni_r;
+                       qc->rx.stream_max_bidi = qcc->largest_bidi_r;
+               }
        }
 
        tasklet_free(qcc->wait_event.tasklet);
@@ -3667,7 +3669,7 @@ static void qcc_release(struct qcc *qcc)
        if (qcc->app_ops) {
                if (qcc->app_ops->release)
                        qcc->app_ops->release(qcc->ctx);
-               if (conn && conn_is_quic(conn) && conn->handle.qc)
+               if (conn && !conn_is_back(conn) && conn_is_quic(conn) && conn->handle.qc)
                        conn->handle.qc->strm_reject = qcc->app_ops->strm_reject;
        }
        TRACE_PROTO("application layer released", QMUX_EV_QCC_END, conn);
index 06b3f3e99a0bac12a5a095de5e2885be2f8b9195..932058f0c086779648b02d4949f88381f3ac34f1 100644 (file)
@@ -975,11 +975,21 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
                {
                        struct qf_stream *strm_frm = &frm->stream;
                        const char fin = frm->type & QUIC_STREAM_FRAME_TYPE_FIN_BIT;
-                       const uint64_t max = quic_stream_is_uni(strm_frm->id) ?
-                         qc->rx.stream_max_uni : qc->rx.stream_max_bidi;
+                       uint64_t max;
 
                        /* The upper layer may not be allocated. */
                        if (!qc_is_conn_ready(qc)) {
+                               if (qc_is_back(qc)) {
+                                       /* Drain STREAM frames on the backend side. MUX should
+                                        * have emitted STOP_SENDING on shut for incomplete streams,
+                                        * so the peer should soon stop emission.
+                                        */
+                                       TRACE_DATA("Ignore stream on backend", QUIC_EV_CONN_PRSHPKT, qc);
+                                       break;
+                               }
+
+                               max = quic_stream_is_uni(strm_frm->id) ?
+                                 qc->rx.stream_max_uni : qc->rx.stream_max_bidi;
                                if (strm_frm->id < max) {
                                        TRACE_DATA("Already closed stream", QUIC_EV_CONN_PRSHPKT, qc);
                                }