Skip to content

Commit

Permalink
QUIC: add a basic heuristic to detect mid-flows
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanNardi committed Sep 10, 2024
1 parent def86ba commit 9e5d0e0
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 5 deletions.
8 changes: 6 additions & 2 deletions src/include/ndpi_typedefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,7 @@ struct ndpi_flow_udp_struct {
u_int32_t xbox_stage:1;

/* NDPI_PROTOCOL_QUIC */
u_int32_t quic_server_cid_stage:2;
u_int32_t quic_0rtt_found:1;
u_int32_t quic_vn_pair:1;

Expand Down Expand Up @@ -942,6 +943,9 @@ struct ndpi_flow_udp_struct {
u_int8_t *quic_reasm_buf;
u_int8_t *quic_reasm_buf_bitmap;
u_int32_t quic_reasm_buf_last_pos;
#define QUIC_SERVER_CID_HEURISTIC_LENGTH 8
u_int8_t quic_server_cid[QUIC_SERVER_CID_HEURISTIC_LENGTH];
u_int8_t quic_client_last_byte;
/* DCID of the first Initial sent by the client */
u_int8_t quic_orig_dest_conn_id[20]; /* Max length is 20 on all QUIC versions */
u_int8_t quic_orig_dest_conn_id_len;
Expand Down Expand Up @@ -1544,8 +1548,8 @@ struct ndpi_flow_struct {
_Static_assert(sizeof(((struct ndpi_flow_struct *)0)->protos) <= 264,
"Size of the struct member protocols increased to more than 264 bytes, "
"please check if this change is necessary.");
_Static_assert(sizeof(struct ndpi_flow_struct) <= 1128,
"Size of the flow struct increased to more than 1120 bytes, "
_Static_assert(sizeof(struct ndpi_flow_struct) <= 1136,
"Size of the flow struct increased to more than 1136 bytes, "
"please check if this change is necessary.");
#endif
#endif
Expand Down
70 changes: 69 additions & 1 deletion src/lib/protocols/quic.c
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,54 @@ static int may_be_gquic_rej(struct ndpi_detection_module_struct *ndpi_struct)
return 0;
}

static int may_be_sh(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow)
{
struct ndpi_packet_struct *packet = &ndpi_struct->packet;
u_int8_t last_byte;

if((packet->payload[0] & 0x40) == 0)
return 0;
if(packet->udp->dest != ntohs(443)) {
if(packet->udp->source == ntohs(443)) {
return -1; /* Keep looking for packets sent by the client */
}
return 0;
}

/* SH packet sent by the client */

/* QUIC never retransmits packet, but we should also somehow check that
* these 3 packets from the client are really different from each other
* to avoid matching retransmissions on some other protocols.
* To avoid saving too much state, simply check the last byte of each packet
* (the idea is that being QUIC fully encrypted, the bytes are somehow always
* different; a weak assumption, but it allow us to save only 1 byte in
* flow structure and it seems to work)
* TODO: do we need something better?
*/

if(packet->payload_packet_len < 1 + QUIC_SERVER_CID_HEURISTIC_LENGTH)
return 0;
last_byte = packet->payload[packet->payload_packet_len - 1];
if(flow->l4.udp.quic_server_cid_stage > 0) {
if(memcmp(flow->l4.udp.quic_server_cid, &packet->payload[1],
QUIC_SERVER_CID_HEURISTIC_LENGTH) != 0 ||
flow->l4.udp.quic_client_last_byte == last_byte)
return 0;
flow->l4.udp.quic_server_cid_stage++;
if(flow->l4.udp.quic_server_cid_stage == 3) {
/* Found QUIC via 3 SHs by client */
return 1;
}
} else {
memcpy(flow->l4.udp.quic_server_cid, &packet->payload[1], QUIC_SERVER_CID_HEURISTIC_LENGTH);
flow->l4.udp.quic_server_cid_stage = 1;
}
flow->l4.udp.quic_client_last_byte = last_byte;
return -1; /* Keep looking for other packets sent by client */
}

static int may_be_0rtt(struct ndpi_detection_module_struct *ndpi_struct,
uint32_t *version)
{
Expand Down Expand Up @@ -1881,9 +1929,16 @@ 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 try only 2 easy cases:
* CHLO/CH is VERY hard. Let try only some easy cases:
* * out-of-order 0-RTT, i.e 0-RTT packets received before the Initial;
* in that case, keep looking for the Initial
* * if we have only SH pkts, focus on standard case where server
* port is 443 and default length of Server CID is >=8 (as it happens
* with most common broswer and apps). Look for 3 consecutive SH
* pkts send by the client and check their CIDs (note that
* some QUIC implementations have Client CID length set to 0, so
* checking pkts sent by server is useless). Since we don't know the
* real CID length, use the min value 8, i.e. QUIC_SERVER_CID_HEURISTIC_LENGTH
* * with only GQUIC packets from server (usefull with unidirectional
* captures) look for Rejection packet
* Avoid the generic cases and let's see if anyone complains...
Expand All @@ -1910,6 +1965,19 @@ static void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct,
flow->protos.tls_quic.quic_version = 0; /* unknown */
return;
}
ret = may_be_sh(ndpi_struct, flow);
if(ret == 1) {
NDPI_LOG_INFO(ndpi_struct, "SH Quic\n");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_QUIC, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
flow->protos.tls_quic.quic_version = 0; /* unknown */
return;
}
if(ret == -1) {
NDPI_LOG_DBG2(ndpi_struct, "Keep looking for SH by client\n");
if(flow->packet_counter > 10 /* TODO */)
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
return;
}
ret = may_be_gquic_rej(ndpi_struct);
if(ret == 1) {
NDPI_LOG_INFO(ndpi_struct, "GQUIC REJ\n");
Expand Down
Binary file added tests/cfgs/default/pcap/quic_sh.pcap
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
DPI Packets (UDP): 2 (2.00 pkts/flow)
Confidence DPI : 1 (flows)
Num dissector calls: 154 (154.00 diss/flow)
Num dissector calls: 155 (155.00 diss/flow)
LRU cache ookla: 0/0/0 (insert/search/found)
LRU cache bittorrent: 0/3/0 (insert/search/found)
LRU cache stun: 0/0/0 (insert/search/found)
Expand Down
2 changes: 1 addition & 1 deletion tests/cfgs/default/result/openvpn.pcap.out
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
DPI Packets (TCP): 24 (8.00 pkts/flow)
DPI Packets (UDP): 24 (3.43 pkts/flow)
Confidence DPI : 10 (flows)
Num dissector calls: 1754 (175.40 diss/flow)
Num dissector calls: 1755 (175.50 diss/flow)
LRU cache ookla: 0/0/0 (insert/search/found)
LRU cache bittorrent: 0/9/0 (insert/search/found)
LRU cache stun: 0/0/0 (insert/search/found)
Expand Down
29 changes: 29 additions & 0 deletions tests/cfgs/default/result/quic_sh.pcap.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
DPI Packets (UDP): 14 (4.67 pkts/flow)
Confidence DPI : 3 (flows)
Num dissector calls: 530 (176.67 diss/flow)
LRU cache ookla: 0/0/0 (insert/search/found)
LRU cache bittorrent: 0/9/0 (insert/search/found)
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)
LRU cache fpc_dns: 0/3/0 (insert/search/found)
Automa host: 0/0 (search/found)
Automa domain: 0/0 (search/found)
Automa tls cert: 0/0 (search/found)
Automa risk mask: 0/0 (search/found)
Automa common alpns: 0/0 (search/found)
Patricia risk mask: 2/0 (search/found)
Patricia risk mask IPv6: 4/0 (search/found)
Patricia risk: 0/0 (search/found)
Patricia risk IPv6: 2/0 (search/found)
Patricia protocols: 1/1 (search/found)
Patricia protocols IPv6: 2/2 (search/found)

QUIC 38 23111 3

Acceptable 38 23111 3

1 UDP [2001:b07:a3d:c112:91b7:b97e:6e2:fad8]:37542 <-> [2606:4700:7::a29f:9804]:443 [proto: 188/QUIC][IP: 220/Cloudflare][Encrypted][Confidence: DPI][FPC: 220/Cloudflare, Confidence: IP address][DPI packets: 5][cat: Web/5][6 pkts/634 bytes <-> 15 pkts/13073 bytes][Goodput ratio: 41/93][0.11 sec][bytes ratio: -0.907 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 7/7 20/86 9/23][Pkt Len c2s/s2c min/avg/max/stddev: 105/90 106/872 109/1262 1/472][Risk: ** Susp Entropy **][Risk Score: 10][Risk Info: Entropy: 6.456 (Executable?)][PLAIN TEXT (vS17md)][Plen Bins: 4,34,0,4,0,0,0,0,0,4,0,4,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,4,0,0,0,0,0,0,0,0,0,40,0,0,0,0,0,0,0,0,0,0]
2 UDP 192.168.1.245:40408 <-> 13.226.175.53:443 [proto: 188/QUIC][IP: 265/AmazonAWS][Encrypted][Confidence: DPI][FPC: 265/AmazonAWS, Confidence: IP address][DPI packets: 3][cat: Web/5][4 pkts/340 bytes <-> 3 pkts/4482 bytes][Goodput ratio: 50/97][0.00 sec][bytes ratio: -0.859 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 0/0 0/0 0/0][Pkt Len c2s/s2c min/avg/max/stddev: 85/1494 85/1494 85/1494 0/0][Plen Bins: 0,57,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,0,0,0,0,0,42,0,0]
3 UDP [2a00:1450:4002:411::200e]:443 <-> [2001:b07:a3d:c112:91b7:b97e:6e2:fad8]:33144 [proto: 188/QUIC][IP: 126/Google][Encrypted][Confidence: DPI][FPC: 126/Google, Confidence: IP address][DPI packets: 6][cat: Web/5][3 pkts/3876 bytes <-> 7 pkts/706 bytes][Goodput ratio: 95/38][0.03 sec][bytes ratio: 0.692 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 0/0 1/0 0/0][Pkt Len c2s/s2c min/avg/max/stddev: 1292/99 1292/101 1292/104 0/2][Risk: ** Susp Entropy **][Risk Score: 10][Risk Info: Entropy: 7.836 (Encrypted or Random?)][Plen Bins: 0,70,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,30,0,0,0,0,0,0,0,0,0]

0 comments on commit 9e5d0e0

Please sign in to comment.