Skip to content

Commit 763d598

Browse files
committed
BUG/MINOR: quic: reorder fragmented RX CRYPTO frames by their offsets
This issue impacts the QUIC listeners. It is the same as the one fixed by this commit: BUG/MINOR: quic: repeat packet parsing to deal with fragmented CRYPTO As chrome, ngtcp2 client decided to fragment its CRYPTO frames but in a much more agressive way. This could be fixed with a list local to qc_parse_pkt_frms() to please chrome thanks to the commit above. But this is not sufficient for ngtcp2 which often splits its ClientHello message into more than 10 fragments with very small ones. This leads the packet parser to interrupt the CRYPTO frames parsing due to the ncbuf gap size limit. To fix this, this patch approximatively proceeds the same way but with an ebtree to reorder the CRYPTO by their offsets. These frames are directly inserted into a local ebtree. Then this ebtree is reused to provide the reordered CRYPTO data to the underlying ncbuf (non contiguous buffer). This way there are very few less chances for the ncbufs used to store CRYPTO data to reach a too much fragmented state. Must be backported as far as 2.6.
1 parent 512ce10 commit 763d598

File tree

2 files changed

+36
-75
lines changed

2 files changed

+36
-75
lines changed

include/haproxy/quic_frame-t.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ struct qf_stop_sending {
143143

144144
struct qf_crypto {
145145
uint64_t offset;
146+
struct eb64_node offset_node;
146147
uint64_t len;
147148
const struct quic_enc_level *qel;
148149
const unsigned char *data;

src/quic_conn.c

Lines changed: 35 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3244,13 +3244,12 @@ static void qc_detach_th_ctx_list(struct quic_conn *qc, int closing)
32443244
static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
32453245
struct quic_enc_level *qel)
32463246
{
3247-
struct list retry_frms = LIST_HEAD_INIT(retry_frms);
3248-
struct quic_frame *frm = NULL, *frm_tmp;
3247+
struct eb_root cf_frms_tree = EB_ROOT;
3248+
struct eb64_node *node;
3249+
struct quic_frame *frm = NULL;
32493250
const unsigned char *pos, *end;
32503251
enum quic_rx_ret_frm ret;
32513252
int fast_retrans = 0;
3252-
/* parsing may be rerun multiple times, but no more than <iter>. */
3253-
int iter = 3, parsing_stage = 0;
32543253

32553254
TRACE_ENTER(QUIC_EV_CONN_PRSHPKT, qc);
32563255
/* Skip the AAD */
@@ -3327,39 +3326,14 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
33273326
break;
33283327
}
33293328
case QUIC_FT_CRYPTO:
3330-
ret = qc_handle_crypto_frm(qc, &frm->crypto, pkt, qel);
3331-
switch (ret) {
3332-
case QUIC_RX_RET_FRM_FATAL:
3333-
goto err;
3334-
3335-
case QUIC_RX_RET_FRM_AGAIN:
3336-
if (parsing_stage == 0) {
3337-
TRACE_STATE("parsing stage set to 1 (AGAIN encountered)", QUIC_EV_CONN_PRSHPKT, qc);
3338-
++parsing_stage;
3339-
}
3340-
/* Save frame in temp list to reparse it later. A new instance must be used for next packet frames. */
3341-
LIST_APPEND(&retry_frms, &frm->list);
3342-
frm = NULL;
3343-
break;
3344-
3345-
case QUIC_RX_RET_FRM_DUP:
3346-
if (qc_is_listener(qc) && qel == &qc->els[QUIC_TLS_ENC_LEVEL_INITIAL] &&
3347-
!(qc->flags & QUIC_FL_CONN_HANDSHAKE_SPEED_UP)) {
3348-
fast_retrans = 1;
3349-
}
3350-
break;
3351-
case QUIC_RX_RET_FRM_DONE:
3352-
if (parsing_stage == 1) {
3353-
TRACE_STATE("parsing stage set to 2 (DONE after AGAIN)", QUIC_EV_CONN_PRSHPKT, qc);
3354-
++parsing_stage;
3355-
}
3356-
break;
3357-
}
3329+
frm->crypto.offset_node.key = frm->crypto.offset;
3330+
eb64_insert(&cf_frms_tree, &frm->crypto.offset_node);
3331+
frm = NULL;
33583332
break;
33593333
case QUIC_FT_NEW_TOKEN:
33603334
if (qc_is_listener(qc)) {
33613335
TRACE_ERROR("reject NEW_TOKEN frame emitted by client",
3362-
QUIC_EV_CONN_PRSHPKT, qc);
3336+
QUIC_EV_CONN_PRSHPKT, qc);
33633337

33643338
/* RFC 9000 19.7. NEW_TOKEN Frames
33653339
* Clients MUST NOT send NEW_TOKEN frames. A server MUST treat receipt
@@ -3526,57 +3500,39 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
35263500
if (frm)
35273501
qc_frm_free(&frm);
35283502

3529-
while (!LIST_ISEMPTY(&retry_frms)) {
3530-
if (--iter <= 0) {
3531-
TRACE_ERROR("interrupt parsing due to max iteration reached",
3532-
QUIC_EV_CONN_PRSHPKT, qc);
3533-
goto err;
3534-
}
3535-
else if (parsing_stage <= 1) {
3536-
TRACE_ERROR("interrupt parsing due to buffering blocked on gap size limit",
3537-
QUIC_EV_CONN_PRSHPKT, qc);
3503+
node = eb64_first(&cf_frms_tree);
3504+
while (node) {
3505+
frm = eb64_entry(node, struct quic_frame, crypto.offset_node);
3506+
/* only CRYPTO frames may be reparsed for now */
3507+
BUG_ON(frm->type != QUIC_FT_CRYPTO);
3508+
node = eb64_next(node);
3509+
ret = qc_handle_crypto_frm(qc, &frm->crypto, pkt, qel);
3510+
switch (ret) {
3511+
case QUIC_RX_RET_FRM_FATAL:
35383512
goto err;
3539-
}
35403513

3541-
parsing_stage = 0;
3542-
list_for_each_entry_safe(frm, frm_tmp, &retry_frms, list) {
3543-
/* only CRYPTO frames may be reparsed for now */
3544-
BUG_ON(frm->type != QUIC_FT_CRYPTO);
3545-
ret = qc_handle_crypto_frm(qc, &frm->crypto, pkt, qel);
3546-
switch (ret) {
3547-
case QUIC_RX_RET_FRM_FATAL:
3548-
goto err;
3514+
case QUIC_RX_RET_FRM_AGAIN:
3515+
TRACE_STATE("AGAIN encountered", QUIC_EV_CONN_PRSHPKT, qc);
3516+
goto err;
35493517

3550-
case QUIC_RX_RET_FRM_AGAIN:
3551-
if (parsing_stage == 0) {
3552-
TRACE_STATE("parsing stage set to 1 (AGAIN encountered)", QUIC_EV_CONN_PRSHPKT, qc);
3553-
++parsing_stage;
3554-
}
3555-
break;
3518+
case QUIC_RX_RET_FRM_DONE:
3519+
TRACE_PROTO("frame handled", QUIC_EV_CONN_PRSAFRM, qc, frm);
3520+
break;
35563521

3557-
case QUIC_RX_RET_FRM_DONE:
3558-
TRACE_PROTO("frame handled after a new parsing iteration",
3559-
QUIC_EV_CONN_PRSAFRM, qc, frm);
3560-
if (parsing_stage == 1) {
3561-
TRACE_STATE("parsing stage set to 2 (DONE after AGAIN)", QUIC_EV_CONN_PRSHPKT, qc);
3562-
++parsing_stage;
3563-
}
3564-
__fallthrough;
3565-
case QUIC_RX_RET_FRM_DUP:
3566-
qc_frm_free(&frm);
3567-
break;
3522+
case QUIC_RX_RET_FRM_DUP:
3523+
if (qc_is_listener(qc) && qel == &qc->els[QUIC_TLS_ENC_LEVEL_INITIAL] &&
3524+
!(qc->flags & QUIC_FL_CONN_HANDSHAKE_SPEED_UP)) {
3525+
fast_retrans = 1;
35683526
}
3527+
break;
35693528
}
35703529

3571-
/* Always reset <frm> as it may be dangling after
3572-
* list_for_each_entry_safe() usage. Especially necessary to
3573-
* prevent a crash if loop is interrupted on max iteration.
3574-
*/
3575-
frm = NULL;
3530+
eb64_delete(&frm->crypto.offset_node);
3531+
qc_frm_free(&frm);
35763532
}
35773533

35783534
/* Error should be returned if some frames cannot be parsed. */
3579-
BUG_ON(!LIST_ISEMPTY(&retry_frms));
3535+
BUG_ON(!eb_is_empty(&cf_frms_tree));
35803536

35813537
if (fast_retrans) {
35823538
struct quic_enc_level *iqel = &qc->els[QUIC_TLS_ENC_LEVEL_INITIAL];
@@ -3613,7 +3569,11 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
36133569
err:
36143570
if (frm)
36153571
qc_frm_free(&frm);
3616-
list_for_each_entry_safe(frm, frm_tmp, &retry_frms, list) {
3572+
node = eb64_first(&cf_frms_tree);
3573+
while (node) {
3574+
frm = eb64_entry(node, struct quic_frame, crypto.offset_node);
3575+
node = eb64_next(node);
3576+
eb64_delete(&frm->crypto.offset_node);
36173577
qc_frm_free(&frm);
36183578
}
36193579

0 commit comments

Comments
 (0)