Skip to content

Commit 9c115d3

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 477a84b commit 9c115d3

File tree

2 files changed

+35
-76
lines changed

2 files changed

+35
-76
lines changed

include/haproxy/quic_frame-t.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ struct qf_stop_sending {
154154
struct qf_crypto {
155155
struct list list;
156156
uint64_t offset;
157+
struct eb64_node offset_node;
157158
uint64_t len;
158159
const struct quic_enc_level *qel;
159160
const unsigned char *data;

src/quic_rx.c

Lines changed: 34 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -820,13 +820,12 @@ static inline unsigned int quic_ack_delay_ms(struct qf_ack *ack_frm,
820820
static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
821821
struct quic_enc_level *qel)
822822
{
823-
struct list retry_frms = LIST_HEAD_INIT(retry_frms);
824-
struct quic_frame *frm = NULL, *frm_tmp;
823+
struct eb_root cf_frms_tree = EB_ROOT;
824+
struct eb64_node *node;
825+
struct quic_frame *frm = NULL;
825826
const unsigned char *pos, *end;
826827
enum quic_rx_ret_frm ret;
827828
int fast_retrans = 0;
828-
/* parsing may be rerun multiple times, but no more than <iter>. */
829-
int iter = 3, parsing_stage = 0;
830829

831830
TRACE_ENTER(QUIC_EV_CONN_PRSHPKT, qc);
832831
/* Skip the AAD */
@@ -904,36 +903,9 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
904903
break;
905904
}
906905
case QUIC_FT_CRYPTO:
907-
ret = qc_handle_crypto_frm(qc, &frm->crypto, pkt, qel);
908-
switch (ret) {
909-
case QUIC_RX_RET_FRM_FATAL:
910-
goto err;
911-
912-
case QUIC_RX_RET_FRM_AGAIN:
913-
if (parsing_stage == 0) {
914-
TRACE_STATE("parsing stage set to 1 (AGAIN encountered)", QUIC_EV_CONN_PRSHPKT, qc);
915-
++parsing_stage;
916-
}
917-
/* Save frame in temp list to reparse it later. A new instance must be used for next packet frames. */
918-
LIST_APPEND(&retry_frms, &frm->list);
919-
frm = NULL;
920-
break;
921-
922-
case QUIC_RX_RET_FRM_DUP:
923-
if (!qc_is_back(qc) && qel == qc->iel &&
924-
!(qc->flags & QUIC_FL_CONN_HANDSHAKE_SPEED_UP)) {
925-
fast_retrans = 1;
926-
}
927-
break;
928-
929-
case QUIC_RX_RET_FRM_DONE:
930-
if (parsing_stage == 1) {
931-
TRACE_STATE("parsing stage set to 2 (DONE after AGAIN)", QUIC_EV_CONN_PRSHPKT, qc);
932-
++parsing_stage;
933-
}
934-
break;
935-
}
936-
906+
frm->crypto.offset_node.key = frm->crypto.offset;
907+
eb64_insert(&cf_frms_tree, &frm->crypto.offset_node);
908+
frm = NULL;
937909
break;
938910
case QUIC_FT_NEW_TOKEN:
939911
if (!qc_is_back(qc)) {
@@ -1121,57 +1093,39 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
11211093
if (frm)
11221094
qc_frm_free(qc, &frm);
11231095

1124-
while (!LIST_ISEMPTY(&retry_frms)) {
1125-
if (--iter <= 0) {
1126-
TRACE_ERROR("interrupt parsing due to max iteration reached",
1127-
QUIC_EV_CONN_PRSHPKT, qc);
1128-
goto err;
1129-
}
1130-
else if (parsing_stage <= 1) {
1131-
TRACE_ERROR("interrupt parsing due to buffering blocked on gap size limit",
1132-
QUIC_EV_CONN_PRSHPKT, qc);
1096+
node = eb64_first(&cf_frms_tree);
1097+
while (node) {
1098+
frm = eb64_entry(node, struct quic_frame, crypto.offset_node);
1099+
/* only CRYPTO frames may be reparsed for now */
1100+
BUG_ON(frm->type != QUIC_FT_CRYPTO);
1101+
node = eb64_next(node);
1102+
ret = qc_handle_crypto_frm(qc, &frm->crypto, pkt, qel);
1103+
switch (ret) {
1104+
case QUIC_RX_RET_FRM_FATAL:
11331105
goto err;
1134-
}
11351106

1136-
parsing_stage = 0;
1137-
list_for_each_entry_safe(frm, frm_tmp, &retry_frms, list) {
1138-
/* only CRYPTO frames may be reparsed for now */
1139-
BUG_ON(frm->type != QUIC_FT_CRYPTO);
1140-
ret = qc_handle_crypto_frm(qc, &frm->crypto, pkt, qel);
1141-
switch (ret) {
1142-
case QUIC_RX_RET_FRM_FATAL:
1143-
goto err;
1107+
case QUIC_RX_RET_FRM_AGAIN:
1108+
TRACE_STATE("AGAIN encountered", QUIC_EV_CONN_PRSHPKT, qc);
1109+
goto err;
11441110

1145-
case QUIC_RX_RET_FRM_AGAIN:
1146-
if (parsing_stage == 0) {
1147-
TRACE_STATE("parsing stage set to 1 (AGAIN encountered)", QUIC_EV_CONN_PRSHPKT, qc);
1148-
++parsing_stage;
1149-
}
1150-
break;
1111+
case QUIC_RX_RET_FRM_DONE:
1112+
TRACE_PROTO("frame handled", QUIC_EV_CONN_PRSAFRM, qc, frm);
1113+
break;
11511114

1152-
case QUIC_RX_RET_FRM_DONE:
1153-
TRACE_PROTO("frame handled after a new parsing iteration",
1154-
QUIC_EV_CONN_PRSAFRM, qc, frm);
1155-
if (parsing_stage == 1) {
1156-
TRACE_STATE("parsing stage set to 2 (DONE after AGAIN)", QUIC_EV_CONN_PRSHPKT, qc);
1157-
++parsing_stage;
1158-
}
1159-
__fallthrough;
1160-
case QUIC_RX_RET_FRM_DUP:
1161-
qc_frm_free(qc, &frm);
1162-
break;
1115+
case QUIC_RX_RET_FRM_DUP:
1116+
if (!qc_is_back(qc) && qel == qc->iel &&
1117+
!(qc->flags & QUIC_FL_CONN_HANDSHAKE_SPEED_UP)) {
1118+
fast_retrans = 1;
11631119
}
1120+
break;
11641121
}
11651122

1166-
/* Always reset <frm> as it may be dangling after
1167-
* list_for_each_entry_safe() usage. Especially necessary to
1168-
* prevent a crash if loop is interrupted on max iteration.
1169-
*/
1170-
frm = NULL;
1123+
eb64_delete(&frm->crypto.offset_node);
1124+
qc_frm_free(qc, &frm);
11711125
}
11721126

11731127
/* Error should be returned if some frames cannot be parsed. */
1174-
BUG_ON(!LIST_ISEMPTY(&retry_frms));
1128+
BUG_ON(!eb_is_empty(&cf_frms_tree));
11751129

11761130
if (fast_retrans && qc->iel && qc->hel) {
11771131
struct quic_enc_level *iqel = qc->iel;
@@ -1207,7 +1161,11 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
12071161
err:
12081162
if (frm)
12091163
qc_frm_free(qc, &frm);
1210-
list_for_each_entry_safe(frm, frm_tmp, &retry_frms, list) {
1164+
node = eb64_first(&cf_frms_tree);
1165+
while (node) {
1166+
frm = eb64_entry(node, struct quic_frame, crypto.offset_node);
1167+
node = eb64_next(node);
1168+
eb64_delete(&frm->crypto.offset_node);
12111169
qc_frm_free(qc, &frm);
12121170
}
12131171

0 commit comments

Comments
 (0)