diff --git a/include/haproxy/mux_quic-t.h b/include/haproxy/mux_quic-t.h index 28a8103a5..aa7b17706 100644 --- a/include/haproxy/mux_quic-t.h +++ b/include/haproxy/mux_quic-t.h @@ -232,6 +232,9 @@ struct qcc_app_ops { void (*inc_err_cnt)(void *ctx, int err_code); /* Set QCC error code as suspicious activity has been detected. */ void (*report_susp)(void *ctx); + + /* Free function to close a stream after MUX layer shutdown. */ + int (*strm_reject)(struct list *out, uint64_t id); }; #endif /* USE_QUIC */ diff --git a/include/haproxy/quic_conn-t.h b/include/haproxy/quic_conn-t.h index ca23260cd..3baad3fc1 100644 --- a/include/haproxy/quic_conn-t.h +++ b/include/haproxy/quic_conn-t.h @@ -409,6 +409,9 @@ struct quic_conn { unsigned int hs_expire; const struct qcc_app_ops *app_ops; + /* Callback to close any stream after MUX closure - set by the MUX itself */ + int (*strm_reject)(struct list *out, uint64_t stream_id); + /* Proxy counters */ struct quic_counters *prx_counters; diff --git a/src/h3.c b/src/h3.c index 2dafb5589..ebf1830e6 100644 --- a/src/h3.c +++ b/src/h3.c @@ -3345,6 +3345,57 @@ static void h3_trace(enum trace_level level, uint64_t mask, } } +/* Cancel a request on stream id . This is useful when the client opens a + * new stream but the MUX has already been released. A STOP_SENDING + + * RESET_STREAM frames are prepared for emission. + * + * Returns 1 on success else 0. + */ +int h3_reject(struct list *out, uint64_t id) +{ + int ret = 0; + struct quic_frame *ss, *rs; + const uint64_t app_error_code = H3_ERR_REQUEST_REJECTED; + + TRACE_ENTER(H3_EV_TX_FRAME); + + /* Do not emit rejection for unknown unidirectional stream as it is + * forbidden to close some of them (H3 control stream and QPACK + * encoder/decoder streams). + */ + if (quic_stream_is_uni(id)) { + ret = 1; + goto out; + } + + ss = qc_frm_alloc(QUIC_FT_STOP_SENDING); + if (!ss) { + TRACE_ERROR("failed to allocate quic_frame", H3_EV_TX_FRAME); + goto out; + } + + ss->stop_sending.id = id; + ss->stop_sending.app_error_code = app_error_code; + + rs = qc_frm_alloc(QUIC_FT_RESET_STREAM); + if (!rs) { + TRACE_ERROR("failed to allocate quic_frame", H3_EV_TX_FRAME); + qc_frm_free(NULL, &ss); + goto out; + } + + rs->reset_stream.id = id; + rs->reset_stream.app_error_code = app_error_code; + rs->reset_stream.final_size = 0; + + LIST_APPEND(out, &ss->list); + LIST_APPEND(out, &rs->list); + ret = 1; + out: + TRACE_LEAVE(H3_EV_TX_FRAME); + return ret; +} + /* HTTP/3 application layer operations */ const struct qcc_app_ops h3_ops = { .init = h3_init, @@ -3360,4 +3411,5 @@ const struct qcc_app_ops h3_ops = { .inc_err_cnt = h3_stats_inc_err_cnt, .report_susp = h3_report_susp, .release = h3_release, + .strm_reject = h3_reject, }; diff --git a/src/mux_quic.c b/src/mux_quic.c index 0f70d9b9b..512d0eda9 100644 --- a/src/mux_quic.c +++ b/src/mux_quic.c @@ -3377,8 +3377,12 @@ static void qcc_release(struct qcc *qcc) qcc_clear_frms(qcc); - if (qcc->app_ops && qcc->app_ops->release) - qcc->app_ops->release(qcc->ctx); + if (qcc->app_ops) { + if (qcc->app_ops->release) + qcc->app_ops->release(qcc->ctx); + if (conn->handle.qc) + conn->handle.qc->strm_reject = qcc->app_ops->strm_reject; + } TRACE_PROTO("application layer released", QMUX_EV_QCC_END, conn); pool_free(pool_head_qcc, qcc); diff --git a/src/quic_conn.c b/src/quic_conn.c index 61552e905..6143fce39 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -377,61 +377,6 @@ void quic_conn_closed_err_count_inc(struct quic_conn *qc, struct quic_frame *frm TRACE_LEAVE(QUIC_EV_CONN_CLOSE, qc); } -/* Cancel a request on connection for stream id . This is useful when - * the client opens a new stream but the MUX has already been released. A - * STOP_SENDING + RESET_STREAM frames are prepared for emission. - * - * TODO this function is closely related to H3. Its place should be in H3 layer - * instead of quic-conn but this requires an architecture adjustment. - * - * Returns 1 on success else 0. - */ -int qc_h3_request_reject(struct quic_conn *qc, uint64_t id) -{ - int ret = 0; - struct quic_frame *ss, *rs; - struct quic_enc_level *qel = qc->ael; - const uint64_t app_error_code = H3_ERR_REQUEST_REJECTED; - - TRACE_ENTER(QUIC_EV_CONN_PRSHPKT, qc); - - /* Do not emit rejection for unknown unidirectional stream as it is - * forbidden to close some of them (H3 control stream and QPACK - * encoder/decoder streams). - */ - if (quic_stream_is_uni(id)) { - ret = 1; - goto out; - } - - ss = qc_frm_alloc(QUIC_FT_STOP_SENDING); - if (!ss) { - TRACE_ERROR("failed to allocate quic_frame", QUIC_EV_CONN_PRSHPKT, qc); - goto out; - } - - ss->stop_sending.id = id; - ss->stop_sending.app_error_code = app_error_code; - - rs = qc_frm_alloc(QUIC_FT_RESET_STREAM); - if (!rs) { - TRACE_ERROR("failed to allocate quic_frame", QUIC_EV_CONN_PRSHPKT, qc); - qc_frm_free(qc, &ss); - goto out; - } - - rs->reset_stream.id = id; - rs->reset_stream.app_error_code = app_error_code; - rs->reset_stream.final_size = 0; - - LIST_APPEND(&qel->pktns->tx.frms, &ss->list); - LIST_APPEND(&qel->pktns->tx.frms, &rs->list); - ret = 1; - out: - TRACE_LEAVE(QUIC_EV_CONN_PRSHPKT, qc); - return ret; -} - /* Remove a quic-conn from its ha_thread_ctx list. If is true, * it will immediately be reinserted in the ha_thread_ctx quic_conns_clo list. */ @@ -1204,6 +1149,7 @@ struct quic_conn *qc_new_conn(void *target, qc->conn = conn; qc->qcc = NULL; qc->app_ops = NULL; + qc->strm_reject = NULL; qc->path = NULL; /* Keyupdate: required to safely call quic_tls_ku_free() from diff --git a/src/quic_rx.c b/src/quic_rx.c index d967a4e65..54a699162 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -14,7 +14,6 @@ #include -#include #include #include #include @@ -962,8 +961,8 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt, } else { TRACE_DEVEL("No mux for new stream", QUIC_EV_CONN_PRSHPKT, qc); - if (qc->app_ops == &h3_ops) { - if (!qc_h3_request_reject(qc, strm_frm->id)) { + if (qc->strm_reject) { + if (!qc->strm_reject(&qc->ael->pktns->tx.frms, strm_frm->id)) { TRACE_ERROR("error on request rejection", QUIC_EV_CONN_PRSHPKT, qc); /* This packet will not be acknowledged */ goto err;