MEDIUM: mux-quic: extend shut to app proto layer

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.
This commit is contained in:
Amaury Denoyelle 2026-04-21 14:58:17 +02:00
parent 64383e655b
commit d0bd6a946b
4 changed files with 58 additions and 20 deletions

View 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);

View File

@ -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,

View 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,
};

View 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);
}