diff options
author | Nardi Ivan <nardi.ivan@gmail.com> | 2023-06-01 10:57:12 +0200 |
---|---|---|
committer | Ivan Nardi <12729895+IvanNardi@users.noreply.github.com> | 2023-06-08 17:07:25 +0200 |
commit | 7150b40c63e7393cddd94d40a2a4d0dd70f80cbc (patch) | |
tree | 89c72ea09d5c2dd27888195362c87741f6edb526 /src/lib/protocols | |
parent | 3e640a13a4decb2068aa82fdbc147b75a063705e (diff) |
QUIC: fix dissection of packets forcing VN
Diffstat (limited to 'src/lib/protocols')
-rw-r--r-- | src/lib/protocols/quic.c | 113 |
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); |