MINOR: mux-quic/h3: emit SETTINGS via MUX tasklet handler

Previously, QUIC MUX application layer was installed and initialized via
MUX init. However, the latter stage involve I/O operations, for example
when using HTTP/3 with the emission of a SETTINGS frame.

Change this to prevent any I/O operations during MUX init. As such,
finalize app_ops callback is now called during the first invokation of
qcc_io_send(), in the context of MUX tasklet. To implement this, a new
application state value is added, to detect the transition from NULL to
INIT stage.
This commit is contained in:
Amaury Denoyelle 2025-02-17 16:00:03 +01:00
parent 188fc45b95
commit 06e7674399
4 changed files with 43 additions and 20 deletions

View File

@ -33,6 +33,7 @@ enum qcs_type {
};
enum qcc_app_st {
QCC_APP_ST_NULL,
QCC_APP_ST_INIT,
QCC_APP_ST_SHUT,
} __attribute__((packed));

View File

@ -1488,7 +1488,7 @@ static ssize_t h3_rcv_buf(struct qcs *qcs, struct buffer *b, int fin)
return -1;
}
/* Function used to emit stream data from <qcs> control uni-stream.
/* Emit SETTINGS frame on <qcs> control uni-stream.
*
* On success return the number of sent bytes. A negative code is used on
* error.
@ -2406,7 +2406,8 @@ static int h3_init(struct qcc *qcc)
return 0;
}
/* Initialize H3 control stream and prepare SETTINGS emission.
/* Open control stream for <ctx> HTTP/3 connection and schedule a SETTINGS
* frame emission on it.
*
* Returns 0 on success else non-zero.
*/
@ -2429,6 +2430,13 @@ static int h3_finalize(void *ctx)
qcs_send_metadata(qcs);
h3c->ctrl_strm = qcs;
/* RFC 9114 7.2.4.2. Initialization
*
* Endpoints MUST NOT require any data to be
* received from the peer prior to sending the SETTINGS frame;
* settings MUST be sent as soon as the transport is ready to
* send data.
*/
if (h3_control_send(qcs, h3c) < 0) {
qcc_set_error(qcc, H3_ERR_INTERNAL_ERROR, 1);
goto err;

View File

@ -1474,28 +1474,13 @@ int qcc_install_app_ops(struct qcc *qcc, const struct qcc_app_ops *app_ops)
TRACE_ENTER(QMUX_EV_QCC_NEW, qcc->conn);
if (app_ops->init && !app_ops->init(qcc)) {
TRACE_ERROR("app ops init error", QMUX_EV_QCC_NEW, qcc->conn);
TRACE_ERROR("application layer install error", QMUX_EV_QCC_NEW, qcc->conn);
goto err;
}
TRACE_PROTO("application layer initialized", QMUX_EV_QCC_NEW, qcc->conn);
TRACE_PROTO("application layer installed", QMUX_EV_QCC_NEW, qcc->conn);
qcc->app_ops = app_ops;
/* RFC 9114 7.2.4.2. Initialization
*
* Endpoints MUST NOT require any data to be
* received from the peer prior to sending the SETTINGS frame;
* settings MUST be sent as soon as the transport is ready to
* send data.
*/
if (qcc->app_ops->finalize) {
if (qcc->app_ops->finalize(qcc->ctx)) {
TRACE_ERROR("app ops finalize error", QMUX_EV_QCC_NEW, qcc->conn);
goto err;
}
tasklet_wakeup(qcc->wait_event.tasklet);
}
TRACE_LEAVE(QMUX_EV_QCC_NEW, qcc->conn);
return 0;
@ -2480,6 +2465,29 @@ static void qcc_wakeup_pacing(struct qcc *qcc)
++qcc->tx.paced_sent_ctr;
}
/* Finalize <qcc> app layer initialization with I/O operations.
*
* Returns 0 on success else non-zero.
*/
static int qcc_app_init(struct qcc *qcc)
{
TRACE_ENTER(QMUX_EV_QCC_SEND, qcc->conn);
if (qcc->app_ops->finalize && qcc->app_ops->finalize(qcc->ctx)) {
TRACE_ERROR("app ops finalize error", QMUX_EV_QCC_NEW, qcc->conn);
goto err;
}
qcc->app_st = QCC_APP_ST_INIT;
TRACE_LEAVE(QMUX_EV_QCC_SEND, qcc->conn);
return 0;
err:
TRACE_DEVEL("leaving on error", QMUX_EV_QCC_SEND, qcc->conn);
return 1;
}
/* Proceed to sending. Loop through all available streams for the <qcc>
* instance and try to send as much as possible.
*
@ -2530,6 +2538,11 @@ static int qcc_io_send(struct qcc *qcc)
goto out;
}
if (qcc->app_st < QCC_APP_ST_INIT) {
if (qcc_app_init(qcc))
goto out;
}
if (!LIST_ISEMPTY(&qcc->lfctl.frms)) {
if (qcc_send_frames(qcc, &qcc->lfctl.frms, 0)) {
TRACE_DEVEL("flow-control frames rejected by transport, aborting send", QMUX_EV_QCC_SEND, qcc->conn);
@ -3042,7 +3055,7 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
conn->ctx = qcc;
qcc->nb_hreq = qcc->nb_sc = 0;
qcc->flags = 0;
qcc->app_st = QCC_APP_ST_INIT;
qcc->app_st = QCC_APP_ST_NULL;
qcc->glitches = 0;
qcc->err = quic_err_transport(QC_ERR_NO_ERROR);

View File

@ -133,6 +133,7 @@ INITCALL1(STG_REGISTER, trace_register_source, TRACE_SOURCE);
static char *qcc_app_st_to_str(const enum qcc_app_st st)
{
switch (st) {
case QCC_APP_ST_NULL: return "NULL";
case QCC_APP_ST_INIT: return "INIT";
case QCC_APP_ST_SHUT: return "SHUT";
default: return "";