2021-12-03 05:36:46 -05:00
|
|
|
#include <haproxy/mux_quic.h>
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2021-10-07 10:44:05 -04:00
|
|
|
#include <import/eb64tree.h>
|
|
|
|
|
|
2021-02-18 03:59:01 -05:00
|
|
|
#include <haproxy/api.h>
|
2024-07-31 11:28:24 -04:00
|
|
|
#include <haproxy/buf.h>
|
2024-02-23 11:32:14 -05:00
|
|
|
#include <haproxy/chunk.h>
|
2021-02-18 03:59:01 -05:00
|
|
|
#include <haproxy/connection.h>
|
2021-12-03 05:36:46 -05:00
|
|
|
#include <haproxy/dynbuf.h>
|
2024-08-13 05:57:50 -04:00
|
|
|
#include <haproxy/global-t.h>
|
2023-01-18 05:52:21 -05:00
|
|
|
#include <haproxy/h3.h>
|
2022-04-15 11:32:04 -04:00
|
|
|
#include <haproxy/list.h>
|
2022-05-13 08:49:05 -04:00
|
|
|
#include <haproxy/ncbuf.h>
|
2021-12-03 05:36:46 -05:00
|
|
|
#include <haproxy/pool.h>
|
2023-01-18 05:52:21 -05:00
|
|
|
#include <haproxy/proxy.h>
|
2022-09-19 11:02:28 -04:00
|
|
|
#include <haproxy/qmux_http.h>
|
2022-09-19 10:12:38 -04:00
|
|
|
#include <haproxy/qmux_trace.h>
|
2022-09-30 12:11:13 -04:00
|
|
|
#include <haproxy/quic_conn.h>
|
2024-08-08 06:04:47 -04:00
|
|
|
#include <haproxy/quic_enc.h>
|
2023-10-18 09:55:38 -04:00
|
|
|
#include <haproxy/quic_fctl.h>
|
2023-01-27 11:47:49 -05:00
|
|
|
#include <haproxy/quic_frame.h>
|
2024-11-18 08:55:41 -05:00
|
|
|
#include <haproxy/quic_pacing.h>
|
2023-01-24 12:20:28 -05:00
|
|
|
#include <haproxy/quic_sock.h>
|
2022-04-19 11:21:11 -04:00
|
|
|
#include <haproxy/quic_stream.h>
|
2022-05-21 17:58:40 -04:00
|
|
|
#include <haproxy/quic_tp-t.h>
|
2025-01-30 12:01:53 -05:00
|
|
|
#include <haproxy/quic_tune.h>
|
2024-10-24 10:32:29 -04:00
|
|
|
#include <haproxy/quic_tx.h>
|
2024-09-18 09:33:30 -04:00
|
|
|
#include <haproxy/session.h>
|
2021-10-07 10:44:05 -04:00
|
|
|
#include <haproxy/ssl_sock-t.h>
|
2022-05-27 03:47:12 -04:00
|
|
|
#include <haproxy/stconn.h>
|
2023-05-15 09:17:28 -04:00
|
|
|
#include <haproxy/time.h>
|
2022-03-24 11:09:16 -04:00
|
|
|
#include <haproxy/trace.h>
|
2023-10-27 09:48:13 -04:00
|
|
|
#include <haproxy/xref.h>
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2021-12-03 05:36:46 -05:00
|
|
|
DECLARE_POOL(pool_head_qcc, "qcc", sizeof(struct qcc));
|
2021-02-18 03:59:01 -05:00
|
|
|
DECLARE_POOL(pool_head_qcs, "qcs", sizeof(struct qcs));
|
2025-02-24 10:22:22 -05:00
|
|
|
DECLARE_STATIC_POOL(pool_head_qc_stream_rxbuf, "qc_stream_rxbuf", sizeof(struct qc_stream_rxbuf));
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2024-09-25 11:55:10 -04:00
|
|
|
static void qmux_ctrl_send(struct qc_stream_desc *, uint64_t data, uint64_t offset);
|
2024-09-25 12:25:08 -04:00
|
|
|
static void qmux_ctrl_room(struct qc_stream_desc *, uint64_t room);
|
2024-09-25 11:55:10 -04:00
|
|
|
|
2024-11-18 08:55:41 -05:00
|
|
|
/* Returns true if pacing should be used for <conn> connection. */
|
|
|
|
|
static int qcc_is_pacing_active(const struct connection *conn)
|
|
|
|
|
{
|
2025-01-30 12:01:53 -05:00
|
|
|
return !(quic_tune.options & QUIC_TUNE_NO_PACING);
|
2024-11-18 08:55:41 -05:00
|
|
|
}
|
|
|
|
|
|
2025-02-24 10:22:22 -05:00
|
|
|
/* Free <rxbuf> instance and its inner data storage attached to <qcs> stream. */
|
|
|
|
|
static void qcs_free_rxbuf(struct qcs *qcs, struct qc_stream_rxbuf *rxbuf)
|
2022-12-12 03:59:50 -05:00
|
|
|
{
|
2025-02-24 10:22:22 -05:00
|
|
|
struct ncbuf *ncbuf;
|
2022-12-12 03:59:50 -05:00
|
|
|
struct buffer buf;
|
|
|
|
|
|
2025-02-24 10:22:22 -05:00
|
|
|
ncbuf = &rxbuf->ncb;
|
|
|
|
|
if (!ncb_is_null(ncbuf)) {
|
|
|
|
|
buf = b_make(ncbuf->area, ncbuf->size, 0, 0);
|
|
|
|
|
b_free(&buf);
|
|
|
|
|
offer_buffers(NULL, 1);
|
|
|
|
|
}
|
2025-02-24 10:28:50 -05:00
|
|
|
rxbuf->ncb = NCBUF_NULL;
|
2023-09-21 11:06:16 -04:00
|
|
|
|
|
|
|
|
/* Reset DEM_FULL as buffer is released. This ensures mux is not woken
|
|
|
|
|
* up from rcv_buf stream callback when demux was previously blocked.
|
|
|
|
|
*/
|
|
|
|
|
qcs->flags &= ~QC_SF_DEM_FULL;
|
2025-02-24 10:22:22 -05:00
|
|
|
|
2025-02-24 10:28:50 -05:00
|
|
|
eb64_delete(&rxbuf->off_node);
|
2025-02-24 10:22:22 -05:00
|
|
|
pool_free(pool_head_qc_stream_rxbuf, rxbuf);
|
2022-12-12 03:59:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Free <qcs> instance. This function is reserved for internal usage : it must
|
|
|
|
|
* only be called on qcs alloc error or on connection shutdown. Else
|
2023-04-01 06:26:42 -04:00
|
|
|
* qcs_destroy must be preferred to handle QUIC flow-control increase.
|
2022-12-12 03:59:50 -05:00
|
|
|
*/
|
|
|
|
|
static void qcs_free(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
2025-02-24 10:28:50 -05:00
|
|
|
struct qc_stream_rxbuf *b;
|
2022-12-12 03:59:50 -05:00
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCS_END, qcc->conn, qcs);
|
2024-08-07 09:36:17 -04:00
|
|
|
TRACE_STATE("releasing QUIC stream", QMUX_EV_QCS_END, qcc->conn, qcs);
|
2022-12-12 03:59:50 -05:00
|
|
|
|
2022-12-20 08:47:16 -05:00
|
|
|
/* Safe to use even if already removed from the list. */
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_opening);
|
2024-12-05 04:48:51 -05:00
|
|
|
LIST_DEL_INIT(&qcs->el_recv);
|
2023-01-03 08:39:24 -05:00
|
|
|
LIST_DEL_INIT(&qcs->el_send);
|
2023-10-18 11:48:11 -04:00
|
|
|
LIST_DEL_INIT(&qcs->el_fctl);
|
2024-01-17 09:15:55 -05:00
|
|
|
LIST_DEL_INIT(&qcs->el_buf);
|
2022-12-12 03:59:50 -05:00
|
|
|
|
|
|
|
|
/* Release stream endpoint descriptor. */
|
|
|
|
|
BUG_ON(qcs->sd && !se_fl_test(qcs->sd, SE_FL_ORPHAN));
|
|
|
|
|
sedesc_free(qcs->sd);
|
2024-08-06 12:59:39 -04:00
|
|
|
qcs->sd = NULL;
|
2022-12-12 03:59:50 -05:00
|
|
|
|
|
|
|
|
/* Release app-layer context. */
|
|
|
|
|
if (qcs->ctx && qcc->app_ops->detach)
|
|
|
|
|
qcc->app_ops->detach(qcs);
|
|
|
|
|
|
|
|
|
|
/* Release qc_stream_desc buffer from quic-conn layer. */
|
2024-09-30 08:39:15 -04:00
|
|
|
if (qcs->stream) {
|
|
|
|
|
qc_stream_desc_sub_send(qcs->stream, NULL);
|
2024-09-25 12:25:08 -04:00
|
|
|
qc_stream_desc_release(qcs->stream, qcs->tx.fc.off_real, qcc);
|
2024-09-30 08:39:15 -04:00
|
|
|
}
|
2022-12-12 03:59:50 -05:00
|
|
|
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
/* Free Rx buffer. */
|
2025-02-24 10:28:50 -05:00
|
|
|
while (!eb_is_empty(&qcs->rx.bufs)) {
|
|
|
|
|
b = container_of(eb64_first(&qcs->rx.bufs),
|
|
|
|
|
struct qc_stream_rxbuf, off_node);
|
|
|
|
|
qcs_free_rxbuf(qcs, b);
|
|
|
|
|
}
|
2022-12-12 03:59:50 -05:00
|
|
|
|
|
|
|
|
/* Remove qcs from qcc tree. */
|
|
|
|
|
eb64_delete(&qcs->by_id);
|
|
|
|
|
|
|
|
|
|
pool_free(pool_head_qcs, qcs);
|
|
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_END, qcc->conn);
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-03 05:36:46 -05:00
|
|
|
/* Allocate a new QUIC streams with id <id> and type <type>. */
|
2022-07-04 09:50:33 -04:00
|
|
|
static struct qcs *qcs_new(struct qcc *qcc, uint64_t id, enum qcs_type type)
|
2021-02-18 03:59:01 -05:00
|
|
|
{
|
2021-12-03 05:36:46 -05:00
|
|
|
struct qcs *qcs;
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCS_NEW, qcc->conn);
|
|
|
|
|
|
2021-12-03 05:36:46 -05:00
|
|
|
qcs = pool_alloc(pool_head_qcs);
|
2022-08-10 10:14:32 -04:00
|
|
|
if (!qcs) {
|
2022-08-10 10:39:54 -04:00
|
|
|
TRACE_ERROR("alloc failure", QMUX_EV_QCS_NEW, qcc->conn);
|
2022-04-27 09:09:27 -04:00
|
|
|
return NULL;
|
2022-08-10 10:14:32 -04:00
|
|
|
}
|
2022-04-27 09:09:27 -04:00
|
|
|
|
|
|
|
|
qcs->stream = NULL;
|
|
|
|
|
qcs->qcc = qcc;
|
|
|
|
|
qcs->flags = QC_SF_NONE;
|
2022-07-01 10:48:42 -04:00
|
|
|
qcs->st = QC_SS_IDLE;
|
2022-04-27 09:17:11 -04:00
|
|
|
qcs->ctx = NULL;
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2022-08-03 05:17:57 -04:00
|
|
|
/* App callback attach may register the stream for http-request wait.
|
|
|
|
|
* These fields must be initialed before.
|
|
|
|
|
*/
|
|
|
|
|
LIST_INIT(&qcs->el_opening);
|
2024-12-05 04:48:51 -05:00
|
|
|
LIST_INIT(&qcs->el_recv);
|
2023-01-03 08:39:24 -05:00
|
|
|
LIST_INIT(&qcs->el_send);
|
2023-10-18 11:48:11 -04:00
|
|
|
LIST_INIT(&qcs->el_fctl);
|
2024-01-17 09:15:55 -05:00
|
|
|
LIST_INIT(&qcs->el_buf);
|
2022-08-03 05:17:57 -04:00
|
|
|
qcs->start = TICK_ETERNITY;
|
|
|
|
|
|
2022-12-12 03:59:50 -05:00
|
|
|
/* store transport layer stream descriptor in qcc tree */
|
|
|
|
|
qcs->id = qcs->by_id.key = id;
|
|
|
|
|
eb64_insert(&qcc->streams_by_id, &qcs->by_id);
|
|
|
|
|
|
2023-10-18 09:55:38 -04:00
|
|
|
/* Different limits can be set by the peer for local and remote bidi streams. */
|
2022-10-21 11:02:18 -04:00
|
|
|
if (quic_stream_is_bidi(id)) {
|
2023-10-18 09:55:38 -04:00
|
|
|
qfctl_init(&qcs->tx.fc, quic_stream_is_local(qcc, id) ?
|
|
|
|
|
qcc->rfctl.msd_bidi_r : qcc->rfctl.msd_bidi_l);
|
2022-10-21 11:02:18 -04:00
|
|
|
}
|
|
|
|
|
else if (quic_stream_is_local(qcc, id)) {
|
2023-10-18 09:55:38 -04:00
|
|
|
qfctl_init(&qcs->tx.fc, qcc->rfctl.msd_uni_l);
|
2022-10-21 11:02:18 -04:00
|
|
|
}
|
2024-02-23 11:32:14 -05:00
|
|
|
else {
|
2024-03-05 09:14:08 -05:00
|
|
|
qfctl_init(&qcs->tx.fc, 0);
|
2024-02-23 11:32:14 -05:00
|
|
|
}
|
2022-03-07 09:47:02 -05:00
|
|
|
|
2025-02-24 10:28:50 -05:00
|
|
|
qcs->rx.bufs = EB_ROOT_UNIQUE;
|
2022-02-14 11:11:09 -05:00
|
|
|
qcs->rx.app_buf = BUF_NULL;
|
2022-05-20 09:05:07 -04:00
|
|
|
qcs->rx.offset = qcs->rx.offset_max = 0;
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2022-10-21 11:02:18 -04:00
|
|
|
if (quic_stream_is_bidi(id)) {
|
|
|
|
|
qcs->rx.msd = quic_stream_is_local(qcc, id) ? qcc->lfctl.msd_bidi_l :
|
|
|
|
|
qcc->lfctl.msd_bidi_r;
|
|
|
|
|
}
|
|
|
|
|
else if (quic_stream_is_remote(qcc, id)) {
|
|
|
|
|
qcs->rx.msd = qcc->lfctl.msd_uni_r;
|
|
|
|
|
}
|
2022-05-16 08:38:25 -04:00
|
|
|
qcs->rx.msd_init = qcs->rx.msd;
|
2022-04-26 05:21:10 -04:00
|
|
|
|
2021-12-03 05:36:46 -05:00
|
|
|
qcs->wait_event.tasklet = NULL;
|
|
|
|
|
qcs->wait_event.events = 0;
|
|
|
|
|
qcs->subs = NULL;
|
|
|
|
|
|
2022-07-04 05:44:38 -04:00
|
|
|
qcs->err = 0;
|
|
|
|
|
|
2024-07-31 12:43:55 -04:00
|
|
|
/* Reset all timers and start base one. */
|
|
|
|
|
tot_time_reset(&qcs->timer.base);
|
|
|
|
|
tot_time_reset(&qcs->timer.buf);
|
|
|
|
|
tot_time_reset(&qcs->timer.fctl);
|
|
|
|
|
tot_time_start(&qcs->timer.base);
|
|
|
|
|
|
2024-06-20 08:41:22 -04:00
|
|
|
qcs->sd = sedesc_new();
|
|
|
|
|
if (!qcs->sd)
|
|
|
|
|
goto err;
|
|
|
|
|
qcs->sd->se = qcs;
|
|
|
|
|
qcs->sd->conn = qcc->conn;
|
|
|
|
|
se_fl_set(qcs->sd, SE_FL_T_MUX | SE_FL_ORPHAN | SE_FL_NOT_FIRST);
|
|
|
|
|
se_expect_no_data(qcs->sd);
|
|
|
|
|
|
|
|
|
|
if (!(global.tune.no_zero_copy_fwd & NO_ZERO_COPY_FWD_QUIC_SND))
|
|
|
|
|
se_fl_set(qcs->sd, SE_FL_MAY_FASTFWD_CONS);
|
|
|
|
|
|
2023-10-11 11:32:04 -04:00
|
|
|
/* Allocate transport layer stream descriptor. Only needed for TX. */
|
|
|
|
|
if (!quic_stream_is_uni(id) || !quic_stream_is_remote(qcc, id)) {
|
|
|
|
|
struct quic_conn *qc = qcc->conn->handle.qc;
|
|
|
|
|
qcs->stream = qc_stream_desc_new(id, type, qcs, qc);
|
|
|
|
|
if (!qcs->stream) {
|
|
|
|
|
TRACE_ERROR("qc_stream_desc alloc failure", QMUX_EV_QCS_NEW, qcc->conn, qcs);
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
2024-09-25 11:55:10 -04:00
|
|
|
|
|
|
|
|
qc_stream_desc_sub_send(qcs->stream, qmux_ctrl_send);
|
2024-09-25 12:25:08 -04:00
|
|
|
qc_stream_desc_sub_room(qcs->stream, qmux_ctrl_room);
|
2023-10-11 11:32:04 -04:00
|
|
|
}
|
|
|
|
|
|
2023-01-24 11:42:21 -05:00
|
|
|
if (qcc->app_ops->attach && qcc->app_ops->attach(qcs, qcc->ctx)) {
|
|
|
|
|
TRACE_ERROR("app proto failure", QMUX_EV_QCS_NEW, qcc->conn, qcs);
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-03 05:36:46 -05:00
|
|
|
out:
|
2024-08-07 09:36:17 -04:00
|
|
|
TRACE_STATE("created new QUIC stream", QMUX_EV_QCS_NEW, qcc->conn, qcs);
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_NEW, qcc->conn, qcs);
|
2021-12-03 05:36:46 -05:00
|
|
|
return qcs;
|
2022-04-27 09:09:27 -04:00
|
|
|
|
|
|
|
|
err:
|
2022-12-12 03:59:50 -05:00
|
|
|
qcs_free(qcs);
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_NEW, qcc->conn);
|
2022-04-27 09:09:27 -04:00
|
|
|
return NULL;
|
2021-02-18 03:59:01 -05:00
|
|
|
}
|
|
|
|
|
|
2022-07-04 05:42:27 -04:00
|
|
|
static forceinline struct stconn *qcs_sc(const struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
return qcs->sd ? qcs->sd->sc : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-25 05:53:18 -04:00
|
|
|
/* Reset the <qcc> inactivity timeout for http-keep-alive timeout. */
|
|
|
|
|
static forceinline void qcc_reset_idle_start(struct qcc *qcc)
|
|
|
|
|
{
|
|
|
|
|
qcc->idle_start = now_ms;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-25 05:21:46 -04:00
|
|
|
/* Decrement <qcc> sc. */
|
|
|
|
|
static forceinline void qcc_rm_sc(struct qcc *qcc)
|
|
|
|
|
{
|
2023-05-11 10:55:30 -04:00
|
|
|
BUG_ON(!qcc->nb_sc); /* Ensure sc count is always valid (ie >=0). */
|
2022-07-25 05:21:46 -04:00
|
|
|
--qcc->nb_sc;
|
2022-07-25 05:53:18 -04:00
|
|
|
|
|
|
|
|
/* Reset qcc idle start for http-keep-alive timeout. Timeout will be
|
|
|
|
|
* refreshed after this on stream detach.
|
|
|
|
|
*/
|
|
|
|
|
if (!qcc->nb_sc && !qcc->nb_hreq)
|
|
|
|
|
qcc_reset_idle_start(qcc);
|
2022-07-25 05:21:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Decrement <qcc> hreq. */
|
|
|
|
|
static forceinline void qcc_rm_hreq(struct qcc *qcc)
|
|
|
|
|
{
|
2023-05-11 10:55:30 -04:00
|
|
|
BUG_ON(!qcc->nb_hreq); /* Ensure http req count is always valid (ie >=0). */
|
2022-07-25 05:21:46 -04:00
|
|
|
--qcc->nb_hreq;
|
2022-07-25 05:53:18 -04:00
|
|
|
|
|
|
|
|
/* Reset qcc idle start for http-keep-alive timeout. Timeout will be
|
|
|
|
|
* refreshed after this on I/O handler.
|
|
|
|
|
*/
|
|
|
|
|
if (!qcc->nb_sc && !qcc->nb_hreq)
|
|
|
|
|
qcc_reset_idle_start(qcc);
|
2022-07-25 05:21:46 -04:00
|
|
|
}
|
|
|
|
|
|
2022-08-02 09:57:16 -04:00
|
|
|
static inline int qcc_is_dead(const struct qcc *qcc)
|
|
|
|
|
{
|
2023-05-04 09:49:02 -04:00
|
|
|
/* Maintain connection if stream endpoints are still active. */
|
|
|
|
|
if (qcc->nb_sc)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/* Connection considered dead if either :
|
2023-11-21 13:54:16 -05:00
|
|
|
* - remote error detected at transport level
|
2023-05-04 09:49:02 -04:00
|
|
|
* - error detected locally
|
2023-10-26 12:17:29 -04:00
|
|
|
* - MUX timeout expired
|
2022-08-02 09:57:16 -04:00
|
|
|
*/
|
2023-05-04 12:52:42 -04:00
|
|
|
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL_DONE) ||
|
2023-05-04 09:49:02 -04:00
|
|
|
!qcc->task) {
|
2022-08-02 09:57:16 -04:00
|
|
|
return 1;
|
2023-05-04 09:49:02 -04:00
|
|
|
}
|
2022-08-02 09:57:16 -04:00
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Return true if the mux timeout should be armed. */
|
|
|
|
|
static inline int qcc_may_expire(struct qcc *qcc)
|
|
|
|
|
{
|
|
|
|
|
return !qcc->nb_sc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Refresh the timeout on <qcc> if needed depending on its state. */
|
|
|
|
|
static void qcc_refresh_timeout(struct qcc *qcc)
|
|
|
|
|
{
|
|
|
|
|
const struct proxy *px = qcc->proxy;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
|
2022-08-10 10:14:32 -04:00
|
|
|
if (!qcc->task) {
|
|
|
|
|
TRACE_DEVEL("already expired", QMUX_EV_QCC_WAKE, qcc->conn);
|
2022-08-02 09:57:16 -04:00
|
|
|
goto leave;
|
2022-08-10 10:14:32 -04:00
|
|
|
}
|
2022-08-02 09:57:16 -04:00
|
|
|
|
2022-08-01 11:59:38 -04:00
|
|
|
/* Check if upper layer is responsible of timeout management. */
|
|
|
|
|
if (!qcc_may_expire(qcc)) {
|
|
|
|
|
TRACE_DEVEL("not eligible for timeout", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
qcc->task->expire = TICK_ETERNITY;
|
|
|
|
|
task_queue(qcc->task);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Frontend timeout management
|
2023-02-08 09:55:24 -05:00
|
|
|
* - shutdown done -> timeout client-fin
|
2022-08-01 11:59:38 -04:00
|
|
|
* - detached streams with data left to send -> default timeout
|
2022-08-03 05:17:57 -04:00
|
|
|
* - stream waiting on incomplete request or no stream yet activated -> timeout http-request
|
2022-08-01 11:59:38 -04:00
|
|
|
* - idle after stream processing -> timeout http-keep-alive
|
2023-01-24 12:20:28 -05:00
|
|
|
*
|
|
|
|
|
* If proxy stop-stop in progress, immediate or spread close will be
|
|
|
|
|
* processed if shutdown already one or connection is idle.
|
2022-08-01 11:59:38 -04:00
|
|
|
*/
|
|
|
|
|
if (!conn_is_back(qcc->conn)) {
|
2025-02-18 05:42:59 -05:00
|
|
|
if (qcc->nb_hreq && qcc->app_st < QCC_APP_ST_SHUT) {
|
2022-08-02 09:57:16 -04:00
|
|
|
TRACE_DEVEL("one or more requests still in progress", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
qcc->task->expire = tick_add_ifset(now_ms, qcc->timeout);
|
2022-08-01 11:59:38 -04:00
|
|
|
task_queue(qcc->task);
|
|
|
|
|
goto leave;
|
2022-08-02 09:57:16 -04:00
|
|
|
}
|
|
|
|
|
|
2023-02-08 09:55:24 -05:00
|
|
|
if ((!LIST_ISEMPTY(&qcc->opening_list) || unlikely(!qcc->largest_bidi_r)) &&
|
2025-02-18 05:42:59 -05:00
|
|
|
qcc->app_st < QCC_APP_ST_SHUT) {
|
2022-08-03 05:17:57 -04:00
|
|
|
int timeout = px->timeout.httpreq;
|
|
|
|
|
struct qcs *qcs = NULL;
|
|
|
|
|
int base_time;
|
2022-08-02 09:57:16 -04:00
|
|
|
|
2022-08-03 05:17:57 -04:00
|
|
|
/* Use start time of first stream waiting on HTTP or
|
|
|
|
|
* qcc idle if no stream not yet used.
|
|
|
|
|
*/
|
|
|
|
|
if (likely(!LIST_ISEMPTY(&qcc->opening_list)))
|
|
|
|
|
qcs = LIST_ELEM(qcc->opening_list.n, struct qcs *, el_opening);
|
|
|
|
|
base_time = qcs ? qcs->start : qcc->idle_start;
|
|
|
|
|
|
|
|
|
|
TRACE_DEVEL("waiting on http request", QMUX_EV_QCC_WAKE, qcc->conn, qcs);
|
|
|
|
|
qcc->task->expire = tick_add_ifset(base_time, timeout);
|
|
|
|
|
}
|
|
|
|
|
else {
|
2025-02-18 05:42:59 -05:00
|
|
|
if (qcc->app_st >= QCC_APP_ST_SHUT) {
|
2023-02-08 09:55:24 -05:00
|
|
|
TRACE_DEVEL("connection in closing", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
qcc->task->expire = tick_add_ifset(now_ms,
|
|
|
|
|
qcc->shut_timeout);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Use http-request timeout if keep-alive timeout not set */
|
|
|
|
|
int timeout = tick_isset(px->timeout.httpka) ?
|
|
|
|
|
px->timeout.httpka : px->timeout.httpreq;
|
|
|
|
|
TRACE_DEVEL("at least one request achieved but none currently in progress", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
qcc->task->expire = tick_add_ifset(qcc->idle_start, timeout);
|
|
|
|
|
}
|
2023-01-24 12:20:28 -05:00
|
|
|
|
|
|
|
|
/* If proxy soft-stop in progress and connection is
|
|
|
|
|
* inactive, close the connection immediately. If a
|
|
|
|
|
* close-spread-time is configured, randomly spread the
|
|
|
|
|
* timer over a closing window.
|
|
|
|
|
*/
|
|
|
|
|
if ((qcc->proxy->flags & (PR_FL_DISABLED|PR_FL_STOPPED)) &&
|
|
|
|
|
!(global.tune.options & GTUNE_DISABLE_ACTIVE_CLOSE)) {
|
|
|
|
|
|
|
|
|
|
/* Wake timeout task immediately if window already expired. */
|
|
|
|
|
int remaining_window = tick_isset(global.close_spread_end) ?
|
|
|
|
|
tick_remain(now_ms, global.close_spread_end) : 0;
|
|
|
|
|
|
|
|
|
|
TRACE_DEVEL("proxy disabled, prepare connection soft-stop", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
if (remaining_window) {
|
|
|
|
|
/* We don't need to reset the expire if it would
|
|
|
|
|
* already happen before the close window end.
|
|
|
|
|
*/
|
|
|
|
|
if (!tick_isset(qcc->task->expire) ||
|
|
|
|
|
tick_is_le(global.close_spread_end, qcc->task->expire)) {
|
|
|
|
|
/* Set an expire value shorter than the current value
|
|
|
|
|
* because the close spread window end comes earlier.
|
|
|
|
|
*/
|
|
|
|
|
qcc->task->expire = tick_add(now_ms,
|
|
|
|
|
statistical_prng_range(remaining_window));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* We are past the soft close window end, wake the timeout
|
|
|
|
|
* task up immediately.
|
|
|
|
|
*/
|
2024-11-15 09:41:21 -05:00
|
|
|
qcc->task->expire = tick_add(now_ms, 0);
|
2023-01-24 12:20:28 -05:00
|
|
|
task_wakeup(qcc->task, TASK_WOKEN_TIMER);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-03 05:17:57 -04:00
|
|
|
}
|
2022-08-02 09:57:16 -04:00
|
|
|
}
|
2022-08-01 11:59:38 -04:00
|
|
|
|
|
|
|
|
/* fallback to default timeout if frontend specific undefined or for
|
|
|
|
|
* backend connections.
|
|
|
|
|
*/
|
|
|
|
|
if (!tick_isset(qcc->task->expire)) {
|
|
|
|
|
TRACE_DEVEL("fallback to default timeout", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
qcc->task->expire = tick_add_ifset(now_ms, qcc->timeout);
|
2022-08-02 09:57:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
task_queue(qcc->task);
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_NEW, qcc->conn);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 10:48:42 -04:00
|
|
|
/* Mark a stream as open if it was idle. This can be used on every
|
|
|
|
|
* successful emission/reception operation to update the stream state.
|
|
|
|
|
*/
|
|
|
|
|
static void qcs_idle_open(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
/* This operation must not be used if the stream is already closed. */
|
|
|
|
|
BUG_ON_HOT(qcs->st == QC_SS_CLO);
|
|
|
|
|
|
|
|
|
|
if (qcs->st == QC_SS_IDLE) {
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_STATE("opening stream", QMUX_EV_QCS_NEW, qcs->qcc->conn, qcs);
|
2022-07-01 10:48:42 -04:00
|
|
|
qcs->st = QC_SS_OPEN;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Close the local channel of <qcs> instance. */
|
|
|
|
|
static void qcs_close_local(struct qcs *qcs)
|
|
|
|
|
{
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_STATE("closing stream locally", QMUX_EV_QCS_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
|
2022-07-01 10:48:42 -04:00
|
|
|
/* The stream must have already been opened. */
|
|
|
|
|
BUG_ON_HOT(qcs->st == QC_SS_IDLE);
|
|
|
|
|
|
|
|
|
|
/* This operation cannot be used multiple times. */
|
|
|
|
|
BUG_ON_HOT(qcs->st == QC_SS_HLOC || qcs->st == QC_SS_CLO);
|
|
|
|
|
|
|
|
|
|
if (quic_stream_is_bidi(qcs->id)) {
|
|
|
|
|
qcs->st = (qcs->st == QC_SS_HREM) ? QC_SS_CLO : QC_SS_HLOC;
|
BUG/MEDIUM: mux-quic: fix nb_hreq decrement
nb_hreq is a counter on qcc for active HTTP requests. It is incremented
for each qcs where a full HTTP request was received. It is decremented
when the stream is closed locally :
- on HTTP response fully transmitted
- on stream reset
A bug will occur if a stream is resetted without having processed a full
HTTP request. nb_hreq will be decremented whereas it was not
incremented. This will lead to a crash when building with
DEBUG_STRICT=2. If BUG_ON_HOT are not active, nb_hreq counter will wrap
which may break the timeout logic for the connection.
This bug was triggered on haproxy.org. It can be reproduced by
simulating the reception of a STOP_SENDING frame instead of a STREAM one
by patching qc_handle_strm_frm() :
+ if (quic_stream_is_bidi(strm_frm->id))
+ qcc_recv_stop_sending(qc->qcc, strm_frm->id, 0);
+ //ret = qcc_recv(qc->qcc, strm_frm->id, strm_frm->len,
+ // strm_frm->offset.key, strm_frm->fin,
+ // (char *)strm_frm->data);
To fix this bug, a qcs is now flagged with a new QC_SF_HREQ_RECV. This
is set when the full HTTP request is received. When the stream is closed
locally, nb_hreq will be decremented only if this flag was set.
This must be backported up to 2.6.
2022-09-19 05:58:24 -04:00
|
|
|
|
|
|
|
|
if (qcs->flags & QC_SF_HREQ_RECV)
|
|
|
|
|
qcc_rm_hreq(qcs->qcc);
|
2022-07-01 10:48:42 -04:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Only local uni streams are valid for this operation. */
|
|
|
|
|
BUG_ON_HOT(quic_stream_is_remote(qcs->qcc, qcs->id));
|
|
|
|
|
qcs->st = QC_SS_CLO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-17 05:16:34 -05:00
|
|
|
/* Returns true if <qcs> can be purged. */
|
|
|
|
|
static int qcs_is_completed(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
/* A stream is completed if fully closed and stconn released, or simply
|
|
|
|
|
* detached and everything already sent.
|
|
|
|
|
*/
|
|
|
|
|
return (qcs->st == QC_SS_CLO && !qcs_sc(qcs)) ||
|
|
|
|
|
(qcs_is_close_local(qcs) && (qcs->flags & QC_SF_DETACH));
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 10:48:42 -04:00
|
|
|
/* Close the remote channel of <qcs> instance. */
|
|
|
|
|
static void qcs_close_remote(struct qcs *qcs)
|
|
|
|
|
{
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_STATE("closing stream remotely", QMUX_EV_QCS_RECV, qcs->qcc->conn, qcs);
|
|
|
|
|
|
2022-07-01 10:48:42 -04:00
|
|
|
/* The stream must have already been opened. */
|
|
|
|
|
BUG_ON_HOT(qcs->st == QC_SS_IDLE);
|
|
|
|
|
|
|
|
|
|
/* This operation cannot be used multiple times. */
|
|
|
|
|
BUG_ON_HOT(qcs->st == QC_SS_HREM || qcs->st == QC_SS_CLO);
|
|
|
|
|
|
|
|
|
|
if (quic_stream_is_bidi(qcs->id)) {
|
|
|
|
|
qcs->st = (qcs->st == QC_SS_HLOC) ? QC_SS_CLO : QC_SS_HREM;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Only remote uni streams are valid for this operation. */
|
|
|
|
|
BUG_ON_HOT(quic_stream_is_local(qcs->qcc, qcs->id));
|
|
|
|
|
qcs->st = QC_SS_CLO;
|
|
|
|
|
}
|
2024-12-17 05:16:34 -05:00
|
|
|
|
|
|
|
|
if (qcs_is_completed(qcs)) {
|
|
|
|
|
BUG_ON(LIST_INLIST(&qcs->el_send));
|
|
|
|
|
TRACE_STATE("add stream in purg_list", QMUX_EV_QCS_RECV, qcs->qcc->conn, qcs);
|
|
|
|
|
LIST_APPEND(&qcs->qcc->purg_list, &qcs->el_send);
|
|
|
|
|
}
|
2022-07-01 10:48:42 -04:00
|
|
|
}
|
|
|
|
|
|
2023-08-04 10:10:18 -04:00
|
|
|
int qcs_is_close_local(struct qcs *qcs)
|
2022-07-01 10:48:42 -04:00
|
|
|
{
|
|
|
|
|
return qcs->st == QC_SS_HLOC || qcs->st == QC_SS_CLO;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-04 10:10:18 -04:00
|
|
|
int qcs_is_close_remote(struct qcs *qcs)
|
2022-07-01 10:48:42 -04:00
|
|
|
{
|
|
|
|
|
return qcs->st == QC_SS_HREM || qcs->st == QC_SS_CLO;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 11:00:54 -04:00
|
|
|
/* Allocate if needed buffer <ncbuf> for stream <qcs>.
|
|
|
|
|
*
|
|
|
|
|
* Returns the buffer instance or NULL on allocation failure.
|
|
|
|
|
*/
|
2023-05-30 09:04:46 -04:00
|
|
|
static struct ncbuf *qcs_get_ncbuf(struct qcs *qcs, struct ncbuf *ncbuf)
|
2022-05-13 08:49:05 -04:00
|
|
|
{
|
|
|
|
|
struct buffer buf = BUF_NULL;
|
|
|
|
|
|
|
|
|
|
if (ncb_is_null(ncbuf)) {
|
MINOR: dynbuf: pass a criticality argument to b_alloc()
The goal is to indicate how critical the allocation is, between the
least one (growing an existing buffer ring) and the topmost one (boot
time allocation for the life of the process).
The 3 tcp-based muxes (h1, h2, fcgi) use a common allocation function
to try to allocate otherwise subscribe. There's currently no distinction
of direction nor part that tries to allocate, and this should be revisited
to improve this situation, particularly when we consider that mux-h2 can
reduce its Tx allocations if needed.
For now, 4 main levels are planned, to translate how the data travels
inside haproxy from a producer to a consumer:
- MUX_RX: buffer used to receive data from the OS
- SE_RX: buffer used to place a transformation of the RX data for
a mux, or to produce a response for an applet
- CHANNEL: the channel buffer for sync recv
- MUX_TX: buffer used to transfer data from the channel to the outside,
generally a mux but there can be a few specificities (e.g.
http client's response buffer passed to the application,
which also gets a transformation of the channel data).
The other levels are a bit different in that they don't strictly need to
allocate for the first two ones, or they're permanent for the last one
(used by compression).
2024-04-16 02:55:20 -04:00
|
|
|
if (!b_alloc(&buf, DB_MUX_RX))
|
2023-05-11 11:00:54 -04:00
|
|
|
return NULL;
|
2022-05-13 08:49:05 -04:00
|
|
|
|
|
|
|
|
*ncbuf = ncb_make(buf.area, buf.size, 0);
|
|
|
|
|
ncb_init(ncbuf, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ncbuf;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-29 13:26:53 -04:00
|
|
|
/* Notify an eventual subscriber on <qcs> or else wakeup up the stconn layer if
|
2022-07-06 08:54:34 -04:00
|
|
|
* initialized.
|
|
|
|
|
*/
|
|
|
|
|
static void qcs_alert(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
if (qcs->subs) {
|
|
|
|
|
qcs_notify_recv(qcs);
|
|
|
|
|
qcs_notify_send(qcs);
|
|
|
|
|
}
|
|
|
|
|
else if (qcs_sc(qcs) && qcs->sd->sc->app_ops->wake) {
|
2023-05-11 07:41:41 -04:00
|
|
|
TRACE_POINT(QMUX_EV_STRM_WAKE, qcs->qcc->conn, qcs);
|
2022-07-06 08:54:34 -04:00
|
|
|
qcs->sd->sc->app_ops->wake(qcs->sd->sc);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-06 05:24:00 -05:00
|
|
|
int qcs_subscribe(struct qcs *qcs, int event_type, struct wait_event *es)
|
|
|
|
|
{
|
2022-03-24 12:10:00 -04:00
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_STRM_SEND|QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
2021-12-06 05:24:00 -05:00
|
|
|
|
|
|
|
|
BUG_ON(event_type & ~(SUB_RETRY_SEND|SUB_RETRY_RECV));
|
|
|
|
|
BUG_ON(qcs->subs && qcs->subs != es);
|
|
|
|
|
|
|
|
|
|
es->events |= event_type;
|
|
|
|
|
qcs->subs = es;
|
|
|
|
|
|
2022-03-24 12:10:00 -04:00
|
|
|
if (event_type & SUB_RETRY_RECV)
|
|
|
|
|
TRACE_DEVEL("subscribe(recv)", QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
if (event_type & SUB_RETRY_SEND)
|
|
|
|
|
TRACE_DEVEL("subscribe(send)", QMUX_EV_STRM_SEND, qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_SEND|QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
|
|
|
|
|
2021-12-06 05:24:00 -05:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void qcs_notify_recv(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
if (qcs->subs && qcs->subs->events & SUB_RETRY_RECV) {
|
2023-05-11 07:41:41 -04:00
|
|
|
TRACE_POINT(QMUX_EV_STRM_WAKE, qcs->qcc->conn, qcs);
|
2021-12-06 05:24:00 -05:00
|
|
|
tasklet_wakeup(qcs->subs->tasklet);
|
|
|
|
|
qcs->subs->events &= ~SUB_RETRY_RECV;
|
|
|
|
|
if (!qcs->subs->events)
|
|
|
|
|
qcs->subs = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void qcs_notify_send(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
if (qcs->subs && qcs->subs->events & SUB_RETRY_SEND) {
|
2023-05-11 07:41:41 -04:00
|
|
|
TRACE_POINT(QMUX_EV_STRM_WAKE, qcs->qcc->conn, qcs);
|
2021-12-06 05:24:00 -05:00
|
|
|
tasklet_wakeup(qcs->subs->tasklet);
|
|
|
|
|
qcs->subs->events &= ~SUB_RETRY_SEND;
|
|
|
|
|
if (!qcs->subs->events)
|
|
|
|
|
qcs->subs = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 11:55:10 -04:00
|
|
|
/* Returns total number of bytes not already sent to quic-conn layer. */
|
|
|
|
|
static uint64_t qcs_prep_bytes(const struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
struct buffer *out = qc_stream_buf_get(qcs->stream);
|
|
|
|
|
uint64_t diff, base_off;
|
|
|
|
|
|
|
|
|
|
if (!out)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/* if ack_offset < buf_offset, it points to an older buffer. */
|
|
|
|
|
base_off = MAX(qcs->stream->buf_offset, qcs->stream->ack_offset);
|
|
|
|
|
diff = qcs->tx.fc.off_real - base_off;
|
|
|
|
|
return b_data(out) - diff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Used as a callback for qc_stream_desc layer to notify about emission of a
|
|
|
|
|
* STREAM frame of <data> length starting at <offset>.
|
|
|
|
|
*/
|
|
|
|
|
static void qmux_ctrl_send(struct qc_stream_desc *stream, uint64_t data, uint64_t offset)
|
|
|
|
|
{
|
|
|
|
|
struct qcs *qcs = stream->ctx;
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
uint64_t diff;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
/* Real off MUST always be the greatest offset sent. */
|
|
|
|
|
BUG_ON(offset > qcs->tx.fc.off_real);
|
|
|
|
|
|
2024-10-21 04:28:18 -04:00
|
|
|
/* Check if the STREAM frame has already been notified. An empty FIN
|
|
|
|
|
* frame must not be considered retransmitted.
|
2024-09-25 11:55:10 -04:00
|
|
|
*/
|
2024-10-21 04:28:18 -04:00
|
|
|
if (data && offset + data <= qcs->tx.fc.off_real) {
|
2024-09-25 11:55:10 -04:00
|
|
|
TRACE_DEVEL("offset already notified", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-21 04:28:18 -04:00
|
|
|
/* An empty STREAM frame is only used to notify FIN. A retransmitted
|
|
|
|
|
* empty FIN cannot be notified as QCS will be unsubscribed first.
|
|
|
|
|
*/
|
|
|
|
|
BUG_ON(!data && !(qcs->flags & QC_SF_FIN_STREAM));
|
|
|
|
|
|
2024-09-25 11:55:10 -04:00
|
|
|
qcs_idle_open(qcs);
|
|
|
|
|
|
|
|
|
|
diff = offset + data - qcs->tx.fc.off_real;
|
|
|
|
|
if (diff) {
|
|
|
|
|
struct quic_fctl *fc_conn = &qcc->tx.fc;
|
|
|
|
|
struct quic_fctl *fc_strm = &qcs->tx.fc;
|
|
|
|
|
|
|
|
|
|
/* Ensure real offset never exceeds soft value. */
|
|
|
|
|
BUG_ON(fc_conn->off_real + diff > fc_conn->off_soft);
|
|
|
|
|
BUG_ON(fc_strm->off_real + diff > fc_strm->off_soft);
|
|
|
|
|
|
|
|
|
|
/* increase offset sum on connection */
|
|
|
|
|
if (qfctl_rinc(fc_conn, diff)) {
|
|
|
|
|
TRACE_STATE("connection flow-control reached",
|
|
|
|
|
QMUX_EV_QCS_SEND, qcc->conn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* increase offset on stream */
|
|
|
|
|
if (qfctl_rinc(fc_strm, diff)) {
|
|
|
|
|
TRACE_STATE("stream flow-control reached",
|
|
|
|
|
QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
}
|
|
|
|
|
/* Release buffer if everything sent and buf is full or stream is waiting for room. */
|
|
|
|
|
if (!qcs_prep_bytes(qcs) &&
|
|
|
|
|
(b_full(&qcs->stream->buf->buf) || qcs->flags & QC_SF_BLK_MROOM)) {
|
|
|
|
|
qc_stream_buf_release(qcs->stream);
|
|
|
|
|
qcs->flags &= ~QC_SF_BLK_MROOM;
|
|
|
|
|
qcs_notify_send(qcs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Add measurement for send rate. This is done at the MUX layer
|
|
|
|
|
* to account only for STREAM frames without retransmission.
|
|
|
|
|
*/
|
|
|
|
|
increment_send_rate(diff, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!qc_stream_buf_get(qcs->stream) || !qcs_prep_bytes(qcs)) {
|
|
|
|
|
/* Remove stream from send_list if all was sent. */
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_send);
|
|
|
|
|
TRACE_STATE("stream sent done", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
if (qcs->flags & (QC_SF_FIN_STREAM|QC_SF_DETACH)) {
|
|
|
|
|
/* Close stream locally. */
|
|
|
|
|
qcs_close_local(qcs);
|
|
|
|
|
|
|
|
|
|
if (qcs->flags & QC_SF_FIN_STREAM) {
|
|
|
|
|
qcs->stream->flags |= QC_SD_FL_WAIT_FOR_FIN;
|
|
|
|
|
/* Reset flag to not emit multiple FIN STREAM frames. */
|
|
|
|
|
qcs->flags &= ~QC_SF_FIN_STREAM;
|
|
|
|
|
}
|
2024-10-21 04:28:18 -04:00
|
|
|
|
|
|
|
|
/* Unsubscribe from streamdesc when everything sent. */
|
|
|
|
|
qc_stream_desc_sub_send(qcs->stream, NULL);
|
2024-12-17 05:16:34 -05:00
|
|
|
|
|
|
|
|
if (qcs_is_completed(qcs)) {
|
|
|
|
|
TRACE_STATE("add stream in purg_list", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
LIST_APPEND(&qcc->purg_list, &qcs->el_send);
|
|
|
|
|
}
|
2024-09-25 11:55:10 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-23 09:26:54 -04:00
|
|
|
/* Returns true if <qcc> buffer window does not have room for a new buffer. */
|
MAJOR: mux-quic: allocate Tx buffers based on congestion window
Each QUIC MUX may allocate buffers for MUX stream emission. These
buffers are then shared with quic_conn to handle ACK reception and
retransmission. A limit on the number of concurrent buffers used per
connection has been defined statically and can be updated via a
configuration option. This commit replaces the limit to instead use the
current underlying congestion window size.
The purpose of this change is to remove the artificial static buffer
count limit, which may be difficult to choose. Indeed, if a connection
performs with minimal loss rate, the buffer count would limit severely
its throughput. It could be increase to fix this, but it also impacts
others connections, even with less optimal performance, causing too many
extra data buffering on the MUX layer. By using the dynamic congestion
window size, haproxy ensures that MUX buffering corresponds roughly to
the network conditions.
Using QCC <buf_in_flight>, a new buffer can be allocated if it is less
than the current window size. If not, QCS emission is interrupted and
haproxy stream layer will subscribe until a new buffer is ready.
One of the criticals parts is to ensure that MUX layer previously
blocked on buffer allocation is properly woken up when sending can be
retried. This occurs on two occasions :
* after an already used Tx buffer is cleared on ACK reception. This case
is already handled by qcc_notify_buf() via quic_stream layer.
* on congestion window increase. A new qcc_notify_buf() invokation is
added into qc_notify_send().
Finally, remove <avail_bufs> QCC field which is now unused.
This commit is labelled MAJOR as it may have unexpected effect and could
cause significant behavior change. For example, in previous
implementation QUIC MUX would be able to buffer more data even if the
congestion window is small. With this patch, data cannot be transferred
from the stream layer which may cause more streams to be shut down on
client timeout. Another effect may be more CPU consumption as the
connection limit would be hit more often, causing more streams to be
interrupted and woken up in cycle.
2024-06-13 11:06:40 -04:00
|
|
|
static inline int qcc_bufwnd_full(const struct qcc *qcc)
|
|
|
|
|
{
|
|
|
|
|
const struct quic_conn *qc = qcc->conn->handle.qc;
|
|
|
|
|
return qcc->tx.buf_in_flight >= qc->path->cwnd;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 12:25:08 -04:00
|
|
|
static void qmux_ctrl_room(struct qc_stream_desc *stream, uint64_t room)
|
|
|
|
|
{
|
|
|
|
|
/* Context is different for active and released streams. */
|
|
|
|
|
struct qcc *qcc = !(stream->flags & QC_SD_FL_RELEASE) ?
|
|
|
|
|
((struct qcs *)stream->ctx)->qcc : stream->ctx;
|
|
|
|
|
qcc_notify_buf(qcc, room);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-13 02:51:46 -04:00
|
|
|
/* Report that one or several stream-desc buffers have been released for <qcc>
|
MAJOR: mux-quic: allocate Tx buffers based on congestion window
Each QUIC MUX may allocate buffers for MUX stream emission. These
buffers are then shared with quic_conn to handle ACK reception and
retransmission. A limit on the number of concurrent buffers used per
connection has been defined statically and can be updated via a
configuration option. This commit replaces the limit to instead use the
current underlying congestion window size.
The purpose of this change is to remove the artificial static buffer
count limit, which may be difficult to choose. Indeed, if a connection
performs with minimal loss rate, the buffer count would limit severely
its throughput. It could be increase to fix this, but it also impacts
others connections, even with less optimal performance, causing too many
extra data buffering on the MUX layer. By using the dynamic congestion
window size, haproxy ensures that MUX buffering corresponds roughly to
the network conditions.
Using QCC <buf_in_flight>, a new buffer can be allocated if it is less
than the current window size. If not, QCS emission is interrupted and
haproxy stream layer will subscribe until a new buffer is ready.
One of the criticals parts is to ensure that MUX layer previously
blocked on buffer allocation is properly woken up when sending can be
retried. This occurs on two occasions :
* after an already used Tx buffer is cleared on ACK reception. This case
is already handled by qcc_notify_buf() via quic_stream layer.
* on congestion window increase. A new qcc_notify_buf() invokation is
added into qc_notify_send().
Finally, remove <avail_bufs> QCC field which is now unused.
This commit is labelled MAJOR as it may have unexpected effect and could
cause significant behavior change. For example, in previous
implementation QUIC MUX would be able to buffer more data even if the
congestion window is small. With this patch, data cannot be transferred
from the stream layer which may cause more streams to be shut down on
client timeout. Another effect may be more CPU consumption as the
connection limit would be hit more often, causing more streams to be
interrupted and woken up in cycle.
2024-06-13 11:06:40 -04:00
|
|
|
* connection. <free_size> represent the sum of freed buffers sizes. May also
|
|
|
|
|
* be used to notify about congestion window increase, in which case
|
|
|
|
|
* <free_size> can be nul.
|
2024-01-17 09:15:55 -05:00
|
|
|
*/
|
MAJOR: mux-quic: allocate Tx buffers based on congestion window
Each QUIC MUX may allocate buffers for MUX stream emission. These
buffers are then shared with quic_conn to handle ACK reception and
retransmission. A limit on the number of concurrent buffers used per
connection has been defined statically and can be updated via a
configuration option. This commit replaces the limit to instead use the
current underlying congestion window size.
The purpose of this change is to remove the artificial static buffer
count limit, which may be difficult to choose. Indeed, if a connection
performs with minimal loss rate, the buffer count would limit severely
its throughput. It could be increase to fix this, but it also impacts
others connections, even with less optimal performance, causing too many
extra data buffering on the MUX layer. By using the dynamic congestion
window size, haproxy ensures that MUX buffering corresponds roughly to
the network conditions.
Using QCC <buf_in_flight>, a new buffer can be allocated if it is less
than the current window size. If not, QCS emission is interrupted and
haproxy stream layer will subscribe until a new buffer is ready.
One of the criticals parts is to ensure that MUX layer previously
blocked on buffer allocation is properly woken up when sending can be
retried. This occurs on two occasions :
* after an already used Tx buffer is cleared on ACK reception. This case
is already handled by qcc_notify_buf() via quic_stream layer.
* on congestion window increase. A new qcc_notify_buf() invokation is
added into qc_notify_send().
Finally, remove <avail_bufs> QCC field which is now unused.
This commit is labelled MAJOR as it may have unexpected effect and could
cause significant behavior change. For example, in previous
implementation QUIC MUX would be able to buffer more data even if the
congestion window is small. With this patch, data cannot be transferred
from the stream layer which may cause more streams to be shut down on
client timeout. Another effect may be more CPU consumption as the
connection limit would be hit more often, causing more streams to be
interrupted and woken up in cycle.
2024-06-13 11:06:40 -04:00
|
|
|
void qcc_notify_buf(struct qcc *qcc, uint64_t free_size)
|
2024-01-17 09:15:55 -05:00
|
|
|
{
|
|
|
|
|
struct qcs *qcs;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
|
2024-08-13 02:51:46 -04:00
|
|
|
/* Cannot have a negative buf_in_flight counter */
|
|
|
|
|
BUG_ON(qcc->tx.buf_in_flight < free_size);
|
|
|
|
|
qcc->tx.buf_in_flight -= free_size;
|
|
|
|
|
|
MAJOR: mux-quic: allocate Tx buffers based on congestion window
Each QUIC MUX may allocate buffers for MUX stream emission. These
buffers are then shared with quic_conn to handle ACK reception and
retransmission. A limit on the number of concurrent buffers used per
connection has been defined statically and can be updated via a
configuration option. This commit replaces the limit to instead use the
current underlying congestion window size.
The purpose of this change is to remove the artificial static buffer
count limit, which may be difficult to choose. Indeed, if a connection
performs with minimal loss rate, the buffer count would limit severely
its throughput. It could be increase to fix this, but it also impacts
others connections, even with less optimal performance, causing too many
extra data buffering on the MUX layer. By using the dynamic congestion
window size, haproxy ensures that MUX buffering corresponds roughly to
the network conditions.
Using QCC <buf_in_flight>, a new buffer can be allocated if it is less
than the current window size. If not, QCS emission is interrupted and
haproxy stream layer will subscribe until a new buffer is ready.
One of the criticals parts is to ensure that MUX layer previously
blocked on buffer allocation is properly woken up when sending can be
retried. This occurs on two occasions :
* after an already used Tx buffer is cleared on ACK reception. This case
is already handled by qcc_notify_buf() via quic_stream layer.
* on congestion window increase. A new qcc_notify_buf() invokation is
added into qc_notify_send().
Finally, remove <avail_bufs> QCC field which is now unused.
This commit is labelled MAJOR as it may have unexpected effect and could
cause significant behavior change. For example, in previous
implementation QUIC MUX would be able to buffer more data even if the
congestion window is small. With this patch, data cannot be transferred
from the stream layer which may cause more streams to be shut down on
client timeout. Another effect may be more CPU consumption as the
connection limit would be hit more often, causing more streams to be
interrupted and woken up in cycle.
2024-06-13 11:06:40 -04:00
|
|
|
if (qcc_bufwnd_full(qcc))
|
|
|
|
|
return;
|
|
|
|
|
|
2024-01-17 09:15:55 -05:00
|
|
|
if (qcc->flags & QC_CF_CONN_FULL) {
|
MAJOR: mux-quic: allocate Tx buffers based on congestion window
Each QUIC MUX may allocate buffers for MUX stream emission. These
buffers are then shared with quic_conn to handle ACK reception and
retransmission. A limit on the number of concurrent buffers used per
connection has been defined statically and can be updated via a
configuration option. This commit replaces the limit to instead use the
current underlying congestion window size.
The purpose of this change is to remove the artificial static buffer
count limit, which may be difficult to choose. Indeed, if a connection
performs with minimal loss rate, the buffer count would limit severely
its throughput. It could be increase to fix this, but it also impacts
others connections, even with less optimal performance, causing too many
extra data buffering on the MUX layer. By using the dynamic congestion
window size, haproxy ensures that MUX buffering corresponds roughly to
the network conditions.
Using QCC <buf_in_flight>, a new buffer can be allocated if it is less
than the current window size. If not, QCS emission is interrupted and
haproxy stream layer will subscribe until a new buffer is ready.
One of the criticals parts is to ensure that MUX layer previously
blocked on buffer allocation is properly woken up when sending can be
retried. This occurs on two occasions :
* after an already used Tx buffer is cleared on ACK reception. This case
is already handled by qcc_notify_buf() via quic_stream layer.
* on congestion window increase. A new qcc_notify_buf() invokation is
added into qc_notify_send().
Finally, remove <avail_bufs> QCC field which is now unused.
This commit is labelled MAJOR as it may have unexpected effect and could
cause significant behavior change. For example, in previous
implementation QUIC MUX would be able to buffer more data even if the
congestion window is small. With this patch, data cannot be transferred
from the stream layer which may cause more streams to be shut down on
client timeout. Another effect may be more CPU consumption as the
connection limit would be hit more often, causing more streams to be
interrupted and woken up in cycle.
2024-06-13 11:06:40 -04:00
|
|
|
TRACE_STATE("buf window now available", QMUX_EV_QCC_WAKE, qcc->conn);
|
2024-01-17 09:15:55 -05:00
|
|
|
qcc->flags &= ~QC_CF_CONN_FULL;
|
|
|
|
|
}
|
|
|
|
|
|
MAJOR: mux-quic: allocate Tx buffers based on congestion window
Each QUIC MUX may allocate buffers for MUX stream emission. These
buffers are then shared with quic_conn to handle ACK reception and
retransmission. A limit on the number of concurrent buffers used per
connection has been defined statically and can be updated via a
configuration option. This commit replaces the limit to instead use the
current underlying congestion window size.
The purpose of this change is to remove the artificial static buffer
count limit, which may be difficult to choose. Indeed, if a connection
performs with minimal loss rate, the buffer count would limit severely
its throughput. It could be increase to fix this, but it also impacts
others connections, even with less optimal performance, causing too many
extra data buffering on the MUX layer. By using the dynamic congestion
window size, haproxy ensures that MUX buffering corresponds roughly to
the network conditions.
Using QCC <buf_in_flight>, a new buffer can be allocated if it is less
than the current window size. If not, QCS emission is interrupted and
haproxy stream layer will subscribe until a new buffer is ready.
One of the criticals parts is to ensure that MUX layer previously
blocked on buffer allocation is properly woken up when sending can be
retried. This occurs on two occasions :
* after an already used Tx buffer is cleared on ACK reception. This case
is already handled by qcc_notify_buf() via quic_stream layer.
* on congestion window increase. A new qcc_notify_buf() invokation is
added into qc_notify_send().
Finally, remove <avail_bufs> QCC field which is now unused.
This commit is labelled MAJOR as it may have unexpected effect and could
cause significant behavior change. For example, in previous
implementation QUIC MUX would be able to buffer more data even if the
congestion window is small. With this patch, data cannot be transferred
from the stream layer which may cause more streams to be shut down on
client timeout. Another effect may be more CPU consumption as the
connection limit would be hit more often, causing more streams to be
interrupted and woken up in cycle.
2024-06-13 11:06:40 -04:00
|
|
|
/* TODO an optimization would be to only wake up a limited count of QCS
|
|
|
|
|
* instances based on <free_size>. But it may not work if a woken QCS
|
|
|
|
|
* is in error and does not try to allocate a buffer, leaving the
|
|
|
|
|
* unwoken QCS indefinitely in the buflist.
|
2024-08-13 05:57:50 -04:00
|
|
|
*/
|
|
|
|
|
while (!LIST_ISEMPTY(&qcc->buf_wait_list)) {
|
2024-01-17 09:15:55 -05:00
|
|
|
qcs = LIST_ELEM(qcc->buf_wait_list.n, struct qcs *, el_buf);
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_buf);
|
2024-07-31 12:43:55 -04:00
|
|
|
tot_time_stop(&qcs->timer.buf);
|
2024-01-17 09:15:55 -05:00
|
|
|
qcs_notify_send(qcs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-04 09:49:02 -04:00
|
|
|
/* A fatal error is detected locally for <qcc> connection. It should be closed
|
2023-05-09 12:01:09 -04:00
|
|
|
* with a CONNECTION_CLOSE using <err> code. Set <app> to true to indicate that
|
|
|
|
|
* the code must be considered as an application level error. This function
|
|
|
|
|
* must not be called more than once by connection.
|
2023-05-04 09:49:02 -04:00
|
|
|
*/
|
2023-05-09 12:01:09 -04:00
|
|
|
void qcc_set_error(struct qcc *qcc, int err, int app)
|
2023-05-04 09:49:02 -04:00
|
|
|
{
|
|
|
|
|
/* This must not be called multiple times per connection. */
|
|
|
|
|
BUG_ON(qcc->flags & QC_CF_ERRL);
|
|
|
|
|
|
|
|
|
|
TRACE_STATE("connection on error", QMUX_EV_QCC_ERR, qcc->conn);
|
|
|
|
|
|
|
|
|
|
qcc->flags |= QC_CF_ERRL;
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc->err = app ? quic_err_app(err) : quic_err_transport(err);
|
2023-05-09 12:20:45 -04:00
|
|
|
|
|
|
|
|
/* TODO
|
2023-05-30 09:04:46 -04:00
|
|
|
* Ensure qcc_io_send() will be conducted to convert QC_CF_ERRL in
|
2023-05-09 12:20:45 -04:00
|
|
|
* QC_CF_ERRL_DONE with CONNECTION_CLOSE frame emission. This may be
|
|
|
|
|
* unnecessary if we are currently in the MUX tasklet context, but it
|
|
|
|
|
* is too tedious too not forget a wakeup outside of this function for
|
|
|
|
|
* the moment.
|
|
|
|
|
*/
|
2025-01-03 04:36:39 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2023-05-04 09:49:02 -04:00
|
|
|
}
|
|
|
|
|
|
2024-05-13 03:05:27 -04:00
|
|
|
/* Increment glitch counter for <qcc> connection by <inc> steps. If configured
|
|
|
|
|
* threshold reached, close the connection with an error code.
|
|
|
|
|
*/
|
2024-11-14 09:38:25 -05:00
|
|
|
int _qcc_report_glitch(struct qcc *qcc, int inc)
|
2024-05-13 03:05:27 -04:00
|
|
|
{
|
|
|
|
|
const int max = global.tune.quic_frontend_glitches_threshold;
|
|
|
|
|
|
|
|
|
|
qcc->glitches += inc;
|
|
|
|
|
if (max && qcc->glitches >= max && !(qcc->flags & QC_CF_ERRL)) {
|
|
|
|
|
if (qcc->app_ops->report_susp) {
|
|
|
|
|
qcc->app_ops->report_susp(qcc->ctx);
|
|
|
|
|
qcc_set_error(qcc, qcc->err.code, 1);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-04 09:50:33 -04:00
|
|
|
/* Open a locally initiated stream for the connection <qcc>. Set <bidi> for a
|
|
|
|
|
* bidirectional stream, else an unidirectional stream is opened. The next
|
|
|
|
|
* available ID on the connection will be used according to the stream type.
|
|
|
|
|
*
|
|
|
|
|
* Returns the allocated stream instance or NULL on error.
|
2021-12-21 05:53:10 -05:00
|
|
|
*/
|
2022-07-08 05:53:22 -04:00
|
|
|
struct qcs *qcc_init_stream_local(struct qcc *qcc, int bidi)
|
2022-07-04 09:50:33 -04:00
|
|
|
{
|
|
|
|
|
struct qcs *qcs;
|
|
|
|
|
enum qcs_type type;
|
|
|
|
|
uint64_t *next;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCS_NEW, qcc->conn);
|
|
|
|
|
|
|
|
|
|
if (bidi) {
|
|
|
|
|
next = &qcc->next_bidi_l;
|
|
|
|
|
type = conn_is_back(qcc->conn) ? QCS_CLT_BIDI : QCS_SRV_BIDI;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
next = &qcc->next_uni_l;
|
|
|
|
|
type = conn_is_back(qcc->conn) ? QCS_CLT_UNI : QCS_SRV_UNI;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* TODO ensure that we won't overflow remote peer flow control limit on
|
|
|
|
|
* streams. Else, we should emit a STREAMS_BLOCKED frame.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
qcs = qcs_new(qcc, *next, type);
|
2022-08-10 10:14:32 -04:00
|
|
|
if (!qcs) {
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
|
2024-06-20 11:51:35 -04:00
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCS_NEW, qcc->conn);
|
2022-07-04 09:50:33 -04:00
|
|
|
return NULL;
|
2022-08-10 10:14:32 -04:00
|
|
|
}
|
2022-07-04 09:50:33 -04:00
|
|
|
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_PROTO("opening local stream", QMUX_EV_QCS_NEW, qcc->conn, qcs);
|
2022-07-04 09:50:33 -04:00
|
|
|
*next += 4;
|
|
|
|
|
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_NEW, qcc->conn, qcs);
|
2022-07-04 09:50:33 -04:00
|
|
|
return qcs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Open a remote initiated stream for the connection <qcc> with ID <id>. The
|
|
|
|
|
* caller is responsible to ensure that a stream with the same ID was not
|
|
|
|
|
* already opened. This function will also create all intermediaries streams
|
|
|
|
|
* with ID smaller than <id> not already opened before.
|
|
|
|
|
*
|
|
|
|
|
* Returns the allocated stream instance or NULL on error.
|
|
|
|
|
*/
|
2022-07-08 05:53:22 -04:00
|
|
|
static struct qcs *qcc_init_stream_remote(struct qcc *qcc, uint64_t id)
|
2021-12-21 05:53:10 -05:00
|
|
|
{
|
2022-03-29 08:57:19 -04:00
|
|
|
struct qcs *qcs = NULL;
|
2022-07-04 09:50:33 -04:00
|
|
|
enum qcs_type type;
|
2022-08-16 05:29:08 -04:00
|
|
|
uint64_t *largest, max_id;
|
2021-12-21 05:53:10 -05:00
|
|
|
|
2022-07-04 09:50:33 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCS_NEW, qcc->conn);
|
|
|
|
|
|
2023-05-11 10:55:30 -04:00
|
|
|
/* Function reserved to remote stream IDs. */
|
|
|
|
|
BUG_ON(quic_stream_is_local(qcc, id));
|
2022-07-04 09:50:33 -04:00
|
|
|
|
|
|
|
|
if (quic_stream_is_bidi(id)) {
|
|
|
|
|
largest = &qcc->largest_bidi_r;
|
|
|
|
|
type = conn_is_back(qcc->conn) ? QCS_SRV_BIDI : QCS_CLT_BIDI;
|
2021-12-21 05:53:10 -05:00
|
|
|
}
|
|
|
|
|
else {
|
2022-07-04 09:50:33 -04:00
|
|
|
largest = &qcc->largest_uni_r;
|
|
|
|
|
type = conn_is_back(qcc->conn) ? QCS_SRV_UNI : QCS_CLT_UNI;
|
|
|
|
|
}
|
2021-12-21 05:53:10 -05:00
|
|
|
|
2022-08-16 05:29:08 -04:00
|
|
|
/* RFC 9000 4.6. Controlling Concurrency
|
|
|
|
|
*
|
|
|
|
|
* An endpoint that receives a frame with a stream ID exceeding the
|
|
|
|
|
* limit it has sent MUST treat this as a connection error of type
|
|
|
|
|
* STREAM_LIMIT_ERROR
|
|
|
|
|
*/
|
|
|
|
|
max_id = quic_stream_is_bidi(id) ? qcc->lfctl.ms_bidi * 4 :
|
|
|
|
|
qcc->lfctl.ms_uni * 4;
|
|
|
|
|
if (id >= max_id) {
|
|
|
|
|
TRACE_ERROR("flow control error", QMUX_EV_QCS_NEW|QMUX_EV_PROTO_ERR, qcc->conn);
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_STREAM_LIMIT_ERROR, 0);
|
2022-08-16 05:29:08 -04:00
|
|
|
goto err;
|
2022-07-04 09:50:33 -04:00
|
|
|
}
|
2021-12-21 05:53:10 -05:00
|
|
|
|
2022-07-04 09:50:33 -04:00
|
|
|
/* Only stream ID not already opened can be used. */
|
|
|
|
|
BUG_ON(id < *largest);
|
2022-03-29 09:15:54 -04:00
|
|
|
|
2024-08-08 06:04:47 -04:00
|
|
|
/* MAX_STREAMS emission must not allowed too big stream ID. */
|
|
|
|
|
BUG_ON(*largest > QUIC_VARINT_8_BYTE_MAX);
|
|
|
|
|
|
2022-07-04 09:50:33 -04:00
|
|
|
while (id >= *largest) {
|
2022-08-16 05:13:45 -04:00
|
|
|
const char *str = *largest < id ? "initializing intermediary remote stream" :
|
|
|
|
|
"initializing remote stream";
|
2021-12-21 05:53:10 -05:00
|
|
|
|
2022-07-04 09:50:33 -04:00
|
|
|
qcs = qcs_new(qcc, *largest, type);
|
|
|
|
|
if (!qcs) {
|
2022-08-10 10:39:54 -04:00
|
|
|
TRACE_ERROR("stream fallocation failure", QMUX_EV_QCS_NEW, qcc->conn);
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto err;
|
2021-12-21 05:53:10 -05:00
|
|
|
}
|
2022-07-04 09:50:33 -04:00
|
|
|
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_PROTO(str, QMUX_EV_QCS_NEW, qcc->conn, qcs);
|
2022-07-04 09:50:33 -04:00
|
|
|
*largest += 4;
|
2021-12-21 05:53:10 -05:00
|
|
|
}
|
|
|
|
|
|
2022-08-10 10:14:32 -04:00
|
|
|
out:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_NEW, qcc->conn, qcs);
|
2022-03-29 08:57:19 -04:00
|
|
|
return qcs;
|
2022-08-10 10:14:32 -04:00
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_NEW, qcc->conn);
|
|
|
|
|
return NULL;
|
2022-07-04 09:50:33 -04:00
|
|
|
}
|
|
|
|
|
|
2024-08-19 04:22:02 -04:00
|
|
|
/* Mark <qcs> as reserved for metadata transfer. As such, future txbuf
|
|
|
|
|
* allocation won't be accounted against connection limit.
|
|
|
|
|
*/
|
|
|
|
|
void qcs_send_metadata(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
/* Reserved for stream with Tx capability. */
|
|
|
|
|
BUG_ON(!qcs->stream);
|
|
|
|
|
/* Cannot use if some data already transferred for this stream. */
|
2024-10-01 05:27:37 -04:00
|
|
|
BUG_ON(qcs->stream->ack_offset || !eb_is_empty(&qcs->stream->buf_tree));
|
2024-08-19 04:22:02 -04:00
|
|
|
|
2024-09-25 12:25:08 -04:00
|
|
|
qcs->flags |= QC_SF_TXBUB_OOB;
|
|
|
|
|
qc_stream_desc_sub_room(qcs->stream, NULL);
|
2024-08-19 04:22:02 -04:00
|
|
|
}
|
|
|
|
|
|
2024-12-31 09:18:52 -05:00
|
|
|
/* Instantiate a streamdesc instance for <qcs> stream. This is necessary to
|
|
|
|
|
* transfer data after a new request reception. <buf> can be used to forward
|
|
|
|
|
* the first received request data. <fin> must be set if the whole request is
|
|
|
|
|
* already received.
|
|
|
|
|
*
|
2025-01-03 10:25:14 -05:00
|
|
|
* Note that if <qcs> is already fully closed, no streamdesc is instantiated.
|
|
|
|
|
* This is useful if a RESET_STREAM was already emitted in response to a
|
|
|
|
|
* STOP_SENDING.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 on success else a negative error code. If stream is already fully
|
|
|
|
|
* closed and nothing is performed, it is considered as a success case.
|
2024-12-31 09:18:52 -05:00
|
|
|
*/
|
2025-01-03 10:16:45 -05:00
|
|
|
int qcs_attach_sc(struct qcs *qcs, struct buffer *buf, char fin)
|
2023-05-15 09:17:28 -04:00
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
struct session *sess = qcc->conn->owner;
|
|
|
|
|
|
2024-12-31 09:18:52 -05:00
|
|
|
TRACE_ENTER(QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
2024-02-14 09:15:09 -05:00
|
|
|
|
2025-01-03 10:25:14 -05:00
|
|
|
if (qcs->st == QC_SS_CLO) {
|
|
|
|
|
TRACE_STATE("skip attach on already closed stream", QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-15 09:17:28 -04:00
|
|
|
/* TODO duplicated from mux_h2 */
|
|
|
|
|
sess->t_idle = ns_to_ms(now_ns - sess->accept_ts) - sess->t_handshake;
|
|
|
|
|
|
2024-12-31 09:18:52 -05:00
|
|
|
if (!sc_new_from_endp(qcs->sd, sess, buf)) {
|
|
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
2025-01-03 10:16:45 -05:00
|
|
|
return -1;
|
2024-12-31 09:18:52 -05:00
|
|
|
}
|
2023-05-15 09:17:28 -04:00
|
|
|
|
|
|
|
|
/* QC_SF_HREQ_RECV must be set once for a stream. Else, nb_hreq counter
|
|
|
|
|
* will be incorrect for the connection.
|
|
|
|
|
*/
|
|
|
|
|
BUG_ON_HOT(qcs->flags & QC_SF_HREQ_RECV);
|
|
|
|
|
qcs->flags |= QC_SF_HREQ_RECV;
|
|
|
|
|
++qcc->nb_sc;
|
|
|
|
|
++qcc->nb_hreq;
|
|
|
|
|
|
|
|
|
|
/* TODO duplicated from mux_h2 */
|
|
|
|
|
sess->accept_date = date;
|
|
|
|
|
sess->accept_ts = now_ns;
|
|
|
|
|
sess->t_handshake = 0;
|
|
|
|
|
sess->t_idle = 0;
|
|
|
|
|
|
|
|
|
|
/* A stream must have been registered for HTTP wait before attaching
|
|
|
|
|
* it to sedesc. See <qcs_wait_http_req> for more info.
|
|
|
|
|
*/
|
|
|
|
|
BUG_ON_HOT(!LIST_INLIST(&qcs->el_opening));
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_opening);
|
|
|
|
|
|
2024-10-10 11:07:36 -04:00
|
|
|
/* rcv_buf may be skipped if request is wholly received on attach.
|
|
|
|
|
* Ensure that similar flags are set for FIN both on rcv_buf and here.
|
|
|
|
|
*/
|
2023-05-12 12:16:31 -04:00
|
|
|
if (fin) {
|
|
|
|
|
TRACE_STATE("report end-of-input", QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
2023-05-25 09:02:24 -04:00
|
|
|
se_fl_set(qcs->sd, SE_FL_EOI);
|
2024-10-10 11:07:36 -04:00
|
|
|
se_expect_data(qcs->sd);
|
2023-05-12 12:16:31 -04:00
|
|
|
}
|
|
|
|
|
|
BUG/MEDIUM: mux-quic: report early error on stream
On STOP_SENDING reception, an error is notified to the stream layer as
no more data can be responded. However, this is not done if the stream
instance is not allocated (already freed for example).
The issue occurs if STOP_SENDING is received and the stream instance is
instantiated after it. It happens if a STREAM frame is received after it
with H3 HEADERS, which is valid in QUIC protocol due to UDP packet
reordering. In this case, stream layer is never notified about the
underlying error. Instead, reponse buffers are silently purged by the
MUX in qmux_strm_snd_buf().
This is suboptimal as there is no point in exchanging data from the
server if it cannot be eventually transferred back to the client.
However, aside from this consideration, no other issue occured. However,
this is not the case with QUIC mux-to-mux implementation. Now, if
mux-to-mux is used, qmux_strm_snd_buf() is bypassed and response if
transferred via nego_ff/done_ff callbacks. However, these functions did
not checked if QCS is already locally closed. This causes a crash when
qcc_send_stream() is called via done_ff.
To fix this crash, there is several approach, one of them would be to
adjust nego_ff/done_ff QUIC callbacks. However, another method has been
chosen. Now stream layer is flagged on error just after its
instantiation if the stream is already locally closed. This ensures that
mux-to-mux won't try to emit data as se_nego_ff() check if the opposide
SD is not on error before continuing.
Note that an alternative solution could be to not instantiate at all
stream layer if QCS is already locally closed. This is the most optimal
solution as it reduce unnecessary allocations and task processing.
However, it's not easy to implement so the easier bug fix has been
chosen for the moment.
This patch is labelled as MEDIUM as it can change behavior of all QCS
instances, wheter mux-to-mux is used or not, and thus could reveal other
architecture issues.
This should fix latest crash occurence on github issue #2392.
It should be backported up to 2.6, until a necessary period of
observation.
2023-12-13 10:28:28 -05:00
|
|
|
/* A QCS can be already locally closed before stream layer
|
|
|
|
|
* instantiation. This notably happens if STOP_SENDING was the first
|
|
|
|
|
* frame received for this instance. In this case, an error is
|
|
|
|
|
* immediately to the stream layer to prevent transmission.
|
|
|
|
|
*
|
|
|
|
|
* TODO it could be better to not instantiate at all the stream layer.
|
|
|
|
|
* However, extra care is required to ensure QCS instance is released.
|
|
|
|
|
*/
|
|
|
|
|
if (unlikely(qcs_is_close_local(qcs) || (qcs->flags & QC_SF_TO_RESET))) {
|
|
|
|
|
TRACE_STATE("report early error", QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
|
|
|
|
se_fl_set_error(qcs->sd);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-03 10:25:14 -05:00
|
|
|
out:
|
2024-12-31 09:18:52 -05:00
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
2025-01-03 10:16:45 -05:00
|
|
|
return 0;
|
2023-05-15 09:17:28 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-04 09:50:33 -04:00
|
|
|
/* Use this function for a stream <id> which is not in <qcc> stream tree. It
|
|
|
|
|
* returns true if the associated stream is closed.
|
|
|
|
|
*/
|
|
|
|
|
static int qcc_stream_id_is_closed(struct qcc *qcc, uint64_t id)
|
|
|
|
|
{
|
|
|
|
|
uint64_t *largest;
|
|
|
|
|
|
|
|
|
|
/* This function must only be used for stream not present in the stream tree. */
|
|
|
|
|
BUG_ON_HOT(eb64_lookup(&qcc->streams_by_id, id));
|
|
|
|
|
|
|
|
|
|
if (quic_stream_is_local(qcc, id)) {
|
|
|
|
|
largest = quic_stream_is_uni(id) ? &qcc->next_uni_l :
|
|
|
|
|
&qcc->next_bidi_l;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
largest = quic_stream_is_uni(id) ? &qcc->largest_uni_r :
|
|
|
|
|
&qcc->largest_bidi_r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return id < *largest;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Retrieve the stream instance from <id> ID. This can be used when receiving
|
|
|
|
|
* STREAM, STREAM_DATA_BLOCKED, RESET_STREAM, MAX_STREAM_DATA or STOP_SENDING
|
2022-07-06 09:43:21 -04:00
|
|
|
* frames. Set to false <receive_only> or <send_only> if these particular types
|
2022-07-07 09:02:32 -04:00
|
|
|
* of streams are not allowed. If the stream instance is found, it is stored in
|
|
|
|
|
* <out>.
|
2022-07-04 09:50:33 -04:00
|
|
|
*
|
2022-07-07 09:02:32 -04:00
|
|
|
* Returns 0 on success else non-zero. On error, a RESET_STREAM or a
|
|
|
|
|
* CONNECTION_CLOSE is automatically emitted. Beware that <out> may be NULL
|
|
|
|
|
* on success if the stream has already been closed.
|
2022-07-04 09:50:33 -04:00
|
|
|
*/
|
2022-07-07 09:02:32 -04:00
|
|
|
int qcc_get_qcs(struct qcc *qcc, uint64_t id, int receive_only, int send_only,
|
|
|
|
|
struct qcs **out)
|
2022-07-04 09:50:33 -04:00
|
|
|
{
|
|
|
|
|
struct eb64_node *node;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
|
2022-07-07 09:02:32 -04:00
|
|
|
*out = NULL;
|
2022-07-04 09:50:33 -04:00
|
|
|
|
2022-07-06 09:43:21 -04:00
|
|
|
if (!receive_only && quic_stream_is_uni(id) && quic_stream_is_remote(qcc, id)) {
|
2022-08-10 10:39:54 -04:00
|
|
|
TRACE_ERROR("receive-only stream not allowed", QMUX_EV_QCC_RECV|QMUX_EV_QCC_NQCS|QMUX_EV_PROTO_ERR, qcc->conn, NULL, &id);
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_STREAM_STATE_ERROR, 0);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto err;
|
2022-07-06 09:43:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!send_only && quic_stream_is_uni(id) && quic_stream_is_local(qcc, id)) {
|
2022-08-10 10:39:54 -04:00
|
|
|
TRACE_ERROR("send-only stream not allowed", QMUX_EV_QCC_RECV|QMUX_EV_QCC_NQCS|QMUX_EV_PROTO_ERR, qcc->conn, NULL, &id);
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_STREAM_STATE_ERROR, 0);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto err;
|
2022-07-06 09:43:21 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-04 09:50:33 -04:00
|
|
|
/* Search the stream in the connection tree. */
|
|
|
|
|
node = eb64_lookup(&qcc->streams_by_id, id);
|
|
|
|
|
if (node) {
|
2022-07-07 09:02:32 -04:00
|
|
|
*out = eb64_entry(node, struct qcs, by_id);
|
|
|
|
|
TRACE_DEVEL("using stream from connection tree", QMUX_EV_QCC_RECV, qcc->conn, *out);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto out;
|
2022-07-04 09:50:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check if stream is already closed. */
|
|
|
|
|
if (qcc_stream_id_is_closed(qcc, id)) {
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_DATA("already closed stream", QMUX_EV_QCC_RECV|QMUX_EV_QCC_NQCS, qcc->conn, NULL, &id);
|
2022-07-07 09:02:32 -04:00
|
|
|
/* Consider this as a success even if <out> is left NULL. */
|
2022-08-10 10:14:32 -04:00
|
|
|
goto out;
|
2022-07-04 09:50:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Create the stream. This is valid only for remote initiated one. A
|
2022-07-29 13:26:53 -04:00
|
|
|
* local stream must have already been explicitly created by the
|
2022-07-04 09:50:33 -04:00
|
|
|
* application protocol layer.
|
|
|
|
|
*/
|
|
|
|
|
if (quic_stream_is_local(qcc, id)) {
|
|
|
|
|
/* RFC 9000 19.8. STREAM Frames
|
|
|
|
|
*
|
|
|
|
|
* An endpoint MUST terminate the connection with error
|
|
|
|
|
* STREAM_STATE_ERROR if it receives a STREAM frame for a locally
|
|
|
|
|
* initiated stream that has not yet been created, or for a send-only
|
|
|
|
|
* stream.
|
|
|
|
|
*/
|
2022-08-10 10:39:54 -04:00
|
|
|
TRACE_ERROR("locally initiated stream not yet created", QMUX_EV_QCC_RECV|QMUX_EV_QCC_NQCS|QMUX_EV_PROTO_ERR, qcc->conn, NULL, &id);
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_STREAM_STATE_ERROR, 0);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto err;
|
2022-07-04 09:50:33 -04:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Remote stream not found - try to open it. */
|
2022-07-07 09:02:32 -04:00
|
|
|
*out = qcc_init_stream_remote(qcc, id);
|
|
|
|
|
if (!*out) {
|
2022-08-10 10:39:54 -04:00
|
|
|
TRACE_ERROR("stream creation error", QMUX_EV_QCC_RECV|QMUX_EV_QCC_NQCS, qcc->conn, NULL, &id);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto err;
|
2022-07-07 09:02:32 -04:00
|
|
|
}
|
2022-07-04 09:50:33 -04:00
|
|
|
}
|
2021-12-21 05:53:10 -05:00
|
|
|
|
|
|
|
|
out:
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn, *out);
|
2022-07-07 09:02:32 -04:00
|
|
|
return 0;
|
2022-08-10 10:14:32 -04:00
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
return 1;
|
2021-12-21 05:53:10 -05:00
|
|
|
}
|
|
|
|
|
|
2025-02-24 10:22:22 -05:00
|
|
|
/* Convert <b> out-of-order storage into a contiguous buffer. */
|
|
|
|
|
static inline struct buffer qcs_b_dup(const struct qc_stream_rxbuf *b)
|
|
|
|
|
{
|
|
|
|
|
if (b) {
|
|
|
|
|
const struct ncbuf *ncb = &b->ncb;
|
|
|
|
|
return b_make(ncb_orig(ncb), ncb->size, ncb->head, ncb_data(ncb, 0));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return BUF_NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 09:29:24 -05:00
|
|
|
/* Transfer data into <qcs> stream <rxbuf> current Rx buffer from its directly
|
|
|
|
|
* following buffer. This is useful when parsing was interrupted due to partial
|
|
|
|
|
* data. If following buffer does not exists, nothing is done.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 if data transfer was performed.
|
|
|
|
|
*/
|
|
|
|
|
static int qcs_transfer_rx_data(struct qcs *qcs, struct qc_stream_rxbuf *rxbuf)
|
|
|
|
|
{
|
|
|
|
|
struct qc_stream_rxbuf *rxbuf_next;
|
|
|
|
|
struct eb64_node *next;
|
|
|
|
|
struct buffer b, b_next;
|
|
|
|
|
enum ncb_ret ncb_ret;
|
|
|
|
|
size_t to_copy;
|
|
|
|
|
int ret = 1;
|
|
|
|
|
|
|
|
|
|
BUG_ON(ncb_is_full(&rxbuf->ncb));
|
|
|
|
|
|
|
|
|
|
next = eb64_next(&rxbuf->off_node);
|
|
|
|
|
if (!next)
|
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
rxbuf_next = container_of(next, struct qc_stream_rxbuf, off_node);
|
|
|
|
|
if (rxbuf_next->off_node.key == rxbuf->off_end &&
|
|
|
|
|
ncb_data(&rxbuf_next->ncb, 0)) {
|
|
|
|
|
eb64_delete(&rxbuf->off_node);
|
|
|
|
|
eb64_delete(next);
|
|
|
|
|
|
|
|
|
|
b = qcs_b_dup(rxbuf);
|
|
|
|
|
b_next = qcs_b_dup(rxbuf_next);
|
|
|
|
|
to_copy = MIN(b_data(&b_next), ncb_size(&rxbuf->ncb) - b_data(&b));
|
|
|
|
|
|
|
|
|
|
ncb_ret = ncb_add(&rxbuf->ncb, ncb_data(&rxbuf->ncb, 0),
|
|
|
|
|
b_head(&b_next), to_copy, NCB_ADD_COMPARE);
|
|
|
|
|
BUG_ON(ncb_ret != NCB_RET_OK);
|
|
|
|
|
|
|
|
|
|
ncb_ret = ncb_advance(&rxbuf_next->ncb, to_copy);
|
|
|
|
|
BUG_ON(ncb_ret != NCB_RET_OK);
|
|
|
|
|
|
|
|
|
|
rxbuf->off_node.key = qcs->rx.offset;
|
|
|
|
|
rxbuf->off_end = qcs->rx.offset + b_data(&b) + to_copy;
|
|
|
|
|
eb64_insert(&qcs->rx.bufs, &rxbuf->off_node);
|
|
|
|
|
|
|
|
|
|
rxbuf_next->off_node.key += to_copy;
|
|
|
|
|
BUG_ON(rxbuf_next->off_node.key > rxbuf_next->off_end);
|
|
|
|
|
|
|
|
|
|
if (rxbuf_next->off_node.key == rxbuf_next->off_end) {
|
|
|
|
|
eb64_insert(&qcs->rx.bufs, &rxbuf_next->off_node);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
b_free(&b_next);
|
|
|
|
|
offer_buffers(NULL, 1);
|
|
|
|
|
pool_free(pool_head_qc_stream_rxbuf, rxbuf_next);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-07 05:43:01 -05:00
|
|
|
/* Returns the Rx buffer instance for <qcs> stream read offset. May be NULL if
|
|
|
|
|
* not already allocated.
|
|
|
|
|
*/
|
2025-02-24 10:28:50 -05:00
|
|
|
static struct qc_stream_rxbuf *qcs_get_curr_rxbuf(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
struct eb64_node *node;
|
|
|
|
|
struct qc_stream_rxbuf *buf;
|
|
|
|
|
|
|
|
|
|
node = eb64_first(&qcs->rx.bufs);
|
2025-03-07 05:43:01 -05:00
|
|
|
if (!node)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
2025-02-24 10:28:50 -05:00
|
|
|
buf = container_of(node, struct qc_stream_rxbuf, off_node);
|
2025-03-07 05:43:01 -05:00
|
|
|
if (qcs->rx.offset < buf->off_node.key) {
|
|
|
|
|
/* first buffer allocated for a future offset */
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Ensures obsolete buffer are not kept inside QCS */
|
|
|
|
|
BUG_ON(buf->off_end < qcs->rx.offset);
|
2025-02-24 10:28:50 -05:00
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-07 05:43:01 -05:00
|
|
|
/* Returns the amount of data readable at <qcs> stream on current buffer. Note
|
|
|
|
|
* that this does account for hypothetical contiguous data divided on other
|
|
|
|
|
* Rx buffers instances.
|
|
|
|
|
*/
|
2025-02-24 10:22:22 -05:00
|
|
|
static ncb_sz_t qcs_rx_avail_data(struct qcs *qcs)
|
2022-06-03 10:40:34 -04:00
|
|
|
{
|
2025-02-24 10:28:50 -05:00
|
|
|
struct qc_stream_rxbuf *b = qcs_get_curr_rxbuf(qcs);
|
|
|
|
|
return b ? ncb_data(&b->ncb, 0) : 0;
|
2022-06-03 10:40:34 -04:00
|
|
|
}
|
|
|
|
|
|
2025-03-04 09:23:28 -05:00
|
|
|
/* Remove <bytes> from <buf> current Rx buffer of <qcs> stream. Flow-control
|
|
|
|
|
* for received offsets may be allocated for the peer if needed.
|
2022-06-03 10:40:34 -04:00
|
|
|
*/
|
2025-03-04 09:23:28 -05:00
|
|
|
static void qcs_consume(struct qcs *qcs, uint64_t bytes, struct qc_stream_rxbuf *buf)
|
2022-06-03 10:40:34 -04:00
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
struct quic_frame *frm;
|
|
|
|
|
enum ncb_ret ret;
|
|
|
|
|
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
|
2025-03-04 09:23:28 -05:00
|
|
|
/* <buf> must be current QCS Rx buffer. */
|
|
|
|
|
BUG_ON_HOT(buf->off_node.key > qcs->rx.offset ||
|
|
|
|
|
qcs->rx.offset >= buf->off_end);
|
|
|
|
|
|
|
|
|
|
ret = ncb_advance(&buf->ncb, bytes);
|
2022-06-03 10:40:34 -04:00
|
|
|
if (ret) {
|
|
|
|
|
ABORT_NOW(); /* should not happens because removal only in data */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qcs->rx.offset += bytes;
|
2025-03-04 09:23:28 -05:00
|
|
|
/* QCS Rx offset must only point directly up to the next buffer. */
|
|
|
|
|
BUG_ON_HOT(qcs->rx.offset > buf->off_end);
|
|
|
|
|
|
2022-12-09 09:00:17 -05:00
|
|
|
/* Not necessary to emit a MAX_STREAM_DATA if all data received. */
|
|
|
|
|
if (qcs->flags & QC_SF_SIZE_KNOWN)
|
|
|
|
|
goto conn_fctl;
|
|
|
|
|
|
2022-06-03 10:40:34 -04:00
|
|
|
if (qcs->rx.msd - qcs->rx.offset < qcs->rx.msd_init / 2) {
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_DATA("increase stream credit via MAX_STREAM_DATA", QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2023-01-27 11:47:49 -05:00
|
|
|
frm = qc_frm_alloc(QUIC_FT_MAX_STREAM_DATA);
|
2023-03-09 04:16:38 -05:00
|
|
|
if (!frm) {
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
|
2023-03-09 04:16:38 -05:00
|
|
|
return;
|
|
|
|
|
}
|
2022-06-03 10:40:34 -04:00
|
|
|
|
|
|
|
|
qcs->rx.msd = qcs->rx.offset + qcs->rx.msd_init;
|
|
|
|
|
|
|
|
|
|
frm->max_stream_data.id = qcs->id;
|
|
|
|
|
frm->max_stream_data.max_stream_data = qcs->rx.msd;
|
|
|
|
|
|
|
|
|
|
LIST_APPEND(&qcc->lfctl.frms, &frm->list);
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2022-06-03 10:40:34 -04:00
|
|
|
}
|
|
|
|
|
|
2022-12-09 09:00:17 -05:00
|
|
|
conn_fctl:
|
2022-06-03 10:40:34 -04:00
|
|
|
qcc->lfctl.offsets_consume += bytes;
|
|
|
|
|
if (qcc->lfctl.md - qcc->lfctl.offsets_consume < qcc->lfctl.md_init / 2) {
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_DATA("increase conn credit via MAX_DATA", QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2023-01-27 11:47:49 -05:00
|
|
|
frm = qc_frm_alloc(QUIC_FT_MAX_DATA);
|
2023-03-09 04:16:38 -05:00
|
|
|
if (!frm) {
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
|
2023-03-09 04:16:38 -05:00
|
|
|
return;
|
|
|
|
|
}
|
2022-06-03 10:40:34 -04:00
|
|
|
|
|
|
|
|
qcc->lfctl.md = qcc->lfctl.offsets_consume + qcc->lfctl.md_init;
|
|
|
|
|
|
|
|
|
|
frm->max_data.max_data = qcc->lfctl.md;
|
|
|
|
|
|
|
|
|
|
LIST_APPEND(&qcs->qcc->lfctl.frms, &frm->list);
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2022-06-03 10:40:34 -04:00
|
|
|
}
|
2022-08-10 10:58:01 -04:00
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2022-06-03 10:40:34 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-18 05:38:22 -04:00
|
|
|
/* Decode the content of STREAM frames already received on the stream instance
|
2025-03-03 04:01:07 -05:00
|
|
|
* <qcs> from the <qcc> connection.
|
2022-05-18 05:38:22 -04:00
|
|
|
*
|
2025-03-03 04:01:07 -05:00
|
|
|
* Returns the result of app_ops rcv_buf callback, which is the number of bytes
|
|
|
|
|
* successfully transcoded, or a negative error code. If no error occurred but
|
|
|
|
|
* decoding cannot proceed due to missing data, the return value is 0. The
|
|
|
|
|
* value 0 may also be returned when dealing with a standalone FIN signal.
|
2022-05-18 05:38:22 -04:00
|
|
|
*/
|
|
|
|
|
static int qcc_decode_qcs(struct qcc *qcc, struct qcs *qcs)
|
|
|
|
|
{
|
2025-02-24 10:28:50 -05:00
|
|
|
struct qc_stream_rxbuf *rxbuf;
|
2022-06-03 10:40:34 -04:00
|
|
|
struct buffer b;
|
2022-06-07 11:30:55 -04:00
|
|
|
ssize_t ret;
|
2022-07-01 05:26:04 -04:00
|
|
|
int fin = 0;
|
2024-09-18 09:33:30 -04:00
|
|
|
int prev_glitches = qcc->glitches;
|
2022-06-03 10:40:34 -04:00
|
|
|
|
2022-05-18 05:38:22 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
|
2025-03-05 09:29:24 -05:00
|
|
|
restart:
|
2025-02-24 10:28:50 -05:00
|
|
|
rxbuf = qcs_get_curr_rxbuf(qcs);
|
|
|
|
|
b = qcs_b_dup(rxbuf);
|
2022-07-01 05:26:04 -04:00
|
|
|
|
BUG/MINOR: mux-quic: do not remotely close stream too early
A stream is considered as remotely closed once we have received all the
data with the FIN bit set.
The condition to close the stream was wrong. In particular, if we
receive an empty STREAM frame with FIN bit set, this would have close
the stream even if we do not have yet received all the data. The
condition is now adjusted to ensure that Rx buffer contains all the data
up to the stream final size.
In most cases, this bug is harmless. However, if compiled with
DEBUG_STRICT=2, a BUG_ON_HOT crash would have been triggered if close is
done too early. This was most notably the case sometimes on interop test
suite with quinn or kwik clients. This can also be artificially
reproduced by simulating reception of an empty STREAM frame with FIN bit
set in qc_handle_strm_frm() :
+ if (strm_frm->fin) {
+ qcc_recv(qc->qcc, strm_frm->id, 0,
+ strm_frm->len, strm_frm->fin,
+ (char *)strm_frm->data);
+ }
ret = qcc_recv(qc->qcc, strm_frm->id, strm_frm->len,
strm_frm->offset.key, strm_frm->fin,
(char *)strm_frm->data);
This must be backported up to 2.6.
2022-09-16 07:30:59 -04:00
|
|
|
/* Signal FIN to application if STREAM FIN received with all data. */
|
|
|
|
|
if (qcs_is_close_remote(qcs))
|
2022-07-01 05:26:04 -04:00
|
|
|
fin = 1;
|
|
|
|
|
|
2025-01-03 10:25:14 -05:00
|
|
|
if (!(qcs->flags & QC_SF_READ_ABORTED)) {
|
2023-12-07 11:43:07 -05:00
|
|
|
ret = qcc->app_ops->rcv_buf(qcs, &b, fin);
|
2024-09-18 09:33:30 -04:00
|
|
|
|
|
|
|
|
if (qcc->glitches != prev_glitches)
|
|
|
|
|
session_add_glitch_ctr(qcc->conn->owner, qcc->glitches - prev_glitches);
|
|
|
|
|
|
2022-12-09 08:58:28 -05:00
|
|
|
if (ret < 0) {
|
|
|
|
|
TRACE_ERROR("decoding error", QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
2023-05-24 08:43:43 -04:00
|
|
|
|
2025-03-05 09:29:24 -05:00
|
|
|
/* App layer cannot decode due to partial data, which is stored
|
|
|
|
|
* at a Rx buffer boundary. Try to realign data if possible and
|
|
|
|
|
* restart decoding.
|
|
|
|
|
*/
|
|
|
|
|
if (!ret && rxbuf && !(qcs->flags & QC_SF_DEM_FULL) &&
|
|
|
|
|
qcs->rx.offset + ncb_data(&rxbuf->ncb, 0) == rxbuf->off_end) {
|
|
|
|
|
if (!qcs_transfer_rx_data(qcs, rxbuf)) {
|
|
|
|
|
TRACE_DEVEL("restart parsing after data realignment", QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
goto restart;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-24 08:43:43 -04:00
|
|
|
if (qcs->flags & QC_SF_TO_RESET) {
|
|
|
|
|
if (qcs_sc(qcs) && !se_fl_test(qcs->sd, SE_FL_ERROR|SE_FL_ERR_PENDING)) {
|
|
|
|
|
se_fl_set_error(qcs->sd);
|
|
|
|
|
qcs_alert(qcs);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-09 08:58:28 -05:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
TRACE_DATA("ignore read on stream", QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
ret = b_data(&b);
|
2022-05-18 05:38:22 -04:00
|
|
|
}
|
|
|
|
|
|
2025-03-04 09:23:28 -05:00
|
|
|
if (rxbuf) {
|
|
|
|
|
if (ret)
|
|
|
|
|
qcs_consume(qcs, ret, rxbuf);
|
|
|
|
|
|
2025-03-07 05:43:01 -05:00
|
|
|
if (ncb_is_empty(&rxbuf->ncb)) {
|
2025-03-04 09:23:28 -05:00
|
|
|
qcs_free_rxbuf(qcs, rxbuf);
|
2025-03-07 05:43:01 -05:00
|
|
|
|
|
|
|
|
/* Close QCS remotely if only one Rx buffer remains and
|
|
|
|
|
* all data with FIN already stored in it. This is
|
|
|
|
|
* necessary to be performed before app_ops rcv_buf to
|
|
|
|
|
* ensure FIN is correctly signalled.
|
|
|
|
|
*/
|
|
|
|
|
if (qcs->flags & QC_SF_SIZE_KNOWN && !eb_is_empty(&qcs->rx.bufs)) {
|
|
|
|
|
const ncb_sz_t avail = qcs_rx_avail_data(qcs);
|
|
|
|
|
if (qcs->rx.offset + avail == qcs->rx.offset_max)
|
|
|
|
|
qcs_close_remote(qcs);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-04 09:23:28 -05:00
|
|
|
}
|
|
|
|
|
|
2023-02-17 03:51:20 -05:00
|
|
|
if (ret || (!b_data(&b) && fin))
|
2022-06-03 10:40:34 -04:00
|
|
|
qcs_notify_recv(qcs);
|
2022-05-18 05:38:22 -04:00
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2025-03-03 04:01:07 -05:00
|
|
|
return ret;
|
2022-08-10 10:14:32 -04:00
|
|
|
|
|
|
|
|
err:
|
2025-03-03 04:01:07 -05:00
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
return ret;
|
2022-05-18 05:38:22 -04:00
|
|
|
}
|
|
|
|
|
|
2023-12-11 09:34:42 -05:00
|
|
|
/* Allocate if needed and retrieve <qcs> stream buffer for data reception.
|
|
|
|
|
*
|
|
|
|
|
* Returns buffer pointer. May be NULL on allocation failure.
|
|
|
|
|
*/
|
|
|
|
|
struct buffer *qcc_get_stream_rxbuf(struct qcs *qcs)
|
|
|
|
|
{
|
MINOR: dynbuf: pass a criticality argument to b_alloc()
The goal is to indicate how critical the allocation is, between the
least one (growing an existing buffer ring) and the topmost one (boot
time allocation for the life of the process).
The 3 tcp-based muxes (h1, h2, fcgi) use a common allocation function
to try to allocate otherwise subscribe. There's currently no distinction
of direction nor part that tries to allocate, and this should be revisited
to improve this situation, particularly when we consider that mux-h2 can
reduce its Tx allocations if needed.
For now, 4 main levels are planned, to translate how the data travels
inside haproxy from a producer to a consumer:
- MUX_RX: buffer used to receive data from the OS
- SE_RX: buffer used to place a transformation of the RX data for
a mux, or to produce a response for an applet
- CHANNEL: the channel buffer for sync recv
- MUX_TX: buffer used to transfer data from the channel to the outside,
generally a mux but there can be a few specificities (e.g.
http client's response buffer passed to the application,
which also gets a transformation of the channel data).
The other levels are a bit different in that they don't strictly need to
allocate for the first two ones, or they're permanent for the last one
(used by compression).
2024-04-16 02:55:20 -04:00
|
|
|
return b_alloc(&qcs->rx.app_buf, DB_MUX_RX);
|
2023-12-11 09:34:42 -05:00
|
|
|
}
|
|
|
|
|
|
2023-11-13 08:57:28 -05:00
|
|
|
/* Allocate if needed and retrieve <qcs> stream buffer for data emission.
|
|
|
|
|
*
|
2024-01-17 09:15:55 -05:00
|
|
|
* <err> is an output argument which is useful to differentiate the failure
|
|
|
|
|
* cause when the buffer cannot be allocated. It is set to 0 if the connection
|
MAJOR: mux-quic: allocate Tx buffers based on congestion window
Each QUIC MUX may allocate buffers for MUX stream emission. These
buffers are then shared with quic_conn to handle ACK reception and
retransmission. A limit on the number of concurrent buffers used per
connection has been defined statically and can be updated via a
configuration option. This commit replaces the limit to instead use the
current underlying congestion window size.
The purpose of this change is to remove the artificial static buffer
count limit, which may be difficult to choose. Indeed, if a connection
performs with minimal loss rate, the buffer count would limit severely
its throughput. It could be increase to fix this, but it also impacts
others connections, even with less optimal performance, causing too many
extra data buffering on the MUX layer. By using the dynamic congestion
window size, haproxy ensures that MUX buffering corresponds roughly to
the network conditions.
Using QCC <buf_in_flight>, a new buffer can be allocated if it is less
than the current window size. If not, QCS emission is interrupted and
haproxy stream layer will subscribe until a new buffer is ready.
One of the criticals parts is to ensure that MUX layer previously
blocked on buffer allocation is properly woken up when sending can be
retried. This occurs on two occasions :
* after an already used Tx buffer is cleared on ACK reception. This case
is already handled by qcc_notify_buf() via quic_stream layer.
* on congestion window increase. A new qcc_notify_buf() invokation is
added into qc_notify_send().
Finally, remove <avail_bufs> QCC field which is now unused.
This commit is labelled MAJOR as it may have unexpected effect and could
cause significant behavior change. For example, in previous
implementation QUIC MUX would be able to buffer more data even if the
congestion window is small. With this patch, data cannot be transferred
from the stream layer which may cause more streams to be shut down on
client timeout. Another effect may be more CPU consumption as the
connection limit would be hit more often, causing more streams to be
interrupted and woken up in cycle.
2024-06-13 11:06:40 -04:00
|
|
|
* buffer window is full. For fatal errors, its value is non-zero.
|
2024-01-17 09:15:55 -05:00
|
|
|
*
|
2024-08-19 04:22:02 -04:00
|
|
|
* Streams reserved for application protocol metadata transfer are not subject
|
|
|
|
|
* to the buffer limit per connection. Hence, for them only a memory error
|
|
|
|
|
* can prevent a buffer allocation.
|
|
|
|
|
*
|
2024-01-17 09:15:55 -05:00
|
|
|
* Returns buffer pointer. May be NULL on allocation failure, in which case
|
|
|
|
|
* <err> will refer to the cause.
|
2023-11-13 08:57:28 -05:00
|
|
|
*/
|
2024-06-13 09:26:51 -04:00
|
|
|
struct buffer *qcc_get_stream_txbuf(struct qcs *qcs, int *err, int small)
|
2023-11-13 08:57:28 -05:00
|
|
|
{
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
struct buffer *out = qc_stream_buf_get(qcs->stream);
|
|
|
|
|
|
2024-01-17 09:15:55 -05:00
|
|
|
/* Stream must not try to reallocate a buffer if currently waiting for one. */
|
|
|
|
|
BUG_ON(LIST_INLIST(&qcs->el_buf));
|
|
|
|
|
|
|
|
|
|
*err = 0;
|
|
|
|
|
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
if (!out) {
|
2024-09-25 12:25:08 -04:00
|
|
|
if (likely(!(qcs->flags & QC_SF_TXBUB_OOB))) {
|
2024-08-19 04:22:02 -04:00
|
|
|
if ((qcc->flags & QC_CF_CONN_FULL)) {
|
|
|
|
|
LIST_APPEND(&qcc->buf_wait_list, &qcs->el_buf);
|
|
|
|
|
tot_time_start(&qcs->timer.buf);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2024-01-17 09:15:55 -05:00
|
|
|
|
MAJOR: mux-quic: allocate Tx buffers based on congestion window
Each QUIC MUX may allocate buffers for MUX stream emission. These
buffers are then shared with quic_conn to handle ACK reception and
retransmission. A limit on the number of concurrent buffers used per
connection has been defined statically and can be updated via a
configuration option. This commit replaces the limit to instead use the
current underlying congestion window size.
The purpose of this change is to remove the artificial static buffer
count limit, which may be difficult to choose. Indeed, if a connection
performs with minimal loss rate, the buffer count would limit severely
its throughput. It could be increase to fix this, but it also impacts
others connections, even with less optimal performance, causing too many
extra data buffering on the MUX layer. By using the dynamic congestion
window size, haproxy ensures that MUX buffering corresponds roughly to
the network conditions.
Using QCC <buf_in_flight>, a new buffer can be allocated if it is less
than the current window size. If not, QCS emission is interrupted and
haproxy stream layer will subscribe until a new buffer is ready.
One of the criticals parts is to ensure that MUX layer previously
blocked on buffer allocation is properly woken up when sending can be
retried. This occurs on two occasions :
* after an already used Tx buffer is cleared on ACK reception. This case
is already handled by qcc_notify_buf() via quic_stream layer.
* on congestion window increase. A new qcc_notify_buf() invokation is
added into qc_notify_send().
Finally, remove <avail_bufs> QCC field which is now unused.
This commit is labelled MAJOR as it may have unexpected effect and could
cause significant behavior change. For example, in previous
implementation QUIC MUX would be able to buffer more data even if the
congestion window is small. With this patch, data cannot be transferred
from the stream layer which may cause more streams to be shut down on
client timeout. Another effect may be more CPU consumption as the
connection limit would be hit more often, causing more streams to be
interrupted and woken up in cycle.
2024-06-13 11:06:40 -04:00
|
|
|
if (qcc_bufwnd_full(qcc)) {
|
|
|
|
|
TRACE_STATE("no more room", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
2024-08-19 04:22:02 -04:00
|
|
|
LIST_APPEND(&qcc->buf_wait_list, &qcs->el_buf);
|
|
|
|
|
tot_time_start(&qcs->timer.buf);
|
|
|
|
|
qcc->flags |= QC_CF_CONN_FULL;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2024-01-17 09:15:55 -05:00
|
|
|
}
|
2024-08-13 05:57:50 -04:00
|
|
|
|
2024-06-13 09:26:51 -04:00
|
|
|
out = qc_stream_buf_alloc(qcs->stream, qcs->tx.fc.off_real, small);
|
2024-08-13 05:57:50 -04:00
|
|
|
if (!out) {
|
|
|
|
|
TRACE_ERROR("stream desc alloc failure", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
*err = 1;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 12:25:08 -04:00
|
|
|
if (likely(!(qcs->flags & QC_SF_TXBUB_OOB)))
|
2024-06-13 09:26:51 -04:00
|
|
|
qcc->tx.buf_in_flight += b_size(out);
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
return out;
|
2023-11-13 08:57:28 -05:00
|
|
|
}
|
|
|
|
|
|
2024-07-29 11:01:38 -04:00
|
|
|
/* Reallocate <qcs> stream buffer to convert a small buffer to a bigger one.
|
|
|
|
|
* Contrary to standard allocation, this function will never stop due to a full
|
|
|
|
|
* buffer window. The smaller buffer is released first which guarantee that the
|
|
|
|
|
* buffer window has room left.
|
|
|
|
|
*
|
|
|
|
|
* Returns buffer pointer or NULL on allocation failure.
|
|
|
|
|
*/
|
|
|
|
|
struct buffer *qcc_realloc_stream_txbuf(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
struct buffer *out = qc_stream_buf_get(qcs->stream);
|
|
|
|
|
|
|
|
|
|
/* Stream must not try to reallocate a buffer if currently waiting for one. */
|
|
|
|
|
BUG_ON(LIST_INLIST(&qcs->el_buf));
|
|
|
|
|
|
2024-09-25 12:25:08 -04:00
|
|
|
if (likely(!(qcs->flags & QC_SF_TXBUB_OOB))) {
|
2024-07-29 11:01:38 -04:00
|
|
|
/* Reduce buffer window. As such there is always some space
|
|
|
|
|
* left for a new buffer allocation.
|
|
|
|
|
*/
|
|
|
|
|
BUG_ON(qcc->tx.buf_in_flight < b_size(out));
|
|
|
|
|
qcc->tx.buf_in_flight -= b_size(out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out = qc_stream_buf_realloc(qcs->stream);
|
|
|
|
|
if (!out) {
|
|
|
|
|
TRACE_ERROR("buffer alloc failure", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 12:25:08 -04:00
|
|
|
if (likely(!(qcs->flags & QC_SF_TXBUB_OOB)))
|
2024-07-29 11:01:38 -04:00
|
|
|
qcc->tx.buf_in_flight += b_size(out);
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
return out && b_size(out) ? out : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-17 10:01:00 -05:00
|
|
|
/* Try to realign <out> buffer for <qcs> stream. This is done only if there is
|
|
|
|
|
* no data waiting for ACK.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 if realign was performed else non-zero.
|
|
|
|
|
*/
|
|
|
|
|
int qcc_realign_stream_txbuf(const struct qcs *qcs, struct buffer *out)
|
|
|
|
|
{
|
|
|
|
|
if (qcs_prep_bytes(qcs) == b_data(out)) {
|
|
|
|
|
b_slow_realign(out, trash.area, b_data(out));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-22 11:03:41 -05:00
|
|
|
/* Release the current <qcs> Tx buffer. This is useful if space left is not
|
|
|
|
|
* enough anymore. A new instance can then be allocated to continue sending.
|
|
|
|
|
*
|
|
|
|
|
* This operation fails if there is not yet sent bytes in the buffer. In this
|
|
|
|
|
* case, stream layer should interrupt sending until further notification.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 if buffer is released and a new one can be allocated or non-zero
|
|
|
|
|
* if there is still remaining data.
|
|
|
|
|
*/
|
|
|
|
|
int qcc_release_stream_txbuf(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
const uint64_t bytes = qcs_prep_bytes(qcs);
|
|
|
|
|
|
|
|
|
|
/* Cannot release buffer if prepared data is not fully sent. */
|
|
|
|
|
if (bytes) {
|
|
|
|
|
qcs->flags |= QC_SF_BLK_MROOM;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qc_stream_buf_release(qcs->stream);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Returns true if stream layer can proceed to emission via <qcs>. */
|
|
|
|
|
int qcc_stream_can_send(const struct qcs *qcs)
|
|
|
|
|
{
|
2024-01-17 09:15:55 -05:00
|
|
|
return !(qcs->flags & QC_SF_BLK_MROOM) && !LIST_INLIST(&qcs->el_buf);
|
2024-01-22 11:03:41 -05:00
|
|
|
}
|
|
|
|
|
|
2023-10-18 11:48:11 -04:00
|
|
|
/* Wakes up every streams of <qcc> which are currently waiting for sending but
|
|
|
|
|
* are blocked on connection flow control.
|
|
|
|
|
*/
|
|
|
|
|
static void qcc_notify_fctl(struct qcc *qcc)
|
|
|
|
|
{
|
|
|
|
|
struct qcs *qcs;
|
|
|
|
|
|
|
|
|
|
while (!LIST_ISEMPTY(&qcc->fctl_list)) {
|
|
|
|
|
qcs = LIST_ELEM(qcc->fctl_list.n, struct qcs *, el_fctl);
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_fctl);
|
2024-07-31 12:43:55 -04:00
|
|
|
tot_time_stop(&qcs->timer.fctl);
|
2023-10-18 11:48:11 -04:00
|
|
|
qcs_notify_send(qcs);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-11 11:53:29 -05:00
|
|
|
/* Free <qcc> STREAM frames in Tx list. */
|
|
|
|
|
static void qcc_clear_frms(struct qcc *qcc)
|
|
|
|
|
{
|
2024-12-12 06:03:37 -05:00
|
|
|
TRACE_STATE("resetting STREAM frames list", QMUX_EV_QCC_SEND, qcc->conn);
|
2024-12-11 11:53:29 -05:00
|
|
|
while (!LIST_ISEMPTY(&qcc->tx.frms)) {
|
|
|
|
|
struct quic_frame *frm = LIST_ELEM(qcc->tx.frms.n, struct quic_frame *, list);
|
|
|
|
|
qc_frm_free(qcc->conn->handle.qc, &frm);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
BUG/MEDIUM: mux-quic: fix crash on RS/SS emission if already close local
A BUG_ON() is present in qcc_send_stream() to ensure that emission is
never performed with a stream already closed locally. However, this
function is also used for RESET_STREAM/STOP_SENDING emission. No
protection exists to ensure that RS/SS is not scheduled after stream
local closure, which would result in this BUG_ON() crash.
This crash can be triggered with the following QUIC client sequence :
1. SS is emitted to open a new stream. QUIC-MUX schedules a RS emission
by and the stream is locally closed.
2. An invalid HTTP/3 request is sent on the same stream, for example
with duplicated pseudo-headers. The objective is to ensure
qcc_abort_stream_read() is called after stream closure, which results
in the following backtrace.
0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
1633 BUG_ON(qcs_is_close_local(qcs));
[ ## gdb ## ] bt
#0 0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
#1 0x000055555566a921 in qcc_abort_stream_read (qcs=0x7ffff0061420) at src/mux_quic.c:1658
#2 0x0000555555685426 in h3_rcv_buf (qcs=0x7ffff0061420, b=0x7ffff748d3f0, fin=0) at src/h3.c:1454
#3 0x0000555555668a67 in qcc_decode_qcs (qcc=0x7ffff0049eb0, qcs=0x7ffff0061420) at src/mux_quic.c:1315
#4 0x000055555566c76e in qcc_recv (qcc=0x7ffff0049eb0, id=12, len=0, offset=23, fin=0 '\000',
data=0x7fffe0049c1c "\366\r,\230\205\354\234\301;\2563\335\037k\306\334\037\260", <incomplete sequence \323>) at src/mux_quic.c:1901
#5 0x0000555555692551 in qc_handle_strm_frm (pkt=0x7fffe00484b0, strm_frm=0x7ffff00539e0, qc=0x7fffe0049220, fin=0 '\000') at src/quic_rx.c:635
#6 0x0000555555694530 in qc_parse_pkt_frms (qc=0x7fffe0049220, pkt=0x7fffe00484b0, qel=0x7fffe0075fc0) at src/quic_rx.c:980
#7 0x0000555555696c7a in qc_treat_rx_pkts (qc=0x7fffe0049220) at src/quic_rx.c:1324
#8 0x00005555556b781b in quic_conn_app_io_cb (t=0x7fffe0037f20, context=0x7fffe0049220, state=49232) at src/quic_conn.c:601
#9 0x0000555555d53788 in run_tasks_from_lists (budgets=0x7ffff748e2b0) at src/task.c:603
#10 0x0000555555d541ae in process_runnable_tasks () at src/task.c:886
#11 0x00005555559c39e9 in run_poll_loop () at src/haproxy.c:2858
#12 0x00005555559c41ea in run_thread_poll_loop (data=0x55555629fb40 <ha_thread_info+64>) at src/haproxy.c:3075
The proper solution is to not execute this BUG_ON() for RS/SS emission.
Indeed, it is valid and can be useful to emit these frames, even after
stream local closure.
To implement this, qcc_send_stream() has been rewritten as a mere
wrapper function around the new internal _qcc_send_stream(). The latter
is used only by QMUX for STREAM, RS and SS emission. Application layer
continue to use the original function for STREAM emission, with the
BUG_ON() still in place there.
This must be backported up to 2.8.
2025-03-20 11:01:16 -04:00
|
|
|
/* Register <qcs> stream for emission of STREAM, STOP_SENDING or RESET_STREAM.
|
|
|
|
|
* Set <urg> to true if stream should be emitted in priority. This is useful
|
|
|
|
|
* when sending STOP_SENDING or RESET_STREAM, or for emission on an application
|
|
|
|
|
* control stream.
|
|
|
|
|
*/
|
|
|
|
|
static void _qcc_send_stream(struct qcs *qcs, int urg)
|
|
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
|
|
|
|
|
qcc_clear_frms(qcc);
|
|
|
|
|
|
|
|
|
|
if (urg) {
|
|
|
|
|
/* qcc_emit_rs_ss() relies on resetted/aborted streams in send_list front. */
|
|
|
|
|
BUG_ON(!(qcs->flags & (QC_SF_TO_RESET|QC_SF_TO_STOP_SENDING|QC_SF_TXBUB_OOB)));
|
|
|
|
|
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_send);
|
|
|
|
|
LIST_INSERT(&qcc->send_list, &qcs->el_send);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (!LIST_INLIST(&qcs->el_send))
|
|
|
|
|
LIST_APPEND(&qcs->qcc->send_list, &qcs->el_send);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-04 05:44:38 -04:00
|
|
|
/* Prepare for the emission of RESET_STREAM on <qcs> with error code <err>. */
|
|
|
|
|
void qcc_reset_stream(struct qcs *qcs, int err)
|
|
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
2024-01-30 05:23:48 -05:00
|
|
|
const uint64_t diff = qcs_prep_bytes(qcs);
|
2022-07-04 05:44:38 -04:00
|
|
|
|
|
|
|
|
if ((qcs->flags & QC_SF_TO_RESET) || qcs_is_close_local(qcs))
|
|
|
|
|
return;
|
|
|
|
|
|
2024-01-17 09:15:55 -05:00
|
|
|
/* TODO if QCS waiting for buffer, it could be removed from
|
|
|
|
|
* <qcc.buf_wait_list> if sending is closed now.
|
|
|
|
|
*/
|
|
|
|
|
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_STATE("reset stream", QMUX_EV_QCS_END, qcc->conn, qcs);
|
2022-07-04 05:44:38 -04:00
|
|
|
qcs->flags |= QC_SF_TO_RESET;
|
|
|
|
|
qcs->err = err;
|
2023-01-06 11:43:11 -05:00
|
|
|
|
2024-01-30 05:23:48 -05:00
|
|
|
if (diff) {
|
2023-10-18 11:48:11 -04:00
|
|
|
const int soft_blocked = qfctl_sblocked(&qcc->tx.fc);
|
|
|
|
|
|
2024-01-30 05:23:48 -05:00
|
|
|
/* Soft offset cannot be inferior to real one. */
|
|
|
|
|
BUG_ON(qcc->tx.fc.off_soft - diff < qcc->tx.fc.off_real);
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
|
2024-02-22 04:12:27 -05:00
|
|
|
/* Subtract to conn flow control data amount prepared on stream not yet sent. */
|
2024-01-30 05:23:48 -05:00
|
|
|
qcc->tx.fc.off_soft -= diff;
|
2023-10-18 11:48:11 -04:00
|
|
|
if (soft_blocked && !qfctl_sblocked(&qcc->tx.fc))
|
|
|
|
|
qcc_notify_fctl(qcc);
|
2024-01-30 05:23:48 -05:00
|
|
|
|
|
|
|
|
/* Reset QCS soft off to prevent BUG_ON() on qcs_destroy(). */
|
|
|
|
|
qcs->tx.fc.off_soft = qcs->tx.fc.off_real;
|
2023-10-18 11:48:11 -04:00
|
|
|
}
|
|
|
|
|
|
2023-12-19 05:22:28 -05:00
|
|
|
/* Report send error to stream-endpoint layer. */
|
|
|
|
|
if (qcs_sc(qcs)) {
|
|
|
|
|
se_fl_set_error(qcs->sd);
|
|
|
|
|
qcs_alert(qcs);
|
|
|
|
|
}
|
|
|
|
|
|
BUG/MEDIUM: mux-quic: fix crash on RS/SS emission if already close local
A BUG_ON() is present in qcc_send_stream() to ensure that emission is
never performed with a stream already closed locally. However, this
function is also used for RESET_STREAM/STOP_SENDING emission. No
protection exists to ensure that RS/SS is not scheduled after stream
local closure, which would result in this BUG_ON() crash.
This crash can be triggered with the following QUIC client sequence :
1. SS is emitted to open a new stream. QUIC-MUX schedules a RS emission
by and the stream is locally closed.
2. An invalid HTTP/3 request is sent on the same stream, for example
with duplicated pseudo-headers. The objective is to ensure
qcc_abort_stream_read() is called after stream closure, which results
in the following backtrace.
0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
1633 BUG_ON(qcs_is_close_local(qcs));
[ ## gdb ## ] bt
#0 0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
#1 0x000055555566a921 in qcc_abort_stream_read (qcs=0x7ffff0061420) at src/mux_quic.c:1658
#2 0x0000555555685426 in h3_rcv_buf (qcs=0x7ffff0061420, b=0x7ffff748d3f0, fin=0) at src/h3.c:1454
#3 0x0000555555668a67 in qcc_decode_qcs (qcc=0x7ffff0049eb0, qcs=0x7ffff0061420) at src/mux_quic.c:1315
#4 0x000055555566c76e in qcc_recv (qcc=0x7ffff0049eb0, id=12, len=0, offset=23, fin=0 '\000',
data=0x7fffe0049c1c "\366\r,\230\205\354\234\301;\2563\335\037k\306\334\037\260", <incomplete sequence \323>) at src/mux_quic.c:1901
#5 0x0000555555692551 in qc_handle_strm_frm (pkt=0x7fffe00484b0, strm_frm=0x7ffff00539e0, qc=0x7fffe0049220, fin=0 '\000') at src/quic_rx.c:635
#6 0x0000555555694530 in qc_parse_pkt_frms (qc=0x7fffe0049220, pkt=0x7fffe00484b0, qel=0x7fffe0075fc0) at src/quic_rx.c:980
#7 0x0000555555696c7a in qc_treat_rx_pkts (qc=0x7fffe0049220) at src/quic_rx.c:1324
#8 0x00005555556b781b in quic_conn_app_io_cb (t=0x7fffe0037f20, context=0x7fffe0049220, state=49232) at src/quic_conn.c:601
#9 0x0000555555d53788 in run_tasks_from_lists (budgets=0x7ffff748e2b0) at src/task.c:603
#10 0x0000555555d541ae in process_runnable_tasks () at src/task.c:886
#11 0x00005555559c39e9 in run_poll_loop () at src/haproxy.c:2858
#12 0x00005555559c41ea in run_thread_poll_loop (data=0x55555629fb40 <ha_thread_info+64>) at src/haproxy.c:3075
The proper solution is to not execute this BUG_ON() for RS/SS emission.
Indeed, it is valid and can be useful to emit these frames, even after
stream local closure.
To implement this, qcc_send_stream() has been rewritten as a mere
wrapper function around the new internal _qcc_send_stream(). The latter
is used only by QMUX for STREAM, RS and SS emission. Application layer
continue to use the original function for STREAM emission, with the
BUG_ON() still in place there.
This must be backported up to 2.8.
2025-03-20 11:01:16 -04:00
|
|
|
_qcc_send_stream(qcs, 1);
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2022-07-04 05:44:38 -04:00
|
|
|
}
|
|
|
|
|
|
BUG/MEDIUM: mux-quic: fix crash on RS/SS emission if already close local
A BUG_ON() is present in qcc_send_stream() to ensure that emission is
never performed with a stream already closed locally. However, this
function is also used for RESET_STREAM/STOP_SENDING emission. No
protection exists to ensure that RS/SS is not scheduled after stream
local closure, which would result in this BUG_ON() crash.
This crash can be triggered with the following QUIC client sequence :
1. SS is emitted to open a new stream. QUIC-MUX schedules a RS emission
by and the stream is locally closed.
2. An invalid HTTP/3 request is sent on the same stream, for example
with duplicated pseudo-headers. The objective is to ensure
qcc_abort_stream_read() is called after stream closure, which results
in the following backtrace.
0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
1633 BUG_ON(qcs_is_close_local(qcs));
[ ## gdb ## ] bt
#0 0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
#1 0x000055555566a921 in qcc_abort_stream_read (qcs=0x7ffff0061420) at src/mux_quic.c:1658
#2 0x0000555555685426 in h3_rcv_buf (qcs=0x7ffff0061420, b=0x7ffff748d3f0, fin=0) at src/h3.c:1454
#3 0x0000555555668a67 in qcc_decode_qcs (qcc=0x7ffff0049eb0, qcs=0x7ffff0061420) at src/mux_quic.c:1315
#4 0x000055555566c76e in qcc_recv (qcc=0x7ffff0049eb0, id=12, len=0, offset=23, fin=0 '\000',
data=0x7fffe0049c1c "\366\r,\230\205\354\234\301;\2563\335\037k\306\334\037\260", <incomplete sequence \323>) at src/mux_quic.c:1901
#5 0x0000555555692551 in qc_handle_strm_frm (pkt=0x7fffe00484b0, strm_frm=0x7ffff00539e0, qc=0x7fffe0049220, fin=0 '\000') at src/quic_rx.c:635
#6 0x0000555555694530 in qc_parse_pkt_frms (qc=0x7fffe0049220, pkt=0x7fffe00484b0, qel=0x7fffe0075fc0) at src/quic_rx.c:980
#7 0x0000555555696c7a in qc_treat_rx_pkts (qc=0x7fffe0049220) at src/quic_rx.c:1324
#8 0x00005555556b781b in quic_conn_app_io_cb (t=0x7fffe0037f20, context=0x7fffe0049220, state=49232) at src/quic_conn.c:601
#9 0x0000555555d53788 in run_tasks_from_lists (budgets=0x7ffff748e2b0) at src/task.c:603
#10 0x0000555555d541ae in process_runnable_tasks () at src/task.c:886
#11 0x00005555559c39e9 in run_poll_loop () at src/haproxy.c:2858
#12 0x00005555559c41ea in run_thread_poll_loop (data=0x55555629fb40 <ha_thread_info+64>) at src/haproxy.c:3075
The proper solution is to not execute this BUG_ON() for RS/SS emission.
Indeed, it is valid and can be useful to emit these frames, even after
stream local closure.
To implement this, qcc_send_stream() has been rewritten as a mere
wrapper function around the new internal _qcc_send_stream(). The latter
is used only by QMUX for STREAM, RS and SS emission. Application layer
continue to use the original function for STREAM emission, with the
BUG_ON() still in place there.
This must be backported up to 2.8.
2025-03-20 11:01:16 -04:00
|
|
|
/* Register <qcs> stream for STREAM emission. Set <urg> to 1 if stream content
|
|
|
|
|
* should be treated in priority compared to other streams. <count> must
|
|
|
|
|
* contains the size of the frame payload, used for flow control accounting.
|
2023-01-09 04:34:25 -05:00
|
|
|
*/
|
2024-01-10 05:09:33 -05:00
|
|
|
void qcc_send_stream(struct qcs *qcs, int urg, int count)
|
2023-01-03 08:39:24 -05:00
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
|
BUG/MEDIUM: mux-quic: fix crash on RS/SS emission if already close local
A BUG_ON() is present in qcc_send_stream() to ensure that emission is
never performed with a stream already closed locally. However, this
function is also used for RESET_STREAM/STOP_SENDING emission. No
protection exists to ensure that RS/SS is not scheduled after stream
local closure, which would result in this BUG_ON() crash.
This crash can be triggered with the following QUIC client sequence :
1. SS is emitted to open a new stream. QUIC-MUX schedules a RS emission
by and the stream is locally closed.
2. An invalid HTTP/3 request is sent on the same stream, for example
with duplicated pseudo-headers. The objective is to ensure
qcc_abort_stream_read() is called after stream closure, which results
in the following backtrace.
0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
1633 BUG_ON(qcs_is_close_local(qcs));
[ ## gdb ## ] bt
#0 0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
#1 0x000055555566a921 in qcc_abort_stream_read (qcs=0x7ffff0061420) at src/mux_quic.c:1658
#2 0x0000555555685426 in h3_rcv_buf (qcs=0x7ffff0061420, b=0x7ffff748d3f0, fin=0) at src/h3.c:1454
#3 0x0000555555668a67 in qcc_decode_qcs (qcc=0x7ffff0049eb0, qcs=0x7ffff0061420) at src/mux_quic.c:1315
#4 0x000055555566c76e in qcc_recv (qcc=0x7ffff0049eb0, id=12, len=0, offset=23, fin=0 '\000',
data=0x7fffe0049c1c "\366\r,\230\205\354\234\301;\2563\335\037k\306\334\037\260", <incomplete sequence \323>) at src/mux_quic.c:1901
#5 0x0000555555692551 in qc_handle_strm_frm (pkt=0x7fffe00484b0, strm_frm=0x7ffff00539e0, qc=0x7fffe0049220, fin=0 '\000') at src/quic_rx.c:635
#6 0x0000555555694530 in qc_parse_pkt_frms (qc=0x7fffe0049220, pkt=0x7fffe00484b0, qel=0x7fffe0075fc0) at src/quic_rx.c:980
#7 0x0000555555696c7a in qc_treat_rx_pkts (qc=0x7fffe0049220) at src/quic_rx.c:1324
#8 0x00005555556b781b in quic_conn_app_io_cb (t=0x7fffe0037f20, context=0x7fffe0049220, state=49232) at src/quic_conn.c:601
#9 0x0000555555d53788 in run_tasks_from_lists (budgets=0x7ffff748e2b0) at src/task.c:603
#10 0x0000555555d541ae in process_runnable_tasks () at src/task.c:886
#11 0x00005555559c39e9 in run_poll_loop () at src/haproxy.c:2858
#12 0x00005555559c41ea in run_thread_poll_loop (data=0x55555629fb40 <ha_thread_info+64>) at src/haproxy.c:3075
The proper solution is to not execute this BUG_ON() for RS/SS emission.
Indeed, it is valid and can be useful to emit these frames, even after
stream local closure.
To implement this, qcc_send_stream() has been rewritten as a mere
wrapper function around the new internal _qcc_send_stream(). The latter
is used only by QMUX for STREAM, RS and SS emission. Application layer
continue to use the original function for STREAM emission, with the
BUG_ON() still in place there.
This must be backported up to 2.8.
2025-03-20 11:01:16 -04:00
|
|
|
/* Cannot send STREAM if already closed. */
|
2023-01-03 08:39:24 -05:00
|
|
|
BUG_ON(qcs_is_close_local(qcs));
|
2025-03-20 13:10:56 -04:00
|
|
|
|
BUG/MEDIUM: mux-quic: fix crash on RS/SS emission if already close local
A BUG_ON() is present in qcc_send_stream() to ensure that emission is
never performed with a stream already closed locally. However, this
function is also used for RESET_STREAM/STOP_SENDING emission. No
protection exists to ensure that RS/SS is not scheduled after stream
local closure, which would result in this BUG_ON() crash.
This crash can be triggered with the following QUIC client sequence :
1. SS is emitted to open a new stream. QUIC-MUX schedules a RS emission
by and the stream is locally closed.
2. An invalid HTTP/3 request is sent on the same stream, for example
with duplicated pseudo-headers. The objective is to ensure
qcc_abort_stream_read() is called after stream closure, which results
in the following backtrace.
0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
1633 BUG_ON(qcs_is_close_local(qcs));
[ ## gdb ## ] bt
#0 0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
#1 0x000055555566a921 in qcc_abort_stream_read (qcs=0x7ffff0061420) at src/mux_quic.c:1658
#2 0x0000555555685426 in h3_rcv_buf (qcs=0x7ffff0061420, b=0x7ffff748d3f0, fin=0) at src/h3.c:1454
#3 0x0000555555668a67 in qcc_decode_qcs (qcc=0x7ffff0049eb0, qcs=0x7ffff0061420) at src/mux_quic.c:1315
#4 0x000055555566c76e in qcc_recv (qcc=0x7ffff0049eb0, id=12, len=0, offset=23, fin=0 '\000',
data=0x7fffe0049c1c "\366\r,\230\205\354\234\301;\2563\335\037k\306\334\037\260", <incomplete sequence \323>) at src/mux_quic.c:1901
#5 0x0000555555692551 in qc_handle_strm_frm (pkt=0x7fffe00484b0, strm_frm=0x7ffff00539e0, qc=0x7fffe0049220, fin=0 '\000') at src/quic_rx.c:635
#6 0x0000555555694530 in qc_parse_pkt_frms (qc=0x7fffe0049220, pkt=0x7fffe00484b0, qel=0x7fffe0075fc0) at src/quic_rx.c:980
#7 0x0000555555696c7a in qc_treat_rx_pkts (qc=0x7fffe0049220) at src/quic_rx.c:1324
#8 0x00005555556b781b in quic_conn_app_io_cb (t=0x7fffe0037f20, context=0x7fffe0049220, state=49232) at src/quic_conn.c:601
#9 0x0000555555d53788 in run_tasks_from_lists (budgets=0x7ffff748e2b0) at src/task.c:603
#10 0x0000555555d541ae in process_runnable_tasks () at src/task.c:886
#11 0x00005555559c39e9 in run_poll_loop () at src/haproxy.c:2858
#12 0x00005555559c41ea in run_thread_poll_loop (data=0x55555629fb40 <ha_thread_info+64>) at src/haproxy.c:3075
The proper solution is to not execute this BUG_ON() for RS/SS emission.
Indeed, it is valid and can be useful to emit these frames, even after
stream local closure.
To implement this, qcc_send_stream() has been rewritten as a mere
wrapper function around the new internal _qcc_send_stream(). The latter
is used only by QMUX for STREAM, RS and SS emission. Application layer
continue to use the original function for STREAM emission, with the
BUG_ON() still in place there.
This must be backported up to 2.8.
2025-03-20 11:01:16 -04:00
|
|
|
_qcc_send_stream(qcs, urg);
|
2023-01-03 08:39:24 -05:00
|
|
|
|
2023-10-18 11:48:11 -04:00
|
|
|
if (count) {
|
|
|
|
|
qfctl_sinc(&qcc->tx.fc, count);
|
2023-10-18 09:55:38 -04:00
|
|
|
qfctl_sinc(&qcs->tx.fc, count);
|
2023-10-18 11:48:11 -04:00
|
|
|
}
|
2023-10-18 09:55:38 -04:00
|
|
|
|
2023-01-03 08:39:24 -05:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 08:58:28 -05:00
|
|
|
/* Prepare for the emission of STOP_SENDING on <qcs>. */
|
|
|
|
|
void qcc_abort_stream_read(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_NEW, qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
if ((qcs->flags & QC_SF_TO_STOP_SENDING) || qcs_is_close_remote(qcs))
|
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
|
|
TRACE_STATE("abort stream read", QMUX_EV_QCS_END, qcc->conn, qcs);
|
|
|
|
|
qcs->flags |= (QC_SF_TO_STOP_SENDING|QC_SF_READ_ABORTED);
|
2023-01-06 11:43:11 -05:00
|
|
|
|
BUG/MEDIUM: mux-quic: fix crash on RS/SS emission if already close local
A BUG_ON() is present in qcc_send_stream() to ensure that emission is
never performed with a stream already closed locally. However, this
function is also used for RESET_STREAM/STOP_SENDING emission. No
protection exists to ensure that RS/SS is not scheduled after stream
local closure, which would result in this BUG_ON() crash.
This crash can be triggered with the following QUIC client sequence :
1. SS is emitted to open a new stream. QUIC-MUX schedules a RS emission
by and the stream is locally closed.
2. An invalid HTTP/3 request is sent on the same stream, for example
with duplicated pseudo-headers. The objective is to ensure
qcc_abort_stream_read() is called after stream closure, which results
in the following backtrace.
0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
1633 BUG_ON(qcs_is_close_local(qcs));
[ ## gdb ## ] bt
#0 0x000055555566a620 in qcc_send_stream (qcs=0x7ffff0061420, urg=1, count=0) at src/mux_quic.c:1633
#1 0x000055555566a921 in qcc_abort_stream_read (qcs=0x7ffff0061420) at src/mux_quic.c:1658
#2 0x0000555555685426 in h3_rcv_buf (qcs=0x7ffff0061420, b=0x7ffff748d3f0, fin=0) at src/h3.c:1454
#3 0x0000555555668a67 in qcc_decode_qcs (qcc=0x7ffff0049eb0, qcs=0x7ffff0061420) at src/mux_quic.c:1315
#4 0x000055555566c76e in qcc_recv (qcc=0x7ffff0049eb0, id=12, len=0, offset=23, fin=0 '\000',
data=0x7fffe0049c1c "\366\r,\230\205\354\234\301;\2563\335\037k\306\334\037\260", <incomplete sequence \323>) at src/mux_quic.c:1901
#5 0x0000555555692551 in qc_handle_strm_frm (pkt=0x7fffe00484b0, strm_frm=0x7ffff00539e0, qc=0x7fffe0049220, fin=0 '\000') at src/quic_rx.c:635
#6 0x0000555555694530 in qc_parse_pkt_frms (qc=0x7fffe0049220, pkt=0x7fffe00484b0, qel=0x7fffe0075fc0) at src/quic_rx.c:980
#7 0x0000555555696c7a in qc_treat_rx_pkts (qc=0x7fffe0049220) at src/quic_rx.c:1324
#8 0x00005555556b781b in quic_conn_app_io_cb (t=0x7fffe0037f20, context=0x7fffe0049220, state=49232) at src/quic_conn.c:601
#9 0x0000555555d53788 in run_tasks_from_lists (budgets=0x7ffff748e2b0) at src/task.c:603
#10 0x0000555555d541ae in process_runnable_tasks () at src/task.c:886
#11 0x00005555559c39e9 in run_poll_loop () at src/haproxy.c:2858
#12 0x00005555559c41ea in run_thread_poll_loop (data=0x55555629fb40 <ha_thread_info+64>) at src/haproxy.c:3075
The proper solution is to not execute this BUG_ON() for RS/SS emission.
Indeed, it is valid and can be useful to emit these frames, even after
stream local closure.
To implement this, qcc_send_stream() has been rewritten as a mere
wrapper function around the new internal _qcc_send_stream(). The latter
is used only by QMUX for STREAM, RS and SS emission. Application layer
continue to use the original function for STREAM emission, with the
BUG_ON() still in place there.
This must be backported up to 2.8.
2025-03-20 11:01:16 -04:00
|
|
|
_qcc_send_stream(qcs, 1);
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2022-12-09 08:58:28 -05:00
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_NEW, qcc->conn, qcs);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-10 10:58:01 -04:00
|
|
|
/* Install the <app_ops> 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)
|
|
|
|
|
{
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_NEW, qcc->conn);
|
|
|
|
|
|
2023-01-25 11:44:36 -05:00
|
|
|
if (app_ops->init && !app_ops->init(qcc)) {
|
2025-02-17 10:00:03 -05:00
|
|
|
TRACE_ERROR("application layer install error", QMUX_EV_QCC_NEW, qcc->conn);
|
2022-08-10 10:58:01 -04:00
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 10:00:03 -05:00
|
|
|
TRACE_PROTO("application layer installed", QMUX_EV_QCC_NEW, qcc->conn);
|
2023-01-25 11:44:36 -05:00
|
|
|
qcc->app_ops = app_ops;
|
2022-08-10 10:58:01 -04:00
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_NEW, qcc->conn);
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_NEW, qcc->conn);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-07 05:43:01 -05:00
|
|
|
/* Retrieves the Rx buffer instance usable to store STREAM data starting at
|
|
|
|
|
* <offset>. It is dynamically allocated if not already instantiated. <len>
|
|
|
|
|
* must contains the size of the STREAM frame. It may be reduced by the
|
|
|
|
|
* function if data is too large relative to the buffer starting offset.
|
|
|
|
|
* Another buffer instance should be allocated to store the remaining data.
|
|
|
|
|
*
|
|
|
|
|
* Returns the buffer instance or NULL in case of error.
|
|
|
|
|
*/
|
|
|
|
|
static struct qc_stream_rxbuf *qcs_get_rxbuf(struct qcs *qcs, uint64_t offset,
|
|
|
|
|
uint64_t *len)
|
|
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
struct eb64_node *node;
|
|
|
|
|
struct qc_stream_rxbuf *buf;
|
|
|
|
|
struct ncbuf *ncbuf;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCS_RECV, qcs->qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
node = eb64_lookup_le(&qcs->rx.bufs, offset);
|
|
|
|
|
if (node)
|
|
|
|
|
buf = container_of(node, struct qc_stream_rxbuf, off_node);
|
|
|
|
|
|
|
|
|
|
if (!node || offset >= buf->off_end) {
|
|
|
|
|
const uint64_t aligned_off = offset - (offset % qmux_stream_rx_bufsz());
|
|
|
|
|
|
|
|
|
|
TRACE_DEVEL("allocating a new entry", QMUX_EV_QCS_RECV, qcs->qcc->conn, qcs);
|
|
|
|
|
buf = pool_alloc(pool_head_qc_stream_rxbuf);
|
|
|
|
|
if (!buf) {
|
|
|
|
|
TRACE_ERROR("qcs rxbuf alloc error", QMUX_EV_QCC_RECV, qcc->conn, qcs);
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf->ncb = NCBUF_NULL;
|
|
|
|
|
buf->off_node.key = aligned_off;
|
|
|
|
|
buf->off_end = aligned_off + qmux_stream_rx_bufsz();
|
|
|
|
|
eb64_insert(&qcs->rx.bufs, &buf->off_node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ncbuf = &buf->ncb;
|
|
|
|
|
if (!qcs_get_ncbuf(qcs, ncbuf) || ncb_is_null(ncbuf)) {
|
|
|
|
|
TRACE_ERROR("receive ncbuf alloc failure", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (offset + *len > buf->off_end)
|
|
|
|
|
*len = buf->off_end - offset;
|
|
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_RECV, qcs->qcc->conn, qcs);
|
|
|
|
|
return buf;
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCS_RECV, qcs->qcc->conn, qcs);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 05:38:22 -04:00
|
|
|
/* Handle a new STREAM frame for stream with id <id>. Payload is pointed by
|
|
|
|
|
* <data> with length <len> and represents the offset <offset>. <fin> is set if
|
|
|
|
|
* the QUIC frame FIN bit is set.
|
2022-02-28 05:37:48 -05:00
|
|
|
*
|
2022-07-07 09:02:32 -04:00
|
|
|
* Returns 0 on success else non-zero. On error, the received frame should not
|
|
|
|
|
* be acknowledged.
|
2022-02-28 05:37:48 -05:00
|
|
|
*/
|
|
|
|
|
int qcc_recv(struct qcc *qcc, uint64_t id, uint64_t len, uint64_t offset,
|
2022-05-18 05:38:22 -04:00
|
|
|
char fin, char *data)
|
2022-02-28 05:37:48 -05:00
|
|
|
{
|
2025-03-04 03:41:44 -05:00
|
|
|
const int fin_standalone = (!len && fin);
|
2022-02-28 05:37:48 -05:00
|
|
|
struct qcs *qcs;
|
2025-03-07 05:43:01 -05:00
|
|
|
enum ncb_ret ncb_ret;
|
|
|
|
|
uint64_t left;
|
|
|
|
|
int ret;
|
2022-02-28 05:37:48 -05:00
|
|
|
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
|
2023-05-04 09:49:02 -04:00
|
|
|
if (qcc->flags & QC_CF_ERRL) {
|
|
|
|
|
TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto err;
|
2022-05-24 08:47:48 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-23 10:12:49 -04:00
|
|
|
/* RFC 9000 19.8. STREAM Frames
|
|
|
|
|
*
|
|
|
|
|
* An endpoint MUST terminate the connection with error
|
|
|
|
|
* STREAM_STATE_ERROR if it receives a STREAM frame for a locally
|
|
|
|
|
* initiated stream that has not yet been created, or for a send-only
|
|
|
|
|
* stream.
|
|
|
|
|
*/
|
2022-07-07 09:02:32 -04:00
|
|
|
if (qcc_get_qcs(qcc, id, 1, 0, &qcs)) {
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_DATA("qcs retrieval error", QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
goto err;
|
2022-07-07 09:02:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!qcs) {
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_DATA("already closed stream", QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
goto out;
|
2022-07-07 09:02:32 -04:00
|
|
|
}
|
2022-02-28 05:37:48 -05:00
|
|
|
|
2022-07-04 04:02:04 -04:00
|
|
|
/* RFC 9000 4.5. Stream Final Size
|
|
|
|
|
*
|
|
|
|
|
* Once a final size for a stream is known, it cannot change. If a
|
|
|
|
|
* RESET_STREAM or STREAM frame is received indicating a change in the
|
|
|
|
|
* final size for the stream, an endpoint SHOULD respond with an error
|
|
|
|
|
* of type FINAL_SIZE_ERROR; see Section 11 for details on error
|
|
|
|
|
* handling.
|
|
|
|
|
*/
|
|
|
|
|
if (qcs->flags & QC_SF_SIZE_KNOWN &&
|
|
|
|
|
(offset + len > qcs->rx.offset_max || (fin && offset + len < qcs->rx.offset_max))) {
|
2022-08-10 10:39:54 -04:00
|
|
|
TRACE_ERROR("final size error", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV|QMUX_EV_PROTO_ERR, qcc->conn, qcs);
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_FINAL_SIZE_ERROR, 0);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto err;
|
2022-07-04 04:02:04 -04:00
|
|
|
}
|
|
|
|
|
|
2022-12-09 10:25:48 -05:00
|
|
|
if (qcs_is_close_remote(qcs)) {
|
|
|
|
|
TRACE_DATA("skipping STREAM for remotely closed", QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-14 09:36:36 -05:00
|
|
|
if (offset + len < qcs->rx.offset ||
|
|
|
|
|
(offset + len == qcs->rx.offset && (!fin || (qcs->flags & QC_SF_SIZE_KNOWN)))) {
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_DATA("already received offset", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
goto out;
|
2022-02-28 05:37:48 -05:00
|
|
|
}
|
|
|
|
|
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_PROTO("receiving STREAM", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2022-07-01 10:48:42 -04:00
|
|
|
qcs_idle_open(qcs);
|
|
|
|
|
|
2022-05-20 09:05:07 -04:00
|
|
|
if (offset + len > qcs->rx.offset_max) {
|
|
|
|
|
uint64_t diff = offset + len - qcs->rx.offset_max;
|
|
|
|
|
qcs->rx.offset_max = offset + len;
|
|
|
|
|
qcc->lfctl.offsets_recv += diff;
|
|
|
|
|
|
|
|
|
|
if (offset + len > qcs->rx.msd ||
|
|
|
|
|
qcc->lfctl.offsets_recv > qcc->lfctl.md) {
|
|
|
|
|
/* RFC 9000 4.1. Data Flow Control
|
|
|
|
|
*
|
|
|
|
|
* A receiver MUST close the connection with an error
|
|
|
|
|
* of type FLOW_CONTROL_ERROR if the sender violates
|
|
|
|
|
* the advertised connection or stream data limits
|
|
|
|
|
*/
|
2022-08-10 10:39:54 -04:00
|
|
|
TRACE_ERROR("flow control error", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV|QMUX_EV_PROTO_ERR,
|
2022-08-10 10:14:32 -04:00
|
|
|
qcc->conn, qcs);
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_FLOW_CONTROL_ERROR, 0);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto err;
|
2022-05-20 09:05:07 -04:00
|
|
|
}
|
|
|
|
|
}
|
2022-02-28 05:37:48 -05:00
|
|
|
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_DATA("newly received offset", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2022-05-13 08:49:05 -04:00
|
|
|
if (offset < qcs->rx.offset) {
|
2022-07-04 03:54:58 -04:00
|
|
|
size_t diff = qcs->rx.offset - offset;
|
|
|
|
|
|
|
|
|
|
len -= diff;
|
|
|
|
|
data += diff;
|
2022-05-13 08:49:05 -04:00
|
|
|
offset = qcs->rx.offset;
|
|
|
|
|
}
|
2022-02-28 05:37:48 -05:00
|
|
|
|
2025-03-07 05:43:01 -05:00
|
|
|
left = len;
|
|
|
|
|
while (left) {
|
|
|
|
|
struct qc_stream_rxbuf *buf;
|
|
|
|
|
ncb_sz_t ncb_off;
|
2025-02-24 10:28:50 -05:00
|
|
|
|
2025-03-07 05:43:01 -05:00
|
|
|
buf = qcs_get_rxbuf(qcs, offset, &len);
|
|
|
|
|
if (!buf) {
|
|
|
|
|
TRACE_ERROR("rxbuf alloc failure", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2025-02-27 05:35:41 -05:00
|
|
|
qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-07 05:43:01 -05:00
|
|
|
/* For oldest buffer, ncb_advance() may already have been performed. */
|
|
|
|
|
ncb_off = offset - MAX(qcs->rx.offset, buf->off_node.key);
|
|
|
|
|
|
|
|
|
|
ncb_ret = ncb_add(&buf->ncb, ncb_off, data, len, NCB_ADD_COMPARE);
|
|
|
|
|
switch (ncb_ret) {
|
2023-02-14 09:36:36 -05:00
|
|
|
case NCB_RET_OK:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case NCB_RET_DATA_REJ:
|
2022-05-20 09:14:57 -04:00
|
|
|
/* RFC 9000 2.2. Sending and Receiving Data
|
|
|
|
|
*
|
|
|
|
|
* An endpoint could receive data for a stream at the
|
|
|
|
|
* same stream offset multiple times. Data that has
|
|
|
|
|
* already been received can be discarded. The data at
|
|
|
|
|
* a given offset MUST NOT change if it is sent
|
|
|
|
|
* multiple times; an endpoint MAY treat receipt of
|
|
|
|
|
* different data at the same offset within a stream as
|
|
|
|
|
* a connection error of type PROTOCOL_VIOLATION.
|
|
|
|
|
*/
|
2022-08-10 10:39:54 -04:00
|
|
|
TRACE_ERROR("overlapping data rejected", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV|QMUX_EV_PROTO_ERR,
|
2022-05-13 08:49:05 -04:00
|
|
|
qcc->conn, qcs);
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_PROTOCOL_VIOLATION, 0);
|
2023-02-14 09:36:36 -05:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
case NCB_RET_GAP_SIZE:
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_DATA("cannot bufferize frame due to gap size limit", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV,
|
|
|
|
|
qcc->conn, qcs);
|
2023-02-14 09:36:36 -05:00
|
|
|
return 1;
|
2022-05-13 08:49:05 -04:00
|
|
|
}
|
2025-03-07 05:43:01 -05:00
|
|
|
|
|
|
|
|
offset += len;
|
|
|
|
|
data += len;
|
|
|
|
|
left -= len;
|
|
|
|
|
len = left;
|
2022-05-13 08:49:05 -04:00
|
|
|
}
|
2022-02-28 05:37:48 -05:00
|
|
|
|
|
|
|
|
if (fin)
|
2022-07-01 10:11:03 -04:00
|
|
|
qcs->flags |= QC_SF_SIZE_KNOWN;
|
2022-02-28 05:37:48 -05:00
|
|
|
|
BUG/MINOR: mux-quic: do not remotely close stream too early
A stream is considered as remotely closed once we have received all the
data with the FIN bit set.
The condition to close the stream was wrong. In particular, if we
receive an empty STREAM frame with FIN bit set, this would have close
the stream even if we do not have yet received all the data. The
condition is now adjusted to ensure that Rx buffer contains all the data
up to the stream final size.
In most cases, this bug is harmless. However, if compiled with
DEBUG_STRICT=2, a BUG_ON_HOT crash would have been triggered if close is
done too early. This was most notably the case sometimes on interop test
suite with quinn or kwik clients. This can also be artificially
reproduced by simulating reception of an empty STREAM frame with FIN bit
set in qc_handle_strm_frm() :
+ if (strm_frm->fin) {
+ qcc_recv(qc->qcc, strm_frm->id, 0,
+ strm_frm->len, strm_frm->fin,
+ (char *)strm_frm->data);
+ }
ret = qcc_recv(qc->qcc, strm_frm->id, strm_frm->len,
strm_frm->offset.key, strm_frm->fin,
(char *)strm_frm->data);
This must be backported up to 2.6.
2022-09-16 07:30:59 -04:00
|
|
|
if (qcs->flags & QC_SF_SIZE_KNOWN &&
|
2025-02-24 10:22:22 -05:00
|
|
|
qcs->rx.offset_max == qcs->rx.offset + qcs_rx_avail_data(qcs)) {
|
2022-07-01 10:48:42 -04:00
|
|
|
qcs_close_remote(qcs);
|
BUG/MINOR: mux-quic: do not remotely close stream too early
A stream is considered as remotely closed once we have received all the
data with the FIN bit set.
The condition to close the stream was wrong. In particular, if we
receive an empty STREAM frame with FIN bit set, this would have close
the stream even if we do not have yet received all the data. The
condition is now adjusted to ensure that Rx buffer contains all the data
up to the stream final size.
In most cases, this bug is harmless. However, if compiled with
DEBUG_STRICT=2, a BUG_ON_HOT crash would have been triggered if close is
done too early. This was most notably the case sometimes on interop test
suite with quinn or kwik clients. This can also be artificially
reproduced by simulating reception of an empty STREAM frame with FIN bit
set in qc_handle_strm_frm() :
+ if (strm_frm->fin) {
+ qcc_recv(qc->qcc, strm_frm->id, 0,
+ strm_frm->len, strm_frm->fin,
+ (char *)strm_frm->data);
+ }
ret = qcc_recv(qc->qcc, strm_frm->id, strm_frm->len,
strm_frm->offset.key, strm_frm->fin,
(char *)strm_frm->data);
This must be backported up to 2.6.
2022-09-16 07:30:59 -04:00
|
|
|
}
|
2022-07-01 10:48:42 -04:00
|
|
|
|
2025-03-07 05:43:01 -05:00
|
|
|
while ((qcs_rx_avail_data(qcs) && !(qcs->flags & QC_SF_DEM_FULL)) ||
|
|
|
|
|
unlikely(fin_standalone && qcs_is_close_remote(qcs))) {
|
|
|
|
|
|
|
|
|
|
ret = qcc_decode_qcs(qcc, qcs);
|
2024-12-05 04:48:51 -05:00
|
|
|
LIST_DEL_INIT(&qcs->el_recv);
|
2022-08-02 09:57:16 -04:00
|
|
|
qcc_refresh_timeout(qcc);
|
2025-03-07 05:43:01 -05:00
|
|
|
|
|
|
|
|
if (ret <= 0)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
BUG_ON_HOT(fin_standalone); /* On fin_standalone <ret> should be NULL, which ensures no infinite loop. */
|
2022-08-02 09:57:16 -04:00
|
|
|
}
|
2022-05-18 05:38:22 -04:00
|
|
|
|
2022-08-10 10:14:32 -04:00
|
|
|
out:
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
2022-02-28 05:37:48 -05:00
|
|
|
return 0;
|
2022-08-10 10:14:32 -04:00
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
return 1;
|
2022-02-28 05:37:48 -05:00
|
|
|
}
|
|
|
|
|
|
2022-03-08 10:23:03 -05:00
|
|
|
/* Handle a new MAX_DATA frame. <max> must contains the maximum data field of
|
|
|
|
|
* the frame.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 on success else non-zero.
|
|
|
|
|
*/
|
|
|
|
|
int qcc_recv_max_data(struct qcc *qcc, uint64_t max)
|
|
|
|
|
{
|
BUG/MEDIUM: mux-quic: prevent BUG_ON() by refreshing frms on MAX_DATA
QUIC MUX emission has been optimized recently by recycling STREAM frames
list between emission cycles. This is done via qcc frms list member. If
new data is available, frames list must be cleared before the next
emission to force the encoding of new STREAM frames.
If a refresh frames list is missed, it would lead to incomplete data
emission on the next transfer. In most cases, this is detected via a
BUG_ON() inside qcc_io_send(), as qcs instances remains in send_list
after a qcc_send_frames() full emission.
A bug was recently found which causes this BUG_ON() crash. This is
directly related to flow control. Indeed, when sending credit is
increased on the connection or a stream, frames list should be cleared
as new larger STREAM frames could be encoded. This was already performed
on MAX_DATA/MAX_STREAM_DATA reception but only if flow-control limit was
unblocked. However this is not the proper condition and it may lead to
insufficient frames refresh and thus this BUG_ON() crash.
Fix this by adjusting the condition for frames refresh on flow control
credit increase. Now, frames list is cleared if real offset is not
blocked and soft offset was equal or greater to the previous limit.
Indeed, this is the only case in which frames refreshing is necessary as
it would result in bigger encoded STREAM frames.
This bug was detected on QUIC interop with go-x-net client. It can also
be reproduced, albeit not systematically, using the following command :
$ ngtcp2-client -q --no-quic-dump --no-http-dump \
--exit-on-all-streams-close --max-data 10 \
127.0.0.1 20443 -n10 "http://127.0.0.1:20443/?s=10k"
This bug appeared with the following patch. As it is scheduled for 3.1
backporting, the current fix should be backported with it.
14710b5e6bf76834343d58db22e00b72590b16fe
MEDIUM/OPTIM: mux-quic: do not rebuild frms list on every send
2024-12-19 08:17:37 -05:00
|
|
|
const int blocked_soft = qfctl_sblocked(&qcc->tx.fc);
|
2023-10-18 11:48:11 -04:00
|
|
|
int unblock_soft = 0, unblock_real = 0;
|
|
|
|
|
|
2022-07-06 09:44:16 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_PROTO("receiving MAX_DATA", QMUX_EV_QCC_RECV, qcc->conn);
|
2023-10-18 11:48:11 -04:00
|
|
|
if (qfctl_set_max(&qcc->tx.fc, max, &unblock_soft, &unblock_real)) {
|
2023-03-22 10:08:01 -04:00
|
|
|
TRACE_DATA("increase remote max-data", QMUX_EV_QCC_RECV, qcc->conn);
|
2022-03-08 10:23:03 -05:00
|
|
|
|
2023-10-18 11:48:11 -04:00
|
|
|
if (unblock_real)
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2023-10-18 11:48:11 -04:00
|
|
|
|
|
|
|
|
if (unblock_soft)
|
|
|
|
|
qcc_notify_fctl(qcc);
|
2024-12-12 06:03:37 -05:00
|
|
|
|
BUG/MEDIUM: mux-quic: prevent BUG_ON() by refreshing frms on MAX_DATA
QUIC MUX emission has been optimized recently by recycling STREAM frames
list between emission cycles. This is done via qcc frms list member. If
new data is available, frames list must be cleared before the next
emission to force the encoding of new STREAM frames.
If a refresh frames list is missed, it would lead to incomplete data
emission on the next transfer. In most cases, this is detected via a
BUG_ON() inside qcc_io_send(), as qcs instances remains in send_list
after a qcc_send_frames() full emission.
A bug was recently found which causes this BUG_ON() crash. This is
directly related to flow control. Indeed, when sending credit is
increased on the connection or a stream, frames list should be cleared
as new larger STREAM frames could be encoded. This was already performed
on MAX_DATA/MAX_STREAM_DATA reception but only if flow-control limit was
unblocked. However this is not the proper condition and it may lead to
insufficient frames refresh and thus this BUG_ON() crash.
Fix this by adjusting the condition for frames refresh on flow control
credit increase. Now, frames list is cleared if real offset is not
blocked and soft offset was equal or greater to the previous limit.
Indeed, this is the only case in which frames refreshing is necessary as
it would result in bigger encoded STREAM frames.
This bug was detected on QUIC interop with go-x-net client. It can also
be reproduced, albeit not systematically, using the following command :
$ ngtcp2-client -q --no-quic-dump --no-http-dump \
--exit-on-all-streams-close --max-data 10 \
127.0.0.1 20443 -n10 "http://127.0.0.1:20443/?s=10k"
This bug appeared with the following patch. As it is scheduled for 3.1
backporting, the current fix should be backported with it.
14710b5e6bf76834343d58db22e00b72590b16fe
MEDIUM/OPTIM: mux-quic: do not rebuild frms list on every send
2024-12-19 08:17:37 -05:00
|
|
|
/* Refresh frms list only if this would result in newer data :
|
|
|
|
|
* a. flow-control is not real blocked
|
|
|
|
|
* b. soft off was equal or greater than previous limit
|
|
|
|
|
*/
|
|
|
|
|
if (!qfctl_rblocked(&qcc->tx.fc) && blocked_soft)
|
2024-12-12 06:03:37 -05:00
|
|
|
qcc_clear_frms(qcc);
|
2022-03-08 10:23:03 -05:00
|
|
|
}
|
2022-07-06 09:44:16 -04:00
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
2022-03-08 10:23:03 -05:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-08 04:39:55 -05:00
|
|
|
/* Handle a new MAX_STREAM_DATA frame. <max> must contains the maximum data
|
|
|
|
|
* field of the frame and <id> is the identifier of the QUIC stream.
|
|
|
|
|
*
|
2022-07-06 09:45:20 -04:00
|
|
|
* Returns 0 on success else non-zero. On error, the received frame should not
|
|
|
|
|
* be acknowledged.
|
2022-03-08 04:39:55 -05:00
|
|
|
*/
|
|
|
|
|
int qcc_recv_max_stream_data(struct qcc *qcc, uint64_t id, uint64_t max)
|
|
|
|
|
{
|
|
|
|
|
struct qcs *qcs;
|
|
|
|
|
|
2022-07-06 09:44:16 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
|
2023-05-04 09:49:02 -04:00
|
|
|
if (qcc->flags & QC_CF_ERRL) {
|
|
|
|
|
TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
|
2023-03-09 09:49:48 -05:00
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-06 09:45:20 -04:00
|
|
|
/* RFC 9000 19.10. MAX_STREAM_DATA Frames
|
|
|
|
|
*
|
|
|
|
|
* Receiving a MAX_STREAM_DATA frame for a locally
|
|
|
|
|
* initiated stream that has not yet been created MUST be treated as a
|
|
|
|
|
* connection error of type STREAM_STATE_ERROR. An endpoint that
|
|
|
|
|
* receives a MAX_STREAM_DATA frame for a receive-only stream MUST
|
|
|
|
|
* terminate the connection with error STREAM_STATE_ERROR.
|
|
|
|
|
*/
|
2023-03-09 09:49:48 -05:00
|
|
|
if (qcc_get_qcs(qcc, id, 0, 1, &qcs))
|
|
|
|
|
goto err;
|
2022-07-06 09:45:20 -04:00
|
|
|
|
|
|
|
|
if (qcs) {
|
BUG/MEDIUM: mux-quic: prevent BUG_ON() by refreshing frms on MAX_DATA
QUIC MUX emission has been optimized recently by recycling STREAM frames
list between emission cycles. This is done via qcc frms list member. If
new data is available, frames list must be cleared before the next
emission to force the encoding of new STREAM frames.
If a refresh frames list is missed, it would lead to incomplete data
emission on the next transfer. In most cases, this is detected via a
BUG_ON() inside qcc_io_send(), as qcs instances remains in send_list
after a qcc_send_frames() full emission.
A bug was recently found which causes this BUG_ON() crash. This is
directly related to flow control. Indeed, when sending credit is
increased on the connection or a stream, frames list should be cleared
as new larger STREAM frames could be encoded. This was already performed
on MAX_DATA/MAX_STREAM_DATA reception but only if flow-control limit was
unblocked. However this is not the proper condition and it may lead to
insufficient frames refresh and thus this BUG_ON() crash.
Fix this by adjusting the condition for frames refresh on flow control
credit increase. Now, frames list is cleared if real offset is not
blocked and soft offset was equal or greater to the previous limit.
Indeed, this is the only case in which frames refreshing is necessary as
it would result in bigger encoded STREAM frames.
This bug was detected on QUIC interop with go-x-net client. It can also
be reproduced, albeit not systematically, using the following command :
$ ngtcp2-client -q --no-quic-dump --no-http-dump \
--exit-on-all-streams-close --max-data 10 \
127.0.0.1 20443 -n10 "http://127.0.0.1:20443/?s=10k"
This bug appeared with the following patch. As it is scheduled for 3.1
backporting, the current fix should be backported with it.
14710b5e6bf76834343d58db22e00b72590b16fe
MEDIUM/OPTIM: mux-quic: do not rebuild frms list on every send
2024-12-19 08:17:37 -05:00
|
|
|
const int blocked_soft = qfctl_sblocked(&qcs->tx.fc);
|
2023-10-18 09:55:38 -04:00
|
|
|
int unblock_soft = 0, unblock_real = 0;
|
|
|
|
|
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_PROTO("receiving MAX_STREAM_DATA", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2023-10-18 09:55:38 -04:00
|
|
|
if (qfctl_set_max(&qcs->tx.fc, max, &unblock_soft, &unblock_real)) {
|
2023-03-22 10:08:01 -04:00
|
|
|
TRACE_DATA("increase remote max-stream-data", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2023-10-18 09:55:38 -04:00
|
|
|
if (unblock_real) {
|
2023-01-03 08:39:24 -05:00
|
|
|
/* TODO optim: only wakeup IO-CB if stream has data to sent. */
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2022-03-08 04:39:55 -05:00
|
|
|
}
|
2023-10-18 09:55:38 -04:00
|
|
|
|
2024-07-31 12:43:55 -04:00
|
|
|
if (unblock_soft) {
|
|
|
|
|
tot_time_stop(&qcs->timer.fctl);
|
2023-10-18 09:55:38 -04:00
|
|
|
qcs_notify_send(qcs);
|
2024-07-31 12:43:55 -04:00
|
|
|
}
|
2024-12-12 06:03:37 -05:00
|
|
|
|
BUG/MEDIUM: mux-quic: prevent BUG_ON() by refreshing frms on MAX_DATA
QUIC MUX emission has been optimized recently by recycling STREAM frames
list between emission cycles. This is done via qcc frms list member. If
new data is available, frames list must be cleared before the next
emission to force the encoding of new STREAM frames.
If a refresh frames list is missed, it would lead to incomplete data
emission on the next transfer. In most cases, this is detected via a
BUG_ON() inside qcc_io_send(), as qcs instances remains in send_list
after a qcc_send_frames() full emission.
A bug was recently found which causes this BUG_ON() crash. This is
directly related to flow control. Indeed, when sending credit is
increased on the connection or a stream, frames list should be cleared
as new larger STREAM frames could be encoded. This was already performed
on MAX_DATA/MAX_STREAM_DATA reception but only if flow-control limit was
unblocked. However this is not the proper condition and it may lead to
insufficient frames refresh and thus this BUG_ON() crash.
Fix this by adjusting the condition for frames refresh on flow control
credit increase. Now, frames list is cleared if real offset is not
blocked and soft offset was equal or greater to the previous limit.
Indeed, this is the only case in which frames refreshing is necessary as
it would result in bigger encoded STREAM frames.
This bug was detected on QUIC interop with go-x-net client. It can also
be reproduced, albeit not systematically, using the following command :
$ ngtcp2-client -q --no-quic-dump --no-http-dump \
--exit-on-all-streams-close --max-data 10 \
127.0.0.1 20443 -n10 "http://127.0.0.1:20443/?s=10k"
This bug appeared with the following patch. As it is scheduled for 3.1
backporting, the current fix should be backported with it.
14710b5e6bf76834343d58db22e00b72590b16fe
MEDIUM/OPTIM: mux-quic: do not rebuild frms list on every send
2024-12-19 08:17:37 -05:00
|
|
|
/* Same refresh condition as qcc_recv_max_data(). */
|
|
|
|
|
if (!qfctl_rblocked(&qcs->tx.fc) && blocked_soft)
|
2024-12-12 06:03:37 -05:00
|
|
|
qcc_clear_frms(qcc);
|
2022-03-08 04:39:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-03 05:17:57 -04:00
|
|
|
if (qcc_may_expire(qcc) && !qcc->nb_hreq)
|
|
|
|
|
qcc_refresh_timeout(qcc);
|
|
|
|
|
|
2022-07-06 09:44:16 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
2022-03-08 04:39:55 -05:00
|
|
|
return 0;
|
2023-03-09 09:49:48 -05:00
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
return 1;
|
2022-03-08 04:39:55 -05:00
|
|
|
}
|
|
|
|
|
|
2022-12-09 10:25:48 -05:00
|
|
|
/* Handle a new RESET_STREAM frame from stream ID <id> with error code <err>
|
|
|
|
|
* and final stream size <final_size>.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 on success else non-zero. On error, the received frame should not
|
|
|
|
|
* be acknowledged.
|
|
|
|
|
*/
|
|
|
|
|
int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t final_size)
|
|
|
|
|
{
|
|
|
|
|
struct qcs *qcs;
|
2025-02-24 10:28:50 -05:00
|
|
|
struct qc_stream_rxbuf *b;
|
2024-09-18 09:33:30 -04:00
|
|
|
int prev_glitches = qcc->glitches;
|
2022-12-09 10:25:48 -05:00
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
|
2023-05-04 09:49:02 -04:00
|
|
|
if (qcc->flags & QC_CF_ERRL) {
|
|
|
|
|
TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
|
2023-03-09 09:49:48 -05:00
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 10:25:48 -05:00
|
|
|
/* RFC 9000 19.4. RESET_STREAM Frames
|
|
|
|
|
*
|
|
|
|
|
* An endpoint that receives a RESET_STREAM frame for a send-only stream
|
|
|
|
|
* MUST terminate the connection with error STREAM_STATE_ERROR.
|
|
|
|
|
*/
|
|
|
|
|
if (qcc_get_qcs(qcc, id, 1, 0, &qcs)) {
|
|
|
|
|
TRACE_ERROR("RESET_STREAM for send-only stream received", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-15 05:31:20 -04:00
|
|
|
/* RFC 9000 3.2. Receiving Stream States
|
|
|
|
|
*
|
|
|
|
|
* A RESET_STREAM signal might be suppressed or withheld
|
|
|
|
|
* if stream data is completely received and is buffered to be read by
|
|
|
|
|
* the application. If the RESET_STREAM is suppressed, the receiving
|
|
|
|
|
* part of the stream remains in "Data Recvd".
|
|
|
|
|
*/
|
2022-12-09 10:25:48 -05:00
|
|
|
if (!qcs || qcs_is_close_remote(qcs))
|
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
TRACE_PROTO("receiving RESET_STREAM", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
qcs_idle_open(qcs);
|
|
|
|
|
|
2023-05-15 05:31:20 -04:00
|
|
|
/* Ensure stream closure is not forbidden by application protocol. */
|
2023-01-30 06:13:22 -05:00
|
|
|
if (qcc->app_ops->close) {
|
|
|
|
|
if (qcc->app_ops->close(qcs, QCC_APP_OPS_CLOSE_SIDE_RD)) {
|
|
|
|
|
TRACE_ERROR("closure rejected by app layer", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 10:25:48 -05:00
|
|
|
if (qcs->rx.offset_max > final_size ||
|
|
|
|
|
((qcs->flags & QC_SF_SIZE_KNOWN) && qcs->rx.offset_max != final_size)) {
|
|
|
|
|
TRACE_ERROR("final size error on RESET_STREAM", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_FINAL_SIZE_ERROR, 0);
|
2022-12-09 10:25:48 -05:00
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-15 05:31:20 -04:00
|
|
|
/* RFC 9000 3.2. Receiving Stream States
|
|
|
|
|
*
|
|
|
|
|
* An
|
|
|
|
|
* implementation MAY interrupt delivery of stream data, discard any
|
|
|
|
|
* data that was not consumed, and signal the receipt of the
|
|
|
|
|
* RESET_STREAM.
|
|
|
|
|
*/
|
|
|
|
|
qcs->flags |= QC_SF_SIZE_KNOWN|QC_SF_RECV_RESET;
|
2022-12-09 10:25:48 -05:00
|
|
|
qcs_close_remote(qcs);
|
2025-02-24 10:28:50 -05:00
|
|
|
while (!eb_is_empty(&qcs->rx.bufs)) {
|
|
|
|
|
b = container_of(eb64_first(&qcs->rx.bufs),
|
|
|
|
|
struct qc_stream_rxbuf, off_node);
|
|
|
|
|
qcs_free_rxbuf(qcs, b);
|
|
|
|
|
}
|
2022-12-09 10:25:48 -05:00
|
|
|
|
|
|
|
|
out:
|
2024-09-18 09:33:30 -04:00
|
|
|
if (qcc->glitches != prev_glitches)
|
|
|
|
|
session_add_glitch_ctr(qcc->conn->owner, qcc->glitches - prev_glitches);
|
|
|
|
|
|
2022-12-09 10:25:48 -05:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-04 05:44:53 -04:00
|
|
|
/* Handle a new STOP_SENDING frame for stream ID <id>. The error code should be
|
|
|
|
|
* specified in <err>.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 on success else non-zero. On error, the received frame should not
|
|
|
|
|
* be acknowledged.
|
|
|
|
|
*/
|
|
|
|
|
int qcc_recv_stop_sending(struct qcc *qcc, uint64_t id, uint64_t err)
|
|
|
|
|
{
|
|
|
|
|
struct qcs *qcs;
|
2024-09-18 09:33:30 -04:00
|
|
|
int prev_glitches = qcc->glitches;
|
2022-07-04 05:44:53 -04:00
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
|
2023-05-04 09:49:02 -04:00
|
|
|
if (qcc->flags & QC_CF_ERRL) {
|
|
|
|
|
TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
|
2023-03-09 09:49:48 -05:00
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-04 05:44:53 -04:00
|
|
|
/* RFC 9000 19.5. STOP_SENDING Frames
|
|
|
|
|
*
|
|
|
|
|
* Receiving a STOP_SENDING frame for a
|
|
|
|
|
* locally initiated stream that has not yet been created MUST be
|
|
|
|
|
* treated as a connection error of type STREAM_STATE_ERROR. An
|
|
|
|
|
* endpoint that receives a STOP_SENDING frame for a receive-only stream
|
|
|
|
|
* MUST terminate the connection with error STREAM_STATE_ERROR.
|
|
|
|
|
*/
|
2023-03-09 09:49:48 -05:00
|
|
|
if (qcc_get_qcs(qcc, id, 0, 1, &qcs))
|
|
|
|
|
goto err;
|
2022-07-04 05:44:53 -04:00
|
|
|
|
|
|
|
|
if (!qcs)
|
|
|
|
|
goto out;
|
|
|
|
|
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_PROTO("receiving STOP_SENDING", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
2022-10-03 11:20:31 -04:00
|
|
|
|
|
|
|
|
/* RFC 9000 3.5. Solicited State Transitions
|
|
|
|
|
*
|
|
|
|
|
* An endpoint is expected to send another STOP_SENDING frame if a
|
|
|
|
|
* packet containing a previous STOP_SENDING is lost. However, once
|
|
|
|
|
* either all stream data or a RESET_STREAM frame has been received for
|
|
|
|
|
* the stream -- that is, the stream is in any state other than "Recv"
|
|
|
|
|
* or "Size Known" -- sending a STOP_SENDING frame is unnecessary.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* TODO thanks to previous RFC clause, STOP_SENDING is ignored if current stream
|
|
|
|
|
* has already been closed locally. This is useful to not emit multiple
|
|
|
|
|
* RESET_STREAM for a single stream. This is functional if stream is
|
|
|
|
|
* locally closed due to all data transmitted, but in this case the RFC
|
|
|
|
|
* advices to use an explicit RESET_STREAM.
|
|
|
|
|
*/
|
|
|
|
|
if (qcs_is_close_local(qcs)) {
|
|
|
|
|
TRACE_STATE("ignoring STOP_SENDING", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-09 11:36:38 -04:00
|
|
|
qcs_idle_open(qcs);
|
|
|
|
|
|
BUG/MEDIUM: h3: handle STOP_SENDING on control stream
Before this patch, STOP_SENDING reception was considered valid even on
H3 control stream. This causes the emission in return of RESET_STREAM
and eventually the closure and freeing of the QCS instance. This then
causes a crash during connection closure as a GOAWAY frame is emitted on
the control stream which is now released.
To fix this crash, STOP_SENDING on the control stream is now properly
rejected as specified by RFC 9114. The new app_ops close callback is
used which in turn will generate a CONNECTION_CLOSE with error
H3_CLOSED_CRITICAL_STREAM.
This bug was detected in github issue #2006. Note that however it is
triggered by an incorrect client behavior. It may be useful to determine
which client behaves like this. If this case is too frequent,
STOP_SENDING should probably be silently ignored.
To reproduce this issue, quiche was patched to emit a STOP_SENDING on
its send() function in quiche/src/lib.rs:
pub fn send(&mut self, out: &mut [u8]) -> Result<(usize, SendInfo)> {
- self.send_on_path(out, None, None)
+ let ret = self.send_on_path(out, None, None);
+ self.streams.mark_stopped(3, true, 0);
+ ret
}
This must be backported up to 2.6 along with the preceeding commit :
MINOR: mux-quic/h3: define close callback
2023-01-30 06:12:43 -05:00
|
|
|
if (qcc->app_ops->close) {
|
|
|
|
|
if (qcc->app_ops->close(qcs, QCC_APP_OPS_CLOSE_SIDE_WR)) {
|
|
|
|
|
TRACE_ERROR("closure rejected by app layer", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 05:04:36 -04:00
|
|
|
/* Manually set EOS if FIN already reached as futures RESET_STREAM will be ignored in this case. */
|
|
|
|
|
if (qcs_sc(qcs) && se_fl_test(qcs->sd, SE_FL_EOI)) {
|
|
|
|
|
se_fl_set(qcs->sd, SE_FL_EOS);
|
|
|
|
|
qcs_alert(qcs);
|
|
|
|
|
}
|
2023-12-19 05:22:28 -05:00
|
|
|
|
2024-05-23 05:04:36 -04:00
|
|
|
/* If not defined yet, set abort info for the sedesc */
|
|
|
|
|
if (!qcs->sd->abort_info.info) {
|
|
|
|
|
qcs->sd->abort_info.info = (SE_ABRT_SRC_MUX_QUIC << SE_ABRT_SRC_SHIFT);
|
|
|
|
|
qcs->sd->abort_info.code = err;
|
2024-04-30 09:15:07 -04:00
|
|
|
}
|
|
|
|
|
|
2022-07-04 05:44:53 -04:00
|
|
|
/* RFC 9000 3.5. Solicited State Transitions
|
|
|
|
|
*
|
|
|
|
|
* An endpoint that receives a STOP_SENDING frame
|
|
|
|
|
* MUST send a RESET_STREAM frame if the stream is in the "Ready" or
|
|
|
|
|
* "Send" state. If the stream is in the "Data Sent" state, the
|
|
|
|
|
* endpoint MAY defer sending the RESET_STREAM frame until the packets
|
|
|
|
|
* containing outstanding data are acknowledged or declared lost. If
|
|
|
|
|
* any outstanding data is declared lost, the endpoint SHOULD send a
|
|
|
|
|
* RESET_STREAM frame instead of retransmitting the data.
|
|
|
|
|
*
|
|
|
|
|
* An endpoint SHOULD copy the error code from the STOP_SENDING frame to
|
|
|
|
|
* the RESET_STREAM frame it sends, but it can use any application error
|
|
|
|
|
* code.
|
|
|
|
|
*/
|
|
|
|
|
qcc_reset_stream(qcs, err);
|
|
|
|
|
|
2022-08-03 05:17:57 -04:00
|
|
|
if (qcc_may_expire(qcc) && !qcc->nb_hreq)
|
|
|
|
|
qcc_refresh_timeout(qcc);
|
|
|
|
|
|
2022-07-04 05:44:53 -04:00
|
|
|
out:
|
2024-09-18 09:33:30 -04:00
|
|
|
if (qcc->glitches != prev_glitches)
|
|
|
|
|
session_add_glitch_ctr(qcc->conn->owner, qcc->glitches - prev_glitches);
|
|
|
|
|
|
2022-07-04 05:44:53 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
return 0;
|
2023-03-09 09:49:48 -05:00
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
return 1;
|
2022-07-04 05:44:53 -04:00
|
|
|
}
|
|
|
|
|
|
2024-08-08 06:04:47 -04:00
|
|
|
#define QUIC_MAX_STREAMS_MAX_ID (1ULL<<60)
|
|
|
|
|
|
2022-05-16 08:29:59 -04:00
|
|
|
/* Signal the closing of remote stream with id <id>. Flow-control for new
|
|
|
|
|
* streams may be allocated for the peer if needed.
|
|
|
|
|
*/
|
|
|
|
|
static int qcc_release_remote_stream(struct qcc *qcc, uint64_t id)
|
2022-02-07 10:09:06 -05:00
|
|
|
{
|
2022-05-16 08:29:59 -04:00
|
|
|
struct quic_frame *frm;
|
|
|
|
|
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCS_END, qcc->conn);
|
|
|
|
|
|
2022-05-16 08:29:59 -04:00
|
|
|
if (quic_stream_is_bidi(id)) {
|
2024-08-08 06:04:47 -04:00
|
|
|
/* RFC 9000 4.6. Controlling Concurrency
|
|
|
|
|
*
|
|
|
|
|
* If a max_streams transport parameter or a MAX_STREAMS frame is
|
|
|
|
|
* received with a value greater than 260, this would allow a maximum
|
|
|
|
|
* stream ID that cannot be expressed as a variable-length integer; see
|
|
|
|
|
* Section 16. If either is received, the connection MUST be closed
|
|
|
|
|
* immediately with a connection error of type TRANSPORT_PARAMETER_ERROR
|
|
|
|
|
* if the offending value was received in a transport parameter or of
|
|
|
|
|
* type FRAME_ENCODING_ERROR if it was received in a frame; see Section
|
|
|
|
|
* 10.2.
|
|
|
|
|
*/
|
|
|
|
|
if (qcc->lfctl.ms_bidi == QUIC_MAX_STREAMS_MAX_ID) {
|
|
|
|
|
TRACE_DATA("maximum streams value reached", QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-16 08:29:59 -04:00
|
|
|
++qcc->lfctl.cl_bidi_r;
|
2024-08-08 06:04:47 -04:00
|
|
|
/* MAX_STREAMS needed if closed streams value more than twice
|
|
|
|
|
* the initial window or reaching the stream ID limit.
|
|
|
|
|
*/
|
|
|
|
|
if (qcc->lfctl.cl_bidi_r > qcc->lfctl.ms_bidi_init / 2 ||
|
|
|
|
|
qcc->lfctl.cl_bidi_r + qcc->lfctl.ms_bidi == QUIC_MAX_STREAMS_MAX_ID) {
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_DATA("increase max stream limit with MAX_STREAMS_BIDI", QMUX_EV_QCC_SEND, qcc->conn);
|
2023-01-27 11:47:49 -05:00
|
|
|
frm = qc_frm_alloc(QUIC_FT_MAX_STREAMS_BIDI);
|
2023-03-09 04:16:38 -05:00
|
|
|
if (!frm) {
|
2023-05-09 12:01:09 -04:00
|
|
|
qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR, 0);
|
2023-03-09 04:16:38 -05:00
|
|
|
goto err;
|
|
|
|
|
}
|
2022-05-16 08:29:59 -04:00
|
|
|
|
|
|
|
|
frm->max_streams_bidi.max_streams = qcc->lfctl.ms_bidi +
|
|
|
|
|
qcc->lfctl.cl_bidi_r;
|
|
|
|
|
LIST_APPEND(&qcc->lfctl.frms, &frm->list);
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2022-05-16 08:29:59 -04:00
|
|
|
|
|
|
|
|
qcc->lfctl.ms_bidi += qcc->lfctl.cl_bidi_r;
|
|
|
|
|
qcc->lfctl.cl_bidi_r = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
2022-12-22 12:56:09 -05:00
|
|
|
/* TODO unidirectional stream flow control with MAX_STREAMS_UNI
|
|
|
|
|
* emission not implemented. It should be unnecessary for
|
|
|
|
|
* HTTP/3 but may be required if other application protocols
|
|
|
|
|
* are supported.
|
2022-08-16 05:29:08 -04:00
|
|
|
*/
|
2022-05-16 08:29:59 -04:00
|
|
|
}
|
|
|
|
|
|
2024-08-08 06:04:47 -04:00
|
|
|
out:
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_END, qcc->conn);
|
2022-05-16 08:29:59 -04:00
|
|
|
return 0;
|
2023-03-09 04:16:38 -05:00
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCS_END, qcc->conn);
|
|
|
|
|
return 1;
|
2022-02-07 10:09:06 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-25 01:45:52 -05:00
|
|
|
/* detaches the QUIC stream from its QCC and releases it to the QCS pool. */
|
2021-12-08 08:42:55 -05:00
|
|
|
static void qcs_destroy(struct qcs *qcs)
|
|
|
|
|
{
|
2023-03-09 09:49:48 -05:00
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
struct connection *conn = qcc->conn;
|
2022-03-29 09:18:44 -04:00
|
|
|
const uint64_t id = qcs->id;
|
2022-02-07 10:09:06 -05:00
|
|
|
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCS_END, conn, qcs);
|
2021-12-08 08:42:55 -05:00
|
|
|
|
2024-02-23 05:41:33 -05:00
|
|
|
if (!(qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL))) {
|
|
|
|
|
/* MUST not removed a stream with sending prepared data left. This is
|
|
|
|
|
* to ensure consistency on connection flow-control calculation.
|
|
|
|
|
*/
|
|
|
|
|
BUG_ON(qcs->tx.fc.off_soft != qcs->tx.fc.off_real);
|
2023-03-22 06:17:59 -04:00
|
|
|
|
2023-03-09 09:49:48 -05:00
|
|
|
if (quic_stream_is_remote(qcc, id))
|
|
|
|
|
qcc_release_remote_stream(qcc, id);
|
|
|
|
|
}
|
2022-02-07 10:09:06 -05:00
|
|
|
|
2022-03-29 12:36:59 -04:00
|
|
|
qcs_free(qcs);
|
2022-03-24 12:10:00 -04:00
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_END, conn);
|
2021-12-08 08:42:55 -05:00
|
|
|
}
|
|
|
|
|
|
2022-04-27 10:44:49 -04:00
|
|
|
/* Prepare a STREAM frame for <qcs> instance using <out> as payload. The frame
|
|
|
|
|
* is appended in <frm_list>. Set <fin> if this is supposed to be the last
|
2023-04-26 05:38:11 -04:00
|
|
|
* stream frame. If <out> is NULL an empty STREAM frame is built : this may be
|
2024-01-24 05:54:41 -05:00
|
|
|
* useful if FIN needs to be sent without any data left. Frame length will be
|
|
|
|
|
* truncated if greater than <fc_conn_wnd>. This allows to prepare several
|
|
|
|
|
* frames in a loop while respecting connection flow control window.
|
2022-04-27 10:44:49 -04:00
|
|
|
*
|
2025-01-02 04:56:13 -05:00
|
|
|
* Returns the payload length of the STREAM frame or a negative error code.
|
2022-04-27 10:44:49 -04:00
|
|
|
*/
|
2022-04-12 05:41:04 -04:00
|
|
|
static int qcs_build_stream_frm(struct qcs *qcs, struct buffer *out, char fin,
|
2024-01-24 05:54:41 -05:00
|
|
|
struct list *frm_list, uint64_t window_conn)
|
2022-04-12 05:41:04 -04:00
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
struct quic_frame *frm;
|
2024-01-24 05:54:41 -05:00
|
|
|
const uint64_t window_stream = qfctl_rcap(&qcs->tx.fc);
|
2024-01-30 05:23:48 -05:00
|
|
|
const uint64_t bytes = qcs_prep_bytes(qcs);
|
|
|
|
|
uint64_t total;
|
2022-04-12 05:41:04 -04:00
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
|
2024-01-30 05:23:48 -05:00
|
|
|
/* This must only be called if there is data left, or at least a standalone FIN. */
|
|
|
|
|
BUG_ON((!out || !b_data(out)) && !fin);
|
2022-04-15 11:29:25 -04:00
|
|
|
|
2024-01-30 05:23:48 -05:00
|
|
|
total = bytes;
|
2022-04-15 11:29:25 -04:00
|
|
|
|
2024-01-24 05:54:41 -05:00
|
|
|
/* do not exceed stream flow control limit */
|
|
|
|
|
if (total > window_stream) {
|
|
|
|
|
TRACE_DATA("do not exceed stream flow control", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
total = window_stream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* do not exceed connection flow control limit */
|
|
|
|
|
if (total > window_conn) {
|
|
|
|
|
TRACE_DATA("do not exceed conn flow control", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
total = window_conn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Reset FIN if bytes to send is capped by flow control. */
|
2024-01-30 05:23:48 -05:00
|
|
|
if (total < bytes)
|
2024-01-24 05:54:41 -05:00
|
|
|
fin = 0;
|
|
|
|
|
|
2022-07-08 11:19:40 -04:00
|
|
|
if (!total && !fin) {
|
|
|
|
|
/* No need to send anything if total is NULL and no FIN to signal. */
|
2022-04-12 05:41:04 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_PROTO("sending STREAM frame", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
2023-01-27 11:47:49 -05:00
|
|
|
frm = qc_frm_alloc(QUIC_FT_STREAM_8);
|
2022-08-10 10:58:01 -04:00
|
|
|
if (!frm) {
|
|
|
|
|
TRACE_ERROR("frame alloc failure", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
2021-09-13 10:13:00 -04:00
|
|
|
goto err;
|
2022-08-10 10:58:01 -04:00
|
|
|
}
|
2021-09-13 10:13:00 -04:00
|
|
|
|
2022-03-29 09:15:54 -04:00
|
|
|
frm->stream.stream = qcs->stream;
|
2022-03-29 09:18:44 -04:00
|
|
|
frm->stream.id = qcs->id;
|
2024-10-01 11:34:55 -04:00
|
|
|
frm->stream.offset = 0;
|
2023-03-07 12:07:08 -05:00
|
|
|
frm->stream.dup = 0;
|
2022-03-10 10:45:53 -05:00
|
|
|
|
2023-04-25 10:39:32 -04:00
|
|
|
if (total) {
|
|
|
|
|
frm->stream.buf = out;
|
2024-01-30 05:23:48 -05:00
|
|
|
frm->stream.data = (unsigned char *)b_peek(out, b_data(out) - bytes);
|
2023-04-25 10:39:32 -04:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Empty STREAM frame. */
|
|
|
|
|
frm->stream.buf = NULL;
|
|
|
|
|
frm->stream.data = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-07 10:50:14 -05:00
|
|
|
/* FIN is positioned only when the buffer has been totally emptied. */
|
2021-09-13 10:13:00 -04:00
|
|
|
if (fin)
|
|
|
|
|
frm->type |= QUIC_STREAM_FRAME_TYPE_FIN_BIT;
|
2022-03-10 10:45:53 -05:00
|
|
|
|
2024-01-09 05:37:56 -05:00
|
|
|
if (qcs->tx.fc.off_real) {
|
2021-09-13 10:13:00 -04:00
|
|
|
frm->type |= QUIC_STREAM_FRAME_TYPE_OFF_BIT;
|
2024-10-01 11:34:55 -04:00
|
|
|
frm->stream.offset = qcs->tx.fc.off_real;
|
2021-09-13 10:13:00 -04:00
|
|
|
}
|
2022-03-10 10:45:53 -05:00
|
|
|
|
2023-04-25 10:39:32 -04:00
|
|
|
/* Always set length bit as we do not know if there is remaining frames
|
|
|
|
|
* in the final packet after this STREAM.
|
|
|
|
|
*/
|
2022-04-12 05:41:04 -04:00
|
|
|
frm->type |= QUIC_STREAM_FRAME_TYPE_LEN_BIT;
|
|
|
|
|
frm->stream.len = total;
|
2021-09-13 10:13:00 -04:00
|
|
|
|
2022-02-09 12:16:49 -05:00
|
|
|
LIST_APPEND(frm_list, &frm->list);
|
2022-03-24 12:10:00 -04:00
|
|
|
|
2021-09-20 11:50:03 -04:00
|
|
|
out:
|
2022-03-25 04:28:10 -04:00
|
|
|
{
|
2022-04-12 05:41:04 -04:00
|
|
|
struct qcs_build_stream_trace_arg arg = {
|
|
|
|
|
.len = frm->stream.len, .fin = fin,
|
2024-10-01 11:34:55 -04:00
|
|
|
.offset = frm->stream.offset,
|
2022-03-25 04:28:10 -04:00
|
|
|
};
|
2022-04-12 05:41:04 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND|QMUX_EV_QCS_BUILD_STRM,
|
2022-03-25 04:28:10 -04:00
|
|
|
qcc->conn, qcs, &arg);
|
|
|
|
|
}
|
2022-03-24 12:10:00 -04:00
|
|
|
|
2025-01-02 04:56:13 -05:00
|
|
|
return total;
|
2021-09-13 10:13:00 -04:00
|
|
|
|
|
|
|
|
err:
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
2021-09-13 10:13:00 -04:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-03 03:50:25 -04:00
|
|
|
/* Returns true if subscribe set, false otherwise. */
|
|
|
|
|
static int qcc_subscribe_send(struct qcc *qcc)
|
|
|
|
|
{
|
|
|
|
|
struct connection *conn = qcc->conn;
|
2023-05-10 05:57:40 -04:00
|
|
|
|
|
|
|
|
/* Do not subscribe if lower layer in error. */
|
|
|
|
|
if (conn->flags & CO_FL_ERROR)
|
|
|
|
|
return 0;
|
|
|
|
|
|
2023-05-03 03:50:25 -04:00
|
|
|
if (qcc->wait_event.events & SUB_RETRY_SEND)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
TRACE_DEVEL("subscribe for send", QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
conn->xprt->subscribe(conn, conn->xprt_ctx, SUB_RETRY_SEND, &qcc->wait_event);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-09 12:16:49 -05:00
|
|
|
/* Wrapper for send on transport layer. Send a list of frames <frms> for the
|
|
|
|
|
* connection <qcc>.
|
|
|
|
|
*
|
2024-11-18 08:55:41 -05:00
|
|
|
* Returns 0 if all data sent with success. On fatal error, a negative error
|
|
|
|
|
* code is returned. A positive 1 is used if emission should be paced.
|
2022-02-09 12:16:49 -05:00
|
|
|
*/
|
2024-11-18 08:55:41 -05:00
|
|
|
static int qcc_send_frames(struct qcc *qcc, struct list *frms, int stream)
|
2022-02-09 12:16:49 -05:00
|
|
|
{
|
2024-10-25 10:31:26 -04:00
|
|
|
enum quic_tx_err ret;
|
2024-11-18 08:55:41 -05:00
|
|
|
struct quic_pacer *pacer = NULL;
|
2024-10-25 10:31:26 -04:00
|
|
|
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
|
|
|
|
|
if (LIST_ISEMPTY(frms)) {
|
2024-08-08 03:30:40 -04:00
|
|
|
TRACE_DEVEL("leaving on no frame to send", QMUX_EV_QCC_SEND, qcc->conn);
|
2024-11-18 08:55:41 -05:00
|
|
|
return -1;
|
2022-03-24 12:10:00 -04:00
|
|
|
}
|
2022-03-18 13:38:19 -04:00
|
|
|
|
2024-11-18 08:55:41 -05:00
|
|
|
if (stream && qcc_is_pacing_active(qcc->conn))
|
|
|
|
|
pacer = &qcc->tx.pacer;
|
|
|
|
|
|
|
|
|
|
ret = qc_send_mux(qcc->conn->handle.qc, frms, pacer);
|
2024-10-25 10:31:26 -04:00
|
|
|
if (ret == QUIC_TX_ERR_FATAL) {
|
2023-05-03 03:50:25 -04:00
|
|
|
TRACE_DEVEL("error on sending", QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
qcc_subscribe_send(qcc);
|
2022-09-26 09:02:31 -04:00
|
|
|
goto err;
|
2023-02-28 09:11:26 -05:00
|
|
|
}
|
2022-03-04 09:29:53 -05:00
|
|
|
|
MINOR: mux-quic: improve opportunistic retry sending for STREAM frames
For the moment, the transport layer function qc_send_app_pkts lacks
features. Most notably, it only send up to a single Tx buffer and won't
retry even if there is frames left and its Tx buffer is now empty.
To overcome this limitation, the MUX implements an opportunistic retry
sending mechanism. qc_send_app_pkts is repeatedly called until the
transport layer is blocked on an external condition (such as congestion
control or a sendto syscall error).
The blocking was detected by inspecting the frame list before and after
qc_send_app_pkts. If no frame has been poped by the function, we
considered the transport layer to be blocked and we stop to send. The
MUX is subscribed on the lower layer to send the frames left.
However, in case of STREAM frames, qc_send_app_pkts might use only a
portion of the data and update the frame offset. So, for STREAM frames,
a new mechanism is implemented : if the offset field of the first frame
has not been incremented, it means the transport layer is blocked.
This should improve transfers execution. Before this change, there is a
possibility of interrupted transfer if the mux has not sent everything
possible and is waiting on a transport signaling which will never
happen.
In the future, qc_send_app_pkts should be extended to retry sending by
itself. All this code burden will be removed from the MUX.
2022-03-10 10:42:23 -05:00
|
|
|
/* If there is frames left at this stage, transport layer is blocked.
|
|
|
|
|
* Subscribe on it to retry later.
|
|
|
|
|
*/
|
2024-11-18 08:55:41 -05:00
|
|
|
if (!LIST_ISEMPTY(frms) && ret != QUIC_TX_ERR_PACING) {
|
2023-05-03 03:50:25 -04:00
|
|
|
TRACE_DEVEL("remaining frames to send", QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
qcc_subscribe_send(qcc);
|
2022-08-10 10:14:32 -04:00
|
|
|
goto err;
|
2022-02-09 12:16:49 -05:00
|
|
|
}
|
|
|
|
|
|
2022-08-11 12:35:55 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_SEND, qcc->conn);
|
2024-11-18 08:55:41 -05:00
|
|
|
return ret == QUIC_TX_ERR_PACING ? 1 : 0;
|
2022-08-10 10:14:32 -04:00
|
|
|
|
|
|
|
|
err:
|
2023-05-10 05:59:10 -04:00
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCC_SEND, qcc->conn);
|
2024-11-18 08:55:41 -05:00
|
|
|
return -1;
|
2022-02-09 12:16:49 -05:00
|
|
|
}
|
|
|
|
|
|
2022-07-04 05:44:38 -04:00
|
|
|
/* Emit a RESET_STREAM on <qcs>.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 if the frame has been successfully sent else non-zero.
|
|
|
|
|
*/
|
|
|
|
|
static int qcs_send_reset(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
struct list frms = LIST_HEAD_INIT(frms);
|
|
|
|
|
struct quic_frame *frm;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCS_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
|
2023-01-27 11:47:49 -05:00
|
|
|
frm = qc_frm_alloc(QUIC_FT_RESET_STREAM);
|
2022-08-10 10:14:32 -04:00
|
|
|
if (!frm) {
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND, qcs->qcc->conn, qcs);
|
2022-07-04 05:44:38 -04:00
|
|
|
return 1;
|
2022-08-10 10:14:32 -04:00
|
|
|
}
|
2022-07-04 05:44:38 -04:00
|
|
|
|
|
|
|
|
frm->reset_stream.id = qcs->id;
|
|
|
|
|
frm->reset_stream.app_error_code = qcs->err;
|
2024-01-09 05:37:56 -05:00
|
|
|
frm->reset_stream.final_size = qcs->tx.fc.off_real;
|
2022-07-04 05:44:38 -04:00
|
|
|
|
|
|
|
|
LIST_APPEND(&frms, &frm->list);
|
2024-11-18 08:55:41 -05:00
|
|
|
if (qcc_send_frames(qcs->qcc, &frms, 0)) {
|
2023-05-09 08:10:55 -04:00
|
|
|
if (!LIST_ISEMPTY(&frms))
|
2023-07-13 12:40:03 -04:00
|
|
|
qc_frm_free(qcs->qcc->conn->handle.qc, &frm);
|
2022-07-04 05:44:38 -04:00
|
|
|
TRACE_DEVEL("cannot send RESET_STREAM", QMUX_EV_QCS_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qcs_close_local(qcs);
|
|
|
|
|
qcs->flags &= ~QC_SF_TO_RESET;
|
|
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 08:58:28 -05:00
|
|
|
/* Emit a STOP_SENDING on <qcs>.
|
|
|
|
|
*
|
|
|
|
|
* Returns 0 if the frame has been successfully sent else non-zero.
|
|
|
|
|
*/
|
|
|
|
|
static int qcs_send_stop_sending(struct qcs *qcs)
|
|
|
|
|
{
|
|
|
|
|
struct list frms = LIST_HEAD_INIT(frms);
|
|
|
|
|
struct quic_frame *frm;
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCS_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
/* RFC 9000 3.3. Permitted Frame Types
|
|
|
|
|
*
|
|
|
|
|
* A
|
|
|
|
|
* receiver MAY send a STOP_SENDING frame in any state where it has not
|
|
|
|
|
* received a RESET_STREAM frame -- that is, states other than "Reset
|
|
|
|
|
* Recvd" or "Reset Read". However, there is little value in sending a
|
|
|
|
|
* STOP_SENDING frame in the "Data Recvd" state, as all stream data has
|
|
|
|
|
* been received. A sender could receive either of these two types of
|
|
|
|
|
* frames in any state as a result of delayed delivery of packets.¶
|
|
|
|
|
*/
|
|
|
|
|
if (qcs_is_close_remote(qcs)) {
|
|
|
|
|
TRACE_STATE("skip STOP_SENDING on remote already closed", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
goto done;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-27 11:47:49 -05:00
|
|
|
frm = qc_frm_alloc(QUIC_FT_STOP_SENDING);
|
2022-12-09 08:58:28 -05:00
|
|
|
if (!frm) {
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
frm->stop_sending.id = qcs->id;
|
|
|
|
|
frm->stop_sending.app_error_code = qcs->err;
|
|
|
|
|
|
|
|
|
|
LIST_APPEND(&frms, &frm->list);
|
2024-11-18 08:55:41 -05:00
|
|
|
if (qcc_send_frames(qcs->qcc, &frms, 0)) {
|
2023-05-09 08:10:55 -04:00
|
|
|
if (!LIST_ISEMPTY(&frms))
|
2023-07-13 12:40:03 -04:00
|
|
|
qc_frm_free(qcc->conn->handle.qc, &frm);
|
2022-12-09 08:58:28 -05:00
|
|
|
TRACE_DEVEL("cannot send STOP_SENDING", QMUX_EV_QCS_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
qcs->flags &= ~QC_SF_TO_STOP_SENDING;
|
|
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
/* Used internally by qcc_io_send function. Proceed to send for <qcs>. A STREAM
|
2024-02-22 04:12:27 -05:00
|
|
|
* frame is generated pointing to QCS stream descriptor content and inserted in
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
* <frms> list. Frame length will be truncated if greater than <window_conn>.
|
|
|
|
|
* This allows to prepare several frames in a loop while respecting connection
|
|
|
|
|
* flow control window.
|
2022-04-15 11:32:04 -04:00
|
|
|
*
|
2025-01-02 04:56:13 -05:00
|
|
|
* Returns the payload length of the STREAM frame or a negative error code.
|
2022-04-15 11:32:04 -04:00
|
|
|
*/
|
2024-01-24 05:54:41 -05:00
|
|
|
static int qcs_send(struct qcs *qcs, struct list *frms, uint64_t window_conn)
|
2022-04-15 11:32:04 -04:00
|
|
|
{
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
struct buffer *out = qc_stream_buf_get(qcs->stream);
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
int flen = 0;
|
|
|
|
|
const char fin = qcs->flags & QC_SF_FIN_STREAM;
|
2022-04-15 11:32:04 -04:00
|
|
|
|
2023-03-22 06:58:32 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
|
2023-01-03 08:39:24 -05:00
|
|
|
/* Cannot send STREAM on remote unidirectional streams. */
|
|
|
|
|
BUG_ON(quic_stream_is_uni(qcs->id) && quic_stream_is_remote(qcc, qcs->id));
|
|
|
|
|
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
/* This function must not be called if there is nothing to send. */
|
2024-01-30 05:23:48 -05:00
|
|
|
BUG_ON(!fin && !qcs_prep_bytes(qcs));
|
2022-04-15 11:32:04 -04:00
|
|
|
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
/* Skip STREAM frame allocation if already subscribed for send.
|
|
|
|
|
* Happens on sendto transient error or network congestion.
|
|
|
|
|
*/
|
|
|
|
|
if (qcc->wait_event.events & SUB_RETRY_SEND) {
|
|
|
|
|
TRACE_DEVEL("already subscribed for sending",
|
|
|
|
|
QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
goto err;
|
2023-04-26 05:38:11 -04:00
|
|
|
}
|
2022-04-15 11:32:04 -04:00
|
|
|
|
|
|
|
|
/* Build a new STREAM frame with <out> buffer. */
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
flen = qcs_build_stream_frm(qcs, out, fin, frms, window_conn);
|
|
|
|
|
if (flen < 0)
|
|
|
|
|
goto err;
|
2022-04-15 11:32:04 -04:00
|
|
|
|
2023-03-22 06:58:32 -04:00
|
|
|
out:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
2024-01-24 05:54:41 -05:00
|
|
|
return flen;
|
2023-04-19 05:42:24 -04:00
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
return -1;
|
2022-04-15 11:32:04 -04:00
|
|
|
}
|
|
|
|
|
|
2024-12-13 11:44:38 -05:00
|
|
|
/* Send RESET_STREAM/STOP_SENDING for streams in <qcc> send_list if requested.
|
|
|
|
|
* Each frame is encoded and emitted separately for now. If a frame cannot be
|
|
|
|
|
* sent, send_list looping is interrupted.
|
2024-12-11 11:53:29 -05:00
|
|
|
*
|
2024-12-13 11:44:38 -05:00
|
|
|
* Returns 0 on success else non-zero.
|
2022-03-22 10:10:29 -04:00
|
|
|
*/
|
2024-12-13 11:44:38 -05:00
|
|
|
static int qcc_emit_rs_ss(struct qcc *qcc)
|
2021-02-18 03:59:01 -05:00
|
|
|
{
|
2024-12-13 11:44:38 -05:00
|
|
|
struct qcs *qcs, *qcs_tmp;
|
2024-11-18 08:55:41 -05:00
|
|
|
|
2024-12-11 11:53:29 -05:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_END, qcc->conn);
|
2023-05-04 09:49:02 -04:00
|
|
|
|
2023-01-06 11:43:11 -05:00
|
|
|
list_for_each_entry_safe(qcs, qcs_tmp, &qcc->send_list, el_send) {
|
|
|
|
|
/* Stream must not be present in send_list if it has nothing to send. */
|
2024-01-30 05:23:48 -05:00
|
|
|
BUG_ON(!(qcs->flags & (QC_SF_FIN_STREAM|QC_SF_TO_STOP_SENDING|QC_SF_TO_RESET)) &&
|
2024-03-05 09:14:08 -05:00
|
|
|
(!qcs->stream || !qcs_prep_bytes(qcs)));
|
2022-05-23 05:39:14 -04:00
|
|
|
|
2024-12-13 11:44:38 -05:00
|
|
|
/* Interrupt looping for the first stream where no RS nor SS is
|
|
|
|
|
* necessary and is not use for "metadata" transfer. These
|
|
|
|
|
* streams are always in front of the send_list.
|
|
|
|
|
*/
|
|
|
|
|
if (!(qcs->flags & (QC_SF_TO_STOP_SENDING|QC_SF_TO_RESET|QC_SF_TXBUB_OOB)))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
TRACE_DATA("prepare for RS/SS transfer", QMUX_EV_QCC_SEND, qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
/* Each RS and SS frame is sent individually. Necessary to
|
|
|
|
|
* ensure it has been emitted as there is no transport callback
|
|
|
|
|
* for now.
|
2023-01-06 11:43:11 -05:00
|
|
|
*
|
2024-12-13 11:44:38 -05:00
|
|
|
* TODO multiplex frames to optimize sending. However, it may
|
|
|
|
|
* not be advisable to mix different streams in the same dgram
|
|
|
|
|
* to avoid interdependency in case of loss.
|
2023-01-06 11:43:11 -05:00
|
|
|
*/
|
2024-12-13 11:44:38 -05:00
|
|
|
|
2023-01-06 11:43:11 -05:00
|
|
|
if (qcs->flags & QC_SF_TO_STOP_SENDING) {
|
|
|
|
|
if (qcs_send_stop_sending(qcs))
|
2024-12-11 11:53:29 -05:00
|
|
|
goto err;
|
2022-02-09 12:16:49 -05:00
|
|
|
|
2024-12-13 11:44:38 -05:00
|
|
|
/* Remove stream from send_list if only SS was necessary. */
|
2024-01-30 05:23:48 -05:00
|
|
|
if (!(qcs->flags & (QC_SF_FIN_STREAM|QC_SF_TO_RESET)) &&
|
2024-03-05 09:14:08 -05:00
|
|
|
(!qcs->stream || !qcs_prep_bytes(qcs))) {
|
2023-01-06 11:43:11 -05:00
|
|
|
LIST_DEL_INIT(&qcs->el_send);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-03-10 10:46:18 -05:00
|
|
|
}
|
|
|
|
|
|
2022-07-04 05:44:38 -04:00
|
|
|
if (qcs->flags & QC_SF_TO_RESET) {
|
2023-01-06 11:43:11 -05:00
|
|
|
if (qcs_send_reset(qcs))
|
2024-12-11 11:53:29 -05:00
|
|
|
goto err;
|
2022-07-04 05:44:38 -04:00
|
|
|
|
2023-01-06 11:43:11 -05:00
|
|
|
/* RFC 9000 3.3. Permitted Frame Types
|
|
|
|
|
*
|
|
|
|
|
* A sender MUST NOT send
|
|
|
|
|
* a STREAM or STREAM_DATA_BLOCKED frame for a stream in the
|
|
|
|
|
* "Reset Sent" state or any terminal state -- that is, after
|
|
|
|
|
* sending a RESET_STREAM frame.
|
|
|
|
|
*/
|
2023-01-03 08:39:24 -05:00
|
|
|
LIST_DEL_INIT(&qcs->el_send);
|
2024-12-17 05:16:34 -05:00
|
|
|
if (qcs_is_completed(qcs)) {
|
|
|
|
|
TRACE_STATE("add stream in purg_list", QMUX_EV_QCC_SEND|QMUX_EV_QCS_SEND, qcc->conn, qcs);
|
|
|
|
|
LIST_APPEND(&qcc->purg_list, &qcs->el_send);
|
|
|
|
|
}
|
2022-04-15 11:30:49 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
2024-12-13 11:44:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Encode STREAM frames into <qcc> tx frms for streams registered into
|
|
|
|
|
* send_list. On each error, related stream is removed from send_list and
|
|
|
|
|
* inserted into <qcs_failed> list.
|
|
|
|
|
*
|
|
|
|
|
* This functions also serves to emit RESET_STREAM and STOP_SENDING frames. In
|
|
|
|
|
* this case, frame is emitted immediately without using <qcc> tx frms. If an
|
|
|
|
|
* error occured during this step, this is considered as fatal. Tx frms is
|
|
|
|
|
* cleared and 0 is returned.
|
|
|
|
|
*
|
2025-01-02 04:59:43 -05:00
|
|
|
* Returns the sum of encoded payload STREAM frames length. Note that 0 can be
|
|
|
|
|
* returned either if no frame was built or only empty payload frames were
|
|
|
|
|
* encoded.
|
2024-12-13 11:44:38 -05:00
|
|
|
*/
|
|
|
|
|
static int qcc_build_frms(struct qcc *qcc, struct list *qcs_failed)
|
|
|
|
|
{
|
|
|
|
|
struct list *frms = &qcc->tx.frms;
|
|
|
|
|
struct qcs *qcs, *qcs_tmp, *first_qcs = NULL;
|
|
|
|
|
uint64_t window_conn = qfctl_rcap(&qcc->tx.fc);
|
|
|
|
|
int ret = 0, total = 0;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_END, qcc->conn);
|
|
|
|
|
|
|
|
|
|
/* Frames list must first be cleared via qcc_clear_frms(). */
|
|
|
|
|
BUG_ON(!LIST_ISEMPTY(&qcc->tx.frms));
|
|
|
|
|
|
|
|
|
|
list_for_each_entry_safe(qcs, qcs_tmp, &qcc->send_list, el_send) {
|
|
|
|
|
/* Check if all QCS were processed. */
|
|
|
|
|
if (qcs == first_qcs)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
TRACE_DATA("prepare for data transfer", QMUX_EV_QCC_SEND, qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
/* Streams with RS/SS must be handled via qcc_emit_rs_ss(). */
|
|
|
|
|
BUG_ON(qcs->flags & (QC_SF_TO_STOP_SENDING|QC_SF_TO_RESET));
|
|
|
|
|
/* Stream must not be present in send_list if it has nothing to send. */
|
|
|
|
|
BUG_ON(!(qcs->flags & QC_SF_FIN_STREAM) && (!qcs->stream || !qcs_prep_bytes(qcs)));
|
2022-04-15 11:29:25 -04:00
|
|
|
|
2024-01-24 05:54:41 -05:00
|
|
|
/* Total sent bytes must not exceed connection window. */
|
|
|
|
|
BUG_ON(total > window_conn);
|
|
|
|
|
|
2023-10-18 11:48:11 -04:00
|
|
|
if (!qfctl_rblocked(&qcc->tx.fc) &&
|
2024-01-24 05:54:41 -05:00
|
|
|
!qfctl_rblocked(&qcs->tx.fc) && window_conn > total) {
|
2024-11-18 03:53:39 -05:00
|
|
|
if ((ret = qcs_send(qcs, frms, window_conn - total)) < 0) {
|
2023-04-19 05:42:24 -04:00
|
|
|
/* Temporarily remove QCS from send-list. */
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_send);
|
2024-12-11 11:53:29 -05:00
|
|
|
LIST_APPEND(qcs_failed, &qcs->el_send);
|
2023-04-19 05:42:24 -04:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
total += ret;
|
2023-04-21 08:48:01 -04:00
|
|
|
if (ret) {
|
|
|
|
|
/* Move QCS with some bytes transferred at the
|
|
|
|
|
* end of send-list for next iterations.
|
|
|
|
|
*/
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_send);
|
|
|
|
|
LIST_APPEND(&qcc->send_list, &qcs->el_send);
|
|
|
|
|
/* Remember first moved QCS as checkpoint to interrupt loop */
|
|
|
|
|
if (!first_qcs)
|
|
|
|
|
first_qcs = qcs;
|
|
|
|
|
}
|
2023-04-19 05:42:24 -04:00
|
|
|
}
|
2022-04-15 11:32:04 -04:00
|
|
|
}
|
2022-04-12 05:41:04 -04:00
|
|
|
|
2024-12-11 11:53:29 -05:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 11:31:10 -05:00
|
|
|
/* Schedule <qcc> after emission was interrupted on pacing. */
|
|
|
|
|
static void qcc_wakeup_pacing(struct qcc *qcc)
|
|
|
|
|
{
|
|
|
|
|
/* Sleep to be able to reemit at least a single packet */
|
|
|
|
|
const int inter = qcc->tx.pacer.cc->algo->pacing_inter(qcc->tx.pacer.cc);
|
|
|
|
|
/* Convert nano to milliseconds rounded up, with 1ms as minimal value. */
|
|
|
|
|
const int expire = MAX((inter + 999999) / 1000000, 1);
|
|
|
|
|
qcc->pacing_task->expire = tick_add_ifset(now_ms, MS_TO_TICKS(expire));
|
|
|
|
|
++qcc->tx.paced_sent_ctr;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-18 10:14:19 -05:00
|
|
|
/* Conduct I/O operations to finalize <qcc> app layer initialization. Note that
|
|
|
|
|
* <qcc> app state may remain NULL even on success, if only a transient
|
|
|
|
|
* blocking was encountered. Finalize operation can be retry later.
|
2025-02-17 10:00:03 -05:00
|
|
|
*
|
|
|
|
|
* Returns 0 on success else non-zero.
|
|
|
|
|
*/
|
|
|
|
|
static int qcc_app_init(struct qcc *qcc)
|
|
|
|
|
{
|
2025-02-18 10:14:19 -05:00
|
|
|
int ret;
|
|
|
|
|
|
2025-02-17 10:00:03 -05:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
|
2025-02-18 10:14:19 -05:00
|
|
|
if (qcc->app_ops->finalize) {
|
|
|
|
|
ret = qcc->app_ops->finalize(qcc->ctx);
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
TRACE_ERROR("app ops finalize error", QMUX_EV_QCC_NEW, qcc->conn);
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
|
TRACE_STATE("cannot finalize app ops yet", QMUX_EV_QCC_NEW, qcc->conn);
|
|
|
|
|
goto again;
|
|
|
|
|
}
|
2025-02-17 10:00:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qcc->app_st = QCC_APP_ST_INIT;
|
|
|
|
|
|
2025-02-18 10:14:19 -05:00
|
|
|
again:
|
2025-02-17 10:00:03 -05:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-11 11:53:29 -05:00
|
|
|
/* Proceed to sending. Loop through all available streams for the <qcc>
|
|
|
|
|
* instance and try to send as much as possible.
|
|
|
|
|
*
|
|
|
|
|
* Returns the total of bytes sent to the transport layer.
|
|
|
|
|
*/
|
2025-01-22 11:31:10 -05:00
|
|
|
static int qcc_io_send(struct qcc *qcc)
|
2024-12-11 11:53:29 -05:00
|
|
|
{
|
|
|
|
|
struct list *frms = &qcc->tx.frms;
|
|
|
|
|
/* Temporary list for QCS on error. */
|
|
|
|
|
struct list qcs_failed = LIST_HEAD_INIT(qcs_failed);
|
|
|
|
|
struct qcs *qcs, *qcs_tmp;
|
|
|
|
|
uint64_t window_conn = qfctl_rcap(&qcc->tx.fc);
|
|
|
|
|
int ret = 0, total = 0, resent;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
|
2025-01-22 11:31:10 -05:00
|
|
|
if (qcc_is_pacing_active(qcc->conn)) {
|
|
|
|
|
/* Always reset pacing_task timer to prevent unnecessary execution. */
|
|
|
|
|
qcc->pacing_task->expire = TICK_ETERNITY;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-11 11:53:29 -05:00
|
|
|
/* TODO if socket in transient error, sending should be temporarily
|
|
|
|
|
* disabled for all frames. However, checking for send subscription is
|
|
|
|
|
* not valid as this may be caused by a congestion error which only
|
|
|
|
|
* apply for STREAM frames.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Check for transport error. */
|
|
|
|
|
if (qcc->flags & QC_CF_ERR_CONN || qcc->conn->flags & CO_FL_ERROR) {
|
|
|
|
|
TRACE_DEVEL("connection on error", QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check for locally detected connection error. */
|
|
|
|
|
if (qcc->flags & QC_CF_ERRL) {
|
|
|
|
|
/* Prepare a CONNECTION_CLOSE if not already done. */
|
|
|
|
|
if (!(qcc->flags & QC_CF_ERRL_DONE)) {
|
|
|
|
|
TRACE_DATA("report a connection error", QMUX_EV_QCC_SEND|QMUX_EV_QCC_ERR, qcc->conn);
|
|
|
|
|
quic_set_connection_close(qcc->conn->handle.qc, qcc->err);
|
|
|
|
|
qcc->flags |= QC_CF_ERRL_DONE;
|
|
|
|
|
}
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (qcc->conn->flags & CO_FL_SOCK_WR_SH) {
|
|
|
|
|
qcc->conn->flags |= CO_FL_ERROR;
|
|
|
|
|
TRACE_DEVEL("connection on error", QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-17 10:00:03 -05:00
|
|
|
if (qcc->app_st < QCC_APP_ST_INIT) {
|
|
|
|
|
if (qcc_app_init(qcc))
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-11 11:53:29 -05:00
|
|
|
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);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-13 11:44:38 -05:00
|
|
|
if (qcc_emit_rs_ss(qcc)) {
|
|
|
|
|
TRACE_DEVEL("emission interrupted on STOP_SENDING/RESET_STREAM send error", QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 06:03:37 -05:00
|
|
|
/* Encode new STREAM frames if list has been previously cleared. */
|
|
|
|
|
if (LIST_ISEMPTY(frms) && !LIST_ISEMPTY(&qcc->send_list)) {
|
|
|
|
|
total = qcc_build_frms(qcc, &qcs_failed);
|
2025-01-02 04:59:43 -05:00
|
|
|
if (LIST_ISEMPTY(frms))
|
2024-12-12 06:03:37 -05:00
|
|
|
goto out;
|
|
|
|
|
}
|
2024-12-11 11:53:29 -05:00
|
|
|
|
2025-01-10 11:18:54 -05:00
|
|
|
if (!LIST_ISEMPTY(frms) && qcc_is_pacing_active(qcc->conn)) {
|
|
|
|
|
if (!quic_pacing_reload(&qcc->tx.pacer)) {
|
2025-01-22 11:31:10 -05:00
|
|
|
qcc_wakeup_pacing(qcc);
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
total = 0;
|
|
|
|
|
goto out;
|
2024-11-18 08:55:41 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-06 11:16:47 -05:00
|
|
|
/* Retry sending until no frame to send, data rejected or connection
|
|
|
|
|
* flow-control limit reached.
|
|
|
|
|
*/
|
2024-12-11 11:53:29 -05:00
|
|
|
while ((ret = qcc_send_frames(qcc, frms, 1)) == 0 && !qfctl_rblocked(&qcc->tx.fc)) {
|
2024-01-24 05:54:41 -05:00
|
|
|
window_conn = qfctl_rcap(&qcc->tx.fc);
|
|
|
|
|
resent = 0;
|
|
|
|
|
|
2023-01-06 11:16:47 -05:00
|
|
|
/* Reloop over <qcc.send_list>. Useful for streams which have
|
|
|
|
|
* fulfilled their qc_stream_desc buf and have now release it.
|
|
|
|
|
*/
|
2023-04-19 05:42:24 -04:00
|
|
|
list_for_each_entry_safe(qcs, qcs_tmp, &qcc->send_list, el_send) {
|
2023-01-06 11:16:47 -05:00
|
|
|
/* Only streams blocked on flow-control or waiting on a
|
|
|
|
|
* new qc_stream_desc should be present in send_list as
|
|
|
|
|
* long as transport layer can handle all data.
|
|
|
|
|
*/
|
2023-10-18 09:55:38 -04:00
|
|
|
BUG_ON(qcs->stream->buf && !qfctl_rblocked(&qcs->tx.fc));
|
2022-04-12 05:41:04 -04:00
|
|
|
|
2024-01-24 05:54:41 -05:00
|
|
|
/* Total sent bytes must not exceed connection window. */
|
|
|
|
|
BUG_ON(resent > window_conn);
|
|
|
|
|
|
|
|
|
|
if (!qfctl_rblocked(&qcs->tx.fc) && window_conn > resent) {
|
2024-11-18 03:53:39 -05:00
|
|
|
if ((ret = qcs_send(qcs, frms, window_conn - resent)) < 0) {
|
2023-04-19 05:42:24 -04:00
|
|
|
LIST_DEL_INIT(&qcs->el_send);
|
|
|
|
|
LIST_APPEND(&qcs_failed, &qcs->el_send);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
total += ret;
|
2024-01-24 05:54:41 -05:00
|
|
|
resent += ret;
|
2023-04-19 05:42:24 -04:00
|
|
|
}
|
2023-01-06 11:16:47 -05:00
|
|
|
}
|
2021-09-13 10:13:00 -04:00
|
|
|
}
|
2021-09-20 11:58:22 -04:00
|
|
|
|
2024-12-11 11:53:29 -05:00
|
|
|
if (ret == 1) {
|
2024-11-18 08:55:41 -05:00
|
|
|
/* qcc_send_frames cannot return 1 if pacing not used. */
|
|
|
|
|
BUG_ON(!qcc_is_pacing_active(qcc->conn));
|
2025-01-22 11:31:10 -05:00
|
|
|
qcc_wakeup_pacing(qcc);
|
2024-11-18 08:55:41 -05:00
|
|
|
}
|
2022-06-10 09:16:40 -04:00
|
|
|
|
2024-12-12 06:03:37 -05:00
|
|
|
out:
|
2023-04-19 05:42:24 -04:00
|
|
|
/* Re-insert on-error QCS at the end of the send-list. */
|
|
|
|
|
if (!LIST_ISEMPTY(&qcs_failed)) {
|
|
|
|
|
list_for_each_entry_safe(qcs, qcs_tmp, &qcs_failed, el_send) {
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_send);
|
|
|
|
|
LIST_APPEND(&qcc->send_list, &qcs->el_send);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-18 11:48:11 -04:00
|
|
|
if (!qfctl_rblocked(&qcc->tx.fc))
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2023-04-19 05:42:24 -04:00
|
|
|
}
|
|
|
|
|
|
2023-05-04 12:52:42 -04:00
|
|
|
if (qcc->conn->flags & CO_FL_ERROR && !(qcc->flags & QC_CF_ERR_CONN)) {
|
|
|
|
|
TRACE_ERROR("error reported by transport layer",
|
|
|
|
|
QMUX_EV_QCC_SEND, qcc->conn);
|
|
|
|
|
qcc->flags |= QC_CF_ERR_CONN;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_SEND, qcc->conn);
|
2022-03-22 10:10:29 -04:00
|
|
|
return total;
|
2021-02-18 03:59:01 -05:00
|
|
|
}
|
|
|
|
|
|
2024-11-29 10:18:12 -05:00
|
|
|
/* Detects QUIC handshake completion. Any SE_FL_WAIT_FOR_HS streams are woken
|
|
|
|
|
* up if wait-for-handshake is active.
|
|
|
|
|
*/
|
|
|
|
|
static void qcc_wait_for_hs(struct qcc *qcc)
|
|
|
|
|
{
|
|
|
|
|
struct connection *conn = qcc->conn;
|
|
|
|
|
struct quic_conn *qc = conn->handle.qc;
|
|
|
|
|
struct eb64_node *node;
|
|
|
|
|
struct qcs *qcs;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
|
|
|
|
|
if (qc->state >= QUIC_HS_ST_COMPLETE) {
|
|
|
|
|
if (conn->flags & CO_FL_EARLY_SSL_HS) {
|
|
|
|
|
TRACE_STATE("mark early data as ready", QMUX_EV_QCC_WAKE, conn);
|
|
|
|
|
conn->flags &= ~CO_FL_EARLY_SSL_HS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* wake-up any stream blocked on early data transfer */
|
|
|
|
|
node = eb64_first(&qcc->streams_by_id);
|
|
|
|
|
while (node) {
|
|
|
|
|
qcs = container_of(node, struct qcs, by_id);
|
|
|
|
|
if (se_fl_test(qcs->sd, SE_FL_WAIT_FOR_HS))
|
|
|
|
|
qcs_notify_recv(qcs);
|
|
|
|
|
node = eb64_next(node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qcc->flags &= ~QC_CF_WAIT_HS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-05 04:48:51 -05:00
|
|
|
/* Proceed on receiving. Loop on streams subscribed in recv_list and performed
|
|
|
|
|
* STREAM frames decoding upon them.
|
2022-05-16 07:54:59 -04:00
|
|
|
*
|
|
|
|
|
* Returns 0 on success else non-zero.
|
|
|
|
|
*/
|
2023-05-30 09:04:46 -04:00
|
|
|
static int qcc_io_recv(struct qcc *qcc)
|
2022-05-16 07:54:59 -04:00
|
|
|
{
|
|
|
|
|
struct qcs *qcs;
|
2025-03-07 05:43:01 -05:00
|
|
|
int ret;
|
2022-05-16 07:54:59 -04:00
|
|
|
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
|
2022-05-23 12:52:11 -04:00
|
|
|
|
2023-05-04 09:49:02 -04:00
|
|
|
if (qcc->flags & QC_CF_ERRL) {
|
|
|
|
|
TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
2022-05-24 08:47:48 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-29 10:18:12 -05:00
|
|
|
if ((qcc->flags & QC_CF_WAIT_HS) && !(qcc->wait_event.events & SUB_RETRY_RECV))
|
|
|
|
|
qcc_wait_for_hs(qcc);
|
|
|
|
|
|
2024-12-05 04:48:51 -05:00
|
|
|
while (!LIST_ISEMPTY(&qcc->recv_list)) {
|
|
|
|
|
qcs = LIST_ELEM(qcc->recv_list.n, struct qcs *, el_recv);
|
|
|
|
|
/* No need to add an uni local stream in recv_list. */
|
|
|
|
|
BUG_ON(quic_stream_is_uni(qcs->id) && quic_stream_is_local(qcc, qcs->id));
|
2025-03-07 05:43:01 -05:00
|
|
|
|
|
|
|
|
while (qcs_rx_avail_data(qcs) && !(qcs->flags & QC_SF_DEM_FULL)) {
|
|
|
|
|
ret = qcc_decode_qcs(qcc, qcs);
|
|
|
|
|
LIST_DEL_INIT(&qcs->el_recv);
|
|
|
|
|
|
|
|
|
|
if (ret <= 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-05-16 07:54:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
|
2022-05-16 07:54:59 -04:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-08 08:04:21 -04:00
|
|
|
|
2024-12-17 05:16:34 -05:00
|
|
|
/* Release all streams which have their transfer operation achieved. */
|
|
|
|
|
static void qcc_purge_streams(struct qcc *qcc)
|
2021-12-08 08:42:55 -05:00
|
|
|
{
|
2024-12-17 05:16:34 -05:00
|
|
|
struct qcs *qcs;
|
2021-12-08 08:42:55 -05:00
|
|
|
|
2022-08-11 12:35:55 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc->conn);
|
2022-07-08 08:04:21 -04:00
|
|
|
|
2024-12-17 05:16:34 -05:00
|
|
|
while (!LIST_ISEMPTY(&qcc->purg_list)) {
|
|
|
|
|
qcs = LIST_ELEM(qcc->purg_list.n, struct qcs *, el_send);
|
2021-12-08 08:42:55 -05:00
|
|
|
|
2024-12-17 05:16:34 -05:00
|
|
|
TRACE_STATE("purging stream", QMUX_EV_QCC_WAKE, qcs->qcc->conn, qcs);
|
|
|
|
|
BUG_ON_HOT(!qcs_is_completed(qcs));
|
|
|
|
|
qcs_destroy(qcs);
|
2021-12-08 08:42:55 -05:00
|
|
|
}
|
|
|
|
|
|
2022-08-11 12:35:55 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_WAKE, qcc->conn);
|
2021-12-08 08:42:55 -05:00
|
|
|
}
|
|
|
|
|
|
2023-01-24 12:18:23 -05:00
|
|
|
/* Execute application layer shutdown. If this operation is not defined, a
|
|
|
|
|
* CONNECTION_CLOSE will be prepared as a fallback. This function is protected
|
2025-02-18 05:42:59 -05:00
|
|
|
* against multiple invocation thanks to <qcc> application state context.
|
2022-07-15 04:32:53 -04:00
|
|
|
*/
|
2023-05-30 09:04:46 -04:00
|
|
|
static void qcc_shutdown(struct qcc *qcc)
|
2022-07-15 04:32:53 -04:00
|
|
|
{
|
2023-01-24 12:18:23 -05:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_END, qcc->conn);
|
2022-07-15 04:32:53 -04:00
|
|
|
|
2023-05-04 12:52:42 -04:00
|
|
|
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)) {
|
2023-05-04 09:49:02 -04:00
|
|
|
TRACE_DATA("connection on error", QMUX_EV_QCC_END, qcc->conn);
|
2023-01-24 12:18:23 -05:00
|
|
|
goto out;
|
2023-03-20 12:34:22 -04:00
|
|
|
}
|
2022-07-15 04:32:53 -04:00
|
|
|
|
2025-02-18 05:42:59 -05:00
|
|
|
if (qcc->app_st >= QCC_APP_ST_SHUT)
|
2023-05-04 09:49:02 -04:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
TRACE_STATE("perform graceful shutdown", QMUX_EV_QCC_END, qcc->conn);
|
BUG/MEDIUM: mux-quic: fix crash on early app-ops release
H3 SETTINGS emission has recently been delayed. The idea is to send it
with the first STREAM to reduce sendto syscall invocation. This was
implemented in the following patch :
3dd79d378c86b3ebf60e029f518add5f1ed54815
MINOR: h3: Send the h3 settings with others streams (requests)
This patch works fine under nominal conditions. However, it will cause a
crash if a HTTP/3 connection is released before having sent any data,
for example when receiving an invalid first request. In this case,
qc_release will first free qcc.app_ops HTTP/3 application protocol layer
via release callback. Then qc_send is called to emit any closing frames
built by app_ops release invocation. However, in qc_send, as no data has
been sent, it will try to complete application layer protocol
intialization, with a SETTINGS emission for HTTP/3. Thus, qcc.app_ops is
reused, which is invalid as it has been just freed. This will cause a
crash with h3_finalize in the call stack.
This bug can be reproduced artificially by generating incomplete HTTP/3
requests. This will in time trigger http-request timeout without any
data send. This is done by editing qc_handle_strm_frm function.
- ret = qcc_recv(qc->qcc, strm_frm->id, strm_frm->len,
+ ret = qcc_recv(qc->qcc, strm_frm->id, strm_frm->len - 1,
strm_frm->offset.key, strm_frm->fin,
(char *)strm_frm->data);
To fix this, application layer closing API has been adjusted to be done
in two-steps. A new shutdown callback is implemented : it is used by the
HTTP/3 layer to generate GOAWAY frame in qc_release prologue.
Application layer context qcc.app_ops is then freed later in qc_release
via the release operation which is now only used to liberate app layer
ressources. This fixes the problem as the intermediary qc_send
invocation will be able to reuse app_ops before it is freed.
This patch fixes the crash, but it would be better to adjust H3 SETTINGS
emission in case of early connection closing : in this case, there is no
need to send it. This should be implemented in a future patch.
This should fix the crash recently experienced by Tristan in github
issue #1801.
This must be backported up to 2.6.
2022-09-14 10:23:47 -04:00
|
|
|
if (qcc->app_ops && qcc->app_ops->shutdown) {
|
|
|
|
|
qcc->app_ops->shutdown(qcc->ctx);
|
2025-01-22 11:31:10 -05:00
|
|
|
qcc_io_send(qcc);
|
2022-07-15 04:32:53 -04:00
|
|
|
}
|
|
|
|
|
else {
|
2024-05-13 03:02:47 -04:00
|
|
|
qcc->err = quic_err_transport(QC_ERR_NO_ERROR);
|
2022-07-15 04:32:53 -04:00
|
|
|
}
|
|
|
|
|
|
2023-05-04 09:49:02 -04:00
|
|
|
/* Register "no error" code at transport layer. Do not use
|
|
|
|
|
* quic_set_connection_close() as retransmission may be performed to
|
|
|
|
|
* finalized transfers. Do not overwrite quic-conn existing code if
|
|
|
|
|
* already set.
|
|
|
|
|
*
|
|
|
|
|
* TODO implement a wrapper function for this in quic-conn module
|
|
|
|
|
*/
|
|
|
|
|
if (!(qcc->conn->handle.qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE))
|
|
|
|
|
qcc->conn->handle.qc->err = qcc->err;
|
|
|
|
|
|
2023-01-24 12:18:23 -05:00
|
|
|
out:
|
2025-02-18 05:42:59 -05:00
|
|
|
qcc->app_st = QCC_APP_ST_SHUT;
|
2023-01-24 12:18:23 -05:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_END, qcc->conn);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-03 12:16:40 -04:00
|
|
|
/* Loop through all qcs from <qcc>. Report error on stream endpoint if
|
|
|
|
|
* connection on error and wake them.
|
|
|
|
|
*/
|
2023-05-30 09:04:46 -04:00
|
|
|
static int qcc_wake_some_streams(struct qcc *qcc)
|
2023-05-03 12:16:40 -04:00
|
|
|
{
|
|
|
|
|
struct qcs *qcs;
|
|
|
|
|
struct eb64_node *node;
|
|
|
|
|
|
|
|
|
|
TRACE_POINT(QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
|
|
|
|
|
for (node = eb64_first(&qcc->streams_by_id); node;
|
|
|
|
|
node = eb64_next(node)) {
|
|
|
|
|
qcs = eb64_entry(node, struct qcs, by_id);
|
|
|
|
|
|
|
|
|
|
if (!qcs_sc(qcs))
|
|
|
|
|
continue;
|
|
|
|
|
|
2023-05-04 12:52:42 -04:00
|
|
|
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)) {
|
2023-05-03 12:16:40 -04:00
|
|
|
TRACE_POINT(QMUX_EV_QCC_WAKE, qcc->conn, qcs);
|
|
|
|
|
se_fl_set_error(qcs->sd);
|
|
|
|
|
qcs_alert(qcs);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-24 12:19:47 -05:00
|
|
|
/* Conduct operations which should be made for <qcc> connection after
|
|
|
|
|
* input/output. Most notably, closed streams are purged which may leave the
|
|
|
|
|
* connection has ready to be released.
|
|
|
|
|
*
|
|
|
|
|
* Returns 1 if <qcc> must be released else 0.
|
|
|
|
|
*/
|
2023-05-30 09:04:46 -04:00
|
|
|
static int qcc_io_process(struct qcc *qcc)
|
2023-01-24 12:19:47 -05:00
|
|
|
{
|
2024-12-17 05:16:34 -05:00
|
|
|
if (!LIST_ISEMPTY(&qcc->purg_list))
|
|
|
|
|
qcc_purge_streams(qcc);
|
2023-01-24 12:19:47 -05:00
|
|
|
|
2023-01-24 12:20:28 -05:00
|
|
|
/* Check if a soft-stop is in progress.
|
|
|
|
|
*
|
|
|
|
|
* TODO this is relevant for frontend connections only.
|
|
|
|
|
*/
|
|
|
|
|
if (unlikely(qcc->proxy->flags & (PR_FL_DISABLED|PR_FL_STOPPED))) {
|
|
|
|
|
int close = 1;
|
|
|
|
|
|
|
|
|
|
/* If using listener socket, soft-stop is not supported. The
|
|
|
|
|
* connection must be closed immediately.
|
|
|
|
|
*/
|
|
|
|
|
if (!qc_test_fd(qcc->conn->handle.qc)) {
|
|
|
|
|
TRACE_DEVEL("proxy disabled with listener socket, closing connection", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
qcc->conn->flags |= (CO_FL_SOCK_RD_SH|CO_FL_SOCK_WR_SH);
|
2025-01-22 11:31:10 -05:00
|
|
|
qcc_io_send(qcc);
|
2023-01-24 12:20:28 -05:00
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRACE_DEVEL("proxy disabled, prepare connection soft-stop", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
|
|
|
|
|
/* If a close-spread-time option is set, we want to avoid
|
|
|
|
|
* closing all the active HTTP3 connections at once so we add a
|
|
|
|
|
* random factor that will spread the closing.
|
|
|
|
|
*/
|
|
|
|
|
if (tick_isset(global.close_spread_end)) {
|
|
|
|
|
int remaining_window = tick_remain(now_ms, global.close_spread_end);
|
|
|
|
|
if (remaining_window) {
|
|
|
|
|
/* This should increase the closing rate the
|
|
|
|
|
* further along the window we are. */
|
|
|
|
|
close = (remaining_window <= statistical_prng_range(global.close_spread_time));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (global.tune.options & GTUNE_DISABLE_ACTIVE_CLOSE) {
|
|
|
|
|
close = 0; /* let the client close his connection himself */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (close)
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc_shutdown(qcc);
|
2023-01-24 12:20:28 -05:00
|
|
|
}
|
|
|
|
|
|
2023-05-03 12:16:40 -04:00
|
|
|
/* Report error if set on stream endpoint layer. */
|
2023-05-04 12:52:42 -04:00
|
|
|
if (qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL))
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc_wake_some_streams(qcc);
|
2023-05-03 12:16:40 -04:00
|
|
|
|
2023-01-24 12:20:28 -05:00
|
|
|
out:
|
2023-01-24 12:19:47 -05:00
|
|
|
if (qcc_is_dead(qcc))
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 11:30:35 -05:00
|
|
|
/* Free all resources allocated for <qcc> connection. */
|
2023-05-30 09:04:46 -04:00
|
|
|
static void qcc_release(struct qcc *qcc)
|
2023-01-24 12:18:23 -05:00
|
|
|
{
|
|
|
|
|
struct connection *conn = qcc->conn;
|
|
|
|
|
struct eb64_node *node;
|
2024-10-02 04:21:02 -04:00
|
|
|
struct quic_conn *qc;
|
2023-01-24 12:18:23 -05:00
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_END, conn);
|
|
|
|
|
|
2025-01-22 11:31:10 -05:00
|
|
|
task_destroy(qcc->pacing_task);
|
|
|
|
|
|
2022-07-15 04:32:53 -04:00
|
|
|
if (qcc->task) {
|
|
|
|
|
task_destroy(qcc->task);
|
|
|
|
|
qcc->task = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* liberate remaining qcs instances */
|
|
|
|
|
node = eb64_first(&qcc->streams_by_id);
|
|
|
|
|
while (node) {
|
|
|
|
|
struct qcs *qcs = eb64_entry(node, struct qcs, by_id);
|
|
|
|
|
node = eb64_next(node);
|
|
|
|
|
qcs_free(qcs);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 12:25:08 -04:00
|
|
|
/* unsubscribe from all remaining qc_stream_desc */
|
2024-10-02 04:21:02 -04:00
|
|
|
if (conn) {
|
|
|
|
|
qc = conn->handle.qc;
|
|
|
|
|
node = eb64_first(&qc->streams_by_id);
|
|
|
|
|
while (node) {
|
|
|
|
|
struct qc_stream_desc *stream = eb64_entry(node, struct qc_stream_desc, by_id);
|
|
|
|
|
qc_stream_desc_sub_room(stream, NULL);
|
|
|
|
|
node = eb64_next(node);
|
|
|
|
|
}
|
2024-09-25 12:25:08 -04:00
|
|
|
}
|
|
|
|
|
|
BUG/MINOR: mux-quic: close all QCS before freeing QCC tasklet
QUIC MUX is freed via qcc_release(). This in turn liberate all the
remaining QCS instances. For each one of them, their corresponding
stream-desc is released via qc_stream_desc_release().
This last function may itself notifies QUIC MUX when new buffers are
available. This is useful when QCS are closed individually without the
whole connection. However, when the connection is closed through
qcc_release(), this may cause issue as some elements of QUIC MUX are
already freed.
In 2.9.6, a bug was detected directly linked to this. Indeed, QCC
instance may be woken up on stream-desc release. If called through
qcc_release(), this is an issue because QCC tasklet is freed before QCS
instances. However, this bug is not systematic and relies on prior
conditions : in particular, QUIC MUX must be under Tx buffers exhaustion
prior to the qcc_release() invocation.
The current dev tree is not impacted by this bug, thanks to QUIC MUX
refactoring. Indeed, notifying accross layers have changed and now
stream-desc release notifies individual QCS instances instead of the QCC
element, which is a safer mechanism. However, to simplify backport
process, bugfix is introduced in the current dev tree as it does not
have any impact.
Note that a proper fix would be to set quic-conn MUX state to
QC_MUX_RELEASED. However, it is not possible to call quic_close()
without having releasing all stream-desc elements first. The simpler
solution was chosen to prevent other breaking issues during backports.
This should fix github issue #2494.
It should be backported up to 2.6. Note that prior to 2.7 qcc_release()
was named qc_release().
2024-03-22 10:13:42 -04:00
|
|
|
tasklet_free(qcc->wait_event.tasklet);
|
|
|
|
|
if (conn && qcc->wait_event.events) {
|
|
|
|
|
conn->xprt->unsubscribe(conn, conn->xprt_ctx,
|
|
|
|
|
qcc->wait_event.events,
|
|
|
|
|
&qcc->wait_event);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-15 04:32:53 -04:00
|
|
|
while (!LIST_ISEMPTY(&qcc->lfctl.frms)) {
|
|
|
|
|
struct quic_frame *frm = LIST_ELEM(qcc->lfctl.frms.n, struct quic_frame *, list);
|
2023-07-13 12:40:03 -04:00
|
|
|
qc_frm_free(qcc->conn->handle.qc, &frm);
|
2022-07-15 04:32:53 -04:00
|
|
|
}
|
|
|
|
|
|
2024-12-11 11:53:29 -05:00
|
|
|
qcc_clear_frms(qcc);
|
2024-11-18 03:53:39 -05:00
|
|
|
|
BUG/MEDIUM: mux-quic: fix crash on early app-ops release
H3 SETTINGS emission has recently been delayed. The idea is to send it
with the first STREAM to reduce sendto syscall invocation. This was
implemented in the following patch :
3dd79d378c86b3ebf60e029f518add5f1ed54815
MINOR: h3: Send the h3 settings with others streams (requests)
This patch works fine under nominal conditions. However, it will cause a
crash if a HTTP/3 connection is released before having sent any data,
for example when receiving an invalid first request. In this case,
qc_release will first free qcc.app_ops HTTP/3 application protocol layer
via release callback. Then qc_send is called to emit any closing frames
built by app_ops release invocation. However, in qc_send, as no data has
been sent, it will try to complete application layer protocol
intialization, with a SETTINGS emission for HTTP/3. Thus, qcc.app_ops is
reused, which is invalid as it has been just freed. This will cause a
crash with h3_finalize in the call stack.
This bug can be reproduced artificially by generating incomplete HTTP/3
requests. This will in time trigger http-request timeout without any
data send. This is done by editing qc_handle_strm_frm function.
- ret = qcc_recv(qc->qcc, strm_frm->id, strm_frm->len,
+ ret = qcc_recv(qc->qcc, strm_frm->id, strm_frm->len - 1,
strm_frm->offset.key, strm_frm->fin,
(char *)strm_frm->data);
To fix this, application layer closing API has been adjusted to be done
in two-steps. A new shutdown callback is implemented : it is used by the
HTTP/3 layer to generate GOAWAY frame in qc_release prologue.
Application layer context qcc.app_ops is then freed later in qc_release
via the release operation which is now only used to liberate app layer
ressources. This fixes the problem as the intermediary qc_send
invocation will be able to reuse app_ops before it is freed.
This patch fixes the crash, but it would be better to adjust H3 SETTINGS
emission in case of early connection closing : in this case, there is no
need to send it. This should be implemented in a future patch.
This should fix the crash recently experienced by Tristan in github
issue #1801.
This must be backported up to 2.6.
2022-09-14 10:23:47 -04:00
|
|
|
if (qcc->app_ops && qcc->app_ops->release)
|
|
|
|
|
qcc->app_ops->release(qcc->ctx);
|
|
|
|
|
TRACE_PROTO("application layer released", QMUX_EV_QCC_END, conn);
|
|
|
|
|
|
2022-07-15 04:32:53 -04:00
|
|
|
pool_free(pool_head_qcc, qcc);
|
|
|
|
|
|
|
|
|
|
if (conn) {
|
|
|
|
|
LIST_DEL_INIT(&conn->stopping_list);
|
|
|
|
|
|
|
|
|
|
conn->mux = NULL;
|
|
|
|
|
conn->ctx = NULL;
|
|
|
|
|
|
|
|
|
|
TRACE_DEVEL("freeing conn", QMUX_EV_QCC_END, conn);
|
|
|
|
|
|
|
|
|
|
conn_stop_tracking(conn);
|
|
|
|
|
conn_full_close(conn);
|
|
|
|
|
if (conn->destroy_cb)
|
|
|
|
|
conn->destroy_cb(conn);
|
|
|
|
|
conn_free(conn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_END);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-30 09:04:46 -04:00
|
|
|
struct task *qcc_io_cb(struct task *t, void *ctx, unsigned int status)
|
2021-02-18 03:59:01 -05:00
|
|
|
{
|
2021-10-05 05:43:50 -04:00
|
|
|
struct qcc *qcc = ctx;
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2022-08-11 12:35:55 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc->conn);
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2024-10-16 05:05:51 -04:00
|
|
|
if (!(qcc->wait_event.events & SUB_RETRY_SEND))
|
2025-01-22 11:31:10 -05:00
|
|
|
qcc_io_send(qcc);
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc_io_recv(qcc);
|
2022-05-16 07:54:59 -04:00
|
|
|
|
2023-05-30 09:04:46 -04:00
|
|
|
if (qcc_io_process(qcc)) {
|
2023-01-24 12:19:47 -05:00
|
|
|
TRACE_STATE("releasing dead connection", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
goto release;
|
|
|
|
|
}
|
2022-07-25 08:58:48 -04:00
|
|
|
|
|
|
|
|
qcc_refresh_timeout(qcc);
|
|
|
|
|
|
2025-01-22 11:31:10 -05:00
|
|
|
/* Trigger pacing task is emission should be retried after some delay. */
|
|
|
|
|
if (qcc_is_pacing_active(qcc->conn)) {
|
|
|
|
|
if (tick_isset(qcc->pacing_task->expire))
|
|
|
|
|
task_queue(qcc->pacing_task);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-11 12:35:55 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_WAKE, qcc->conn);
|
2024-12-12 09:28:35 -05:00
|
|
|
|
2022-08-11 12:35:55 -04:00
|
|
|
return NULL;
|
2022-03-24 12:10:00 -04:00
|
|
|
|
2022-08-11 12:35:55 -04:00
|
|
|
release:
|
2023-12-18 11:30:35 -05:00
|
|
|
qcc_shutdown(qcc);
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc_release(qcc);
|
2024-12-12 09:28:35 -05:00
|
|
|
|
2022-08-11 12:35:55 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_WAKE);
|
2024-12-12 09:28:35 -05:00
|
|
|
|
2021-02-18 03:59:01 -05:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 11:31:10 -05:00
|
|
|
static struct task *qcc_pacing_task(struct task *t, void *ctx, unsigned int state)
|
|
|
|
|
{
|
|
|
|
|
struct qcc *qcc = ctx;
|
|
|
|
|
int expired = tick_is_expired(t->expire, now_ms);
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
|
|
|
|
|
if (!expired) {
|
|
|
|
|
if (!tick_isset(t->expire))
|
|
|
|
|
TRACE_DEVEL("cancelled pacing task", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
goto requeue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Reschedule I/O immediately. */
|
|
|
|
|
tasklet_wakeup_after(NULL, qcc->wait_event.tasklet);
|
|
|
|
|
|
|
|
|
|
requeue:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
return t;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-30 09:04:46 -04:00
|
|
|
static struct task *qcc_timeout_task(struct task *t, void *ctx, unsigned int state)
|
2022-01-13 10:28:06 -05:00
|
|
|
{
|
|
|
|
|
struct qcc *qcc = ctx;
|
|
|
|
|
int expired = tick_is_expired(t->expire, now_ms);
|
|
|
|
|
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_WAKE, qcc ? qcc->conn : NULL);
|
2022-01-13 10:28:06 -05:00
|
|
|
|
|
|
|
|
if (qcc) {
|
|
|
|
|
if (!expired) {
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_DEVEL("not expired", QMUX_EV_QCC_WAKE, qcc->conn);
|
|
|
|
|
goto requeue;
|
2022-01-13 10:28:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!qcc_may_expire(qcc)) {
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_DEVEL("cannot expired", QMUX_EV_QCC_WAKE, qcc->conn);
|
2022-01-13 10:28:06 -05:00
|
|
|
t->expire = TICK_ETERNITY;
|
2022-08-10 10:14:32 -04:00
|
|
|
goto requeue;
|
2022-01-13 10:28:06 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
task_destroy(t);
|
2022-02-21 04:05:16 -05:00
|
|
|
|
2022-03-24 12:10:00 -04:00
|
|
|
if (!qcc) {
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_DEVEL("no more qcc", QMUX_EV_QCC_WAKE);
|
|
|
|
|
goto out;
|
2022-03-24 12:10:00 -04:00
|
|
|
}
|
2022-02-21 04:05:16 -05:00
|
|
|
|
2023-10-26 12:17:29 -04:00
|
|
|
/* Mark timeout as triggered by setting task to NULL. */
|
2022-01-13 10:28:06 -05:00
|
|
|
qcc->task = NULL;
|
|
|
|
|
|
2022-07-25 08:58:48 -04:00
|
|
|
/* TODO depending on the timeout condition, different shutdown mode
|
|
|
|
|
* should be used. For http keep-alive or disabled proxy, a graceful
|
|
|
|
|
* shutdown should occurs. For all other cases, an immediate close
|
|
|
|
|
* seems legitimate.
|
|
|
|
|
*/
|
2022-08-10 10:58:01 -04:00
|
|
|
if (qcc_is_dead(qcc)) {
|
|
|
|
|
TRACE_STATE("releasing dead connection", QMUX_EV_QCC_WAKE, qcc->conn);
|
2023-12-18 11:30:35 -05:00
|
|
|
qcc_shutdown(qcc);
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc_release(qcc);
|
2022-08-10 10:58:01 -04:00
|
|
|
}
|
2022-01-13 10:28:06 -05:00
|
|
|
|
2022-08-10 10:14:32 -04:00
|
|
|
out:
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_WAKE);
|
2022-01-13 10:28:06 -05:00
|
|
|
return NULL;
|
2022-08-10 10:14:32 -04:00
|
|
|
|
|
|
|
|
requeue:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_WAKE);
|
|
|
|
|
return t;
|
2022-01-13 10:28:06 -05:00
|
|
|
}
|
|
|
|
|
|
2023-12-18 13:12:32 -05:00
|
|
|
/* Minimal initialization of <qcc> members to use qcc_release() safely. */
|
|
|
|
|
static void _qcc_init(struct qcc *qcc)
|
|
|
|
|
{
|
|
|
|
|
qcc->conn = NULL;
|
2025-01-22 11:31:10 -05:00
|
|
|
qcc->pacing_task = NULL;
|
2023-12-18 13:12:32 -05:00
|
|
|
qcc->task = NULL;
|
|
|
|
|
qcc->wait_event.tasklet = NULL;
|
|
|
|
|
qcc->app_ops = NULL;
|
|
|
|
|
qcc->streams_by_id = EB_ROOT_UNIQUE;
|
|
|
|
|
LIST_INIT(&qcc->lfctl.frms);
|
2024-11-18 03:53:39 -05:00
|
|
|
LIST_INIT(&qcc->tx.frms);
|
2023-12-18 13:12:32 -05:00
|
|
|
}
|
|
|
|
|
|
2023-05-30 08:51:57 -04:00
|
|
|
static int qmux_init(struct connection *conn, struct proxy *prx,
|
|
|
|
|
struct session *sess, struct buffer *input)
|
2021-02-18 03:59:01 -05:00
|
|
|
{
|
2021-12-03 05:36:46 -05:00
|
|
|
struct qcc *qcc;
|
2022-03-07 09:47:02 -05:00
|
|
|
struct quic_transport_params *lparams, *rparams;
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2022-08-10 10:58:01 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_QCC_NEW);
|
|
|
|
|
|
2021-12-03 05:36:46 -05:00
|
|
|
qcc = pool_alloc(pool_head_qcc);
|
2022-08-10 10:58:01 -04:00
|
|
|
if (!qcc) {
|
|
|
|
|
TRACE_ERROR("alloc failure", QMUX_EV_QCC_NEW);
|
2023-12-18 13:12:32 -05:00
|
|
|
goto err;
|
2022-08-10 10:58:01 -04:00
|
|
|
}
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2023-12-18 13:12:32 -05:00
|
|
|
_qcc_init(qcc);
|
2021-12-03 05:36:46 -05:00
|
|
|
conn->ctx = qcc;
|
2022-07-25 05:21:46 -04:00
|
|
|
qcc->nb_hreq = qcc->nb_sc = 0;
|
2022-02-01 09:14:24 -05:00
|
|
|
qcc->flags = 0;
|
2025-02-17 10:00:03 -05:00
|
|
|
qcc->app_st = QCC_APP_ST_NULL;
|
2024-05-13 03:05:27 -04:00
|
|
|
qcc->glitches = 0;
|
2023-12-18 13:13:09 -05:00
|
|
|
qcc->err = quic_err_transport(QC_ERR_NO_ERROR);
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2021-12-08 09:12:01 -05:00
|
|
|
/* Server parameters, params used for RX flow control. */
|
2022-04-11 08:18:10 -04:00
|
|
|
lparams = &conn->handle.qc->rx.params;
|
2021-12-08 09:12:01 -05:00
|
|
|
|
2022-03-21 12:13:32 -04:00
|
|
|
qcc->lfctl.ms_bidi = qcc->lfctl.ms_bidi_init = lparams->initial_max_streams_bidi;
|
2022-08-16 05:29:08 -04:00
|
|
|
qcc->lfctl.ms_uni = lparams->initial_max_streams_uni;
|
2022-04-26 05:21:10 -04:00
|
|
|
qcc->lfctl.msd_bidi_l = lparams->initial_max_stream_data_bidi_local;
|
|
|
|
|
qcc->lfctl.msd_bidi_r = lparams->initial_max_stream_data_bidi_remote;
|
2022-10-21 11:02:18 -04:00
|
|
|
qcc->lfctl.msd_uni_r = lparams->initial_max_stream_data_uni;
|
2022-03-21 12:13:32 -04:00
|
|
|
qcc->lfctl.cl_bidi_r = 0;
|
2022-02-07 10:09:06 -05:00
|
|
|
|
2022-05-16 10:19:59 -04:00
|
|
|
qcc->lfctl.md = qcc->lfctl.md_init = lparams->initial_max_data;
|
2022-05-20 09:05:07 -04:00
|
|
|
qcc->lfctl.offsets_recv = qcc->lfctl.offsets_consume = 0;
|
2022-05-16 10:19:59 -04:00
|
|
|
|
2022-04-11 08:18:10 -04:00
|
|
|
rparams = &conn->handle.qc->tx.params;
|
2023-10-18 11:48:11 -04:00
|
|
|
qfctl_init(&qcc->tx.fc, rparams->initial_max_data);
|
2022-03-07 09:47:02 -05:00
|
|
|
qcc->rfctl.msd_bidi_l = rparams->initial_max_stream_data_bidi_local;
|
|
|
|
|
qcc->rfctl.msd_bidi_r = rparams->initial_max_stream_data_bidi_remote;
|
2022-10-21 11:02:18 -04:00
|
|
|
qcc->rfctl.msd_uni_l = rparams->initial_max_stream_data_uni;
|
2022-03-07 09:47:02 -05:00
|
|
|
|
2024-08-13 02:51:46 -04:00
|
|
|
qcc->tx.buf_in_flight = 0;
|
2024-08-13 05:57:50 -04:00
|
|
|
|
2024-11-19 05:27:00 -05:00
|
|
|
if (qcc_is_pacing_active(conn)) {
|
2024-11-18 08:55:41 -05:00
|
|
|
quic_pacing_init(&qcc->tx.pacer, &conn->handle.qc->path->cc);
|
2024-11-19 05:27:00 -05:00
|
|
|
qcc->tx.paced_sent_ctr = 0;
|
2025-01-22 11:31:10 -05:00
|
|
|
|
|
|
|
|
/* Initialize pacing_task. */
|
|
|
|
|
qcc->pacing_task = task_new_here();
|
|
|
|
|
if (!qcc->pacing_task) {
|
|
|
|
|
TRACE_ERROR("pacing task alloc failure", QMUX_EV_QCC_NEW);
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
qcc->pacing_task->process = qcc_pacing_task;
|
|
|
|
|
qcc->pacing_task->context = qcc;
|
|
|
|
|
qcc->pacing_task->expire = TICK_ETERNITY;
|
|
|
|
|
qcc->pacing_task->state |= TASK_F_WANTS_TIME;
|
2024-11-19 05:27:00 -05:00
|
|
|
}
|
2024-11-18 08:55:41 -05:00
|
|
|
|
2022-07-04 09:50:33 -04:00
|
|
|
if (conn_is_back(conn)) {
|
|
|
|
|
qcc->next_bidi_l = 0x00;
|
|
|
|
|
qcc->largest_bidi_r = 0x01;
|
|
|
|
|
qcc->next_uni_l = 0x02;
|
|
|
|
|
qcc->largest_uni_r = 0x03;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
qcc->largest_bidi_r = 0x00;
|
|
|
|
|
qcc->next_bidi_l = 0x01;
|
|
|
|
|
qcc->largest_uni_r = 0x02;
|
|
|
|
|
qcc->next_uni_l = 0x03;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-03 05:36:46 -05:00
|
|
|
qcc->wait_event.tasklet = tasklet_new();
|
2022-08-10 10:58:01 -04:00
|
|
|
if (!qcc->wait_event.tasklet) {
|
|
|
|
|
TRACE_ERROR("taslket alloc failure", QMUX_EV_QCC_NEW);
|
2023-12-18 13:12:32 -05:00
|
|
|
goto err;
|
2022-08-10 10:58:01 -04:00
|
|
|
}
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2024-12-05 04:48:51 -05:00
|
|
|
LIST_INIT(&qcc->recv_list);
|
2023-01-03 08:39:24 -05:00
|
|
|
LIST_INIT(&qcc->send_list);
|
2023-10-18 11:48:11 -04:00
|
|
|
LIST_INIT(&qcc->fctl_list);
|
2024-01-17 09:15:55 -05:00
|
|
|
LIST_INIT(&qcc->buf_wait_list);
|
2024-12-17 05:16:34 -05:00
|
|
|
LIST_INIT(&qcc->purg_list);
|
2022-04-15 11:32:04 -04:00
|
|
|
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc->wait_event.tasklet->process = qcc_io_cb;
|
2021-12-03 05:36:46 -05:00
|
|
|
qcc->wait_event.tasklet->context = qcc;
|
2024-11-21 10:20:15 -05:00
|
|
|
qcc->wait_event.tasklet->state |= TASK_F_WANTS_TIME;
|
2022-03-18 17:49:22 -04:00
|
|
|
qcc->wait_event.events = 0;
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2022-07-22 10:16:03 -04:00
|
|
|
qcc->proxy = prx;
|
2022-01-13 10:28:06 -05:00
|
|
|
/* haproxy timeouts */
|
2023-12-18 13:12:32 -05:00
|
|
|
if (conn_is_back(conn)) {
|
2023-02-08 09:55:24 -05:00
|
|
|
qcc->timeout = prx->timeout.server;
|
|
|
|
|
qcc->shut_timeout = tick_isset(prx->timeout.serverfin) ?
|
|
|
|
|
prx->timeout.serverfin : prx->timeout.server;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
qcc->timeout = prx->timeout.client;
|
|
|
|
|
qcc->shut_timeout = tick_isset(prx->timeout.clientfin) ?
|
|
|
|
|
prx->timeout.clientfin : prx->timeout.client;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-26 12:17:29 -04:00
|
|
|
/* Always allocate task even if timeout is unset. In MUX code, if task
|
|
|
|
|
* is NULL, it indicates that a timeout has stroke earlier.
|
|
|
|
|
*/
|
|
|
|
|
qcc->task = task_new_here();
|
|
|
|
|
if (!qcc->task) {
|
|
|
|
|
TRACE_ERROR("timeout task alloc failure", QMUX_EV_QCC_NEW);
|
2023-12-18 13:12:32 -05:00
|
|
|
goto err;
|
2022-04-21 10:29:27 -04:00
|
|
|
}
|
2023-10-26 12:17:29 -04:00
|
|
|
qcc->task->process = qcc_timeout_task;
|
|
|
|
|
qcc->task->context = qcc;
|
|
|
|
|
qcc->task->expire = tick_add_ifset(now_ms, qcc->timeout);
|
|
|
|
|
|
2022-07-25 05:53:18 -04:00
|
|
|
qcc_reset_idle_start(qcc);
|
2022-08-03 05:17:57 -04:00
|
|
|
LIST_INIT(&qcc->opening_list);
|
2022-01-13 10:28:06 -05:00
|
|
|
|
2022-04-11 08:18:10 -04:00
|
|
|
HA_ATOMIC_STORE(&conn->handle.qc->qcc, qcc);
|
2023-01-25 11:44:36 -05:00
|
|
|
|
2023-12-18 13:12:32 -05:00
|
|
|
/* Register conn as app_ops may use it. */
|
|
|
|
|
qcc->conn = conn;
|
|
|
|
|
|
2023-01-25 11:44:36 -05:00
|
|
|
if (qcc_install_app_ops(qcc, conn->handle.qc->app_ops)) {
|
2023-12-18 13:12:32 -05:00
|
|
|
TRACE_PROTO("Cannot install app layer", QMUX_EV_QCC_NEW|QMUX_EV_QCC_ERR, conn);
|
|
|
|
|
goto err;
|
2023-01-25 11:44:36 -05:00
|
|
|
}
|
|
|
|
|
|
2023-01-18 05:52:21 -05:00
|
|
|
if (qcc->app_ops == &h3_ops)
|
|
|
|
|
proxy_inc_fe_cum_sess_ver_ctr(sess->listener, prx, 3);
|
|
|
|
|
|
2023-04-19 11:58:39 -04:00
|
|
|
/* Register conn for idle front closing. This is done once everything is allocated. */
|
|
|
|
|
if (!conn_is_back(conn))
|
|
|
|
|
LIST_APPEND(&mux_stopping_data[tid].list, &conn->stopping_list);
|
|
|
|
|
|
2021-12-03 05:36:46 -05:00
|
|
|
/* init read cycle */
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2024-11-29 10:18:12 -05:00
|
|
|
/* MUX is initialized before QUIC handshake completion if early data
|
|
|
|
|
* received. Flag connection to delay stream processing if
|
|
|
|
|
* wait-for-handshake is active.
|
|
|
|
|
*/
|
|
|
|
|
if (conn->handle.qc->state < QUIC_HS_ST_COMPLETE) {
|
|
|
|
|
if (!(conn->flags & CO_FL_EARLY_SSL_HS)) {
|
|
|
|
|
TRACE_STATE("flag connection with early data", QMUX_EV_QCC_WAKE, conn);
|
|
|
|
|
conn->flags |= CO_FL_EARLY_SSL_HS;
|
|
|
|
|
/* subscribe for handshake completion */
|
|
|
|
|
conn->xprt->subscribe(conn, conn->xprt_ctx, SUB_RETRY_RECV,
|
|
|
|
|
&qcc->wait_event);
|
|
|
|
|
qcc->flags |= QC_CF_WAIT_HS;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 13:12:32 -05:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_NEW, conn);
|
2021-12-03 05:36:46 -05:00
|
|
|
return 0;
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2023-12-18 13:12:32 -05:00
|
|
|
err:
|
2023-12-18 13:13:09 -05:00
|
|
|
/* Prepare CONNECTION_CLOSE, using INTERNAL_ERROR as fallback code if unset. */
|
|
|
|
|
if (!(conn->handle.qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE)) {
|
|
|
|
|
struct quic_err err = qcc && qcc->err.code ?
|
|
|
|
|
qcc->err : quic_err_transport(QC_ERR_INTERNAL_ERROR);
|
|
|
|
|
quic_set_connection_close(conn->handle.qc, err);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-18 13:12:32 -05:00
|
|
|
if (qcc) {
|
|
|
|
|
/* In case of MUX init failure, session will ensure connection is freed. */
|
|
|
|
|
qcc->conn = NULL;
|
|
|
|
|
qcc_release(qcc);
|
2025-02-17 04:54:41 -05:00
|
|
|
conn->ctx = NULL;
|
2023-12-18 13:12:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRACE_DEVEL("leaving on error", QMUX_EV_QCC_NEW, conn);
|
2021-12-03 05:36:46 -05:00
|
|
|
return -1;
|
2021-02-18 03:59:01 -05:00
|
|
|
}
|
|
|
|
|
|
2023-05-30 08:51:57 -04:00
|
|
|
static void qmux_destroy(void *ctx)
|
2022-04-13 10:54:52 -04:00
|
|
|
{
|
|
|
|
|
struct qcc *qcc = ctx;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_END, qcc->conn);
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc_release(qcc);
|
2022-04-13 10:54:52 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_END);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-30 08:51:57 -04:00
|
|
|
static void qmux_strm_detach(struct sedesc *sd)
|
2021-02-18 03:59:01 -05:00
|
|
|
{
|
2022-05-27 10:09:35 -04:00
|
|
|
struct qcs *qcs = sd->se;
|
2021-12-06 10:03:47 -05:00
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_STRM_END, qcc->conn, qcs);
|
2021-12-06 10:03:47 -05:00
|
|
|
|
2022-07-01 10:48:42 -04:00
|
|
|
/* TODO this BUG_ON_HOT() is not correct as the stconn layer may detach
|
|
|
|
|
* from the stream even if it is not closed remotely at the QUIC layer.
|
|
|
|
|
* This happens for example when a stream must be closed due to a
|
|
|
|
|
* rejected request. To better handle these cases, it will be required
|
|
|
|
|
* to implement shutr/shutw MUX operations. Once this is done, this
|
|
|
|
|
* BUG_ON_HOT() statement can be adjusted.
|
|
|
|
|
*/
|
|
|
|
|
//BUG_ON_HOT(!qcs_is_close_remote(qcs));
|
2022-07-25 05:21:46 -04:00
|
|
|
|
|
|
|
|
qcc_rm_sc(qcc);
|
2022-04-04 10:15:06 -04:00
|
|
|
|
2023-05-04 12:52:42 -04:00
|
|
|
if (!qcs_is_close_local(qcs) &&
|
|
|
|
|
!(qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL))) {
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_STATE("remaining data, detaching qcs", QMUX_EV_STRM_END, qcc->conn, qcs);
|
2021-12-08 08:42:55 -05:00
|
|
|
qcs->flags |= QC_SF_DETACH;
|
2022-07-25 08:58:48 -04:00
|
|
|
qcc_refresh_timeout(qcc);
|
2022-08-10 10:14:32 -04:00
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_END, qcc->conn, qcs);
|
2021-12-08 08:42:55 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-06 10:03:47 -05:00
|
|
|
qcs_destroy(qcs);
|
2022-02-01 04:33:09 -05:00
|
|
|
|
2022-04-04 10:15:06 -04:00
|
|
|
if (qcc_is_dead(qcc)) {
|
2022-08-10 10:42:35 -04:00
|
|
|
TRACE_STATE("killing dead connection", QMUX_EV_STRM_END, qcc->conn);
|
2022-08-12 09:56:21 -04:00
|
|
|
goto release;
|
2022-04-04 10:15:06 -04:00
|
|
|
}
|
2023-10-26 12:17:29 -04:00
|
|
|
else {
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_DEVEL("refreshing connection's timeout", QMUX_EV_STRM_END, qcc->conn);
|
2022-07-25 08:58:48 -04:00
|
|
|
qcc_refresh_timeout(qcc);
|
|
|
|
|
}
|
2022-08-10 10:14:32 -04:00
|
|
|
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_END, qcc->conn);
|
2022-08-12 09:56:21 -04:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
release:
|
2023-12-18 11:30:35 -05:00
|
|
|
qcc_shutdown(qcc);
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc_release(qcc);
|
2022-08-12 09:56:21 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_END);
|
|
|
|
|
return;
|
2021-02-18 03:59:01 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-03 05:36:46 -05:00
|
|
|
/* Called from the upper layer, to receive data */
|
2023-05-30 08:51:57 -04:00
|
|
|
static size_t qmux_strm_rcv_buf(struct stconn *sc, struct buffer *buf,
|
|
|
|
|
size_t count, int flags)
|
2021-02-18 03:59:01 -05:00
|
|
|
{
|
2022-05-27 04:09:11 -04:00
|
|
|
struct qcs *qcs = __sc_mux_strm(sc);
|
2023-05-15 05:35:45 -04:00
|
|
|
struct qcc *qcc = qcs->qcc;
|
2022-02-14 11:11:09 -05:00
|
|
|
size_t ret = 0;
|
2022-02-14 11:11:32 -05:00
|
|
|
char fin = 0;
|
2022-02-14 11:11:09 -05:00
|
|
|
|
2023-05-15 05:35:45 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2022-09-19 11:02:28 -04:00
|
|
|
ret = qcs_http_rcv_buf(qcs, buf, count, &fin);
|
2022-02-14 11:11:09 -05:00
|
|
|
|
|
|
|
|
if (b_data(&qcs->rx.app_buf)) {
|
2022-05-27 10:09:35 -04:00
|
|
|
se_fl_set(qcs->sd, SE_FL_RCV_MORE | SE_FL_WANT_ROOM);
|
2022-02-14 11:11:09 -05:00
|
|
|
}
|
|
|
|
|
else {
|
2022-05-27 10:09:35 -04:00
|
|
|
se_fl_clr(qcs->sd, SE_FL_RCV_MORE | SE_FL_WANT_ROOM);
|
2022-02-14 11:11:09 -05:00
|
|
|
|
2023-05-12 12:16:31 -04:00
|
|
|
/* Set end-of-input when full message properly received. */
|
2023-02-23 08:52:09 -05:00
|
|
|
if (fin) {
|
2023-05-15 05:35:45 -04:00
|
|
|
TRACE_STATE("report end-of-input", QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
2023-05-25 09:02:24 -04:00
|
|
|
se_fl_set(qcs->sd, SE_FL_EOI);
|
2022-02-14 11:11:09 -05:00
|
|
|
|
2023-02-23 08:52:09 -05:00
|
|
|
/* If request EOM is reported to the upper layer, it means the
|
|
|
|
|
* QCS now expects data from the opposite side.
|
|
|
|
|
*/
|
|
|
|
|
se_expect_data(qcs->sd);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-15 05:31:20 -04:00
|
|
|
/* Set end-of-stream on read closed. */
|
|
|
|
|
if (qcs->flags & QC_SF_RECV_RESET ||
|
|
|
|
|
qcc->conn->flags & CO_FL_SOCK_RD_SH) {
|
|
|
|
|
TRACE_STATE("report end-of-stream", QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
|
|
|
|
se_fl_set(qcs->sd, SE_FL_EOS);
|
|
|
|
|
|
|
|
|
|
/* Set error if EOI not reached. This may happen on
|
|
|
|
|
* RESET_STREAM reception or connection error.
|
|
|
|
|
*/
|
|
|
|
|
if (!se_fl_test(qcs->sd, SE_FL_EOI)) {
|
|
|
|
|
TRACE_STATE("report error on stream aborted", QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
2023-05-25 09:02:24 -04:00
|
|
|
se_fl_set(qcs->sd, SE_FL_ERROR);
|
2023-05-15 05:31:20 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-15 05:35:45 -04:00
|
|
|
if (se_fl_test(qcs->sd, SE_FL_ERR_PENDING)) {
|
|
|
|
|
TRACE_STATE("report error", QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
|
|
|
|
se_fl_set(qcs->sd, SE_FL_ERROR);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-14 11:11:09 -05:00
|
|
|
if (b_size(&qcs->rx.app_buf)) {
|
|
|
|
|
b_free(&qcs->rx.app_buf);
|
|
|
|
|
offer_buffers(NULL, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-03 09:30:04 -04:00
|
|
|
/* Restart demux if it was interrupted on full buffer. */
|
|
|
|
|
if (ret && qcs->flags & QC_SF_DEM_FULL) {
|
2023-09-21 11:06:16 -04:00
|
|
|
/* Ensure DEM_FULL is only set if there is available data to
|
|
|
|
|
* ensure we never do unnecessary wakeup here.
|
2023-05-03 09:30:04 -04:00
|
|
|
*/
|
2025-02-24 10:22:22 -05:00
|
|
|
BUG_ON(!qcs_rx_avail_data(qcs));
|
2023-05-03 09:30:04 -04:00
|
|
|
|
2022-05-02 05:07:06 -04:00
|
|
|
qcs->flags &= ~QC_SF_DEM_FULL;
|
2024-12-05 04:48:51 -05:00
|
|
|
if (!(qcc->flags & QC_CF_ERRL)) {
|
|
|
|
|
LIST_APPEND(&qcc->recv_list, &qcs->el_recv);
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2024-12-05 04:48:51 -05:00
|
|
|
}
|
2022-05-02 05:07:06 -04:00
|
|
|
}
|
2022-02-14 11:11:09 -05:00
|
|
|
|
2023-05-15 05:35:45 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_RECV, qcc->conn, qcs);
|
2022-03-24 12:10:00 -04:00
|
|
|
|
2022-02-14 11:11:09 -05:00
|
|
|
return ret;
|
2021-02-18 03:59:01 -05:00
|
|
|
}
|
|
|
|
|
|
2023-05-30 08:51:57 -04:00
|
|
|
static size_t qmux_strm_snd_buf(struct stconn *sc, struct buffer *buf,
|
|
|
|
|
size_t count, int flags)
|
2021-02-18 03:59:01 -05:00
|
|
|
{
|
2022-05-27 04:09:11 -04:00
|
|
|
struct qcs *qcs = __sc_mux_strm(sc);
|
2024-01-30 05:23:48 -05:00
|
|
|
const size_t old_data = qcs_prep_bytes(qcs);
|
2023-05-03 12:16:40 -04:00
|
|
|
size_t ret = 0;
|
2022-09-19 11:14:27 -04:00
|
|
|
char fin;
|
2022-03-24 12:10:00 -04:00
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2024-01-17 09:15:55 -05:00
|
|
|
/* Stream must not be woken up if already waiting for conn buffer. */
|
|
|
|
|
BUG_ON(LIST_INLIST(&qcs->el_buf));
|
|
|
|
|
|
2023-12-19 05:25:18 -05:00
|
|
|
/* Sending forbidden if QCS is locally closed (FIN or RESET_STREAM sent). */
|
|
|
|
|
BUG_ON(qcs_is_close_local(qcs) || (qcs->flags & QC_SF_TO_RESET));
|
|
|
|
|
|
2022-09-13 10:49:21 -04:00
|
|
|
/* stream layer has been detached so no transfer must occur after. */
|
|
|
|
|
BUG_ON_HOT(qcs->flags & QC_SF_DETACH);
|
|
|
|
|
|
2023-05-03 12:16:40 -04:00
|
|
|
/* Report error if set on stream endpoint layer. */
|
2023-05-04 12:52:42 -04:00
|
|
|
if (qcs->qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)) {
|
2023-05-03 12:16:40 -04:00
|
|
|
se_fl_set(qcs->sd, SE_FL_ERROR);
|
|
|
|
|
TRACE_DEVEL("connection in error", QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-18 11:48:11 -04:00
|
|
|
if (qfctl_sblocked(&qcs->qcc->tx.fc)) {
|
|
|
|
|
TRACE_DEVEL("leaving on connection flow control",
|
|
|
|
|
QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
if (!LIST_INLIST(&qcs->el_fctl)) {
|
|
|
|
|
TRACE_DEVEL("append to fctl-list",
|
|
|
|
|
QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
LIST_APPEND(&qcs->qcc->fctl_list, &qcs->el_fctl);
|
2024-07-31 12:43:55 -04:00
|
|
|
tot_time_start(&qcs->timer.fctl);
|
2023-10-18 11:48:11 -04:00
|
|
|
}
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-18 09:55:38 -04:00
|
|
|
if (qfctl_sblocked(&qcs->tx.fc)) {
|
|
|
|
|
TRACE_DEVEL("leaving on flow-control reached",
|
|
|
|
|
QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
2024-07-31 12:43:55 -04:00
|
|
|
tot_time_start(&qcs->timer.fctl);
|
2023-10-18 09:55:38 -04:00
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-19 11:14:27 -04:00
|
|
|
ret = qcs_http_snd_buf(qcs, buf, count, &fin);
|
2023-04-24 11:50:23 -04:00
|
|
|
if (fin) {
|
|
|
|
|
TRACE_STATE("reached stream fin", QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
2022-09-19 11:14:27 -04:00
|
|
|
qcs->flags |= QC_SF_FIN_STREAM;
|
2023-04-24 11:50:23 -04:00
|
|
|
}
|
2022-09-19 11:14:27 -04:00
|
|
|
|
2023-01-10 04:41:41 -05:00
|
|
|
if (ret || fin) {
|
2024-01-30 05:23:48 -05:00
|
|
|
const size_t data = qcs_prep_bytes(qcs) - old_data;
|
MAJOR: mux-quic: remove intermediary Tx buffer
Previously, QUIC MUX sending was implemented with data transfered along
two different buffer instances per stream.
The first QCS buffer was used for HTX blocks conversion into H3 (or
other application protocol) during snd_buf stream callback. QCS instance
is then registered for sending via qcc_io_cb().
For each sending QCS, data memcpy is performed from the first to a
secondary buffer. A STREAM frame is produced for each QCS based on the
content of their secondary buffer.
This model is useful for QUIC MUX which has a major difference with
other muxes : data must be preserved longer, even after sent to the
lower layer. Data references is shared with quic-conn layer which
implements retransmission and data deletion on ACK reception.
This double buffering stages was the first model implemented and remains
active until today. One of its major drawbacks is that it requires
memcpy invocation for every data transferred between the two buffers.
Another important drawback is that the first buffer was is allocated by
each QCS individually without restriction. On the other hand, secondary
buffers are accounted for the connection. A bottleneck can appear if
secondary buffer pool is exhausted, causing unnecessary haproxy
buffering.
The purpose of this commit is to completely break this model. The first
buffer instance is removed. Now, application protocols will directly
allocate buffer from qc_stream_desc layer. This removes completely the
memcpy invocation.
This commit has a lot of code modifications. The most obvious one is the
removal of <qcs.tx.buf> field. Now, qcc_get_stream_txbuf() returns a
buffer instance from qc_stream_desc layer. qcs_xfer_data() which was
responsible for the memcpy between the two buffers is also completely
removed. Offset fields of QCS and QCC are now incremented directly by
qcc_send_stream(). These values are used as boundary with flow control
real offset to delimit the STREAM frames built.
As this change has a big impact on the code, this commit is only the
first part to fully support single buffer emission. For the moment, some
limitations are reintroduced and will be fixed in the next patches :
* on snd_buf if QCS sent buffer in used has room but not enough for the
application protocol to store its content
* on snd_buf if QCS sent buffer is NULL and allocation cannot succeeds
due to connection pool exhaustion
One final important aspect is that extra care is necessary now in
snd_buf callback. The same buffer instance is referenced by both the
stream and quic-conn layer. As such, some operation such as realign
cannot be done anymore freely.
2024-01-16 10:47:57 -05:00
|
|
|
if (data || fin)
|
|
|
|
|
qcc_send_stream(qcs, 0, data);
|
2024-11-29 08:28:09 -05:00
|
|
|
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
/* Wake up MUX to emit newly transferred data. */
|
2022-09-19 11:14:27 -04:00
|
|
|
if (!(qcs->qcc->wait_event.events & SUB_RETRY_SEND))
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcs->qcc->wait_event.tasklet);
|
2022-09-19 11:14:27 -04:00
|
|
|
}
|
2021-02-18 03:59:01 -05:00
|
|
|
|
2022-07-01 10:48:42 -04:00
|
|
|
end:
|
2022-03-24 12:10:00 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
|
|
|
|
|
return ret;
|
2021-02-18 03:59:01 -05:00
|
|
|
}
|
|
|
|
|
|
2023-10-27 09:48:13 -04:00
|
|
|
|
2023-12-07 05:01:50 -05:00
|
|
|
static size_t qmux_strm_nego_ff(struct stconn *sc, struct buffer *input,
|
2024-01-24 05:12:05 -05:00
|
|
|
size_t count, unsigned int flags)
|
2023-10-27 09:48:13 -04:00
|
|
|
{
|
|
|
|
|
struct qcs *qcs = __sc_mux_strm(sc);
|
|
|
|
|
size_t ret = 0;
|
|
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
|
2024-01-17 09:15:55 -05:00
|
|
|
/* Stream must not be woken up if already waiting for conn buffer. */
|
|
|
|
|
BUG_ON(LIST_INLIST(&qcs->el_buf));
|
|
|
|
|
|
2023-12-19 05:25:18 -05:00
|
|
|
/* Sending forbidden if QCS is locally closed (FIN or RESET_STREAM sent). */
|
|
|
|
|
BUG_ON(qcs_is_close_local(qcs) || (qcs->flags & QC_SF_TO_RESET));
|
|
|
|
|
|
2023-10-27 09:48:13 -04:00
|
|
|
/* stream layer has been detached so no transfer must occur after. */
|
|
|
|
|
BUG_ON_HOT(qcs->flags & QC_SF_DETACH);
|
|
|
|
|
|
|
|
|
|
if (!qcs->qcc->app_ops->nego_ff || !qcs->qcc->app_ops->done_ff) {
|
2023-12-29 14:05:20 -05:00
|
|
|
/* Fast forwarding is not supported by the QUIC application layer */
|
2023-10-27 09:48:13 -04:00
|
|
|
qcs->sd->iobuf.flags |= IOBUF_FL_NO_FF;
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-21 05:15:19 -05:00
|
|
|
if (qcs->qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL)) {
|
|
|
|
|
/* Disable fast-forward if connection is on error. Eventually,
|
|
|
|
|
* error will be reported to stream-conn if snd_buf is invoked.
|
|
|
|
|
*/
|
|
|
|
|
TRACE_DEVEL("connection in error", QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
qcs->sd->iobuf.flags |= IOBUF_FL_NO_FF;
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-18 11:48:11 -04:00
|
|
|
if (qfctl_sblocked(&qcs->qcc->tx.fc)) {
|
|
|
|
|
TRACE_DEVEL("leaving on connection flow control", QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
if (!LIST_INLIST(&qcs->el_fctl)) {
|
|
|
|
|
TRACE_DEVEL("append to fctl-list", QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
LIST_APPEND(&qcs->qcc->fctl_list, &qcs->el_fctl);
|
|
|
|
|
}
|
|
|
|
|
qcs->sd->iobuf.flags |= IOBUF_FL_FF_BLOCKED;
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-18 09:55:38 -04:00
|
|
|
if (qfctl_sblocked(&qcs->tx.fc)) {
|
|
|
|
|
TRACE_DEVEL("leaving on flow-control reached", QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
qcs->sd->iobuf.flags |= IOBUF_FL_FF_BLOCKED;
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-27 09:48:13 -04:00
|
|
|
/* Alawys disable splicing */
|
|
|
|
|
qcs->sd->iobuf.flags |= IOBUF_FL_NO_SPLICING;
|
|
|
|
|
|
|
|
|
|
ret = qcs->qcc->app_ops->nego_ff(qcs, count);
|
|
|
|
|
if (!ret)
|
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
|
|
/* forward remaining input data */
|
|
|
|
|
if (b_data(input)) {
|
|
|
|
|
size_t xfer = ret;
|
|
|
|
|
|
|
|
|
|
if (xfer > b_data(input))
|
|
|
|
|
xfer = b_data(input);
|
|
|
|
|
b_add(qcs->sd->iobuf.buf, qcs->sd->iobuf.offset);
|
|
|
|
|
qcs->sd->iobuf.data = b_xfer(qcs->sd->iobuf.buf, input, xfer);
|
|
|
|
|
b_sub(qcs->sd->iobuf.buf, qcs->sd->iobuf.offset);
|
|
|
|
|
|
|
|
|
|
/* Cannot forward more data, wait for room */
|
2023-11-28 16:24:45 -05:00
|
|
|
if (b_data(input)) {
|
|
|
|
|
ret = 0;
|
2023-10-27 09:48:13 -04:00
|
|
|
goto end;
|
2023-11-28 16:24:45 -05:00
|
|
|
}
|
2023-10-27 09:48:13 -04:00
|
|
|
}
|
|
|
|
|
ret -= qcs->sd->iobuf.data;
|
|
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-07 05:01:50 -05:00
|
|
|
static size_t qmux_strm_done_ff(struct stconn *sc)
|
2023-10-27 09:48:13 -04:00
|
|
|
{
|
|
|
|
|
struct qcs *qcs = __sc_mux_strm(sc);
|
|
|
|
|
struct qcc *qcc = qcs->qcc;
|
|
|
|
|
struct sedesc *sd = qcs->sd;
|
2024-01-10 05:09:33 -05:00
|
|
|
size_t total = 0, data = sd->iobuf.data;
|
2023-10-27 09:48:13 -04:00
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
|
2023-12-07 09:53:02 -05:00
|
|
|
if (sd->iobuf.flags & IOBUF_FL_EOI) {
|
|
|
|
|
TRACE_STATE("reached stream fin", QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
2023-10-27 09:48:13 -04:00
|
|
|
qcs->flags |= QC_SF_FIN_STREAM;
|
2023-12-07 09:53:02 -05:00
|
|
|
}
|
2023-10-27 09:48:13 -04:00
|
|
|
|
2024-06-04 06:04:48 -04:00
|
|
|
if (!(qcs->flags & QC_SF_FIN_STREAM) && !sd->iobuf.data) {
|
|
|
|
|
TRACE_STATE("no data sent", QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
|
|
|
|
|
2024-06-04 12:10:51 -04:00
|
|
|
/* There is nothing to forward and the SD was blocked after a
|
|
|
|
|
* successful nego by the producer. We can try to release the
|
|
|
|
|
* TXBUF to retry. In this case, the TX buf MUST exist.
|
2024-06-04 06:04:48 -04:00
|
|
|
*/
|
2024-06-04 12:10:51 -04:00
|
|
|
if ((qcs->sd->iobuf.flags & IOBUF_FL_FF_WANT_ROOM) && !qcc_release_stream_txbuf(qcs))
|
|
|
|
|
qcs->sd->iobuf.flags &= ~(IOBUF_FL_FF_BLOCKED|IOBUF_FL_FF_WANT_ROOM);
|
2023-10-27 09:48:13 -04:00
|
|
|
goto end;
|
2024-06-04 06:04:48 -04:00
|
|
|
}
|
2023-10-27 09:48:13 -04:00
|
|
|
|
|
|
|
|
total = qcs->qcc->app_ops->done_ff(qcs);
|
2024-12-02 10:24:45 -05:00
|
|
|
if (total || qcs->flags & QC_SF_FIN_STREAM)
|
|
|
|
|
qcc_send_stream(qcs, 0, total);
|
2023-10-27 09:48:13 -04:00
|
|
|
|
2024-12-02 10:24:45 -05:00
|
|
|
/* Reset stconn iobuf information. */
|
|
|
|
|
qcs->sd->iobuf.buf = NULL;
|
|
|
|
|
qcs->sd->iobuf.offset = 0;
|
|
|
|
|
qcs->sd->iobuf.data = 0;
|
2024-11-29 08:28:09 -05:00
|
|
|
|
2023-10-27 09:48:13 -04:00
|
|
|
if (!(qcs->qcc->wait_event.events & SUB_RETRY_SEND))
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2023-10-27 09:48:13 -04:00
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_SEND, qcs->qcc->conn, qcs);
|
2024-12-02 10:24:45 -05:00
|
|
|
return data;
|
2023-10-27 09:48:13 -04:00
|
|
|
}
|
|
|
|
|
|
2023-12-07 05:01:50 -05:00
|
|
|
static int qmux_strm_resume_ff(struct stconn *sc, unsigned int flags)
|
2023-10-27 09:48:13 -04:00
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-18 03:59:01 -05:00
|
|
|
/* Called from the upper layer, to subscribe <es> to events <event_type>. The
|
|
|
|
|
* event subscriber <es> is not allowed to change from a previous call as long
|
|
|
|
|
* as at least one event is still subscribed. The <event_type> must only be a
|
|
|
|
|
* combination of SUB_RETRY_RECV and SUB_RETRY_SEND. It always returns 0.
|
|
|
|
|
*/
|
2023-05-30 08:51:57 -04:00
|
|
|
static int qmux_strm_subscribe(struct stconn *sc, int event_type,
|
|
|
|
|
struct wait_event *es)
|
2021-02-18 03:59:01 -05:00
|
|
|
{
|
2022-05-27 04:09:11 -04:00
|
|
|
return qcs_subscribe(__sc_mux_strm(sc), event_type, es);
|
2021-02-18 03:59:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Called from the upper layer, to unsubscribe <es> from events <event_type>.
|
|
|
|
|
* The <es> pointer is not allowed to differ from the one passed to the
|
|
|
|
|
* subscribe() call. It always returns zero.
|
|
|
|
|
*/
|
2023-05-30 08:51:57 -04:00
|
|
|
static int qmux_strm_unsubscribe(struct stconn *sc, int event_type, struct wait_event *es)
|
2021-02-18 03:59:01 -05:00
|
|
|
{
|
2022-05-27 04:09:11 -04:00
|
|
|
struct qcs *qcs = __sc_mux_strm(sc);
|
2021-02-18 03:59:01 -05:00
|
|
|
|
|
|
|
|
BUG_ON(event_type & ~(SUB_RETRY_SEND|SUB_RETRY_RECV));
|
|
|
|
|
BUG_ON(qcs->subs && qcs->subs != es);
|
|
|
|
|
|
|
|
|
|
es->events &= ~event_type;
|
|
|
|
|
if (!es->events)
|
|
|
|
|
qcs->subs = NULL;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-30 08:51:57 -04:00
|
|
|
static int qmux_wake(struct connection *conn)
|
2022-01-31 09:41:14 -05:00
|
|
|
{
|
|
|
|
|
struct qcc *qcc = conn->ctx;
|
2022-04-06 10:13:09 -04:00
|
|
|
|
|
|
|
|
TRACE_ENTER(QMUX_EV_QCC_WAKE, conn);
|
2022-01-31 09:41:14 -05:00
|
|
|
|
2023-05-30 09:04:46 -04:00
|
|
|
if (qcc_io_process(qcc)) {
|
2023-01-24 12:19:47 -05:00
|
|
|
TRACE_STATE("releasing dead connection", QMUX_EV_QCC_WAKE, qcc->conn);
|
2022-04-06 10:13:09 -04:00
|
|
|
goto release;
|
2023-01-24 12:19:47 -05:00
|
|
|
}
|
|
|
|
|
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc_wake_some_streams(qcc);
|
2022-04-06 10:13:09 -04:00
|
|
|
|
2022-07-25 08:58:48 -04:00
|
|
|
qcc_refresh_timeout(qcc);
|
|
|
|
|
|
2022-04-06 10:13:09 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_WAKE, conn);
|
|
|
|
|
return 0;
|
2022-01-31 09:41:14 -05:00
|
|
|
|
2022-04-06 10:13:09 -04:00
|
|
|
release:
|
2023-12-18 11:30:35 -05:00
|
|
|
qcc_shutdown(qcc);
|
2023-05-30 09:04:46 -04:00
|
|
|
qcc_release(qcc);
|
2022-08-10 10:14:32 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_QCC_WAKE);
|
2022-01-31 09:41:14 -05:00
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-04 04:09:16 -04:00
|
|
|
static void qmux_strm_shut(struct stconn *sc, unsigned int mode, struct se_abort_info *reason)
|
2022-12-21 04:21:58 -05:00
|
|
|
{
|
|
|
|
|
struct qcs *qcs = __sc_mux_strm(sc);
|
2023-04-24 11:50:23 -04:00
|
|
|
struct qcc *qcc = qcs->qcc;
|
2022-12-21 04:21:58 -05:00
|
|
|
|
2024-04-18 03:56:11 -04:00
|
|
|
if (!(mode & (SE_SHW_SILENT|SE_SHW_NORMAL)))
|
|
|
|
|
return;
|
|
|
|
|
|
2023-04-24 11:50:23 -04:00
|
|
|
TRACE_ENTER(QMUX_EV_STRM_SHUT, qcc->conn, qcs);
|
2022-12-21 04:21:58 -05:00
|
|
|
|
2023-04-24 11:50:23 -04:00
|
|
|
/* Early closure reported if QC_SF_FIN_STREAM not yet set. */
|
2022-12-21 04:21:58 -05:00
|
|
|
if (!qcs_is_close_local(qcs) &&
|
|
|
|
|
!(qcs->flags & (QC_SF_FIN_STREAM|QC_SF_TO_RESET))) {
|
2023-04-24 11:50:23 -04:00
|
|
|
|
2024-10-18 11:46:06 -04:00
|
|
|
/* 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) {
|
2023-05-04 12:52:42 -04:00
|
|
|
if (!(qcc->flags & (QC_CF_ERR_CONN|QC_CF_ERRL))) {
|
2023-05-10 04:41:47 -04:00
|
|
|
TRACE_STATE("set FIN STREAM",
|
|
|
|
|
QMUX_EV_STRM_SHUT, qcc->conn, qcs);
|
|
|
|
|
qcs->flags |= QC_SF_FIN_STREAM;
|
2024-01-10 05:09:33 -05:00
|
|
|
qcc_send_stream(qcs, 0, 0);
|
2023-05-10 04:41:47 -04:00
|
|
|
}
|
2023-04-24 11:50:23 -04:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* RESET_STREAM necessary. */
|
2023-12-19 05:22:28 -05:00
|
|
|
qcc_reset_stream(qcs, 0);
|
2023-04-24 11:50:23 -04:00
|
|
|
}
|
|
|
|
|
|
MEDIUM: mux-quic: remove pacing specific code on qcc_io_cb
Pacing was recently implemented by QUIC MUX. Its tasklet is rescheduled
until next emission timer is reached. To improve performance, an
alternate execution of qcc_io_cb was performed when rescheduled due to
pacing. This was implemented using TASK_F_USR1 flag.
However, this model is fragile, in particular when several events
happened alongside pacing scheduling. This has caused some issue
recently, most notably when MUX is subscribed on transport layer on
receive for handshake completion while pacing emission is performed in
parallel. MUX qcc_io_cb() would not execute the default code path, which
means the reception event is silently ignored.
Recent patches have reworked several parts of qcc_io_cb. The objective
was to improve performance with better algorithm on send and receive
part. Most notable, qcc frames list is only cleared when new data is
available for emission. With this, pacing alternative code is now mostly
unneeded. As such, this patch removes it. The following changes are
performed :
* TASK_F_USR1 is now not used by QUIC MUX. As such, tasklet_wakeup()
default invokation can now replace obsolete wrappers
qcc_wakeup/qcc_wakeup_pacing
* qcc_purge_sending is removed. On pacing rescheduling, all qcc_io_cb()
is executed. This is less error-prone, in particular when pacing is
mixed with other events like receive handling. This renders the code
less fragile, as it completely solves the described issue above.
This should be backported up to 3.1.
2024-12-12 08:53:49 -05:00
|
|
|
tasklet_wakeup(qcc->wait_event.tasklet);
|
2022-12-21 04:21:58 -05:00
|
|
|
}
|
|
|
|
|
|
2023-05-03 12:17:19 -04:00
|
|
|
out:
|
2023-04-24 11:50:23 -04:00
|
|
|
TRACE_LEAVE(QMUX_EV_STRM_SHUT, qcc->conn, qcs);
|
2022-12-21 04:21:58 -05:00
|
|
|
}
|
2022-03-24 11:09:16 -04:00
|
|
|
|
2024-04-30 10:12:31 -04:00
|
|
|
static int qmux_ctl(struct connection *conn, enum mux_ctl_type mux_ctl, void *output)
|
|
|
|
|
{
|
|
|
|
|
struct qcc *qcc = conn->ctx;
|
|
|
|
|
|
|
|
|
|
switch (mux_ctl) {
|
|
|
|
|
case MUX_CTL_EXIT_STATUS:
|
|
|
|
|
return MUX_ES_UNKNOWN;
|
|
|
|
|
|
2024-05-13 03:05:27 -04:00
|
|
|
case MUX_CTL_GET_GLITCHES:
|
|
|
|
|
return qcc->glitches;
|
|
|
|
|
|
2024-04-30 10:18:07 -04:00
|
|
|
case MUX_CTL_GET_NBSTRM: {
|
|
|
|
|
struct qcs *qcs;
|
|
|
|
|
unsigned int nb_strm = qcc->nb_sc;
|
|
|
|
|
|
|
|
|
|
list_for_each_entry(qcs, &qcc->opening_list, el_opening)
|
|
|
|
|
nb_strm++;
|
|
|
|
|
return nb_strm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case MUX_CTL_GET_MAXSTRM:
|
|
|
|
|
return qcc->lfctl.ms_bidi_init;
|
|
|
|
|
|
2024-04-30 10:12:31 -04:00
|
|
|
default:
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-28 09:12:51 -05:00
|
|
|
static int qmux_sctl(struct stconn *sc, enum mux_sctl_type mux_sctl, void *output)
|
|
|
|
|
{
|
|
|
|
|
int ret = 0;
|
2024-07-31 11:28:24 -04:00
|
|
|
const struct qcs *qcs = __sc_mux_strm(sc);
|
|
|
|
|
const struct qcc *qcc = qcs->qcc;
|
|
|
|
|
union mux_sctl_dbg_str_ctx *dbg_ctx;
|
|
|
|
|
struct buffer *buf;
|
2023-11-28 09:12:51 -05:00
|
|
|
|
|
|
|
|
switch (mux_sctl) {
|
|
|
|
|
case MUX_SCTL_SID:
|
|
|
|
|
if (output)
|
|
|
|
|
*((int64_t *)output) = qcs->id;
|
|
|
|
|
return ret;
|
|
|
|
|
|
2024-07-31 11:28:24 -04:00
|
|
|
case MUX_SCTL_DBG_STR:
|
|
|
|
|
dbg_ctx = output;
|
|
|
|
|
buf = get_trash_chunk();
|
|
|
|
|
|
|
|
|
|
if (dbg_ctx->arg.debug_flags & MUX_SCTL_DBG_STR_L_MUXS)
|
|
|
|
|
qmux_dump_qcs_info(buf, qcs);
|
|
|
|
|
|
|
|
|
|
if (dbg_ctx->arg.debug_flags & MUX_SCTL_DBG_STR_L_MUXC)
|
|
|
|
|
qmux_dump_qcc_info(buf, qcc);
|
|
|
|
|
|
|
|
|
|
if (dbg_ctx->arg.debug_flags & MUX_SCTL_DBG_STR_L_CONN)
|
|
|
|
|
chunk_appendf(buf, " conn.flg=%#08x", qcc->conn->flags);
|
|
|
|
|
|
2024-08-01 05:35:04 -04:00
|
|
|
if (dbg_ctx->arg.debug_flags & MUX_SCTL_DBG_STR_L_XPRT)
|
|
|
|
|
qcc->conn->xprt->dump_info(buf, qcc->conn);
|
|
|
|
|
|
2024-07-31 11:28:24 -04:00
|
|
|
dbg_ctx->ret.buf = *buf;
|
|
|
|
|
return ret;
|
|
|
|
|
|
2023-11-28 09:12:51 -05:00
|
|
|
default:
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-02 10:00:40 -04:00
|
|
|
/* for debugging with CLI's "show sess" command. May emit multiple lines, each
|
|
|
|
|
* new one being prefixed with <pfx>, if <pfx> is not NULL, otherwise a single
|
|
|
|
|
* line is used. Each field starts with a space so it's safe to print it after
|
|
|
|
|
* existing fields.
|
|
|
|
|
*/
|
2023-05-30 08:51:57 -04:00
|
|
|
static int qmux_strm_show_sd(struct buffer *msg, struct sedesc *sd, const char *pfx)
|
2022-09-02 10:00:40 -04:00
|
|
|
{
|
|
|
|
|
struct qcs *qcs = sd->se;
|
|
|
|
|
struct qcc *qcc;
|
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
|
|
if (!qcs)
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
chunk_appendf(msg, " qcs=%p .flg=%#x .id=%llu .st=%s .ctx=%p, .err=%#llx",
|
|
|
|
|
qcs, qcs->flags, (ull)qcs->id, qcs_st_to_str(qcs->st), qcs->ctx, (ull)qcs->err);
|
|
|
|
|
|
|
|
|
|
if (pfx)
|
|
|
|
|
chunk_appendf(msg, "\n%s", pfx);
|
|
|
|
|
|
|
|
|
|
qcc = qcs->qcc;
|
|
|
|
|
chunk_appendf(msg, " qcc=%p .flg=%#x .nbsc=%llu .nbhreq=%llu, .task=%p",
|
|
|
|
|
qcc, qcc->flags, (ull)qcc->nb_sc, (ull)qcc->nb_hreq, qcc->task);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-05-30 08:51:57 -04:00
|
|
|
static const struct mux_ops qmux_ops = {
|
|
|
|
|
.init = qmux_init,
|
|
|
|
|
.destroy = qmux_destroy,
|
|
|
|
|
.detach = qmux_strm_detach,
|
|
|
|
|
.rcv_buf = qmux_strm_rcv_buf,
|
|
|
|
|
.snd_buf = qmux_strm_snd_buf,
|
2023-12-07 05:01:50 -05:00
|
|
|
.nego_fastfwd = qmux_strm_nego_ff,
|
|
|
|
|
.done_fastfwd = qmux_strm_done_ff,
|
|
|
|
|
.resume_fastfwd = qmux_strm_resume_ff,
|
2023-05-30 08:51:57 -04:00
|
|
|
.subscribe = qmux_strm_subscribe,
|
|
|
|
|
.unsubscribe = qmux_strm_unsubscribe,
|
|
|
|
|
.wake = qmux_wake,
|
2024-10-18 11:46:06 -04:00
|
|
|
.shut = qmux_strm_shut,
|
2024-04-30 10:12:31 -04:00
|
|
|
.ctl = qmux_ctl,
|
2023-11-28 09:12:51 -05:00
|
|
|
.sctl = qmux_sctl,
|
2023-05-30 08:51:57 -04:00
|
|
|
.show_sd = qmux_strm_show_sd,
|
2022-04-26 05:54:08 -04:00
|
|
|
.flags = MX_FL_HTX|MX_FL_NO_UPG|MX_FL_FRAMED,
|
2022-04-11 03:29:21 -04:00
|
|
|
.name = "QUIC",
|
2021-02-18 03:59:01 -05:00
|
|
|
};
|
|
|
|
|
|
2024-02-23 11:32:14 -05:00
|
|
|
void qcc_show_quic(struct qcc *qcc)
|
|
|
|
|
{
|
2024-08-21 09:30:17 -04:00
|
|
|
const struct quic_conn *qc = qcc->conn->handle.qc;
|
2024-02-23 11:32:14 -05:00
|
|
|
struct eb64_node *node;
|
2024-08-21 09:30:17 -04:00
|
|
|
|
|
|
|
|
chunk_appendf(&trash, " qcc=0x%p flags=0x%x sc=%llu hreq=%llu bwnd=%llu/%llu\n",
|
|
|
|
|
qcc, qcc->flags, (ullong)qcc->nb_sc, (ullong)qcc->nb_hreq,
|
|
|
|
|
(ullong)qcc->tx.buf_in_flight, (ullong)qc->path->cwnd);
|
2024-02-23 11:32:14 -05:00
|
|
|
|
2024-11-19 05:27:00 -05:00
|
|
|
if (qcc_is_pacing_active(qcc->conn)) {
|
|
|
|
|
chunk_appendf(&trash, " pacing int_sent=%d last_sent=%d\n",
|
|
|
|
|
qcc->tx.paced_sent_ctr,
|
|
|
|
|
qcc->tx.pacer.last_sent);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-23 11:32:14 -05:00
|
|
|
node = eb64_first(&qcc->streams_by_id);
|
|
|
|
|
while (node) {
|
|
|
|
|
struct qcs *qcs = eb64_entry(node, struct qcs, by_id);
|
|
|
|
|
chunk_appendf(&trash, " qcs=0x%p id=%llu flags=0x%x st=%s",
|
|
|
|
|
qcs, (ullong)qcs->id, qcs->flags,
|
|
|
|
|
qcs_st_to_str(qcs->st));
|
|
|
|
|
if (!quic_stream_is_uni(qcs->id) || !quic_stream_is_local(qcc, qcs->id))
|
|
|
|
|
chunk_appendf(&trash, " rxoff=%llu", (ullong)qcs->rx.offset);
|
|
|
|
|
if (!quic_stream_is_uni(qcs->id) || !quic_stream_is_remote(qcc, qcs->id))
|
2024-09-26 04:49:54 -04:00
|
|
|
chunk_appendf(&trash, " txoff=%llu(%llu) msd=%llu",
|
|
|
|
|
(ullong)qcs->tx.fc.off_real,
|
2024-11-21 09:18:41 -05:00
|
|
|
(ullong)qcs->tx.fc.off_soft - (ullong)qcs->tx.fc.off_real,
|
2024-09-26 04:49:54 -04:00
|
|
|
(ullong)qcs->tx.fc.limit);
|
2024-02-23 11:32:14 -05:00
|
|
|
chunk_appendf(&trash, "\n");
|
|
|
|
|
node = eb64_next(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-18 03:59:01 -05:00
|
|
|
static struct mux_proto_list mux_proto_quic =
|
2023-05-30 08:51:57 -04:00
|
|
|
{ .token = IST("quic"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_FE, .mux = &qmux_ops };
|
2021-02-18 03:59:01 -05:00
|
|
|
|
|
|
|
|
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_quic);
|