]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: mux-quic: support max bidi streams value set by the peer
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 18 Jun 2025 07:25:39 +0000 (09:25 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 18 Jun 2025 15:25:27 +0000 (17:25 +0200)
Implement support for MAX_STREAMS frame. On frontend, this was mostly
useless as haproxy would never initiate new bidirectional streams.
However, this becomes necessary to control stream flow-control when
using QUIC as a client on the backend side.

Parsing of MAX_STREAMS is implemented via new qcc_recv_max_streams().
This allows to update <ms_uni>/<ms_bidi> QCC fields.

This patch is necessary to achieve QUIC backend connection reuse.

include/haproxy/mux_quic-t.h
include/haproxy/mux_quic.h
src/mux_quic.c
src/quic_rx.c

index 559111d4acabe80352a98c6e85f0085a7f3cc968..c24db79623edd52c8e2ab052fcb801908805d63b 100644 (file)
@@ -67,6 +67,7 @@ struct qcc {
        /* flow-control fields set by the peer which we must respect. */
        struct {
                uint64_t ms_uni; /* max sub-ID of uni stream allowed by the peer */
+               uint64_t ms_bidi; /* max sub-ID of bidi stream allowed by the peer */
 
                uint64_t md; /* connection flow control limit updated on MAX_DATA frames reception */
                uint64_t msd_bidi_l; /* initial max-stream-data from peer on local bidi streams */
index 01391a6ad34d245f1189ea3101a7aefe60d5819b..d0b7fac81a2b3e6361f0241750da025860ac530a 100644 (file)
@@ -44,6 +44,7 @@ int qcc_recv(struct qcc *qcc, uint64_t id, uint64_t len, uint64_t offset,
              char fin, char *data);
 int qcc_recv_max_data(struct qcc *qcc, uint64_t max);
 int qcc_recv_max_stream_data(struct qcc *qcc, uint64_t id, uint64_t max);
+int qcc_recv_max_streams(struct qcc *qcc, uint64_t max, int bidi);
 int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t final_size);
 int qcc_recv_stop_sending(struct qcc *qcc, uint64_t id, uint64_t err);
 
index 936e73e348e31db47eef655ba7feac51e8702cdf..a1de84000ca2276608c0ed80b707d57ccbe73b3e 100644 (file)
@@ -789,8 +789,9 @@ int _qcc_report_glitch(struct qcc *qcc, int inc)
 int qcc_fctl_avail_streams(const struct qcc *qcc, int bidi)
 {
        if (bidi) {
-               /* TODO */
-               return 0;
+               const uint64_t next = qcc->next_bidi_l / 4;
+               BUG_ON(qcc->rfctl.ms_bidi < next);
+               return qcc->rfctl.ms_bidi - next;
        }
        else {
                const uint64_t next = qcc->next_uni_l / 4;
@@ -2032,6 +2033,57 @@ int qcc_recv_max_stream_data(struct qcc *qcc, uint64_t id, uint64_t max)
        return 1;
 }
 
+/* Handle a MAX_STREAMS frame. <max> must contains the cumulative number of
+ * streams that can be opened. <bidi> is a boolean set if this refers to
+ * bidirectional streams.
+ *
+ * Returns 0 on success else non-zero. On error, the received frame should not
+ * be acknowledged.
+ */
+int qcc_recv_max_streams(struct qcc *qcc, uint64_t max, int bidi)
+{
+       TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
+
+       if (qcc->flags & QC_CF_ERRL) {
+               TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
+               goto err;
+       }
+
+       /* RFC 9000 19.11. MAX_STREAMS Frames
+        *
+        *  This value cannot exceed 2^60, as it is not possible to
+        *  encode stream IDs larger than 2^62-1. Receipt of a frame that
+        *  permits opening of a stream larger than this limit MUST be treated
+        *  as a connection error of type FRAME_ENCODING_ERROR.
+        */
+       if (max > QUIC_VARINT_8_BYTE_MAX) {
+               TRACE_ERROR("invalid MAX_STREAMS value", QMUX_EV_QCC_RECV, qcc->conn);
+               qcc_set_error(qcc, QC_ERR_FRAME_ENCODING_ERROR, 0);
+               goto err;
+       }
+
+       TRACE_PROTO("receiving MAX_STREAMS", QMUX_EV_QCC_RECV, qcc->conn);
+       if (bidi) {
+               if (max > qcc->rfctl.ms_bidi) {
+                       TRACE_DATA("increase remote max-streams-bidi", QMUX_EV_QCC_RECV, qcc->conn);
+                       qcc->rfctl.ms_bidi = max;
+               }
+       }
+       else {
+               /* TODO no extra unidirectional streams open after connection
+                * startup, so uni MAX_STREAMS flow-control is not necessary
+                * for now.
+                */
+       }
+
+       TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
+       return 0;
+
+ err:
+       TRACE_DEVEL("leaving on error", QMUX_EV_QCC_RECV, qcc->conn);
+       return 1;
+}
+
 /* Handle a new RESET_STREAM frame from stream ID <id> with error code <err>
  * and final stream size <final_size>.
  *
@@ -3452,6 +3504,7 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
        rparams = &conn->handle.qc->tx.params;
        qfctl_init(&qcc->tx.fc, rparams->initial_max_data);
        qcc->rfctl.ms_uni = rparams->initial_max_streams_uni;
+       qcc->rfctl.ms_bidi = rparams->initial_max_streams_bidi;
        qcc->rfctl.msd_bidi_l = rparams->initial_max_stream_data_bidi_local;
        qcc->rfctl.msd_bidi_r = rparams->initial_max_stream_data_bidi_remote;
        qcc->rfctl.msd_uni_l = rparams->initial_max_stream_data_uni;
index e659bef9ec7d9b2c285c42c7e0f61c86d9115e08..f6903291ec5a3456f38383adaea15eea7be6142e 100644 (file)
@@ -1011,6 +1011,24 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
                        break;
                case QUIC_FT_MAX_STREAMS_BIDI:
                case QUIC_FT_MAX_STREAMS_UNI:
+                       if (qc->mux_state == QC_MUX_READY) {
+                               int bidi;
+                               struct qf_max_streams *ms_frm;
+
+                               if (frm->type == QUIC_FT_MAX_STREAMS_BIDI) {
+                                       bidi = 1;
+                                       ms_frm = &frm->max_streams_bidi;
+                               }
+                               else {
+                                       bidi = 0;
+                                       ms_frm = &frm->max_streams_uni;
+                               }
+
+                               if (qcc_recv_max_streams(qc->qcc, ms_frm->max_streams, bidi)) {
+                                       TRACE_ERROR("qcc_recv_max_streams() failed", QUIC_EV_CONN_PRSHPKT, qc);
+                                       goto err;
+                               }
+                       }
                        break;
                case QUIC_FT_DATA_BLOCKED:
                        qc->cntrs.data_blocked++;