]> git.kaiwu.me - haproxy.git/commitdiff
MEDIUM: mux-quic: extend shut to app proto layer
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Tue, 21 Apr 2026 12:58:17 +0000 (14:58 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 30 Apr 2026 15:18:20 +0000 (17:18 +0200)
Previously, shut callback was entirely implemented in QUIC mux layer.
However, this operation depends on the above application protocol, as it
may define its own closure procedure and error codes. This is the case
notably with HTTP/3 specification.

This patch defines a stream shut API between QUIC mux and application
protocol layers via the new qcc_app_ops callback lclose(). The closure
reason is specified via an enum argument. Application protcol can then
perform the stream closure as intended.

This patch is only an architecture adjustment but should not have any
functional impact. Stream closure logic was moved identically from QUIC
mux into h3 and h09 lclose callback.

include/haproxy/mux_quic-t.h
src/h3.c
src/hq_interop.c
src/mux_quic.c

index 02db4e8a5aa6ab1d44d50a489a2dddbe4268396e..6a0bfe6cb2fb02e2fdc9c98c12e2d3314ac0400b 100644 (file)
@@ -214,6 +214,12 @@ enum qcc_app_ops_close_side {
        QCC_APP_OPS_CLOSE_SIDE_WR /* Write channel closed (STOP_SENDING received). */
 };
 
+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,
+};
+
 /* QUIC application layer operations */
 struct qcc_app_ops {
        const char *alpn;
@@ -236,8 +242,10 @@ struct qcc_app_ops {
        size_t (*nego_ff)(struct qcs *qcs, size_t count);
        size_t (*done_ff)(struct qcs *qcs);
 
-       /* Notify about <qcs> stream closure. */
+       /* Notify about <qcs> stream remote closure. */
        int (*close)(struct qcs *qcs, enum qcc_app_ops_close_side side);
+       /* Notify about <qcs> stream upper layer closure. */
+       void (*lclose)(struct qcs *qcs, enum qcc_app_ops_lclose_mode mode);
        /* Free <qcs> stream app context. */
        void (*detach)(struct qcs *qcs);
 
index a899370ce868e5f3f3cc547d9124055cb604017c..8d633bfc7075b2122a447ef30e269716b80cc377 100644 (file)
--- a/src/h3.c
+++ b/src/h3.c
@@ -3095,6 +3095,33 @@ static int h3_close(struct qcs *qcs, enum qcc_app_ops_close_side side)
        return 0;
 }
 
+static void h3_lclose(struct qcs *qcs, enum qcc_app_ops_lclose_mode mode)
+{
+       TRACE_ENTER(H3_EV_H3S_END, qcs->qcc->conn, qcs);
+
+       switch (mode) {
+       case QCC_APP_OPS_LCLO_MODE_NORMAL:
+               /* Close stream with FIN. This can only be performed if at
+                * least HEADERS frame was emitted, or else some clients close
+                * the connection with H3_FRAME_UNEXPECTED.
+                */
+               if (qcs->tx.fc.off_soft) {
+                       qcs->flags |= QC_SF_FIN_STREAM;
+                       qcc_send_stream(qcs, 0, 0);
+               }
+               else {
+                       qcc_reset_stream(qcs, 0, se_tevt_type_shutw);
+               }
+               break;
+
+       default:
+               qcc_reset_stream(qcs, 0, 0);
+               break;
+       }
+
+       TRACE_LEAVE(H3_EV_H3S_END, qcs->qcc->conn, qcs);
+}
+
 /* Allocates HTTP/3 stream context relative to <qcs>. If the operation cannot
  * be performed, an error is returned and <qcs> context is unchanged.
  *
@@ -3500,6 +3527,7 @@ const struct qcc_app_ops h3_ops = {
        .nego_ff     = h3_nego_ff,
        .done_ff     = h3_done_ff,
        .close       = h3_close,
+       .lclose      = h3_lclose,
        .detach      = h3_detach,
        .shutdown    = h3_shutdown,
        .inc_err_cnt = h3_stats_inc_err_cnt,
index 8268e62a726fcf46c43437ce3471b0cf7c323d48..35a0e3358f8e7f4c27df4b1eaf2dff91d3a30f53 100644 (file)
@@ -323,6 +323,20 @@ static int hq_interop_attach(struct qcs *qcs, void *conn_ctx)
        return 0;
 }
 
+static void hq_interop_lclose(struct qcs *qcs, enum qcc_app_ops_lclose_mode mode)
+{
+       switch (mode) {
+       case QCC_APP_OPS_LCLO_MODE_NORMAL:
+               qcs->flags |= QC_SF_FIN_STREAM;
+               qcc_send_stream(qcs, 0, 0);
+               break;
+
+       default:
+               qcc_reset_stream(qcs, 0, 0);
+               break;
+       }
+}
+
 const struct qcc_app_ops hq_interop_ops = {
        .alpn       = "hq-interop",
 
@@ -331,4 +345,5 @@ const struct qcc_app_ops hq_interop_ops = {
        .nego_ff    = hq_interop_nego_ff,
        .done_ff    = hq_interop_done_ff,
        .attach     = hq_interop_attach,
+       .lclose     = hq_interop_lclose,
 };
index 1bf13dc0f7cd704a88b3eb9f0fb104a5151c18e5..4208029aa6b6b73372402d22f67ecee6f2aeab66 100644 (file)
@@ -4591,25 +4591,12 @@ static void qmux_strm_shut(struct stconn *sc, unsigned int mode, struct se_abort
        /* Early closure reported if QC_SF_FIN_STREAM not yet set. */
        if (!qcs_is_close_local(qcs) &&
            !(qcs->flags & (QC_SF_FIN_STREAM|QC_SF_TO_RESET))) {
-
-               /* Close stream with FIN if length unknown and some data are
-                * ready to be/already transmitted.
-                * TODO select closure method on app proto layer
-                */
-               if (qcs->flags & QC_SF_UNKNOWN_PL_LENGTH &&
-                   qcs->tx.fc.off_soft) {
-                       if (!(qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL))) {
-                               TRACE_STATE("set FIN STREAM",
-                                           QMUX_EV_STRM_SHUT, qcc->conn, qcs);
-                               qcs->flags |= QC_SF_FIN_STREAM;
-                               qcc_send_stream(qcs, 0, 0);
-                       }
-               }
-               else {
-                       /* RESET_STREAM necessary. */
-                       qcc_reset_stream(qcs, 0, 0);
-               }
-
+               if (qcs->flags & QC_SF_UNKNOWN_PL_LENGTH)
+                       qcc->app_ops->lclose(qcs, QCC_APP_OPS_LCLO_MODE_NORMAL);
+               else if (se_fl_test(qcs->sd, SE_FL_KILL_CONN))
+                       qcc->app_ops->lclose(qcs, QCC_APP_OPS_LCLO_MODE_KILL_CONN);
+               else
+                       qcc->app_ops->lclose(qcs, QCC_APP_OPS_LCLO_MODE_ABORT);
                tasklet_wakeup(qcc->wait_event.tasklet);
        }