summaryrefslogtreecommitdiff
path: root/decoders/quic/quic_decoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'decoders/quic/quic_decoder.c')
-rw-r--r--decoders/quic/quic_decoder.c758
1 files changed, 758 insertions, 0 deletions
diff --git a/decoders/quic/quic_decoder.c b/decoders/quic/quic_decoder.c
new file mode 100644
index 0000000..2e3c765
--- /dev/null
+++ b/decoders/quic/quic_decoder.c
@@ -0,0 +1,758 @@
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include "quic_util.h"
+#include "quic_decoder.h"
+
+struct quic_client_hello_msg_hdr
+{
+ uint8_t handshake_type;
+ uint8_t client_hello_len[3];
+ uint16_t tls_version;
+ uint8_t random[32];
+};
+
+static int gquic_pkn_bit2length(unsigned char bit_value)
+{
+ switch (bit_value)
+ {
+ case 0x30:
+ return 6;
+ case 0x20:
+ return 4;
+ case 0x10:
+ return 2;
+ default:
+ return 1;
+ }
+ return 1;
+}
+
+static int copy_extension_tag(const unsigned char *tag_start_pos, int tag_len, char **out, size_t *out_len)
+{
+ if (tag_start_pos != NULL && tag_len > 0)
+ {
+ FREE(*out);
+ *out = CALLOC(1, tag_len + 1);
+ memcpy((void *)*out, tag_start_pos, tag_len);
+ *out_len = tag_len;
+ return tag_len;
+ }
+ return 0;
+}
+
+// Source: https://wise2.ipac.caltech.edu/staff/slw/docs/html/varint_8h_source.html
+static int msb2_varint_decode(const unsigned char *buf, long *out)
+{
+ unsigned long val = buf[0] & 0x3f;
+ unsigned int nfollow = 1 << (buf[0] >> 6);
+ switch (nfollow - 1)
+ {
+ case 7:
+ val = (val << 8) | buf[nfollow - 7]; /*fail through*/
+ case 6:
+ val = (val << 8) | buf[nfollow - 6]; /*fail through*/
+ case 5:
+ val = (val << 8) | buf[nfollow - 5]; /*fail through*/
+ case 4:
+ val = (val << 8) | buf[nfollow - 4]; /*fail through*/
+ case 3:
+ val = (val << 8) | buf[nfollow - 3]; /*fail through*/
+ case 2:
+ val = (val << 8) | buf[nfollow - 2]; /*fail through*/
+ case 1:
+ val = (val << 8) | buf[nfollow - 1];
+ case 0:
+ break;
+ }
+ *out = val;
+ return nfollow;
+}
+
+/*
+
+//https://docs.google.com/document/d/1FcpCJGTDEMblAs-Bm5TYuqhHyUqeWpqrItw2vkMFsdY/edit
+
+Long Header (used for packets that are sent prior to the completion of version negotiation and establishment of 1-RTT keys):
++-+-+-+-+-+-+-+-+
+|1|1|T|T|R|R|P|P|
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Version (32) |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|DCIL(4)|SCIL(4)|
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Destination Connection ID (0/64) ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Source Connection ID (0/64) ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+| Packet Number (8/16/24/32) ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+*/
+
+static int join_client_hello_frames(const unsigned char *payload, int payload_len, unsigned char *join_payload, int *join_payload_len)
+{
+ int joined_length = 0;
+ int payload_offset = 0;
+ long frame_type = 0, frame_offset = 0, frame_length = 0;
+
+ for (; payload_offset < payload_len;)
+ {
+ frame_type = payload[payload_offset++]; // Frame Type=1
+ if (frame_type == IQUIC_FRAME_PADDING || frame_type == IQUIC_FRAME_PING)
+ {
+ continue;
+ }
+
+ payload_offset += msb2_varint_decode((const unsigned char *)(payload + payload_offset), &frame_offset);
+ payload_offset += msb2_varint_decode((const unsigned char *)(payload + payload_offset), &frame_length);
+
+ if (joined_length + frame_length > (*join_payload_len) || frame_offset < 0 || frame_offset + frame_length > payload_len)
+ {
+ return -1;
+ }
+ memcpy(join_payload + frame_offset, payload + payload_offset, frame_length);
+ joined_length += frame_length;
+ payload_offset += frame_length;
+ }
+ (*join_payload_len) = joined_length;
+ return joined_length;
+}
+
+static enum QUIC_VERSION_T parse_gquic_version_44to48_header(enum QUIC_VERSION_T quic_version, const char *payload, int payload_len UNUSED, int *payload_offset)
+{
+ unsigned pkn_length = 0;
+ unsigned char client_CID_len = 0;
+ unsigned char server_CID_len = 0;
+ unsigned char public_flags = payload[*payload_offset];
+ *payload_offset += 1; // skip public flags
+
+ *payload_offset += sizeof(int); // skip version
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ if ((payload[*payload_offset]) & GQUIC_VERSION_44to48_CID_MASK)
+ {
+ client_CID_len = (payload[*payload_offset] & GQUIC_VERSION_44to48_CID_MASK) + 3;
+ }
+ if ((payload[*payload_offset] >> 4) & GQUIC_VERSION_44to48_CID_MASK)
+ {
+ server_CID_len = ((payload[*payload_offset] >> 4) & GQUIC_VERSION_44to48_CID_MASK) + 3;
+ }
+
+ *payload_offset += 1; // both connection_id length
+ *payload_offset += server_CID_len; // Destination connection id length
+ *payload_offset += client_CID_len; // source connection id length
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ pkn_length = (public_flags & GQUIC_VERSION_44to48_PKN_LEN_MASK) + 1;
+ *payload_offset += pkn_length;
+
+ *payload_offset += 12; // message authentication hash
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ return quic_version;
+}
+
+int parse_special_frame_stream(struct quic_info *quic_info, const unsigned char *payload, int payload_len)
+{
+ int tag_num = 0;
+ int payload_offset = 0;
+ unsigned int message_tag;
+ int pass_tag_num = 0;
+ int ext_tag_type = 0;
+ int tag_value_start_offset = 0;
+ int one_tag_len = 0;
+ int parse_result = PARSE_RESULT_VERSION;
+ int one_tag_offset_end = 0, pre_one_tag_offset_end = 0;
+
+ if (payload_len - payload_offset <= 8)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ switch (quic_info->version)
+ {
+ case GQUIC_VERSION_Q041:
+ payload_offset += 1; // unknown
+ case GQUIC_VERSION_Q044:
+ message_tag = (unsigned int)ntohl(*(unsigned int *)(payload + payload_offset));
+ payload_offset += 4;
+
+ tag_num = *(int *)(payload + payload_offset);
+ payload_offset += 4; // tag_num
+ break;
+ default:
+ message_tag = (unsigned int)ntohl(*(unsigned int *)(payload + payload_offset));
+ payload_offset += 4;
+
+ tag_num = *(unsigned short *)(payload + payload_offset);
+ payload_offset += 2; // tag_num
+ payload_offset += 2; // padding
+ break;
+ }
+
+ if (message_tag != CHLO || tag_num > 64 || tag_num <= 0)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ tag_value_start_offset = payload_offset + tag_num * 4 * 2; // skip length of type and offset, type(offset)=szieof(int)
+
+ while (tag_num > pass_tag_num)
+ {
+ ext_tag_type = ntohl(*(unsigned int *)(payload + payload_offset));
+ payload_offset += sizeof(int);
+
+ one_tag_offset_end = *(unsigned int *)(payload + payload_offset);
+ payload_offset += sizeof(int);
+
+ one_tag_len = one_tag_offset_end - pre_one_tag_offset_end;
+ if (one_tag_len <= 0 || (one_tag_offset_end >= payload_len) || (one_tag_len + tag_value_start_offset) > payload_len)
+ {
+ break;
+ }
+
+ switch (ext_tag_type)
+ {
+ case TAG_UAID:
+ copy_extension_tag(payload + tag_value_start_offset, one_tag_len, &quic_info->chlo.user_agent, &quic_info->chlo.user_agent_len);
+ parse_result = PARSE_RESULT_CLIENT_HELLO;
+ break;
+ case TAG_SNI:
+ copy_extension_tag(payload + tag_value_start_offset, one_tag_len, &quic_info->chlo.sni, &quic_info->chlo.sni_len);
+ parse_result = PARSE_RESULT_CLIENT_HELLO;
+ break;
+ default:
+ break;
+ }
+
+ pass_tag_num++;
+ tag_value_start_offset += one_tag_len;
+ // total_tag_len += one_tag_len;
+ pre_one_tag_offset_end = one_tag_offset_end;
+ }
+ return parse_result;
+}
+
+int parse_quic_transport_parameter(struct quic_info *quic_info, const unsigned char *quic_para, int quic_para_len)
+{
+ int one_para_length = 0;
+ int para_offset = 0;
+ long one_para_type = 0;
+
+ while (quic_para_len > para_offset)
+ {
+ para_offset += msb2_varint_decode((const unsigned char *)(quic_para + para_offset), &one_para_type);
+ switch (one_para_type)
+ {
+ case EXT_QUIC_PARAM_USER_AGENT: // 2021-10-20 deprecated
+ one_para_length = quic_para[para_offset++]; // length=1
+ if (one_para_length + para_offset > quic_para_len)
+ {
+ return 0;
+ }
+ para_offset += copy_extension_tag(quic_para + para_offset, one_para_length, &quic_info->chlo.user_agent, &quic_info->chlo.user_agent_len);
+ return 1;
+ default:
+ one_para_length = (int)(quic_para[para_offset++]); // length=1
+ if (one_para_length < 0 || one_para_length > quic_para_len)
+ {
+ break;
+ }
+ para_offset += one_para_length;
+ break;
+ }
+ }
+ return 0;
+}
+
+int parse_extension_server_name(struct quic_info *quic_info, const unsigned char *ext_server_name, int ext_server_name_length)
+{
+ unsigned short sni_type = 0;
+ unsigned short sni_length = 0;
+ unsigned short extension_offset = 0;
+ unsigned short server_name_list_len = 0;
+
+ server_name_list_len = ntohs(*(unsigned short *)(ext_server_name + extension_offset)); // Server Name List length
+ if (server_name_list_len == 0 || server_name_list_len > ext_server_name_length)
+ {
+ return 0;
+ }
+ extension_offset += 2; // Server Name List length
+
+ sni_type = ext_server_name[extension_offset++]; // Server Name type
+ if (sni_type != EXTENSION_SERVER_NAME)
+ {
+ return 0;
+ }
+
+ sni_length = ntohs(*(unsigned short *)(ext_server_name + extension_offset)); // Server Name length
+ if (sni_length == 0 || sni_length > ext_server_name_length)
+ {
+ return 0;
+ }
+ extension_offset += 2;
+ copy_extension_tag(ext_server_name + extension_offset, sni_length, &quic_info->chlo.sni, &quic_info->chlo.sni_len);
+ return 1;
+}
+
+int parse_tls_client_hello(struct quic_info *quic_info, const unsigned char *payload, int payload_len)
+{
+ int ret = 0, skip_len = 0;
+ int payload_offset = 0;
+ int extension_offset = 0;
+ const unsigned char *extension_start_pos = NULL;
+ int parse_result = PARSE_RESULT_VERSION;
+ unsigned short one_ext_type = 0, one_ext_len = 0, extension_total_len = 0;
+
+ if (payload_len - payload_offset <= (int)sizeof(struct quic_client_hello_msg_hdr))
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ // handshake type(1), client hello length(3), ssl_version(2), Random(32)
+ payload_offset += sizeof(struct quic_client_hello_msg_hdr);
+
+ skip_len = payload[payload_offset++]; // Session ID length
+ if (payload_len - payload_offset <= skip_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += skip_len;
+
+ skip_len = ntohs(*(unsigned short *)(payload + payload_offset)); // Ciper Suites length
+ if (payload_len - payload_offset <= skip_len + 2)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += skip_len + 2;
+
+ skip_len = payload[payload_offset++]; // Compression Methods
+ if (payload_len - payload_offset < skip_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += skip_len;
+
+ extension_total_len = ntohs(*(unsigned short *)(payload + payload_offset)); // Extension length
+ if (payload_len - payload_offset < extension_total_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ payload_offset += 2;
+ extension_start_pos = payload + payload_offset;
+
+ while (extension_total_len > extension_offset)
+ {
+ one_ext_type = ntohs(*(unsigned short *)(extension_start_pos + extension_offset)); // Extension type
+ extension_offset += 2;
+
+ one_ext_len = ntohs(*(unsigned short *)(extension_start_pos + extension_offset)); // length
+ extension_offset += 2;
+
+ if (extension_total_len - extension_offset < one_ext_len)
+ {
+ break;
+ }
+
+ switch (one_ext_type)
+ {
+ case EXTENSION_SERVER_NAME:
+ ret = parse_extension_server_name(quic_info, extension_start_pos + extension_offset, one_ext_len);
+ break;
+ case EXTENSION_QUIC_PARAM_TLS_13:
+ case EXTENSION_QUIC_PARAM_TLS_33:
+ ret = parse_quic_transport_parameter(quic_info, extension_start_pos + extension_offset, one_ext_len);
+ break;
+ default:
+ break;
+ }
+
+ if (ret == 1)
+ {
+ ret = 0;
+ parse_result = PARSE_RESULT_CLIENT_HELLO;
+ }
+ extension_offset += one_ext_len;
+ }
+ return parse_result;
+}
+
+int qk_get_chlo_length(const unsigned char *payload, int payload_len UNUSED)
+{
+ const struct quic_client_hello_msg_hdr *chlo_hdr = (const struct quic_client_hello_msg_hdr *)payload;
+ int chlo_len = 0;
+ chlo_len |= ((int)chlo_hdr->client_hello_len[0] << 16);
+ chlo_len |= ((int)chlo_hdr->client_hello_len[1] << 8);
+ chlo_len |= ((int)chlo_hdr->client_hello_len[2]);
+ return chlo_len;
+}
+
+/*
+ 1: is chlo, and complete!
+ 0: is chlo, but fragment;
+ -1: not support
+*/
+int qk_chlo_is_complete(enum QUIC_VERSION_T quic_version, const unsigned char *payload, int payload_len)
+{
+ if (!((quic_version >= MVFST_VERSION_00 && quic_version <= MVFST_VERSION_0F) ||
+ (quic_version >= GQUIC_VERSION_T050 && quic_version <= GQUIC_VERSION_T059) ||
+ (quic_version >= IQUIC_VERSION_I022 && quic_version <= IQUIC_VERSION_I029) ||
+ (quic_version == IQUIC_VERSION_RFC9000)))
+ {
+ return -1;
+ }
+
+ if ((payload[0] != IQUIC_FRAME_CRYPTO) && (payload[0] != 0x08))
+ {
+ return -1;
+ }
+ int payload_offset = 0;
+ long frame_offset, frame_length;
+ payload_offset = 1; // skip frame type
+ payload_offset += msb2_varint_decode(payload + payload_offset, &frame_offset);
+ payload_offset += msb2_varint_decode(payload + payload_offset, &frame_length);
+
+ if (payload[payload_offset] != QUIC_HANDSHAKE_TYPE_CLIENTHELLO)
+ {
+ return -1;
+ }
+ int expect_len = qk_get_chlo_length(payload + payload_offset, payload_len - payload_offset);
+ if (payload_len - payload_offset >= expect_len)
+ {
+ return 1;
+ }
+ return 0;
+}
+
+int parse_quic_decrypted_payload(struct quic_info *quic_info, const unsigned char *payload, int payload_len, uint32_t chlo_frag_max_size)
+{
+ unsigned char join_payload[chlo_frag_max_size];
+ int join_payload_len = sizeof(join_payload);
+ unsigned int quic_version = quic_info->version;
+
+ if ((quic_version >= MVFST_VERSION_00 && quic_version <= MVFST_VERSION_0F) ||
+ (quic_version >= GQUIC_VERSION_T050 && quic_version <= GQUIC_VERSION_T059) ||
+ (quic_version >= IQUIC_VERSION_I022 && quic_version <= IQUIC_VERSION_I029) ||
+ (quic_version == IQUIC_VERSION_RFC9000))
+ {
+ join_payload_len = join_client_hello_frames(payload, payload_len, join_payload, &join_payload_len);
+ if (join_payload_len <= 0)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ if (join_payload[0] == QUIC_HANDSHAKE_TYPE_CLIENTHELLO)
+ {
+ return parse_tls_client_hello(quic_info, join_payload, join_payload_len);
+ }
+ }
+ else // if(quic_version>=GQUIC_VERSION_Q047 && quic_version<=GQUIC_VERSION_Q059)
+ {
+ return parse_special_frame_stream(quic_info, payload + 4, payload_len - 4); // Frame type=1,offset=1,length=2
+ }
+ return PARSE_RESULT_VERSION;
+}
+
+int parse_quic_uncryption_payload(struct quic_info *quic_info, const unsigned char *payload, int payload_len)
+{
+ int stream_id_len = 0;
+ int offset_len = 0;
+ unsigned char frame_type;
+ int payload_offset = 0;
+
+ frame_type = payload[payload_offset];
+ payload_offset += 1; // skip frame_type
+
+ // https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/edit#
+ // Frame Type: The Frame Type byte is an 8-bit value containing various flags (1fdooossB):
+ if (frame_type & GQUIC_SPECIAL_FRAME_STREAM || (frame_type & 0xC0) == GQUIC_SPECIAL_FRAME_ACK)
+ {
+ stream_id_len = (frame_type & GQUIC_SPECIAL_FRAME_STREAM_ID) + 1;
+ if (payload_len - payload_offset <= stream_id_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+
+ payload_offset += stream_id_len; // stream ID length
+
+ if (frame_type & GQUIC_SPECIAL_FRAME_STREAM_DLEN)
+ {
+ if (payload_len - payload_offset < 2)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += 2; // data length
+ }
+
+ if (frame_type & GQUIC_SPECIAL_FRAME_STREAM_OFFSET)
+ { // The next three 'ooo' bits encode the length of the Offset header field as 0, 16, 24, 32, 40, 48, 56, or 64 bits long.
+ offset_len = (((frame_type & GQUIC_SPECIAL_FRAME_STREAM_OFFSET)) >> 2) + 1;
+ if (payload_len - payload_offset <= offset_len)
+ {
+ return PARSE_RESULT_VERSION;
+ }
+ payload_offset += offset_len; // data length
+ }
+
+ return parse_special_frame_stream(quic_info, payload + payload_offset, payload_len - payload_offset);
+ }
+ return PARSE_RESULT_VERSION;
+}
+
+/*
+//https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/edit
+
+--- src
+ 0 1 2 3 4 8
++--------+--------+--------+--------+--------+--- ---+
+| Public | Connection ID (64) ... | ->
+|Flags(8)| (optional) |
++--------+--------+--------+--------+--------+--- ---+
+
+ 9 10 11 12
++--------+--------+--------+--------+
+| QUIC Version (32) | ->
+| (optional) |
++--------+--------+--------+--------+
+
+
+ 13 14 15 16 17 18 19 20
++--------+--------+--------+--------+--------+--------+--------+--------+
+| Diversification Nonce | ->
+| (optional) |
++--------+--------+--------+--------+--------+--------+--------+--------+
+
+ 21 22 23 24 25 26 27 28
++--------+--------+--------+--------+--------+--------+--------+--------+
+| Diversification Nonce Continued | ->
+| (optional) |
++--------+--------+--------+--------+--------+--------+--------+--------+
+
+ 29 30 31 32 33 34 35 36
++--------+--------+--------+--------+--------+--------+--------+--------+
+| Diversification Nonce Continued | ->
+| (optional) |
++--------+--------+--------+--------+--------+--------+--------+--------+
+
+ 37 38 39 40 41 42 43 44
++--------+--------+--------+--------+--------+--------+--------+--------+
+| Diversification Nonce Continued | ->
+| (optional) |
++--------+--------+--------+--------+--------+--------+--------+--------+
+
+
+ 45 46 47 48 49 50
++--------+--------+--------+--------+--------+--------+
+| Packet Number (8, 16, 32, or 48) |
+| (variable length) |
++--------+--------+--------+--------+--------+--------+
+
+*/
+enum QUIC_VERSION_T identify_gquic_version0to43(const char *payload, int payload_len UNUSED, int *payload_offset)
+{
+ unsigned char pkn_length = 0;
+ unsigned char public_flags = 0;
+ enum QUIC_VERSION_T quic_version = QUIC_VERSION_UNKNOWN;
+
+ public_flags = payload[*payload_offset];
+ *payload_offset += 1;
+
+ if (!(public_flags & GQUIC_PUBLIC_FLAG_VERSION))
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+
+ /*
+ 0x08 = Indicates the full 8 byte Connection ID is present in the packet.
+ This must be set in all packets until negotiated to a different value for a given direction
+ (e.g., client may request fewer bytes of the Connection ID be presented)
+ */
+ if (public_flags & GQUIC_PUBLIC_FLAG_CID)
+ {
+ *payload_offset += 8; // CID length
+ }
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+
+ if (public_flags & GQUIC_PUBLIC_FLAG_VERSION && (*(unsigned char *)(payload + *payload_offset) == 0x51))
+ {
+ quic_version = (enum QUIC_VERSION_T)ntohl(*(unsigned int *)(payload + *payload_offset));
+ *payload_offset += sizeof(int); // skip version
+ }
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ if (quic_version < GQUIC_VERSION_Q001 || quic_version > GQUIC_VERSION_Q043)
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+
+ pkn_length = gquic_pkn_bit2length(public_flags & GQUIC_VERSION_0to43_PKN_LEN_MASK);
+ *payload_offset += pkn_length; // packet number length
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ if (public_flags == GQUIC_PUBLIC_FLAG_NONCE)
+ {
+ *payload_offset += 32; // diversification nonce
+ }
+
+ // Version 11 reduced the length of null encryption authentication tag from 16 to 12 bytes
+ if (quic_version > GQUIC_VERSION_Q010)
+ {
+ *payload_offset += 12;
+ }
+ else
+ {
+ *payload_offset += 16;
+ }
+ if (*payload_offset > payload_len) // check memory overflow
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ // Version 34 removed entropy bits from packets and ACK frames,
+ // removed private flag from packet header and changed the ACK format to specify ranges of packets acknowledged rather than missing ranges.
+ if (quic_version < GQUIC_VERSION_Q034)
+ {
+ *payload_offset += 1; // private flags
+ }
+ return quic_version;
+}
+
+enum QUIC_VERSION_T identify_quic_version(const char *payload, int payload_len, int *payload_offset)
+{
+ enum QUIC_VERSION_T quic_version = (enum QUIC_VERSION_T)ntohl(*(unsigned int *)(payload + (*payload_offset + 1)));
+ if (quic_version >= GQUIC_VERSION_Q044 && quic_version <= GQUIC_VERSION_Q048)
+ {
+ if (QUIC_VERSION_UNKNOWN == parse_gquic_version_44to48_header(quic_version, payload, payload_len, payload_offset))
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ return quic_version;
+ }
+ else if (
+ (quic_version == GQUIC_VERSION_Q099) ||
+ (quic_version == PICOQUIC_VERSION_30) ||
+ (quic_version == PQUIC_VERSION_PROX) ||
+ (quic_version == GQUIC_VERSION_T099) ||
+ (quic_version >= GQUIC_VERSION_Q049 && quic_version <= GQUIC_VERSION_Q050) ||
+ (quic_version >= GQUIC_VERSION_Q051 && quic_version <= GQUIC_VERSION_Q059) ||
+ (quic_version >= GQUIC_VERSION_T048 && quic_version <= GQUIC_VERSION_T049) ||
+ (quic_version >= GQUIC_VERSION_T050 && quic_version <= GQUIC_VERSION_T059) ||
+ (quic_version >= QUANT_VERSION_00 && quic_version <= QUANT_VERSION_FF) ||
+ (quic_version >= QUIC_GO_VERSION_00 && quic_version <= QUIC_GO_VERSION_FF) ||
+ (quic_version >= QUICLY_VERSION_00 && quic_version <= QUICLY_VERSION_FF) ||
+ (quic_version >= MSQUIC_VERSION_00 && quic_version <= MSQUIC_VERSION_0F) ||
+ (quic_version >= MOZQUIC_VERSION_00 && quic_version <= MOZQUIC_VERSION_0F) ||
+ (quic_version >= MVFST_VERSION_00 && quic_version <= MVFST_VERSION_0F) ||
+ (quic_version >= IQUIC_VERSION_I001 && quic_version <= IQUIC_VERSION_I032) ||
+ (quic_version == IQUIC_VERSION_RFC9000))
+ {
+ return quic_version;
+ }
+ return QUIC_VERSION_UNKNOWN;
+}
+
+enum QUIC_VERSION_T is_quic_protocol(const char *payload, int payload_len, int *payload_offset)
+{
+ enum QUIC_VERSION_T quic_version = QUIC_VERSION_UNKNOWN;
+ unsigned char frame_type = (unsigned char)(payload[0]);
+
+ if (payload_len <= 4)
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+
+ if (frame_type & QUIC_LONG_HEADER_MASK)
+ {
+ quic_version = identify_quic_version(payload, payload_len, payload_offset);
+ }
+ else if (frame_type < QUIC_LONG_HEADER_MASK)
+ {
+ quic_version = identify_gquic_version0to43(payload, payload_len, payload_offset);
+ }
+ else
+ {
+ return QUIC_VERSION_UNKNOWN;
+ }
+ return quic_version;
+}
+
+int quic_version_to_readable_string(unsigned int version, char *buff, size_t buff_len)
+{
+ assert(buff != NULL);
+ if (version >= GQUIC_VERSION_Q001 && version <= GQUIC_VERSION_Q099)
+ {
+ snprintf(buff, buff_len, "Google QUIC %02u", (((version >> 8) & 0x0000000F) * 10) + (version & 0x0000000F));
+ return 1;
+ }
+ if (version >= GQUIC_VERSION_T048 && version <= GQUIC_VERSION_T099)
+ {
+ snprintf(buff, buff_len, "Google QUIC with TLS %02u", (((version >> 8) & 0x0000000F) * 10) + (version & 0x0000000F));
+ return 1;
+ }
+ if (version == IQUIC_VERSION_RFC9000)
+ {
+ snprintf(buff, buff_len, "IETF QUIC RFC9000");
+ return 1;
+ }
+ if (version >= IQUIC_VERSION_I001 && version <= IQUIC_VERSION_I032)
+ {
+ snprintf(buff, buff_len, "IETF QUIC %02u", (((version >> 16) & 0x000000FF) * 10) + (((version >> 8) & 0x000000FF) * 10) + (version & 0x000000FF));
+ return 1;
+ }
+ if (version >= QUANT_VERSION_00 && version <= QUANT_VERSION_FF)
+ {
+ snprintf(buff, buff_len, "NetApp QUANT %02u", (version & 0x000000FF));
+ return 1;
+ }
+ if (version == PICOQUIC_VERSION_30)
+ {
+ snprintf(buff, buff_len, "Private Octopus");
+ return 1;
+ }
+ if (version == PQUIC_VERSION_PROX)
+ {
+ snprintf(buff, buff_len, "Proxied QUIC");
+ return 1;
+ }
+ if (version >= QUIC_GO_VERSION_00 && version <= QUIC_GO_VERSION_FF)
+ {
+ snprintf(buff, buff_len, "quic-go QGO %02u", (version & 0x000000FF));
+ return 1;
+ }
+ if (version >= MSQUIC_VERSION_00 && version <= MSQUIC_VERSION_0F)
+ {
+ snprintf(buff, buff_len, "Microsoft MsQuic %02u", (version & 0x0000000F));
+ return 1;
+ }
+ if (version >= MOZQUIC_VERSION_00 && version <= MOZQUIC_VERSION_0F)
+ {
+ snprintf(buff, buff_len, "Mozilla MozQuic %02u", (version & 0x0000000F));
+ return 1;
+ }
+ if (version >= MVFST_VERSION_00 && version <= MVFST_VERSION_0F)
+ {
+ snprintf(buff, buff_len, "Facebook mvfst %02u", (version & 0x0000000F));
+ return 1;
+ }
+ snprintf(buff, buff_len, "Unknown QUIC VERSION");
+ return 0;
+}