MEDIUM: xprt_qstrm: implement QMux record parsing

This patch implements the new QMux record layer parsing for xprt_qstrm.
This is mostly similar to the MUX code from the previous patch.

Along with this change, a new xprt_qstrm layer accessor exposes the
possible remaining record length after Transport parameters parsing.
This can only occur when xprt_qstrm Rx buffer is not completely emptied
due to other following frames. If stored in the same record, MUX layer
has to know the remaining record length.

Thus, xprt_qstrm_rxrlen() is now used in qmux_init() to preinitialize
<rx.rlen> QCC field.
This commit is contained in:
Amaury Denoyelle 2026-04-08 14:13:05 +02:00
parent 5271cdaca3
commit 792e055c7c
3 changed files with 35 additions and 6 deletions

View File

@ -5,5 +5,6 @@ const struct quic_transport_params *xprt_qstrm_lparams(const void *context);
const struct quic_transport_params *xprt_qstrm_rparams(const void *context);
struct buffer *xprt_qstrm_rxbuf(const void *context);
size_t xprt_qstrm_rxrlen(const void *context);
#endif /* _HAPROXY_XPRT_QSTRM_H */

View File

@ -3802,8 +3802,13 @@ static int qmux_init(struct connection *conn, struct proxy *prx,
/* Retrieve data if xprt read too much */
xprt_buf = xprt_qstrm_rxbuf(conn->xprt_ctx);
if (unlikely(b_data(xprt_buf)))
if (unlikely(b_data(xprt_buf))) {
b_xfer(&qcc->rx.qstrm_buf, xprt_buf, b_data(xprt_buf));
qcc->rx.rlen = xprt_qstrm_rxrlen(conn->xprt_ctx);
}
else {
qcc->rx.rlen = 0;
}
}
if (conn_is_back(conn)) {

View File

@ -5,6 +5,7 @@
#include <haproxy/fd.h>
#include <haproxy/global.h>
#include <haproxy/mux_quic.h>
#include <haproxy/mux_quic_qstrm.h>
#include <haproxy/pool.h>
#include <haproxy/quic_frame.h>
#include <haproxy/quic_tp-t.h>
@ -22,6 +23,7 @@ struct xprt_qstrm_ctx {
struct buffer txbuf;
struct buffer rxbuf;
size_t rxrlen;
};
DECLARE_STATIC_TYPED_POOL(xprt_qstrm_ctx_pool, "xprt_qstrm_ctx", struct xprt_qstrm_ctx);
@ -45,11 +47,17 @@ struct buffer *xprt_qstrm_rxbuf(void *context)
return &ctx->rxbuf;
}
size_t xprt_qstrm_rxrlen(const void *context)
{
const struct xprt_qstrm_ctx *ctx = context;
return ctx->rxrlen;
}
int conn_recv_qstrm(struct connection *conn, struct xprt_qstrm_ctx *ctx, int flag)
{
struct quic_frame frm;
struct buffer *buf = &ctx->rxbuf;
const unsigned char *pos, *end;
const unsigned char *pos, *old, *end;
size_t ret;
if (!conn_ctrl_ready(conn))
@ -72,18 +80,32 @@ int conn_recv_qstrm(struct connection *conn, struct xprt_qstrm_ctx *ctx, int fla
if (!b_data(buf))
goto not_ready;
pos = (unsigned char *)b_orig(buf);
end = (unsigned char *)(b_orig(buf) + b_data(buf));
if (!qc_parse_frm_type(&frm, &pos, end, NULL))
/* Read record length. */
if (!b_quic_dec_int(&ctx->rxrlen, buf, NULL))
goto not_ready;
/* Reject too small or too big records. */
if (!ctx->rxrlen || ctx->rxrlen > b_size(buf))
goto fail;
if (ctx->rxrlen > b_data(buf))
goto not_ready;
old = pos = (unsigned char *)b_head(buf);
end = pos + ctx->rxrlen;
if (!qc_parse_frm_type(&frm, &pos, end, NULL))
goto fail;
/* TODO close connection with TRANSPORT_PARAMETER_ERROR if frame not present. */
BUG_ON(frm.type != QUIC_FT_QX_TRANSPORT_PARAMETERS);
if (!qc_parse_frm_payload(&frm, &pos, end, NULL))
goto not_ready;
goto fail;
ctx->rparams = frm.qmux_transport_params.params;
b_del(buf, pos - old);
/* <end> delimiter should guarantee than frame length does not go beyong the record end */
BUG_ON(ctx->rxrlen < pos - old);
ctx->rxrlen -= (pos - old);
conn->flags &= ~flag;
return 1;
@ -235,6 +257,7 @@ static int xprt_qstrm_init(struct connection *conn, void **xprt_ctx)
ctx->ops_lower = NULL;
ctx->rxbuf = BUF_NULL;
ctx->rxrlen = 0;
ctx->txbuf = BUF_NULL;
memset(&ctx->rparams, 0, sizeof(struct quic_transport_params));