From 940e1820f6f3138f3d03a6e4167059b3772f18cc Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Tue, 3 Mar 2026 09:47:29 +0100 Subject: [PATCH] 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 QUIC xprt callback. This new architecture should be cleaner as it better highlights the responsibility of each layers in the ALPN/app negotiation. --- include/haproxy/mux_quic-t.h | 2 ++ include/haproxy/mux_quic.h | 2 +- include/haproxy/quic_conn-t.h | 3 ++- include/haproxy/quic_conn.h | 2 +- src/h3.c | 2 ++ src/hq_interop.c | 2 ++ src/mux_quic.c | 28 ++++++++++++++++++++-------- src/quic_conn.c | 20 +++++++++++--------- src/quic_ssl.c | 4 ++-- src/ssl_sock.c | 2 +- src/xprt_quic.c | 18 +++++++++++++++++- 11 files changed, 61 insertions(+), 24 deletions(-) diff --git a/include/haproxy/mux_quic-t.h b/include/haproxy/mux_quic-t.h index aa7b17706..44f0ceb18 100644 --- a/include/haproxy/mux_quic-t.h +++ b/include/haproxy/mux_quic-t.h @@ -200,6 +200,8 @@ enum qcc_app_ops_close_side { /* QUIC application layer operations */ struct qcc_app_ops { + const char *alpn; + /* Initialize connection app context. */ int (*init)(struct qcc *qcc); /* Finish connection initialization if prelude required. */ diff --git a/include/haproxy/mux_quic.h b/include/haproxy/mux_quic.h index b44ec2d6b..2c8086297 100644 --- a/include/haproxy/mux_quic.h +++ b/include/haproxy/mux_quic.h @@ -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 stream for http-request timeout. If the stream is not yet * attached in the configured delay, qcc timeout task will be triggered. This diff --git a/include/haproxy/quic_conn-t.h b/include/haproxy/quic_conn-t.h index 3baad3fc1..0a7ce3951 100644 --- a/include/haproxy/quic_conn-t.h +++ b/include/haproxy/quic_conn-t.h @@ -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); diff --git a/include/haproxy/quic_conn.h b/include/haproxy/quic_conn.h index c34681e7d..3424389a2 100644 --- a/include/haproxy/quic_conn.h +++ b/include/haproxy/quic_conn.h @@ -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); diff --git a/src/h3.c b/src/h3.c index ebf1830e6..0b7c53303 100644 --- a/src/h3.c +++ b/src/h3.c @@ -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, diff --git a/src/hq_interop.c b/src/hq_interop.c index cf1cf7c83..8268e62a7 100644 --- a/src/hq_interop.c +++ b/src/hq_interop.c @@ -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, diff --git a/src/mux_quic.c b/src/mux_quic.c index 512d0eda9..5b672b174 100644 --- a/src/mux_quic.c +++ b/src/mux_quic.c @@ -1688,26 +1688,38 @@ void qcc_abort_stream_read(struct qcs *qcs) TRACE_LEAVE(QMUX_EV_QCC_NEW, qcc->conn, qcs); } -/* Install the applicative layer of a QUIC connection on mux . +/* Install the applicative layer of a QUIC connection on mux . * 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; } diff --git a/src/quic_conn.c b/src/quic_conn.c index 2d87a42a9..26c67c7d4 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -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 QUIC connection. - * Return 1 if succeeded, 0 if not. +/* Register the negotiated TLS ALPN of length for 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 and 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; diff --git a/src/quic_ssl.c b/src/quic_ssl.c index 88f3268d0..710fef4e4 100644 --- a/src/quic_ssl.c +++ b/src/quic_ssl.c @@ -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; diff --git a/src/ssl_sock.c b/src/ssl_sock.c index ff266f0ea..aeb408ffa 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -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; } diff --git a/src/xprt_quic.c b/src/xprt_quic.c index ca687f021..4670f6a86 100644 --- a/src/xprt_quic.c +++ b/src/xprt_quic.c @@ -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",