Skip to content

Commit 933cce6

Browse files
committed
BUG/MINOR: quic: repeat packet parsing to deal with fragmented CRYPTO
A ClientHello may be splitted accross several different CRYPTO frames, then mixed in a single QUIC packet. This is used notably by clients such as chrome to render the first Initial packet opaque to middleboxes. Each packet frame is handled sequentially. Out-of-order CRYPTO frames are buffered in a ncbuf, until gaps are filled and data is transferred to the SSL stack. If CRYPTO frames are heavily splitted with small fragments, buffering may fail as ncbuf does not support small gaps. This causes the whole packet to be rejected and unacknowledged. It could be solved if the client reemits its ClientHello after remixing its CRYPTO frames. This patch is written to improve CRYPTO frame parsing. Each CRYPTO frames which cannot be buffered due to ncbuf limitation are now stored in a temporary list. Packet parsing is completed until all frames have been handled. If temporary list is not empty, reparsing is done on the stored frames. With the newly buffered CRYPTO frames, ncbuf insert operation may this time succeeds if the frame now covers a whole gap. Reparsing will loop until either no progress can be made or it has been done at least 3 times, to prevent CPU utilization. This patch should fix github issue #2776. This should be backported up to 2.6, after a period of observation. Note that it relies on the following refactor patches : MINOR: quic: extend return value of CRYPTO parsing MINOR: quic: use dynamically allocated frame on parsing MINOR: quic: simplify qc_parse_pkt_frms() return path
1 parent 61a63f0 commit 933cce6

File tree

2 files changed

+74
-5
lines changed

2 files changed

+74
-5
lines changed

include/haproxy/quic_rx-t.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ struct quic_rx_packet {
6161
enum quic_rx_ret_frm {
6262
QUIC_RX_RET_FRM_DONE = 0, /* frame handled correctly */
6363
QUIC_RX_RET_FRM_DUP, /* frame ignored as already handled previously */
64+
QUIC_RX_RET_FRM_AGAIN, /* frame cannot be handled temporarily, caller may retry during another parsing round */
6465
QUIC_RX_RET_FRM_FATAL, /* error during frame handling, packet must not be acknowledged */
6566
};
6667

src/quic_rx.c

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -675,12 +675,14 @@ static enum quic_rx_ret_frm qc_handle_crypto_frm(struct quic_conn *qc,
675675
TRACE_ERROR("overlapping data rejected", QUIC_EV_CONN_PRSHPKT, qc);
676676
quic_set_connection_close(qc, quic_err_transport(QC_ERR_PROTOCOL_VIOLATION));
677677
qc_notify_err(qc);
678+
goto err;
678679
}
679680
else if (ncb_ret == NCB_RET_GAP_SIZE) {
680-
TRACE_ERROR("cannot bufferize frame due to gap size limit",
681-
QUIC_EV_CONN_PRSHPKT, qc);
681+
TRACE_DATA("cannot bufferize frame due to gap size limit",
682+
QUIC_EV_CONN_PRSHPKT, qc);
683+
ret = QUIC_RX_RET_FRM_AGAIN;
684+
goto done;
682685
}
683-
goto err;
684686
}
685687

686688
/* Reschedule with TASK_HEAVY if CRYPTO data ready for decoding. */
@@ -773,10 +775,13 @@ static inline unsigned int quic_ack_delay_ms(struct qf_ack *ack_frm,
773775
static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
774776
struct quic_enc_level *qel)
775777
{
776-
struct quic_frame *frm = NULL;
778+
struct list retry_frms = LIST_HEAD_INIT(retry_frms);
779+
struct quic_frame *frm = NULL, *frm_tmp;
777780
const unsigned char *pos, *end;
778781
enum quic_rx_ret_frm ret;
779782
int fast_retrans = 0;
783+
/* parsing may be rerun multiple times, but no more than <iter>. */
784+
int iter = 3, parsing_stage = 0;
780785

781786
TRACE_ENTER(QUIC_EV_CONN_PRSHPKT, qc);
782787
/* Skip the AAD */
@@ -858,16 +863,32 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
858863
switch (ret) {
859864
case QUIC_RX_RET_FRM_FATAL:
860865
goto err;
866+
867+
case QUIC_RX_RET_FRM_AGAIN:
868+
if (parsing_stage == 0) {
869+
TRACE_STATE("parsing stage set to 1 (AGAIN encountered)", QUIC_EV_CONN_PRSHPKT, qc);
870+
++parsing_stage;
871+
}
872+
/* Save frame in temp list to reparse it later. A new instance must be used for next packet frames. */
873+
LIST_APPEND(&retry_frms, &frm->list);
874+
frm = NULL;
875+
break;
876+
861877
case QUIC_RX_RET_FRM_DUP:
862878
if (qc_is_listener(qc) && qel == qc->iel &&
863879
!(qc->flags & QUIC_FL_CONN_HANDSHAKE_SPEED_UP)) {
864880
fast_retrans = 1;
865881
}
866882
break;
883+
867884
case QUIC_RX_RET_FRM_DONE:
868-
/* nothing to do here */
885+
if (parsing_stage == 1) {
886+
TRACE_STATE("parsing stage set to 2 (DONE after AGAIN)", QUIC_EV_CONN_PRSHPKT, qc);
887+
++parsing_stage;
888+
}
869889
break;
870890
}
891+
871892
break;
872893
case QUIC_FT_NEW_TOKEN:
873894
/* TODO */
@@ -1015,6 +1036,49 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
10151036
}
10161037
}
10171038

1039+
while (!LIST_ISEMPTY(&retry_frms)) {
1040+
if (--iter <= 0) {
1041+
TRACE_ERROR("interrupt parsing due to max iteration reached",
1042+
QUIC_EV_CONN_PRSHPKT, qc);
1043+
goto err;
1044+
}
1045+
else if (parsing_stage == 1) {
1046+
TRACE_ERROR("interrupt parsing due to buffering blocked on gap size limit",
1047+
QUIC_EV_CONN_PRSHPKT, qc);
1048+
goto err;
1049+
}
1050+
1051+
parsing_stage = 0;
1052+
list_for_each_entry_safe(frm, frm_tmp, &retry_frms, list) {
1053+
/* only CRYPTO frames may be reparsed for now */
1054+
BUG_ON(frm->type != QUIC_FT_CRYPTO);
1055+
ret = qc_handle_crypto_frm(qc, &frm->crypto, pkt, qel);
1056+
switch (ret) {
1057+
case QUIC_RX_RET_FRM_FATAL:
1058+
goto err;
1059+
1060+
case QUIC_RX_RET_FRM_AGAIN:
1061+
if (parsing_stage == 0) {
1062+
TRACE_STATE("parsing stage set to 1 (AGAIN encountered)", QUIC_EV_CONN_PRSHPKT, qc);
1063+
++parsing_stage;
1064+
}
1065+
break;
1066+
1067+
case QUIC_RX_RET_FRM_DONE:
1068+
TRACE_PROTO("frame handled after a new parsing iteration",
1069+
QUIC_EV_CONN_PRSAFRM, qc, frm);
1070+
if (parsing_stage == 1) {
1071+
TRACE_STATE("parsing stage set to 2 (DONE after AGAIN)", QUIC_EV_CONN_PRSHPKT, qc);
1072+
++parsing_stage;
1073+
}
1074+
__fallthrough;
1075+
case QUIC_RX_RET_FRM_DUP:
1076+
qc_frm_free(qc, &frm);
1077+
break;
1078+
}
1079+
}
1080+
}
1081+
10181082
if (fast_retrans && qc->iel && qc->hel) {
10191083
struct quic_enc_level *iqel = qc->iel;
10201084
struct quic_enc_level *hqel = qc->hel;
@@ -1049,6 +1113,10 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt,
10491113
err:
10501114
if (frm)
10511115
qc_frm_free(qc, &frm);
1116+
list_for_each_entry_safe(frm, frm_tmp, &retry_frms, list) {
1117+
qc_frm_free(qc, &frm);
1118+
}
1119+
10521120
TRACE_DEVEL("leaving on error", QUIC_EV_CONN_PRSHPKT, qc);
10531121
return 0;
10541122
}

0 commit comments

Comments
 (0)