From 5854fc08cc3985697580ad1ffac165ca483f4008 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Fri, 9 Dec 2022 16:25:48 +0100 Subject: [PATCH] MINOR: mux-quic: handle RESET_STREAM reception Implement RESET_STREAM reception by mux-quic. On reception, qcs instance will be mark as remotely closed and its Rx buffer released. The stream layer will be flagged on error if still attached. This commit is part of implementing H3 errors at the stream level. Indeed, on H3 stream errors, STOP_SENDING + RESET_STREAM should be emitted. The STOP_SENDING will in turn generate a RESET_STREAM by the remote peer which will be handled thanks to this patch. This should be backported up to 2.7. --- include/haproxy/mux_quic.h | 1 + src/mux_quic.c | 59 ++++++++++++++++++++++++++++++++++++++ src/quic_conn.c | 7 +++-- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/include/haproxy/mux_quic.h b/include/haproxy/mux_quic.h index 908a02fba..0a88f6b4b 100644 --- a/include/haproxy/mux_quic.h +++ b/include/haproxy/mux_quic.h @@ -25,6 +25,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_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); void qcc_streams_sent_done(struct qcs *qcs, uint64_t data, uint64_t offset); diff --git a/src/mux_quic.c b/src/mux_quic.c index e4721fd39..c780ebcad 100644 --- a/src/mux_quic.c +++ b/src/mux_quic.c @@ -888,6 +888,11 @@ int qcc_recv(struct qcc *qcc, uint64_t id, uint64_t len, uint64_t offset, goto err; } + if (qcs_is_close_remote(qcs)) { + TRACE_DATA("skipping STREAM for remotely closed", QMUX_EV_QCC_RECV, qcc->conn); + goto out; + } + if (offset + len <= qcs->rx.offset) { /* TODO offset may have been received without FIN first and now * with it. In this case, it must be notified to be able to @@ -1055,6 +1060,60 @@ int qcc_recv_max_stream_data(struct qcc *qcc, uint64_t id, uint64_t max) return 0; } +/* Handle a new RESET_STREAM frame from stream ID with error code + * and final stream size . + * + * Returns 0 on success else non-zero. On error, the received frame should not + * be acknowledged. + */ +int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t final_size) +{ + struct qcs *qcs; + + TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn); + + /* RFC 9000 19.4. RESET_STREAM Frames + * + * An endpoint that receives a RESET_STREAM frame for a send-only stream + * MUST terminate the connection with error STREAM_STATE_ERROR. + */ + if (qcc_get_qcs(qcc, id, 1, 0, &qcs)) { + TRACE_ERROR("RESET_STREAM for send-only stream received", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs); + qcc_emit_cc(qcc, QC_ERR_STREAM_STATE_ERROR); + goto err; + } + + if (!qcs || qcs_is_close_remote(qcs)) + goto out; + + TRACE_PROTO("receiving RESET_STREAM", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs); + qcs_idle_open(qcs); + + if (qcs->rx.offset_max > final_size || + ((qcs->flags & QC_SF_SIZE_KNOWN) && qcs->rx.offset_max != final_size)) { + TRACE_ERROR("final size error on RESET_STREAM", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs); + qcc_emit_cc(qcc, QC_ERR_FINAL_SIZE_ERROR); + goto err; + } + + qcs->flags |= QC_SF_SIZE_KNOWN; + qcs_close_remote(qcs); + qc_free_ncbuf(qcs, &qcs->rx.ncbuf); + + if (qcs_sc(qcs)) { + se_fl_set_error(qcs->sd); + qcs_alert(qcs); + } + + out: + TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn); + return 0; + + err: + TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn); + return 1; +} + /* Handle a new STOP_SENDING frame for stream ID . The error code should be * specified in . * diff --git a/src/quic_conn.c b/src/quic_conn.c index ddb4bbbc6..e3a8b4634 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -2802,8 +2802,11 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt, break; } case QUIC_FT_RESET_STREAM: - /* TODO: handle this frame at STREAM level */ - break; + if (qc->mux_state == QC_MUX_READY) { + struct quic_reset_stream *rs = &frm.reset_stream; + qcc_recv_reset_stream(qc->qcc, rs->id, rs->app_error_code, rs->final_size); + } + break; case QUIC_FT_STOP_SENDING: { struct quic_stop_sending *stop_sending = &frm.stop_sending;