aboutsummaryrefslogtreecommitdiff
path: root/src/lib/protocols
diff options
context:
space:
mode:
authorNardi Ivan <nardi.ivan@gmail.com>2023-06-01 10:57:12 +0200
committerIvan Nardi <12729895+IvanNardi@users.noreply.github.com>2023-06-08 17:07:25 +0200
commit7150b40c63e7393cddd94d40a2a4d0dd70f80cbc (patch)
tree89c72ea09d5c2dd27888195362c87741f6edb526 /src/lib/protocols
parent3e640a13a4decb2068aa82fdbc147b75a063705e (diff)
QUIC: fix dissection of packets forcing VN
Diffstat (limited to 'src/lib/protocols')
-rw-r--r--src/lib/protocols/quic.c113
1 files changed, 105 insertions, 8 deletions
diff --git a/src/lib/protocols/quic.c b/src/lib/protocols/quic.c
index fe5ec1621..562c299ac 100644
--- a/src/lib/protocols/quic.c
+++ b/src/lib/protocols/quic.c
@@ -72,6 +72,10 @@ extern int is_valid_rtp_payload_type(uint8_t type);
#define QUIC_MAX_CID_LENGTH 20
+static int is_version_forcing_vn(uint32_t version)
+{
+ return (version & 0x0F0F0F0F) == 0x0a0a0a0a; /* Forcing Version Negotiation */
+}
static int is_version_gquic(uint32_t version)
{
return ((version & 0xFFFFFF00) == 0x54303500) /* T05X */ ||
@@ -85,7 +89,7 @@ static int is_version_quic(uint32_t version)
return version == V_1 ||
((version & 0xFFFFFF00) == 0xFF000000) /* IETF Drafts*/ ||
((version & 0xFFFFF000) == 0xfaceb000) /* Facebook */ ||
- ((version & 0x0F0F0F0F) == 0x0a0a0a0a) /* Forcing Version Negotiation */ ||
+ is_version_forcing_vn(version) ||
(version == V_2);
}
static int is_version_valid(uint32_t version)
@@ -110,12 +114,13 @@ static uint8_t get_u8_quic_ver(uint32_t version)
/* "Versions that follow the pattern 0x?a?a?a?a are reserved for use in
forcing version negotiation to be exercised".
- It is tricky to return a correct draft version: such number is primarly
- used to select a proper salt (which depends on the version itself), but
- we don't have a real version here! Let's hope that we need to handle
- only latest drafts... */
- if ((version & 0x0F0F0F0F) == 0x0a0a0a0a)
- return 29;
+ We can't return a correct draft version because we don't have a real
+ version here! That means that we can't decode any data and we can dissect
+ only the cleartext header.
+ Let's return v1 (any other numbers should be fine, anyway) to only allow
+ the dissection of the (expected) long header */
+ if(is_version_forcing_vn(version))
+ return 34;
/* QUIC Version 2 */
if (version == V_2)
@@ -1583,7 +1588,7 @@ static int may_be_initial_pkt(struct ndpi_detection_module_struct *ndpi_struct,
if the packet includes a token provided by the server in a NEW_TOKEN
frame on a connection where the server also included the
grease_quic_bit transport parameter." */
- if((*version & 0x0F0F0F0F) == 0x0a0a0a0a &&
+ if(is_version_forcing_vn(*version) &&
!(pub_bit1 == 1 && pub_bit2 == 1)) {
NDPI_LOG_DBG2(ndpi_struct, "Version 0x%x with first byte 0x%x\n", *version, first_byte);
return 0;
@@ -1707,6 +1712,84 @@ static int ndpi_search_quic_extra(struct ndpi_detection_module_struct *ndpi_stru
return 0;
}
+static int is_vn(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow)
+{
+ struct ndpi_packet_struct *packet = &ndpi_struct->packet;
+ u_int32_t version;
+ u_int8_t first_byte;
+ u_int8_t pub_bit1;
+ u_int8_t dest_conn_id_len, source_conn_id_len;
+
+ /* RFC 8999 6 */
+
+ /* First byte + version (4) + 2 CID lengths (set to 0) + at least one supported version */
+ if(packet->payload_packet_len < 11) {
+ return 0;
+ }
+
+ first_byte = packet->payload[0];
+ pub_bit1 = ((first_byte & 0x80) != 0);
+ if(!pub_bit1) {
+ NDPI_LOG_DBG2(ndpi_struct, "Not a long header\n");
+ return 0;
+ }
+
+ version = ntohl(*((u_int32_t *)&packet->payload[1]));
+ if(version != 0) {
+ NDPI_LOG_DBG2(ndpi_struct, "Invalid version 0x%x\n", version);
+ return 0;
+ }
+
+ /* Check that CIDs lengths are valid: QUIC limits the CID length to 20 */
+ dest_conn_id_len = packet->payload[5];
+ if(5 + 1 + dest_conn_id_len >= packet->payload_packet_len) {
+ NDPI_LOG_DBG2(ndpi_struct, "Invalid Length %d\n", packet->payload_packet_len);
+ return 0;
+ }
+ source_conn_id_len = packet->payload[5 + 1 + dest_conn_id_len];
+ if (dest_conn_id_len > QUIC_MAX_CID_LENGTH ||
+ source_conn_id_len > QUIC_MAX_CID_LENGTH) {
+ NDPI_LOG_DBG2(ndpi_struct, "Invalid CIDs length %u %u",
+ dest_conn_id_len, source_conn_id_len);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int ndpi_search_quic_extra_vn(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow)
+{
+ struct ndpi_packet_struct *packet = &ndpi_struct->packet;
+
+ /* We are elaborating a packet following the Forcing VN, i.e. we are expecting:
+ 1) first a VN packet (from the server)
+ 2) then a "standard" Initial from the client */
+ /* TODO: could we unify ndpi_search_quic() and ndpi_search_quic_extra_vn() somehow? */
+
+ NDPI_LOG_DBG(ndpi_struct, "search QUIC extra func VN\n");
+
+ if(packet->payload_packet_len == 0)
+ return 1; /* Keep going */
+
+ if(flow->l4.udp.quic_vn_pair == 0) {
+ if(is_vn(ndpi_struct, flow)) {
+ NDPI_LOG_DBG(ndpi_struct, "Valid VN\n");
+ flow->l4.udp.quic_vn_pair = 1;
+ return 1;
+ } else {
+ NDPI_LOG_DBG(ndpi_struct, "Invalid reply to a Force VN. Stop\n");
+ flow->extra_packets_func = NULL;
+ return 0; /* Stop */
+ }
+ } else {
+ flow->extra_packets_func = NULL;
+ ndpi_search_quic(ndpi_struct, flow);
+ return 0;
+ }
+}
+
static void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow)
{
@@ -1775,6 +1858,20 @@ static void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct,
}
/*
+ * 3a) Forcing VN. There is no payload to analyze yet.
+ * Expecteed flow:
+ * *) C->S: Forcing VN
+ * *) S->C: VN
+ * *) C->S: "Standard" Initial with crypto data
+ */
+ if(is_version_forcing_vn(version)) {
+ NDPI_LOG_DBG(ndpi_struct, "Forcing VN\n");
+ flow->max_extra_packets_to_check = 4; /* TODO */
+ flow->extra_packets_func = ndpi_search_quic_extra_vn;
+ return;
+ }
+
+ /*
* 4) Extract the Payload from Initial Packets
*/
clear_payload = get_clear_payload(ndpi_struct, version, &clear_payload_len);