Skip to content

Commit 994040a

Browse files
authored
[!] fix: randomly skipping packet numbers to align with RFC9000 21.4 (#483)
1 parent cf58412 commit 994040a

File tree

5 files changed

+97
-13
lines changed

5 files changed

+97
-13
lines changed

Diff for: include/xquic/xquic.h

+2
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,8 @@ typedef struct xqc_conn_settings_s {
14881488
uint64_t extended_ack_features;
14891489
uint64_t max_receive_timestamps_per_ack;
14901490
uint64_t receive_timestamps_exponent;
1491+
1492+
uint8_t disable_pn_skipping;
14911493
} xqc_conn_settings_t;
14921494

14931495

Diff for: src/transport/xqc_conn.c

+7-8
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ xqc_conn_settings_t internal_default_conn_settings = {
114114

115115
.extended_ack_features = 0,
116116
.max_receive_timestamps_per_ack = 0,
117-
.receive_timestamps_exponent = 0
117+
.receive_timestamps_exponent = 0,
118+
.disable_pn_skipping = 0
118119
};
119120

120121

@@ -2456,7 +2457,8 @@ xqc_conn_enc_packet(xqc_connection_t *conn,
24562457

24572458
/* generate packet number and update packet length, might do packet number encoding here */
24582459
xqc_pn_ctl_t *pn_ctl = xqc_get_pn_ctl(conn, path);
2459-
packet_out->po_pkt.pkt_num = pn_ctl->ctl_packet_number[packet_out->po_pkt.pkt_pns]++;
2460+
xqc_send_ctl_set_next_pn_for_packet(conn, pn_ctl, packet_out, current_time);
2461+
24602462
xqc_write_packet_number(packet_out->po_ppktno, packet_out->po_pkt.pkt_num, XQC_PKTNO_BITS);
24612463
xqc_long_packet_update_length(packet_out);
24622464
xqc_short_packet_update_key_phase(packet_out, conn->key_update_ctx.cur_out_key_phase);
@@ -2590,7 +2592,6 @@ xqc_send_packet_with_pn(xqc_connection_t *conn, xqc_path_ctx_t *path, xqc_packet
25902592

25912593
/* deliver packet to send control */
25922594
xqc_pn_ctl_t *pn_ctl = xqc_get_pn_ctl(conn, path);
2593-
pn_ctl->ctl_packet_number[packet_out->po_pkt.pkt_pns]++;
25942595

25952596
xqc_conn_log_sent_packet(conn, packet_out, now);
25962597
xqc_send_ctl_on_packet_sent(path->path_send_ctl, pn_ctl, packet_out, now);
@@ -2620,7 +2621,9 @@ xqc_enc_packet_with_pn(xqc_connection_t *conn, xqc_path_ctx_t *path, xqc_packet_
26202621

26212622
/* generate packet number */
26222623
xqc_pn_ctl_t *pn_ctl = xqc_get_pn_ctl(conn, path);
2623-
packet_out->po_pkt.pkt_num = pn_ctl->ctl_packet_number[packet_out->po_pkt.pkt_pns];
2624+
xqc_usec_t current_time = xqc_monotonic_timestamp();
2625+
2626+
xqc_send_ctl_set_next_pn_for_packet(conn, pn_ctl, packet_out, current_time);
26242627
xqc_write_packet_number(packet_out->po_ppktno, packet_out->po_pkt.pkt_num, XQC_PKTNO_BITS);
26252628
xqc_long_packet_update_length(packet_out);
26262629
xqc_short_packet_update_key_phase(packet_out, conn->key_update_ctx.cur_out_key_phase);
@@ -6368,7 +6371,6 @@ xqc_conn_send_path_challenge(xqc_connection_t *conn, xqc_path_ctx_t *path)
63686371
xqc_packet_out_t *packet_out;
63696372
xqc_usec_t now;
63706373
ssize_t sent;
6371-
xqc_pn_ctl_t *pn_ctl;
63726374

63736375

63746376
/* send data */
@@ -6434,9 +6436,6 @@ xqc_conn_send_path_challenge(xqc_connection_t *conn, xqc_path_ctx_t *path)
64346436
xqc_frame_type_2_str(conn->engine, packet_out->po_frame_types), path->path_send_ctl->ctl_bytes_in_flight, now);
64356437
}
64366438

6437-
pn_ctl = xqc_get_pn_ctl(conn, path);
6438-
pn_ctl->ctl_packet_number[packet_out->po_pkt.pkt_pns]++;
6439-
64406439
end:
64416440
xqc_send_queue_remove_send(&packet_out->po_list);
64426441
xqc_send_queue_insert_free(packet_out, &conn->conn_send_queue->sndq_free_packets, conn->conn_send_queue);

Diff for: src/transport/xqc_packet_parser.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,14 @@ xqc_packet_decode_packet_number(xqc_packet_number_t largest_pn, xqc_packet_numbe
138138

139139
candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
140140

141-
if (candidate_pn + pn_hwin <= expected_pn) {
141+
// To fully align with RFC9000
142+
if ((candidate_pn + pn_hwin <= expected_pn)
143+
&& (candidate_pn < ((1ULL << 62) - pn_win)))
144+
{
142145
return candidate_pn + pn_win;
143146
}
144147

145-
if (candidate_pn > expected_pn + pn_hwin && candidate_pn > pn_win) {
148+
if (candidate_pn > expected_pn + pn_hwin && candidate_pn >= pn_win) {
146149
return candidate_pn - pn_win;
147150
}
148151

Diff for: src/transport/xqc_send_ctl.c

+70-3
Original file line numberDiff line numberDiff line change
@@ -778,15 +778,43 @@ xqc_send_ctl_on_packet_sent(xqc_send_ctl_t *send_ctl, xqc_pn_ctl_t *pn_ctl, xqc_
778778
}
779779
}
780780

781+
782+
xqc_int_t
783+
xqc_send_ctl_detect_optimistic_ack_attack(xqc_send_ctl_t *send_ctl,
784+
xqc_pn_ctl_t *pn_ctl, xqc_ack_info_t *const ack_info, xqc_usec_t ack_recv_time)
785+
{
786+
xqc_connection_t *conn = send_ctl->ctl_conn;
787+
xqc_pktno_range_t *range = NULL;
788+
xqc_pkt_num_space_t pns = ack_info->pns;
789+
int i;
790+
791+
if (!conn->conn_settings.disable_pn_skipping
792+
/* only check application data */
793+
&& pns == XQC_PNS_APP_DATA
794+
/* if ack is received after the next skip chance,
795+
it is unlikely to be optimistic ack */
796+
&& ack_recv_time < pn_ctl->ctl_next_skip_chance)
797+
{
798+
for (i = ack_info->n_ranges - 1; i >= 0; i--) {
799+
range = &ack_info->ranges[i];
800+
/* check if skipped packet numbers are acked */
801+
if (xqc_max(range->low, pn_ctl->ctl_skipped_pn_low) <= xqc_min(range->high, pn_ctl->ctl_skipped_pn_high)) {
802+
xqc_log(conn->log, XQC_LOG_ERROR, "|optimistic ack attack detected|ack_range:%ui-%ui|skipped_range:%ui-%ui|", range->low, range->high, pn_ctl->ctl_skipped_pn_low, pn_ctl->ctl_skipped_pn_high);
803+
return -XQC_EPROTO;
804+
}
805+
}
806+
}
807+
808+
return XQC_OK;
809+
}
810+
811+
781812
/**
782813
* OnAckReceived
783814
*/
784815
int
785816
xqc_send_ctl_on_ack_received(xqc_send_ctl_t *send_ctl, xqc_pn_ctl_t *pn_ctl, xqc_send_queue_t *send_queue, xqc_ack_info_t *const ack_info, xqc_usec_t ack_recv_time, xqc_bool_t ack_on_same_path)
786817
{
787-
/* ack info里包含的packet不一定会是send_ctl这条路径发出的 */
788-
/* info里的largest ack 不一定是send_ctl这条路径的largest ack */
789-
790818
xqc_connection_t *conn = send_ctl->ctl_conn;
791819

792820
xqc_packet_out_t *packet_out;
@@ -803,6 +831,11 @@ xqc_send_ctl_on_ack_received(xqc_send_ctl_t *send_ctl, xqc_pn_ctl_t *pn_ctl, xqc
803831
unsigned char need_del_record = 0;
804832
int stream_frame_acked = 0;
805833

834+
if (xqc_send_ctl_detect_optimistic_ack_attack(send_ctl, pn_ctl, ack_info, ack_recv_time) < 0) {
835+
XQC_CONN_ERR(conn, TRA_PROTOCOL_VIOLATION);
836+
return -XQC_EPROTO;
837+
}
838+
806839
xqc_packet_number_t largest_acked_ack = xqc_ack_sent_record_on_ack(&pn_ctl->ack_sent_record[pns], ack_info);
807840
if (largest_acked_ack > pn_ctl->ctl_largest_acked_ack[pns]) {
808841
pn_ctl->ctl_largest_acked_ack[pns] = largest_acked_ack;
@@ -1864,3 +1897,37 @@ uint64_t
18641897
xqc_send_ctl_get_pacing_rate(xqc_send_ctl_t *send_ctl) {
18651898
return xqc_pacing_rate_calc(&send_ctl->ctl_pacing);
18661899
}
1900+
1901+
1902+
void
1903+
xqc_send_ctl_set_next_pn_for_packet(xqc_connection_t *conn, xqc_pn_ctl_t *pn_ctl,
1904+
xqc_packet_out_t *packet_out, xqc_usec_t current_time)
1905+
{
1906+
if (conn->conn_settings.disable_pn_skipping) {
1907+
packet_out->po_pkt.pkt_num = pn_ctl->ctl_packet_number[packet_out->po_pkt.pkt_pns]++;
1908+
1909+
} else {
1910+
packet_out->po_pkt.pkt_num = pn_ctl->ctl_packet_number[packet_out->po_pkt.pkt_pns];
1911+
if (packet_out->po_pkt.pkt_num > 0
1912+
&& packet_out->po_pkt.pkt_pns == XQC_PNS_APP_DATA)
1913+
{
1914+
/* randomly skip some packet numbers to avoid optimistic ack attacks */
1915+
if (current_time > pn_ctl->ctl_next_skip_chance) {
1916+
/* we have 20% chance to skip some packet numbers */
1917+
/* we must skip some packet numbers if pn has been increased up to 2^10 since the last skip */
1918+
if (current_time % 10 < 2
1919+
|| packet_out->po_pkt.pkt_num > (pn_ctl->ctl_skipped_pn_high + 1024))
1920+
{
1921+
/* the number of skipped packet numbers is also random */
1922+
pn_ctl->ctl_skipped_pn_low = packet_out->po_pkt.pkt_num;
1923+
pn_ctl->ctl_skipped_pn_high = pn_ctl->ctl_skipped_pn_low + current_time % 7;
1924+
packet_out->po_pkt.pkt_num = pn_ctl->ctl_skipped_pn_high + 1;
1925+
/* we should keep this range for 3xPTO to detect malicious ACKs */
1926+
pn_ctl->ctl_next_skip_chance = current_time + 3 * xqc_conn_get_max_pto(conn);
1927+
xqc_log(conn->log, XQC_LOG_DEBUG, "|optimistic ack detection|skipped_range:%ui-%ui|next_skip_chance:%ui|current_time:%ui|", pn_ctl->ctl_skipped_pn_low, pn_ctl->ctl_skipped_pn_high, pn_ctl->ctl_next_skip_chance, current_time);
1928+
}
1929+
}
1930+
}
1931+
pn_ctl->ctl_packet_number[packet_out->po_pkt.pkt_pns] = packet_out->po_pkt.pkt_num + 1;
1932+
}
1933+
}

Diff for: src/transport/xqc_send_ctl.h

+13
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ typedef struct xqc_pn_ctl_s {
6565
/* record ack sent */
6666
xqc_ack_sent_record_t ack_sent_record[XQC_PNS_N];
6767

68+
69+
/* fields are used for detecting optimistic ack attacks */
70+
/* we skip pn in [ctl_skipped_pn_low, ctl_skipped_pn_high] */
71+
xqc_packet_number_t ctl_skipped_pn_low;
72+
xqc_packet_number_t ctl_skipped_pn_high;
73+
xqc_usec_t ctl_next_skip_chance;
74+
6875
} xqc_pn_ctl_t;
6976

7077
typedef struct xqc_send_ctl_s {
@@ -273,4 +280,10 @@ xqc_packet_number_t xqc_send_ctl_get_pkt_num_gap(xqc_send_ctl_t *send_ctl, xqc_p
273280
uint64_t xqc_send_ctl_get_est_bw(xqc_send_ctl_t *send_ctl);
274281
uint64_t xqc_send_ctl_get_pacing_rate(xqc_send_ctl_t *send_ctl);
275282

283+
xqc_int_t xqc_send_ctl_detect_optimistic_ack_attack(xqc_send_ctl_t *send_ctl,
284+
xqc_pn_ctl_t *pn_ctl, xqc_ack_info_t *const ack_info, xqc_usec_t ack_recv_time);
285+
286+
void xqc_send_ctl_set_next_pn_for_packet(xqc_connection_t *conn, xqc_pn_ctl_t *pn_ctl,
287+
xqc_packet_out_t *packet_out, xqc_usec_t current_time);
288+
276289
#endif /* _XQC_SEND_CTL_H_INCLUDED_ */

0 commit comments

Comments
 (0)