/*
* quic.c
*
* Copyright (C) 2012-20 - ntop.org
*
* This module is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This module is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License.
* If not, see .
*
* Based on code of:
* Andrea Buscarinu -
* Michele Campus -
*
*/
#if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__
#include
#endif
#include "ndpi_protocol_ids.h"
#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_QUIC
#include "ndpi_api.h"
/* This dissector handles GQUIC and IETF-QUIC both.
Main references:
* https://groups.google.com/a/chromium.org/g/proto-quic/c/wVHBir-uRU0?pli=1
* https://groups.google.com/a/chromium.org/g/proto-quic/c/OAVgFqw2fko/m/jCbjP0AVAAAJ
* https://groups.google.com/a/chromium.org/g/proto-quic/c/OAVgFqw2fko/m/-NYxlh88AgAJ
* https://docs.google.com/document/d/1FcpCJGTDEMblAs-Bm5TYuqhHyUqeWpqrItw2vkMFsdY/edit
* https://tools.ietf.org/html/draft-ietf-quic-tls-29
* https://tools.ietf.org/html/draft-ietf-quic-transport-29
*/
/* Versions */
#define V_Q024 0x51303234
#define V_Q025 0x51303235
#define V_Q030 0x51303330
#define V_Q033 0x51303333
#define V_Q034 0x51303334
#define V_Q035 0x51303335
#define V_Q037 0x51303337
#define V_Q039 0x51303339
#define V_Q043 0x51303433
#define V_Q046 0x51303436
#define V_Q050 0x51303530
#define V_MVFST_22 0xfaceb001
#define V_MVFST_27 0xfaceb002
#define QUIC_MAX_CID_LENGTH 20
static int is_version_gquic(uint32_t version)
{
return ((version & 0xFFFFFF00) == 0x51303500) /* Q05X */ ||
((version & 0xFFFFFF00) == 0x51303400) /* Q04X */ ||
((version & 0xFFFFFF00) == 0x51303300) /* Q03X */ ||
((version & 0xFFFFFF00) == 0x51303200) /* Q02X */;
}
static int is_version_quic(uint32_t version)
{
return ((version & 0xFFFFFF00) == 0xFF000000) /* IETF */ ||
((version & 0xFFFFF000) == 0xfaceb000) /* Facebook */;
}
static int is_version_valid(uint32_t version)
{
return is_version_gquic(version) || is_version_quic(version);
}
static uint8_t get_u8_quic_ver(uint32_t version)
{
if((version >> 8) == 0xff0000)
return (uint8_t)version;
return 0;
}
static int is_quic_ver_greater_than(uint32_t version, uint8_t min_version)
{
return get_u8_quic_ver(version) >= min_version;
}
static uint8_t get_u8_gquic_ver(uint32_t version)
{
if(is_version_gquic(version)) {
version = ntohl(((uint16_t)version) << 16);
return atoi((char *)&version);
}
return 0;
}
static int is_gquic_ver_less_than(uint32_t version, uint8_t max_version)
{
uint8_t u8_ver = get_u8_gquic_ver(version);
return u8_ver && u8_ver <= max_version;
}
static int is_version_supported(uint32_t version)
{
return (version == V_Q024 ||
version == V_Q025 ||
version == V_Q030 ||
version == V_Q033 ||
version == V_Q034 ||
version == V_Q035 ||
version == V_Q037 ||
version == V_Q039 ||
version == V_Q043 ||
version == V_Q046 ||
version == V_Q050 ||
version == V_MVFST_22 ||
version == V_MVFST_27 ||
is_quic_ver_greater_than(version, 23));
}
static int quic_len(const uint8_t *buf, uint64_t *value)
{
*value = buf[0];
switch((*value) >> 6) {
case 0:
(*value) &= 0x3F;
return 1;
case 1:
*value = ntohs(*(uint16_t *)buf) & 0x3FFF;
return 2;
case 2:
*value = ntohl(*(uint32_t *)buf) & 0x3FFFFFFF;
return 4;
case 3:
*value = ndpi_ntohll(*(uint64_t *)buf) & 0x3FFFFFFFFFFFFFFF;
return 8;
default: /* No Possible */
return 0;
}
}
static uint16_t gquic_get_u16(const uint8_t *buf, uint32_t version)
{
if(version >= V_Q039)
return ntohs(*(uint16_t *)buf);
return (*(uint16_t *)buf);
}
static const uint8_t *get_crypto_data(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow,
uint32_t version,
u_int8_t *clear_payload, uint32_t clear_payload_len,
uint64_t *crypto_data_len)
{
const u_int8_t *crypto_data;
uint32_t counter;
uint8_t first_nonzero_payload_byte, offset_len;
uint64_t unused;
counter = 0;
while(clear_payload[counter] == 0 && counter < clear_payload_len)
counter += 1;
if(counter >= clear_payload_len)
return NULL;
first_nonzero_payload_byte = clear_payload[counter];
NDPI_LOG_DBG2(ndpi_struct, "first_nonzero_payload_byte 0x%x\n", first_nonzero_payload_byte);
if(is_gquic_ver_less_than(version, 46)) {
if(first_nonzero_payload_byte == 0x40 ||
first_nonzero_payload_byte == 0x60) {
/* Probably an ACK/NACK frame: this CHLO is not the first one but try
decoding it nonetheless */
counter += (first_nonzero_payload_byte == 0x40) ? 6 : 9;
if(counter >= clear_payload_len)
return NULL;
first_nonzero_payload_byte = clear_payload[counter];
}
if((first_nonzero_payload_byte != 0xA0) &&
(first_nonzero_payload_byte != 0xA4)) {
NDPI_LOG_DBG(ndpi_struct, "Unexpected frame 0x%x version 0x%x\n",\
first_nonzero_payload_byte, version);
return NULL;
}
offset_len = (first_nonzero_payload_byte & 0x1C) >> 2;
if(offset_len > 0)
offset_len += 1;
if(counter + 2 + offset_len + 2 /*gquic_get_u16 reads 2 bytes */ > clear_payload_len)
return NULL;
if(clear_payload[counter + 1] != 0x01) {
NDPI_LOG_ERR(ndpi_struct, "Unexpected stream ID version 0x%x\n", version);
return NULL;
}
counter += 2 + offset_len;
*crypto_data_len = gquic_get_u16(&clear_payload[counter], version);
counter += 2;
crypto_data = &clear_payload[counter];
} else if(version == V_Q050) {
if(first_nonzero_payload_byte == 0x40 ||
first_nonzero_payload_byte == 0x60) {
/* Probably an ACK/NACK frame: this CHLO is not the first one but try
decoding it nonetheless */
counter += (first_nonzero_payload_byte == 0x40) ? 6 : 9;
if(counter >= clear_payload_len)
return NULL;
first_nonzero_payload_byte = clear_payload[counter];
}
if(first_nonzero_payload_byte != 0x08) {
NDPI_LOG_DBG(ndpi_struct, "Unexpected frame 0x%x\n", first_nonzero_payload_byte);
return NULL;
}
counter += 1;
if(counter + 8 + 8 >= clear_payload_len) /* quic_len reads 8 bytes, at most */
return NULL;
counter += quic_len(&clear_payload[counter], &unused);
counter += quic_len(&clear_payload[counter], crypto_data_len);
crypto_data = &clear_payload[counter];
} else { /* All other versions */
if(first_nonzero_payload_byte != 0x06) {
if(first_nonzero_payload_byte != 0x02 &&
first_nonzero_payload_byte != 0x1C) {
NDPI_LOG_ERR(ndpi_struct, "Unexpected frame 0x%x\n", first_nonzero_payload_byte);
} else {
NDPI_LOG_DBG(ndpi_struct, "Unexpected ACK/CC frame\n");
}
return NULL;
}
if(counter + 2 + 8 >= clear_payload_len) /* quic_len reads 8 bytes, at most */
return NULL;
if(clear_payload[counter + 1] != 0x00) {
NDPI_LOG_ERR(ndpi_struct, "Unexpected crypto stream offset 0x%x\n",
clear_payload[counter + 1]);
return NULL;
}
counter += 2;
counter += quic_len(&clear_payload[counter], crypto_data_len);
crypto_data = &clear_payload[counter];
}
if(*crypto_data_len + counter > clear_payload_len) {
NDPI_LOG_ERR(ndpi_struct, "Invalid length %lu + %d > %d version 0x%x\n",
*crypto_data_len, counter, clear_payload_len, version);
return NULL;
}
return crypto_data;
}
static uint8_t *get_clear_payload(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow,
uint32_t version, uint32_t *clear_payload_len)
{
struct ndpi_packet_struct *packet = &flow->packet;
u_int8_t *clear_payload;
u_int8_t dest_conn_id_len, source_conn_id_len;
if(is_gquic_ver_less_than(version, 43)) {
clear_payload = (uint8_t *)&packet->payload[26];
*clear_payload_len = packet->payload_packet_len - 26;
/* Skip Private-flag field for version for < Q34 */
if(is_gquic_ver_less_than(version, 33)) {
clear_payload += 1;
(*clear_payload_len) -= 1;
}
} else if(version == V_Q046) {
if(packet->payload[5] != 0x50) {
NDPI_LOG_DBG(ndpi_struct, "Q46 invalid conn id len 0x%x\n",
packet->payload[5]);
return NULL;
}
clear_payload = (uint8_t *)&packet->payload[30];
*clear_payload_len = packet->payload_packet_len - 30;
} else {
dest_conn_id_len = packet->payload[5];
if(dest_conn_id_len == 0 ||
dest_conn_id_len > QUIC_MAX_CID_LENGTH) {
NDPI_LOG_DBG(ndpi_struct, "Packet 0x%x with dest_conn_id_len %d\n",
version, dest_conn_id_len);
return NULL;
}
source_conn_id_len = packet->payload[6 + dest_conn_id_len];
if(source_conn_id_len > QUIC_MAX_CID_LENGTH) {
NDPI_LOG_DBG(ndpi_struct, "Packet 0x%x with source_conn_id_len %d\n",
version, source_conn_id_len);
return NULL;
}
/* TODO */
clear_payload = NULL;
}
return clear_payload;
}
static void process_chlo(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow,
const u_int8_t *crypto_data, uint32_t crypto_data_len)
{
const uint8_t *tag;
uint32_t i;
uint16_t num_tags;
uint32_t prev_offset;
uint32_t tag_offset_start, offset, len, sni_len;
ndpi_protocol_match_result ret_match;
if(crypto_data_len < 6)
return;
if(memcmp(crypto_data, "CHLO", 4) != 0) {
NDPI_LOG_ERR(ndpi_struct, "Unexpected handshake message");
return;
}
num_tags = (*(uint16_t *)&crypto_data[4]);
tag_offset_start = 8 + 8 * num_tags;
prev_offset = 0;
for(i = 0; i < num_tags; i++) {
if(8 + 8 * i + 8 >= crypto_data_len)
break;
tag = &crypto_data[8 + 8 * i];
offset = *((u_int32_t *)&crypto_data[8 + 8 * i + 4]);
if(prev_offset > offset)
break;
len = offset - prev_offset;
if(tag_offset_start + prev_offset + len > crypto_data_len)
break;
#if 0
printf("crypto_data_len %u prev_offset %u offset %u len %d\n",
crypto_data_len, prev_offset, offset, len);
#endif
if((memcmp(tag, "SNI\0", 4) == 0) &&
(tag_offset_start + prev_offset + len < crypto_data_len)) {
sni_len = MIN(len, sizeof(flow->host_server_name) - 1);
memcpy(flow->host_server_name,
&crypto_data[tag_offset_start + prev_offset], sni_len);
NDPI_LOG_DBG2(ndpi_struct, "SNI: [%s]\n", flow->host_server_name);
ndpi_match_host_subprotocol(ndpi_struct, flow,
(char *)flow->host_server_name,
strlen((const char*)flow->host_server_name),
&ret_match, NDPI_PROTOCOL_QUIC);
return;
}
prev_offset = offset;
}
if(i != num_tags)
NDPI_LOG_DBG(ndpi_struct, "Something went wrong in tags iteration\n");
}
static int may_be_initial_pkt(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow,
uint32_t *version)
{
struct ndpi_packet_struct *packet = &flow->packet;
u_int8_t first_byte;
u_int8_t pub_bit1, pub_bit2, pub_bit3, pub_bit4, pub_bit5, pub_bit7, pub_bit8;
/* According to draft-ietf-quic-transport-29: "Clients MUST ensure that UDP
datagrams containing Initial packets have UDP payloads of at least 1200
bytes". Similar limit exists for previous versions */
if(packet->payload_packet_len < 1200) {
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);
pub_bit5 = ((first_byte & 0x08) != 0);
pub_bit7 = ((first_byte & 0x02) != 0);
pub_bit8 = ((first_byte & 0x01) != 0);
*version = 0;
if(pub_bit1) {
*version = ntohl(*((u_int32_t *)&packet->payload[1]));
} else if(pub_bit5 && !pub_bit2) {
if(!pub_bit8) {
NDPI_LOG_DBG2(ndpi_struct, "Packet without version\n")
} else {
*version = ntohl(*((u_int32_t *)&packet->payload[9]));
}
}
if(!is_version_valid(*version)) {
NDPI_LOG_DBG2(ndpi_struct, "Invalid version 0x%x\n", *version);
return 0;
}
if(is_gquic_ver_less_than(*version, 43) &&
(!pub_bit5 || pub_bit3 != 0 || pub_bit4 != 0)) {
NDPI_LOG_ERR(ndpi_struct, "Version 0x%x invalid flags 0x%x\n",
*version, first_byte);
return 0;
}
if((*version == V_Q046) &&
(pub_bit7 != 1 || pub_bit8 != 1)) {
NDPI_LOG_ERR(ndpi_struct, "Q46 invalid flag 0x%x\n", first_byte);
return 0;
}
if((is_version_quic(*version) || (*version == V_Q046) || (*version == V_Q050)) &&
(pub_bit3 != 0 || pub_bit4 != 0)) {
NDPI_LOG_DBG2(ndpi_struct, "Version 0x%x not Initial Packet\n", *version);
return 0;
}
/* TODO: add some other checks to avoid false positives */
return 1;
}
/* ***************************************************************** */
void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow)
{
u_int32_t version;
u_int8_t *clear_payload;
uint32_t clear_payload_len;
const u_int8_t *crypto_data;
uint64_t crypto_data_len;
int is_quic;
NDPI_LOG_DBG2(ndpi_struct, "search QUIC\n");
/* Buffers: packet->payload ---> clear_payload ---> crypto_data */
/*
* 1) (Very) basic heuristic to check if it is a QUIC packet.
* The first packet of each QUIC session should contain a valid
* 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...
*/
is_quic = may_be_initial_pkt(ndpi_struct, flow, &version);
if(!is_quic) {
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
return;
}
/*
* 2) Ok, this packet seems to be QUIC
*/
NDPI_LOG_INFO(ndpi_struct, "found QUIC\n");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_QUIC, NDPI_PROTOCOL_UNKNOWN);
/*
* 3) Skip not supported versions
*/
if(!is_version_supported(version)) {
NDPI_LOG_ERR(ndpi_struct, "Unsupported version 0x%x\n", version)
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
return;
}
/*
* 4) Extract the Payload from Initial Packets
*/
clear_payload = get_clear_payload(ndpi_struct, flow, version, &clear_payload_len);
if(!clear_payload) {
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
return;
}
/*
* 5) Extract Crypto Data from the Payload
*/
crypto_data = get_crypto_data(ndpi_struct, flow, version,
clear_payload, clear_payload_len,
&crypto_data_len);
if(!crypto_data) {
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
return;
}
/*
* 6) Process ClientHello/CHLO from the Crypto Data
*/
if(is_version_gquic(version)) {
process_chlo(ndpi_struct, flow, crypto_data, crypto_data_len);
}
}
/* ***************************************************************** */
void init_quic_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id,
NDPI_PROTOCOL_BITMASK *detection_bitmask)
{
ndpi_set_bitmask_protocol_detection("QUIC", ndpi_struct, detection_bitmask, *id,
NDPI_PROTOCOL_QUIC, ndpi_search_quic,
NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD,
SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK);
*id += 1;
}