MEDIUM: quic/mux-quic: adjust app-ops install

This patch reworks the installation of app-ops layer by QUIC MUX.
Previously, app_ops field was stored directly into the quic_conn
structure. Then the MUX reused it directly during its qmux_init().

This patch removes app_ops field from quic_conn and replaces it with a
copy of the negotiated ALPN. By using quic_alpn_to_app_ops(), it ensures
it remains compatible with a known application layer.

On the MUX layer, qcc_install_app_ops() now uses the standard
conn_get_alpn() to retrieve the ALPN from the transport layer. This is
done via the newly defined <get_alpn> QUIC xprt callback.

This new architecture should be cleaner as it better highlights the
responsibility of each layers in the ALPN/app negotiation.
This commit is contained in:
Amaury Denoyelle 2026-03-03 09:47:29 +01:00
parent 9c7cf1c684
commit 940e1820f6
11 changed files with 61 additions and 24 deletions

View File

@ -200,6 +200,8 @@ enum qcc_app_ops_close_side {
/* QUIC application layer operations */
struct qcc_app_ops {
const char *alpn;
/* Initialize <qcc> connection app context. */
int (*init)(struct qcc *qcc);
/* Finish connection initialization if prelude required. */

View File

@ -91,7 +91,7 @@ static inline char *qcs_st_to_str(enum qcs_state st)
}
}
int qcc_install_app_ops(struct qcc *qcc, const struct qcc_app_ops *app_ops);
int qcc_install_app_ops(struct qcc *qcc);
/* Register <qcs> stream for http-request timeout. If the stream is not yet
* attached in the configured delay, qcc timeout task will be triggered. This

View File

@ -400,6 +400,8 @@ struct quic_conn {
struct eb_root streams_by_id; /* qc_stream_desc tree */
const char *alpn;
/* MUX */
struct qcc *qcc;
struct task *timer_task;
@ -408,7 +410,6 @@ struct quic_conn {
/* Handshake expiration date */
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);

View File

@ -204,7 +204,7 @@ static inline void *qc_counters(enum obj_type *o, const struct stats_module *m)
void chunk_frm_appendf(struct buffer *buf, const struct quic_frame *frm);
void quic_set_connection_close(struct quic_conn *qc, const struct quic_err err);
void quic_set_tls_alert(struct quic_conn *qc, int alert);
int quic_set_app_ops(struct quic_conn *qc, const char *alpn, int alpn_len);
int qc_register_alpn(struct quic_conn *qc, const char *alpn, int alpn_len);
int qc_check_dcid(struct quic_conn *qc, unsigned char *dcid, size_t dcid_len);
void qc_notify_err(struct quic_conn *qc);

View File

@ -3398,6 +3398,8 @@ int h3_reject(struct list *out, uint64_t id)
/* HTTP/3 application layer operations */
const struct qcc_app_ops h3_ops = {
.alpn = "h3",
.init = h3_init,
.finalize = h3_finalize,
.attach = h3_attach,

View File

@ -324,6 +324,8 @@ static int hq_interop_attach(struct qcs *qcs, void *conn_ctx)
}
const struct qcc_app_ops hq_interop_ops = {
.alpn = "hq-interop",
.rcv_buf = hq_interop_rcv_buf,
.snd_buf = hq_interop_snd_buf,
.nego_ff = hq_interop_nego_ff,

View File

@ -1688,26 +1688,38 @@ void qcc_abort_stream_read(struct qcs *qcs)
TRACE_LEAVE(QMUX_EV_QCC_NEW, qcc->conn, qcs);
}
/* Install the <app_ops> applicative layer of a QUIC connection on mux <qcc>.
/* Install the applicative layer of a QUIC connection on mux <qcc>.
* Returns 0 on success else non-zero.
*/
int qcc_install_app_ops(struct qcc *qcc, const struct qcc_app_ops *app_ops)
int qcc_install_app_ops(struct qcc *qcc)
{
TRACE_ENTER(QMUX_EV_QCC_NEW, qcc->conn);
struct connection *conn = qcc->conn;
const struct qcc_app_ops *app_ops;
const char *alpn;
int alpn_len;
TRACE_ENTER(QMUX_EV_QCC_NEW, conn);
if (!conn_get_alpn(conn, &alpn, &alpn_len))
goto err;
app_ops = quic_alpn_to_app_ops(alpn, alpn_len);
if (!app_ops)
goto err;
if (app_ops->init && !app_ops->init(qcc)) {
TRACE_ERROR("application layer install error", QMUX_EV_QCC_NEW, qcc->conn);
TRACE_ERROR("application layer install error", QMUX_EV_QCC_NEW, conn);
goto err;
}
TRACE_PROTO("application layer installed", QMUX_EV_QCC_NEW, qcc->conn);
TRACE_PROTO("application layer installed", QMUX_EV_QCC_NEW, conn);
qcc->app_ops = app_ops;
TRACE_LEAVE(QMUX_EV_QCC_NEW, qcc->conn);
TRACE_LEAVE(QMUX_EV_QCC_NEW, conn);
return 0;
err:
TRACE_LEAVE(QMUX_EV_QCC_NEW, qcc->conn);
TRACE_LEAVE(QMUX_EV_QCC_NEW, conn);
return 1;
}
@ -3740,7 +3752,7 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
/* Register conn as app_ops may use it. */
qcc->conn = conn;
if (qcc_install_app_ops(qcc, conn->handle.qc->app_ops)) {
if (qcc_install_app_ops(qcc)) {
TRACE_PROTO("Cannot install app layer", QMUX_EV_QCC_NEW|QMUX_EV_QCC_ERR, conn);
goto err;
}

View File

@ -267,21 +267,23 @@ void quic_set_tls_alert(struct quic_conn *qc, int alert)
TRACE_LEAVE(QUIC_EV_CONN_SSLALERT, qc);
}
/* Set the application for <qc> QUIC connection.
* Return 1 if succeeded, 0 if not.
/* Register the negotiated TLS ALPN <alpn> of length <alpn_len> for <qc> QUIC
* connection. This checks that the protocol is compatible with the QUIC stack.
*
* Returns 1 on success else 0.
*/
int quic_set_app_ops(struct quic_conn *qc, const char *alpn, int alpn_len)
int qc_register_alpn(struct quic_conn *qc, const char *alpn, int alpn_len)
{
if (!(qc->app_ops = quic_alpn_to_app_ops(alpn, alpn_len)))
const struct qcc_app_ops *app_ops;
if (!(app_ops = quic_alpn_to_app_ops(alpn, alpn_len)))
return 0;
qc->alpn = app_ops->alpn;
return 1;
}
/* Try to reuse <alpn> ALPN and <etps> early transport parameters.
* This function also sets the application operations calling
* quic_set_app_ops().
* Return 1 if succeeded, 0 if not.
*/
int quic_reuse_srv_params(struct quic_conn *qc,
@ -292,7 +294,7 @@ int quic_reuse_srv_params(struct quic_conn *qc,
TRACE_ENTER(QUIC_EV_CONN_NEW, qc);
if (!alpn || !quic_set_app_ops(qc, alpn, strlen(alpn)))
if (!alpn || !qc_register_alpn(qc, alpn, strlen(alpn)))
goto err;
qc_early_transport_params_reuse(qc, &qc->tx.params, etps);
@ -1124,6 +1126,7 @@ struct quic_conn *qc_new_conn(void *target,
LIST_INIT(&qc->rx.pkt_list);
qc->streams_by_id = EB_ROOT_UNIQUE;
qc->alpn = NULL;
/* Required to call free_quic_conn_cids() from quic_conn_release() */
qc->cids = NULL;
@ -1143,7 +1146,6 @@ struct quic_conn *qc_new_conn(void *target,
qc->xprt_ctx = NULL;
qc->conn = conn;
qc->qcc = NULL;
qc->app_ops = NULL;
qc->strm_reject = NULL;
qc->path = NULL;

View File

@ -1004,7 +1004,7 @@ int qc_ssl_do_hanshake(struct quic_conn *qc, struct ssl_sock_ctx *ctx)
/* Check the alpn could be negotiated */
if (!qc_is_back(qc)) {
if (!qc->app_ops) {
if (!qc->alpn) {
TRACE_ERROR("No negotiated ALPN", QUIC_EV_CONN_IO_CB, qc, &state);
quic_set_tls_alert(qc, SSL_AD_NO_APPLICATION_PROTOCOL);
goto err;
@ -1016,7 +1016,7 @@ int qc_ssl_do_hanshake(struct quic_conn *qc, struct ssl_sock_ctx *ctx)
qc->conn->flags &= ~(CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN);
if (!ssl_sock_get_alpn(qc->conn, ctx, &alpn, &alpn_len) ||
!quic_set_app_ops(qc, alpn, alpn_len)) {
!qc_register_alpn(qc, alpn, alpn_len)) {
TRACE_ERROR("No negotiated ALPN", QUIC_EV_CONN_IO_CB, qc, &state);
quic_set_tls_alert(qc, SSL_AD_NO_APPLICATION_PROTOCOL);
goto err;

View File

@ -2242,7 +2242,7 @@ static int ssl_sock_advertise_alpn_protos(SSL *s, const unsigned char **out,
}
#ifdef USE_QUIC
if (qc && !quic_set_app_ops(qc, (const char *)*out, *outlen)) {
if (qc && !qc_register_alpn(qc, (const char *)*out, *outlen)) {
quic_set_tls_alert(qc, SSL_AD_NO_APPLICATION_PROTOCOL);
return SSL_TLSEXT_ERR_NOACK;
}

View File

@ -239,6 +239,22 @@ static void qc_xprt_dump_info(struct buffer *msg, const struct connection *conn)
quic_dump_qc_info(msg, conn->handle.qc);
}
static int qc_get_alpn(const struct connection *conn, void *xprt_ctx, const char **str, int *len)
{
struct quic_conn *qc = conn->handle.qc;
int ret = 0;
TRACE_ENTER(QUIC_EV_CONN_NEW, qc);
if (qc->alpn) {
*str = qc->alpn;
*len = strlen(qc->alpn);
ret = 1;
}
TRACE_LEAVE(QUIC_EV_CONN_NEW, qc);
return ret;
}
/* transport-layer operations for QUIC connections. */
static struct xprt_ops ssl_quic = {
.close = quic_close,
@ -250,7 +266,7 @@ static struct xprt_ops ssl_quic = {
.destroy_bind_conf = ssl_sock_destroy_bind_conf,
.prepare_srv = ssl_sock_prepare_srv_ctx,
.destroy_srv = ssl_sock_free_srv_ctx,
.get_alpn = ssl_sock_get_alpn,
.get_alpn = qc_get_alpn,
.get_ssl_sock_ctx = qc_get_ssl_sock_ctx,
.dump_info = qc_xprt_dump_info,
.name = "QUIC",