MINOR: mux-quic: support max bidi streams value set by the peer

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.
This commit is contained in:
Amaury Denoyelle 2025-06-18 09:25:39 +02:00
parent 805a070ab9
commit 06cab99a0e
4 changed files with 75 additions and 2 deletions

View 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 */

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

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

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