diff --git a/include/haproxy/mux_quic-t.h b/include/haproxy/mux_quic-t.h index 25bab15d9..7ec467386 100644 --- a/include/haproxy/mux_quic-t.h +++ b/include/haproxy/mux_quic-t.h @@ -220,6 +220,7 @@ enum qcc_app_ops_lclose_mode { QCC_APP_OPS_LCLO_MODE_NORMAL, QCC_APP_OPS_LCLO_MODE_ABORT, QCC_APP_OPS_LCLO_MODE_KILL_CONN, + QCC_APP_OPS_LCLO_MODE_READ, }; /* QUIC application layer operations */ diff --git a/src/h3.c b/src/h3.c index 32d8f33da..d11005eb0 100644 --- a/src/h3.c +++ b/src/h3.c @@ -3282,6 +3282,21 @@ static void h3_lclose(struct qcs *qcs, enum qcc_app_ops_lclose_mode mode) { TRACE_ENTER(H3_EV_H3S_END, qcs->qcc->conn, qcs); + /* RFC 9114 4.1.1. Request Cancellation and Rejection + * + * Servers MUST NOT use the H3_REQUEST_REJECTED error code for requests + * that were partially or fully processed. When a server abandons a + * response after partial processing, it SHOULD abort its response + * stream with the error code H3_REQUEST_CANCELLED. + * Client SHOULD use the error code H3_REQUEST_CANCELLED to cancel requests. + */ + + /* Following the above recommendations, H3_REQUEST_REJECTED is never + * used in practice in haproxy as a server on shut as upper stream + * layer is instantiated immediately with HEADERS content attached. The + * only exception is for stream closure after GOAWAY emission. + */ + switch (mode) { case QCC_APP_OPS_LCLO_MODE_NORMAL: /* Close stream with FIN. This can only be performed if at @@ -3312,6 +3327,11 @@ static void h3_lclose(struct qcs *qcs, enum qcc_app_ops_lclose_mode mode) muxc_tevt_type_graceful_shut); } break; + + case QCC_APP_OPS_LCLO_MODE_READ: + TRACE_STATE("request stream read channel closure", H3_EV_H3S_END, qcs->qcc->conn, qcs); + qcc_abort_stream_read(qcs, H3_ERR_REQUEST_CANCELLED); + break; } TRACE_LEAVE(H3_EV_H3S_END, qcs->qcc->conn, qcs); diff --git a/src/hq_interop.c b/src/hq_interop.c index 50c6791d8..4d2b4a80f 100644 --- a/src/hq_interop.c +++ b/src/hq_interop.c @@ -424,6 +424,10 @@ static void hq_interop_lclose(struct qcs *qcs, enum qcc_app_ops_lclose_mode mode if (!(qcs->qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL))) qcc_set_error(qcs->qcc, 0, 0, muxc_tevt_type_graceful_shut); break; + + case QCC_APP_OPS_LCLO_MODE_READ: + qcc_abort_stream_read(qcs, 0); + break; } } diff --git a/src/mux_quic.c b/src/mux_quic.c index 6960c5994..dcf21a726 100644 --- a/src/mux_quic.c +++ b/src/mux_quic.c @@ -4711,8 +4711,12 @@ static void qcm_strm_shut(struct stconn *sc, unsigned int mode, struct se_abort_ TRACE_ENTER(QMUX_EV_STRM_SHUT, qcc->conn, qcs); + /* Not implemented. */ + BUG_ON(mode & SE_SHR_DRAIN); + /* Early closure reported if QC_SF_FIN_STREAM not yet set. */ - if (!qcs_is_close_local(qcs) && + if ((mode & (SE_SHW_SILENT|SE_SHW_NORMAL)) && + !qcs_is_close_local(qcs) && !(qcs->flags & (QC_SF_FIN_STREAM|QC_SF_TO_RESET))) { if (qcs->flags & QC_SF_UNKNOWN_PL_LENGTH) @@ -4726,6 +4730,11 @@ static void qcm_strm_shut(struct stconn *sc, unsigned int mode, struct se_abort_ tasklet_wakeup(qcc->wait_event.tasklet); } + if ((mode & SE_SHR_RESET) && + !qcs_is_close_remote(qcs) && !(qcs->flags & (QC_SF_READ_ABORTED))) { + qcc->app_ops->lclose(qcs, QCC_APP_OPS_LCLO_MODE_READ); + } + out: TRACE_LEAVE(QMUX_EV_STRM_SHUT, qcc->conn, qcs); }