diff --git a/include/haproxy/mux_quic-t.h b/include/haproxy/mux_quic-t.h index 559111d4a..c24db7962 100644 --- a/include/haproxy/mux_quic-t.h +++ b/include/haproxy/mux_quic-t.h @@ -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 */ diff --git a/include/haproxy/mux_quic.h b/include/haproxy/mux_quic.h index 01391a6ad..d0b7fac81 100644 --- a/include/haproxy/mux_quic.h +++ b/include/haproxy/mux_quic.h @@ -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); diff --git a/src/mux_quic.c b/src/mux_quic.c index 936e73e34..a1de84000 100644 --- a/src/mux_quic.c +++ b/src/mux_quic.c @@ -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. must contains the cumulative number of + * streams that can be opened. 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 with error code * and final stream 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; diff --git a/src/quic_rx.c b/src/quic_rx.c index e659bef9e..f6903291e 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -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++;