mirror of
https://github.com/haproxy/haproxy.git
synced 2026-03-06 07:10:43 -05:00
MINOR: quic/h3: reorganize stream reject after MUX closure
The QUIC MUX layer is closed after its transport counterpart. This may be necessary then to reject any new streams opened by the remote peer. This operation is dependent however from the application protocol. Previously, a function qc_h3_request_reject() was directly implemented in quic_conn source file for use when HTTP/3 was previously negotiated. However, this solution was not evolutive and broke layering. This patch introduces a new proper separation with a <strm_reject> callback defined in quic_conn structure. When set, it will be used to preemptively close any new stream. QUIC MUX is responsible to set it just before its closure. No functional change. This patch is purely a refactoring with a better architecture design. Especially, H3 specific code from transport layer is now completely removed.
This commit is contained in:
parent
58830990d0
commit
4120faf289
6 changed files with 67 additions and 60 deletions
|
|
@ -232,6 +232,9 @@ struct qcc_app_ops {
|
|||
void (*inc_err_cnt)(void *ctx, int err_code);
|
||||
/* Set QCC error code as suspicious activity has been detected. */
|
||||
void (*report_susp)(void *ctx);
|
||||
|
||||
/* Free function to close a stream after MUX layer shutdown. */
|
||||
int (*strm_reject)(struct list *out, uint64_t id);
|
||||
};
|
||||
|
||||
#endif /* USE_QUIC */
|
||||
|
|
|
|||
|
|
@ -409,6 +409,9 @@ struct quic_conn {
|
|||
unsigned int hs_expire;
|
||||
|
||||
const struct qcc_app_ops *app_ops;
|
||||
/* Callback to close any stream after MUX closure - set by the MUX itself */
|
||||
int (*strm_reject)(struct list *out, uint64_t stream_id);
|
||||
|
||||
/* Proxy counters */
|
||||
struct quic_counters *prx_counters;
|
||||
|
||||
|
|
|
|||
52
src/h3.c
52
src/h3.c
|
|
@ -3345,6 +3345,57 @@ static void h3_trace(enum trace_level level, uint64_t mask,
|
|||
}
|
||||
}
|
||||
|
||||
/* Cancel a request on stream id <id>. This is useful when the client opens a
|
||||
* new stream but the MUX has already been released. A STOP_SENDING +
|
||||
* RESET_STREAM frames are prepared for emission.
|
||||
*
|
||||
* Returns 1 on success else 0.
|
||||
*/
|
||||
int h3_reject(struct list *out, uint64_t id)
|
||||
{
|
||||
int ret = 0;
|
||||
struct quic_frame *ss, *rs;
|
||||
const uint64_t app_error_code = H3_ERR_REQUEST_REJECTED;
|
||||
|
||||
TRACE_ENTER(H3_EV_TX_FRAME);
|
||||
|
||||
/* Do not emit rejection for unknown unidirectional stream as it is
|
||||
* forbidden to close some of them (H3 control stream and QPACK
|
||||
* encoder/decoder streams).
|
||||
*/
|
||||
if (quic_stream_is_uni(id)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ss = qc_frm_alloc(QUIC_FT_STOP_SENDING);
|
||||
if (!ss) {
|
||||
TRACE_ERROR("failed to allocate quic_frame", H3_EV_TX_FRAME);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ss->stop_sending.id = id;
|
||||
ss->stop_sending.app_error_code = app_error_code;
|
||||
|
||||
rs = qc_frm_alloc(QUIC_FT_RESET_STREAM);
|
||||
if (!rs) {
|
||||
TRACE_ERROR("failed to allocate quic_frame", H3_EV_TX_FRAME);
|
||||
qc_frm_free(NULL, &ss);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rs->reset_stream.id = id;
|
||||
rs->reset_stream.app_error_code = app_error_code;
|
||||
rs->reset_stream.final_size = 0;
|
||||
|
||||
LIST_APPEND(out, &ss->list);
|
||||
LIST_APPEND(out, &rs->list);
|
||||
ret = 1;
|
||||
out:
|
||||
TRACE_LEAVE(H3_EV_TX_FRAME);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* HTTP/3 application layer operations */
|
||||
const struct qcc_app_ops h3_ops = {
|
||||
.init = h3_init,
|
||||
|
|
@ -3360,4 +3411,5 @@ const struct qcc_app_ops h3_ops = {
|
|||
.inc_err_cnt = h3_stats_inc_err_cnt,
|
||||
.report_susp = h3_report_susp,
|
||||
.release = h3_release,
|
||||
.strm_reject = h3_reject,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3377,8 +3377,12 @@ static void qcc_release(struct qcc *qcc)
|
|||
|
||||
qcc_clear_frms(qcc);
|
||||
|
||||
if (qcc->app_ops && qcc->app_ops->release)
|
||||
qcc->app_ops->release(qcc->ctx);
|
||||
if (qcc->app_ops) {
|
||||
if (qcc->app_ops->release)
|
||||
qcc->app_ops->release(qcc->ctx);
|
||||
if (conn->handle.qc)
|
||||
conn->handle.qc->strm_reject = qcc->app_ops->strm_reject;
|
||||
}
|
||||
TRACE_PROTO("application layer released", QMUX_EV_QCC_END, conn);
|
||||
|
||||
pool_free(pool_head_qcc, qcc);
|
||||
|
|
|
|||
|
|
@ -377,61 +377,6 @@ void quic_conn_closed_err_count_inc(struct quic_conn *qc, struct quic_frame *frm
|
|||
TRACE_LEAVE(QUIC_EV_CONN_CLOSE, qc);
|
||||
}
|
||||
|
||||
/* Cancel a request on connection <qc> for stream id <id>. This is useful when
|
||||
* the client opens a new stream but the MUX has already been released. A
|
||||
* STOP_SENDING + RESET_STREAM frames are prepared for emission.
|
||||
*
|
||||
* TODO this function is closely related to H3. Its place should be in H3 layer
|
||||
* instead of quic-conn but this requires an architecture adjustment.
|
||||
*
|
||||
* Returns 1 on success else 0.
|
||||
*/
|
||||
int qc_h3_request_reject(struct quic_conn *qc, uint64_t id)
|
||||
{
|
||||
int ret = 0;
|
||||
struct quic_frame *ss, *rs;
|
||||
struct quic_enc_level *qel = qc->ael;
|
||||
const uint64_t app_error_code = H3_ERR_REQUEST_REJECTED;
|
||||
|
||||
TRACE_ENTER(QUIC_EV_CONN_PRSHPKT, qc);
|
||||
|
||||
/* Do not emit rejection for unknown unidirectional stream as it is
|
||||
* forbidden to close some of them (H3 control stream and QPACK
|
||||
* encoder/decoder streams).
|
||||
*/
|
||||
if (quic_stream_is_uni(id)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ss = qc_frm_alloc(QUIC_FT_STOP_SENDING);
|
||||
if (!ss) {
|
||||
TRACE_ERROR("failed to allocate quic_frame", QUIC_EV_CONN_PRSHPKT, qc);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ss->stop_sending.id = id;
|
||||
ss->stop_sending.app_error_code = app_error_code;
|
||||
|
||||
rs = qc_frm_alloc(QUIC_FT_RESET_STREAM);
|
||||
if (!rs) {
|
||||
TRACE_ERROR("failed to allocate quic_frame", QUIC_EV_CONN_PRSHPKT, qc);
|
||||
qc_frm_free(qc, &ss);
|
||||
goto out;
|
||||
}
|
||||
|
||||
rs->reset_stream.id = id;
|
||||
rs->reset_stream.app_error_code = app_error_code;
|
||||
rs->reset_stream.final_size = 0;
|
||||
|
||||
LIST_APPEND(&qel->pktns->tx.frms, &ss->list);
|
||||
LIST_APPEND(&qel->pktns->tx.frms, &rs->list);
|
||||
ret = 1;
|
||||
out:
|
||||
TRACE_LEAVE(QUIC_EV_CONN_PRSHPKT, qc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Remove a <qc> quic-conn from its ha_thread_ctx list. If <closing> is true,
|
||||
* it will immediately be reinserted in the ha_thread_ctx quic_conns_clo list.
|
||||
*/
|
||||
|
|
@ -1204,6 +1149,7 @@ struct quic_conn *qc_new_conn(void *target,
|
|||
qc->conn = conn;
|
||||
qc->qcc = NULL;
|
||||
qc->app_ops = NULL;
|
||||
qc->strm_reject = NULL;
|
||||
qc->path = NULL;
|
||||
|
||||
/* Keyupdate: required to safely call quic_tls_ku_free() from
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
#include <haproxy/quic_rx.h>
|
||||
|
||||
#include <haproxy/h3.h>
|
||||
#include <haproxy/list.h>
|
||||
#include <haproxy/ncbmbuf.h>
|
||||
#include <haproxy/proto_quic.h>
|
||||
|
|
@ -962,8 +961,8 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
|
|||
}
|
||||
else {
|
||||
TRACE_DEVEL("No mux for new stream", QUIC_EV_CONN_PRSHPKT, qc);
|
||||
if (qc->app_ops == &h3_ops) {
|
||||
if (!qc_h3_request_reject(qc, strm_frm->id)) {
|
||||
if (qc->strm_reject) {
|
||||
if (!qc->strm_reject(&qc->ael->pktns->tx.frms, strm_frm->id)) {
|
||||
TRACE_ERROR("error on request rejection", QUIC_EV_CONN_PRSHPKT, qc);
|
||||
/* This packet will not be acknowledged */
|
||||
goto err;
|
||||
|
|
|
|||
Loading…
Reference in a new issue