Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QUIC: add a basic heuristic to detect mid-flows #2550

Merged
merged 2 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/include/ndpi_typedefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,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 @@ -938,6 +939,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 @@ -1543,8 +1547,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
6 changes: 4 additions & 2 deletions src/lib/protocols/rtp.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,11 @@ static void ndpi_rtp_search(struct ndpi_detection_module_struct *ndpi_struct,

if(is_rtp == IS_RTP) {
if(flow->rtp_stage == 2) {
if(flow->l4.udp.line_pkts[0] >= 2 && flow->l4.udp.line_pkts[1] >= 2) {
if(flow->l4_proto == IPPROTO_UDP &&
flow->l4.udp.line_pkts[0] >= 2 && flow->l4.udp.line_pkts[1] >= 2) {
/* It seems that it is a LINE stuff; let its dissector to evaluate */
} else if(flow->l4.udp.epicgames_stage > 0) {
} else if(flow->l4_proto == IPPROTO_UDP &&
flow->l4.udp.epicgames_stage > 0) {
/* It seems that it is a EpicGames stuff; let its dissector to evaluate */
} else if(flow->rtp_seq_set[packet->packet_direction] &&
flow->rtp_seq[packet->packet_direction] == seq) {
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]
Loading