diff options
author | Nardi Ivan <nardi.ivan@gmail.com> | 2022-08-10 18:25:44 +0200 |
---|---|---|
committer | Toni <matzeton@googlemail.com> | 2022-08-24 15:38:30 +0200 |
commit | 8bfb1712d8b69c1faf2d9e23e741659c06f4a7df (patch) | |
tree | e7cc5e1bd04a9c5ce2b3ea164bc2bb4cca780967 | |
parent | 0c8bc9f0555fa19d56bb686a2233772ae408f77b (diff) |
QUIC: add support for 0-RTT packets received before the Initial
RFC9001 4.6.1: "A client that wishes to send 0-RTT packets uses the
early_data extension in the ClientHello message of a subsequent handshake;
see Section 4.2.10 of [TLS13]. It then sends application data in 0-RTT
packets."
That means the client sends before the CH (in the Initial) and then the
0-RTT (in the same UDP datagram or not)".
However, because of packet loss or out-of-order delivery, it might
happens that a 0-RTT packet is received before the Initial (the original
one or a retransmission).
For example, Google and Facebook servers save 0-RTT packets for a small
amount of time in hopes of receiving the corresponding Initial.
Update the QUIC dissector to detect 0-RTT packets and keep looking for
the Initial.
Issue found by @utoni in #1706; the trace example has been taken from that
PR.
-rw-r--r-- | src/include/ndpi_typedefs.h | 3 | ||||
-rw-r--r-- | src/lib/protocols/quic.c | 91 | ||||
-rw-r--r-- | tests/pcap/quic_0RTT.pcap | bin | 2644 -> 8468 bytes | |||
-rw-r--r-- | tests/result/quic_0RTT.pcap.out | 21 |
4 files changed, 101 insertions, 14 deletions
diff --git a/src/include/ndpi_typedefs.h b/src/include/ndpi_typedefs.h index 377ed0e2e..94c22a667 100644 --- a/src/include/ndpi_typedefs.h +++ b/src/include/ndpi_typedefs.h @@ -787,6 +787,9 @@ struct ndpi_flow_udp_struct { /* NDPI_PROTOCOL_XBOX */ u_int32_t xbox_stage:1; + /* NDPI_PROTOCOL_QUIC */ + u_int32_t quic_0rtt_found:1; + /* NDPI_PROTOCOL_SKYPE */ u_int8_t skype_crc[4]; diff --git a/src/lib/protocols/quic.c b/src/lib/protocols/quic.c index 530ba27b2..79fd5a823 100644 --- a/src/lib/protocols/quic.c +++ b/src/lib/protocols/quic.c @@ -1433,6 +1433,67 @@ static void process_chlo(struct ndpi_detection_module_struct *ndpi_struct, } } +static int may_be_0rtt(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) +{ + struct ndpi_packet_struct *packet = &ndpi_struct->packet; + uint32_t version; + u_int8_t first_byte; + u_int8_t pub_bit1, pub_bit2, pub_bit3, pub_bit4; + u_int8_t dest_conn_id_len, source_conn_id_len; + + /* First byte + version + dest_conn_id_len */ + if(packet->payload_packet_len < 5 + 1) { + NDPI_LOG_DBG2(ndpi_struct, "Pkt too short\n"); + return 0; + } + + first_byte = packet->payload[0]; + pub_bit1 = ((first_byte & 0x80) != 0); + pub_bit2 = ((first_byte & 0x40) != 0); + pub_bit3 = ((first_byte & 0x20) != 0); + pub_bit4 = ((first_byte & 0x10) != 0); + + version = ntohl(*((u_int32_t *)&packet->payload[1])); + + /* IETF versions, Long header, fixed bit (ignore QUIC-bit-greased case), 0RTT */ + + if(!(is_version_quic(version) && + pub_bit1 && pub_bit2)) { + NDPI_LOG_DBG2(ndpi_struct, "Invalid header or version\n"); + return 0; + } + if(!is_version_quic_v2(version) && + (pub_bit3 != 0 || pub_bit4 != 1)) { + NDPI_LOG_DBG2(ndpi_struct, "Version 0x%x not 0-RTT Packet\n", version); + return 0; + } + if(is_version_quic_v2(version) && + (pub_bit3 != 1 || pub_bit4 != 0)) { + NDPI_LOG_DBG2(ndpi_struct, "Version 0x%x not 0-RTT Packet\n", version); + return 0; + } + + /* Check that CIDs lengths are valid */ + dest_conn_id_len = packet->payload[5]; + if(packet->payload_packet_len < 5 + 1 + dest_conn_id_len) { + NDPI_LOG_DBG2(ndpi_struct, "Dcid too short\n"); + return 0; + } + source_conn_id_len = packet->payload[5 + 1 + dest_conn_id_len]; + if(packet->payload_packet_len < 5 + 1 + dest_conn_id_len + 1 + source_conn_id_len) { + NDPI_LOG_DBG2(ndpi_struct, "Scid too short\n"); + return 0; + } + if(dest_conn_id_len > QUIC_MAX_CID_LENGTH || + source_conn_id_len > QUIC_MAX_CID_LENGTH) { + NDPI_LOG_DBG2(ndpi_struct, "Version 0x%x invalid CIDs length %u %u\n", + version, dest_conn_id_len, source_conn_id_len); + return 0; + } + + return 1; +} static int may_be_initial_pkt(struct ndpi_detection_module_struct *ndpi_struct, uint32_t *version) @@ -1623,7 +1684,7 @@ static void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct, uint32_t clear_payload_len = 0; const u_int8_t *crypto_data; uint64_t crypto_data_len; - int is_quic; + int is_initial_quic, ret; NDPI_LOG_DBG2(ndpi_struct, "search QUIC\n"); @@ -1635,12 +1696,32 @@ static void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct, * CHLO/ClientHello message and we need (only) it to sub-classify * the flow. * Detecting QUIC sessions where the first captured packet is not a - * CHLO/CH is VERY hard. Let's try avoiding it and let's see if - * anyone complains... + * CHLO/CH is VERY hard. Let try only 1 easy case: + * * out-of-order 0-RTT, i.e 0-RTT packets received before the Initial; + * in that case, keep looking for the Initial + * Avoid the generic cases and let's see if anyone complains... */ - is_quic = may_be_initial_pkt(ndpi_struct, &version); - if(!is_quic) { + is_initial_quic = may_be_initial_pkt(ndpi_struct, &version); + if(!is_initial_quic) { + if(!is_ch_reassembler_pending(flow)) { /* Better safe than sorry */ + ret = may_be_0rtt(ndpi_struct, flow); + if(ret == 1) { + NDPI_LOG_DBG(ndpi_struct, "Found 0-RTT, keep looking for Initial\n"); + flow->l4.udp.quic_0rtt_found = 1; + if(flow->packet_counter >= 3) { + /* We haven't still found an Initial.. give up */ + NDPI_LOG_INFO(ndpi_struct, "QUIC 0RTT\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_QUIC, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI); + } + return; + } else if(flow->l4.udp.quic_0rtt_found == 1) { + /* Unknown packet (probably an Handshake one) after a 0-RTT */ + NDPI_LOG_INFO(ndpi_struct, "QUIC 0RTT (without Initial)\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_QUIC, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI); + return; + } + } NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } diff --git a/tests/pcap/quic_0RTT.pcap b/tests/pcap/quic_0RTT.pcap Binary files differindex 7ade88654..95e7c2b6c 100644 --- a/tests/pcap/quic_0RTT.pcap +++ b/tests/pcap/quic_0RTT.pcap diff --git a/tests/result/quic_0RTT.pcap.out b/tests/result/quic_0RTT.pcap.out index 102ca515e..9496dd97f 100644 --- a/tests/result/quic_0RTT.pcap.out +++ b/tests/result/quic_0RTT.pcap.out @@ -1,8 +1,8 @@ Guessed flow protos: 0 -DPI Packets (UDP): 1 (1.00 pkts/flow) -Confidence DPI : 1 (flows) -Num dissector calls: 64 (64.00 diss/flow) +DPI Packets (UDP): 4 (2.00 pkts/flow) +Confidence DPI : 2 (flows) +Num dissector calls: 186 (93.00 diss/flow) LRU cache ookla: 0/0/0 (insert/search/found) LRU cache bittorrent: 0/0/0 (insert/search/found) LRU cache zoom: 0/0/0 (insert/search/found) @@ -10,20 +10,23 @@ LRU cache stun: 0/0/0 (insert/search/found) LRU cache tls_cert: 0/0/0 (insert/search/found) LRU cache mining: 0/0/0 (insert/search/found) LRU cache msteams: 0/0/0 (insert/search/found) -Automa host: 2/0 (search/found) -Automa domain: 1/0 (search/found) +Automa host: 3/1 (search/found) +Automa domain: 2/0 (search/found) Automa tls cert: 0/0 (search/found) Automa risk mask: 1/0 (search/found) Automa common alpns: 0/0 (search/found) -Patricia risk mask: 0/0 (search/found) +Patricia risk mask: 2/0 (search/found) Patricia risk: 0/0 (search/found) -Patricia protocols: 0/0 (search/found) +Patricia protocols: 4/3 (search/found) +Google 15 5178 1 QUIC 2 2588 1 JA3 Host Stats: IP Address # JA3C - 1 ::1 1 + 1 192.168.2.100 1 + 2 ::1 1 - 1 UDP [::1]:60459 <-> [::1]:4443 [proto: 188/QUIC][Encrypted][Confidence: DPI][cat: Web/5][1 pkts/1294 bytes <-> 1 pkts/1294 bytes][Goodput ratio: 95/95][0.00 sec][Hostname/SNI: abcd][ALPN: h3-32][TLS Supported Versions: TLSv1.3;TLSv1.3 (draft);TLSv1.3 (draft);TLSv1.3 (draft)][Risk: ** Known Proto on Non Std Port **][Risk Score: 50][Risk Info: No server to client traffic][TLSv1.3][JA3C: a7b629a5bd67bfc25e2c78b3daa4c12f][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0] + 1 UDP 192.168.2.100:51972 <-> 142.250.181.227:443 [proto: 188.126/QUIC.Google][Encrypted][Confidence: DPI][cat: Web/5][7 pkts/2168 bytes <-> 8 pkts/3010 bytes][Goodput ratio: 86/89][0.23 sec][Hostname/SNI: ssl.gstatic.com][ALPN: h3][TLS Supported Versions: TLSv1.3][bytes ratio: -0.163 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 36/10 121/30 45/14][Pkt Len c2s/s2c min/avg/max/stddev: 75/67 310/376 1292/1292 416/426][TLSv1.3][JA3C: 06b6b2a2cba0b7deeaaa6a3d8374d627][Plen Bins: 26,20,20,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0] + 2 UDP [::1]:60459 <-> [::1]:4443 [proto: 188/QUIC][Encrypted][Confidence: DPI][cat: Web/5][1 pkts/1294 bytes <-> 1 pkts/1294 bytes][Goodput ratio: 95/95][0.00 sec][Hostname/SNI: abcd][ALPN: h3-32][TLS Supported Versions: TLSv1.3;TLSv1.3 (draft);TLSv1.3 (draft);TLSv1.3 (draft)][Risk: ** Known Proto on Non Std Port **][Risk Score: 50][Risk Info: No server to client traffic][TLSv1.3][JA3C: a7b629a5bd67bfc25e2c78b3daa4c12f][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0] |