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 */
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;
}
}
+/* Cancel a request on stream id <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,
.inc_err_cnt = h3_stats_inc_err_cnt,
.report_susp = h3_report_susp,
.release = h3_release,
+ .strm_reject = h3_reject,
};
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);
TRACE_LEAVE(QUIC_EV_CONN_CLOSE, qc);
}
-/* Cancel a request on connection <qc> for stream id <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 <qc> quic-conn from its ha_thread_ctx list. If <closing> is true,
* it will immediately be reinserted in the ha_thread_ctx quic_conns_clo list.
*/
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
#include <haproxy/quic_rx.h>
-#include <haproxy/h3.h>
#include <haproxy/list.h>
#include <haproxy/ncbmbuf.h>
#include <haproxy/proto_quic.h>
}
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;