diff options
| author | lijia <[email protected]> | 2024-10-25 15:30:31 +0800 |
|---|---|---|
| committer | lijia <[email protected]> | 2024-11-08 11:52:03 +0800 |
| commit | a01ac0e7274854a16ce125a059b3d2ab5fb7d7cf (patch) | |
| tree | 571788768c273dec1f7db0a4744ecbe505259a37 /decoders | |
| parent | d0a868591470a4a9d71a65a5d540058e72c8d92c (diff) | |
quicv2.0 rebase onto stellar develop-2.0dev-quic-v2.0
Diffstat (limited to 'decoders')
| -rw-r--r-- | decoders/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | decoders/quic/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | decoders/quic/quic_decoder.c | 758 | ||||
| -rw-r--r-- | decoders/quic/quic_decoder.h | 306 | ||||
| -rw-r--r-- | decoders/quic/quic_deprotection.c | 1129 | ||||
| -rw-r--r-- | decoders/quic/quic_deprotection.h | 111 | ||||
| -rw-r--r-- | decoders/quic/quic_module.c | 97 | ||||
| -rw-r--r-- | decoders/quic/quic_process.c | 386 | ||||
| -rw-r--r-- | decoders/quic/quic_process.h | 116 | ||||
| -rw-r--r-- | decoders/quic/quic_util.h | 28 | ||||
| -rw-r--r-- | decoders/quic/version.map | 10 |
11 files changed, 2952 insertions, 1 deletions
diff --git a/decoders/CMakeLists.txt b/decoders/CMakeLists.txt index 7946822..6c6a42f 100644 --- a/decoders/CMakeLists.txt +++ b/decoders/CMakeLists.txt @@ -2,4 +2,5 @@ add_subdirectory(lpi_plus) #add_subdirectory(http) #add_subdirectory(socks) #add_subdirectory(stratum) -#add_subdirectory(session_flags)
\ No newline at end of file +#add_subdirectory(session_flags) +add_subdirectory(quic) diff --git a/decoders/quic/CMakeLists.txt b/decoders/quic/CMakeLists.txt new file mode 100644 index 0000000..9636387 --- /dev/null +++ b/decoders/quic/CMakeLists.txt @@ -0,0 +1,9 @@ +add_definitions(-fPIC) +file(GLOB QUIC_SRC "*.c") +add_library(quic ${QUIC_SRC}) +add_definitions ("-Wno-error=implicit-fallthrough") +target_include_directories(quic PUBLIC ${CMAKE_SOURCE_DIR}/deps/) +target_include_directories(quic PUBLIC ${CMAKE_SOURCE_DIR}/include/) +set_target_properties(quic PROPERTIES LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/version.map") +set_target_properties(quic PROPERTIES PREFIX "") +target_link_libraries(quic toml openssl-crypto-static openssl-ssl-static)
\ No newline at end of file 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; +} diff --git a/decoders/quic/quic_decoder.h b/decoders/quic/quic_decoder.h new file mode 100644 index 0000000..05212da --- /dev/null +++ b/decoders/quic/quic_decoder.h @@ -0,0 +1,306 @@ +#pragma once +#include "stellar/quic.h" + +#define QUIC_LONG_HEADER_MASK 0x80 + +#define GQUIC_VERSION_44to48_CID_MASK 0x0F +#define GQUIC_VERSION_44to48_PKN_LEN_MASK 0x03 +#define GQUIC_VERSION_0to43_PKN_LEN_MASK 0x30 + +#define GQUIC_PUBLIC_FLAG_VERSION 0x01 +#define GQUIC_PUBLIC_FLAG_RST 0x02 +#define GQUIC_PUBLIC_FLAG_NONCE 0x04 +#define GQUIC_PUBLIC_FLAG_CID 0x08 +#define GQUIC_PUBLIC_FLAG_PKT_NUM 0x30 + +// GQIIC Frame type +#define GQUIC_SPECIAL_FRAME_FLAG 0xE0 // Special Frame Types +#define GQUIC_SPECIAL_FRAME_STREAM 0x80 +#define GQUIC_SPECIAL_FRAME_ACK 0x40 +#define GQUIC_SPECIAL_FRAME_CONGEST_FB 0x20 + +#define GQUIC_SPECIAL_FRAME_STREAM_FIN 0x40 // FIN +#define GQUIC_SPECIAL_FRAME_STREAM_DLEN 0x20 // stream length +#define GQUIC_SPECIAL_FRAME_STREAM_OFFSET 0x1C // offset header field +#define GQUIC_SPECIAL_FRAME_STREAM_ID 0x03 // offset header field + +#define GQUIC_REGULAR_FRAME_PADDING 0x00 +#define GQUIC_REGULAR_FRAME_RST_STREAM 0x01 +#define GQUIC_REGULAR_FRAME_CONNECTION_CLOSE 0x02 +#define GQUIC_REGULAR_FRAME_GOAWAY 0x03 +#define GQUIC_REGULAR_FRAME_WINDOW_UPDATE 0x04 +#define GQUIC_REGULAR_FRAME_BLOCKED 0x05 +#define GQUIC_REGULAR_FRAME_STOP_WAITING 0x06 +#define GQUIC_REGULAR_FRAME_PING 0x07 + +// https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types +#define IQUIC_FRAME_PADDING 0x00 +#define IQUIC_FRAME_PING 0x01 +#define IQUIC_FRAME_ACK 0x02 +#define IQUIC_FRAME_RST_STREAM 0x03 +#define IQUIC_FRAME_STOP_WAITING 0x04 +#define IQUIC_FRAME_CRYPTO 0x06 +#define IQUIC_FRAME_NEW_TOKEN 0x07 +#define IQUIC_FRAME_STREAM 0x08 +#define IQUIC_FRAME_MAX_DATA 0x10 +#define IQUIC_FRAME_MAX_STREAM_DATA 0x11 +#define IQUIC_FRAME_MAX_STREAMS 0x12 +#define IQUIC_FRAME_DATA_BLOCKED 0x13 +#define IQUIC_FRAME_STREAM_DATA_BLOCKED 0x14 +#define IQUIC_FRAME_STREAMS_BLOCKED 0x15 +#define IQUIC_FRAME_NEW_CONNECTION_ID 0x18 +#define IQUIC_FRAME_RETIRE_CONNECTION_ID 0x19 +#define IQUIC_FRAME_PATH_CHALLENGE 0x1A +#define IQUIC_FRAME_PATH_RESPONSE 0x1B +#define IQUIC_FRAME_CONNECTION_CLOSE 0x1C +#define IQUIC_FRAME_APPLICATION_CLOSE 0x1D +#define IQUIC_FRAME_HANDSHAKE_DONE 0x1E + +#define QUIC_HANDSHAKE_TYPE_CLIENTHELLO 0x01 + +// https://datatracker.ietf.org/doc/html/draft-ietf-quic-transport-27#section-12.4 +// IQIIC Frame type (GQUIC_Q046 is iQUIC 17) + +/**************************************************************************/ +/* Message tag */ +/**************************************************************************/ +#define CHLO 0x43484C4F +#define SHLO 0x53484C4F +#define REJ 0x52454A00 +#define PRST 0x50525354 + +/**************************************************************************/ +/* Tag */ +/**************************************************************************/ +#define TAG_SNI 0x534E4900 +#define TAG_VER 0x56455200 +#define TAG_UAID 0x55414944 + +#define EXTENSION_SERVER_NAME 0x0000 +#define EXTENSION_SUPPORT_GROUP 0x000A +#define EXTENSION_APP_PROT_NEGO 0x0010 // application layer protocol negotiation +#define EXTENSION_SIG_ALGORITHM 0x000D +#define EXTENSION_KEY_SHARE 0x0033 +#define EXTENSION_PSK_EXCHANGE 0x002D +#define EXTENSION_SUPP_SSL_VER 0x002B +#define EXTENSION_QUIC_PARAM_TLS_33 0x0039 /* draft-ietf-quic-tls-33 */ +#define EXTENSION_QUIC_PARAM_TLS_13 0xFFA5 /* 0xffa5 draft-ietf-quic-tls-13 */ +#define EXTENSION_COMPRESS_CERT 0x001B +#define EXTENTION_UNKNOWN 0x4469 + +// https://www.iana.org/assignments/quic/quic.xhtml +#define EXT_QUIC_PARAM_ORIGINAL_DST_CONN_ID 0x00 +#define EXT_QUIC_PARAM_MAX_IDLE_TIMEOUT 0x01 +#define EXT_QUIC_PARAM_STATELESS_RST_TOKEN 0x02 +#define EXT_QUIC_PARAM_MAX_UDP_PAYLOAD 0x03 +#define EXT_QUIC_PARAM_MAX_INIT_DATA 0x04 +#define EXT_QUIC_PARAM_MAX_STREAM_BIDI_LOCAL 0x05 +#define EXT_QUIC_PARAM_MAX_STREAM_BIDI_REMOTE 0x06 +#define EXT_QUIC_PARAM_MAX_STREAM_UNI 0x07 +#define EXT_QUIC_PARAM_MAX_STREAMS_BIDI 0x08 +#define EXT_QUIC_PARAM_MAX_STREAMS_UNI 0x09 +#define EXT_QUIC_PARAM_ACK_DELAY_EXPONENT 0x0A +#define EXT_QUIC_PARAM_MAX_ACK_DELAY 0x0B +#define EXT_QUIC_PARAM_DISABLE_ACTIVE_MIGRATION 0x0C +#define EXT_QUIC_PARAM_PREFERRED_ADDRESS 0x0D +#define EXT_QUIC_PARAM_ACTIVE_CONN_ID_LINIT 0x0E +#define EXT_QUIC_PARAM_INIT_SRC_CONN_ID 0x0F +#define EXT_QUIC_PARAM_RETRY_SRC_CONN_ID 0x10 +#define EXT_QUIC_PARAM_MAX_DATAGRAM_FRAME_SIZE 0x20 +#define EXT_QUIC_PARAM_INIT_RTT 0x3127 +#define EXT_QUIC_PARAM_GOOGLE_CONN_OPTIONS 0x3128 +#define EXT_QUIC_PARAM_USER_AGENT 0x3129 // 2021-10-20 deprecated +#define EXT_QUIC_PARAM_QUIC_VERSION 0x4752 + +// https://github.com/quicwg/base-drafts/wiki/QUIC-Versions +enum QUIC_VERSION_T +{ + QUIC_VERSION_UNKNOWN = 0, + // NetApp + QUANT_VERSION_00 = 0x45474700, + QUANT_VERSION_FF = 0x454747FF, + + // Private Octopus + PICOQUIC_VERSION_30 = 0x50435130, + + // google + GQUIC_VERSION_Q001 = 0x51303031, + GQUIC_VERSION_Q002 = 0x51303032, + GQUIC_VERSION_Q003 = 0x51303033, + GQUIC_VERSION_Q004 = 0x51303034, + GQUIC_VERSION_Q005 = 0x51303035, + GQUIC_VERSION_Q006 = 0x51303036, + GQUIC_VERSION_Q007 = 0x51303037, + GQUIC_VERSION_Q008 = 0x51303038, + GQUIC_VERSION_Q009 = 0x51303039, + + GQUIC_VERSION_Q010 = 0x51303130, + GQUIC_VERSION_Q011 = 0x51303131, + GQUIC_VERSION_Q012 = 0x51303132, + GQUIC_VERSION_Q013 = 0x51303133, + GQUIC_VERSION_Q014 = 0x51303134, + GQUIC_VERSION_Q015 = 0x51303135, + GQUIC_VERSION_Q016 = 0x51303136, + GQUIC_VERSION_Q017 = 0x51303137, + GQUIC_VERSION_Q018 = 0x51303138, + GQUIC_VERSION_Q019 = 0x51303139, + + GQUIC_VERSION_Q020 = 0x51303230, + GQUIC_VERSION_Q021 = 0x51303231, + GQUIC_VERSION_Q022 = 0x51303232, + GQUIC_VERSION_Q023 = 0x51303233, + GQUIC_VERSION_Q024 = 0x51303234, + GQUIC_VERSION_Q025 = 0x51303235, + GQUIC_VERSION_Q026 = 0x51303236, + GQUIC_VERSION_Q027 = 0x51303237, + GQUIC_VERSION_Q028 = 0x51303238, + GQUIC_VERSION_Q029 = 0x51303239, + + GQUIC_VERSION_Q030 = 0x51303330, + GQUIC_VERSION_Q031 = 0x51303331, + GQUIC_VERSION_Q032 = 0x51303332, + GQUIC_VERSION_Q033 = 0x51303333, + GQUIC_VERSION_Q034 = 0x51303334, + GQUIC_VERSION_Q035 = 0x51303335, + GQUIC_VERSION_Q036 = 0x51303336, + GQUIC_VERSION_Q037 = 0x51303337, + GQUIC_VERSION_Q038 = 0x51303338, + GQUIC_VERSION_Q039 = 0x51303339, + + GQUIC_VERSION_Q040 = 0x51303430, + GQUIC_VERSION_Q041 = 0x51303431, + GQUIC_VERSION_Q042 = 0x51303432, + GQUIC_VERSION_Q043 = 0x51303433, + GQUIC_VERSION_Q044 = 0x51303434, + GQUIC_VERSION_Q045 = 0x51303435, + GQUIC_VERSION_Q046 = 0x51303436, + GQUIC_VERSION_Q047 = 0x51303437, + GQUIC_VERSION_Q048 = 0x51303438, + GQUIC_VERSION_Q049 = 0x51303439, + + GQUIC_VERSION_Q050 = 0x51303530, + GQUIC_VERSION_Q051 = 0x51303531, + GQUIC_VERSION_Q052 = 0x51303532, + GQUIC_VERSION_Q053 = 0x51303533, + GQUIC_VERSION_Q054 = 0x51303534, + GQUIC_VERSION_Q055 = 0x51303535, + GQUIC_VERSION_Q056 = 0x51303536, + GQUIC_VERSION_Q057 = 0x51303537, + GQUIC_VERSION_Q058 = 0x51303538, + GQUIC_VERSION_Q059 = 0x51303539, + + GQUIC_VERSION_Q099 = 0x51303939, + + // Google QUIC with TLS 48 - 49 (T048 - T049) + GQUIC_VERSION_T048 = 0x54303438, + GQUIC_VERSION_T049 = 0x54303439, + + // Google QUIC with TLS 50 - 59 (T050 - T059) + GQUIC_VERSION_T050 = 0x54303530, + GQUIC_VERSION_T051 = 0x54303531, + GQUIC_VERSION_T052 = 0x54303532, + GQUIC_VERSION_T053 = 0x54303533, + GQUIC_VERSION_T054 = 0x54303534, + GQUIC_VERSION_T055 = 0x54303535, + GQUIC_VERSION_T056 = 0x54303536, + GQUIC_VERSION_T057 = 0x54303537, + GQUIC_VERSION_T058 = 0x54303538, + GQUIC_VERSION_T059 = 0x54303539, + + // Google QUIC with TLS 99 (T099) + GQUIC_VERSION_T099 = 0x54303939, + + // Google Proxied QUIC + PQUIC_VERSION_PROX = 0x50524f58, + + // quic-go + QUIC_GO_VERSION_00 = 0x51474F00, + QUIC_GO_VERSION_FF = 0x51474FFF, + + // quicly + QUICLY_VERSION_00 = 0x91c17000, + QUICLY_VERSION_FF = 0x91c170FF, + + // Microsoft + MSQUIC_VERSION_00 = 0xabcd0000, + MSQUIC_VERSION_0F = 0xabcd000F, + + // Mozilla + MOZQUIC_VERSION_00 = 0xf123f0c0, + MOZQUIC_VERSION_0F = 0xf123f0cF, + + // Facebook + MVFST_VERSION_00 = 0xfaceb000, + MVFST_VERSION_01 = 0xfaceb001, + MVFST_VERSION_02 = 0xfaceb002, + MVFST_VERSION_03 = 0xfaceb003, + MVFST_VERSION_04 = 0xfaceb004, + MVFST_VERSION_05 = 0xfaceb005, + MVFST_VERSION_06 = 0xfaceb006, + MVFST_VERSION_07 = 0xfaceb007, + MVFST_VERSION_08 = 0xfaceb008, + MVFST_VERSION_09 = 0xfaceb009, + MVFST_VERSION_0A = 0xfaceb00A, + MVFST_VERSION_0B = 0xfaceb00B, + MVFST_VERSION_0C = 0xfaceb00C, + MVFST_VERSION_0D = 0xfaceb00D, + MVFST_VERSION_0E = 0xfaceb00E, + MVFST_VERSION_0F = 0xfaceb00F, + + // IETF + IQUIC_VERSION_RFC9000 = 0x00000001, + IQUIC_VERSION_I001 = 0xFF000001, + IQUIC_VERSION_I002 = 0xFF000002, + IQUIC_VERSION_I003 = 0xFF000003, + IQUIC_VERSION_I004 = 0xFF000004, + IQUIC_VERSION_I005 = 0xFF000005, + IQUIC_VERSION_I006 = 0xFF000006, + IQUIC_VERSION_I007 = 0xFF000007, + IQUIC_VERSION_I008 = 0xFF000008, + IQUIC_VERSION_I009 = 0xFF000009, + IQUIC_VERSION_I010 = 0xFF00000A, + IQUIC_VERSION_I011 = 0xFF00000B, + IQUIC_VERSION_I012 = 0xFF00000C, + IQUIC_VERSION_I013 = 0xFF00000D, + IQUIC_VERSION_I014 = 0xFF00000E, + IQUIC_VERSION_I015 = 0xFF00000F, + IQUIC_VERSION_I016 = 0xFF000010, + IQUIC_VERSION_I017 = 0xFF000011, + IQUIC_VERSION_I018 = 0xFF000012, + IQUIC_VERSION_I019 = 0xFF000013, + IQUIC_VERSION_I020 = 0xFF000014, + IQUIC_VERSION_I021 = 0xFF000015, + IQUIC_VERSION_I022 = 0xFF000016, + IQUIC_VERSION_I023 = 0xFF000017, + IQUIC_VERSION_I024 = 0xFF000018, + IQUIC_VERSION_I025 = 0xFF000019, + IQUIC_VERSION_I026 = 0xFF00001A, + IQUIC_VERSION_I027 = 0xFF00001B, + IQUIC_VERSION_I028 = 0xFF00001C, + IQUIC_VERSION_I029 = 0xFF00001D, + IQUIC_VERSION_I030 = 0xFF00001E, + IQUIC_VERSION_I031 = 0xFF00001F, + IQUIC_VERSION_I032 = 0xFF000020 +}; + +enum PARSE_RESULT +{ + PARSE_RESULT_UNKNOWN, + PARSE_RESULT_VERSION, + PARSE_RESULT_CLIENT_HELLO, + PARSE_RESULT_PAYLOAD, + PARSE_RESULT_MAX +}; + +struct quic_info +{ + uint32_t version; + struct quic_client_hello chlo; + // struct quic_encrypted_payload enc_payload; +}; + +enum QUIC_VERSION_T is_quic_protocol(const char *payload, int payload_len, int *payload_offset); +int qk_get_chlo_length(const unsigned char *payload, int payload_len); +int qk_chlo_is_complete(enum QUIC_VERSION_T quic_version, const unsigned char *payload, int payload_len); +int parse_quic_decrypted_payload(struct quic_info *quic_info, const unsigned char *payload, int payload_len,uint32_t chlo_frag_max_size); +int parse_quic_uncryption_payload(struct quic_info *quic_info, const unsigned char *payload, int payload_len);
\ No newline at end of file diff --git a/decoders/quic/quic_deprotection.c b/decoders/quic/quic_deprotection.c new file mode 100644 index 0000000..9bc96b4 --- /dev/null +++ b/decoders/quic/quic_deprotection.c @@ -0,0 +1,1129 @@ +#include "quic_deprotection.h" + +#include <inttypes.h> +#include <openssl/evp.h> +#include <openssl/kdf.h> +#include <openssl/sha.h> + +#define QUIC_IV_LEN 12 /* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */ +#define QUIC_HP_LEN 5 /* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */ + +#define QUIC_MAX_CID_LEN 20 +#define QUIC_AES_128_KEY_LEN 16 +#define QUIC_MIN_INITIAL_SIZE 1200 +#define QUIC_UNSET_PN (uint64_t) - 1 + +/* + * RFC 9000, 17.2. Long Header Packets + * 17.3. Short Header Packets + * QUIC flags in first byte + */ +#define QUIC_PKT_LONG 0x80 /* header form */ +#define QUIC_PKT_FIXED_BIT 0x40 +#define QUIC_PKT_TYPE 0x30 /* in long packet */ +#define QUIC_PKT_KPHASE 0x04 /* in short packet */ + +#define quic_pkt_header_is_long(flags) ((flags) & QUIC_PKT_LONG) +#define quic_pkt_header_is_short(flags) (((flags) & QUIC_PKT_LONG) == 0) +#define quic_pkt_hp_mask(flags) (quic_pkt_header_is_long(flags) ? 0x0F : 0x1F) +#define quic_pkt_rb_mask(flags) (quic_pkt_header_is_long(flags) ? 0x0C : 0x18) + +/* Long packet types */ +#define quic_pkt_level_is_initial(flags) (((flags) & QUIC_PKT_TYPE) == 0x00) +#define quic_pkt_level_is_zrtt(flags) (((flags) & QUIC_PKT_TYPE) == 0x10) +#define quic_pkt_level_is_handshake(flags) (((flags) & QUIC_PKT_TYPE) == 0x20) + +/* MAX MIN */ +#define MAX(val1, val2) ((val1 < val2) ? (val2) : (val1)) +#define MIN(val1, val2) ((val1 > val2) ? (val2) : (val1)) + +#define quic_pkt_level_to_name(lvl) \ + (lvl == ssl_encryption_application) ? "application" \ + : (lvl == ssl_encryption_initial) ? "initial" \ + : (lvl == ssl_encryption_handshake) ? "handshake" \ + : "early" + +typedef struct +{ + const EVP_CIPHER *c; + const EVP_CIPHER *hp; + const EVP_MD *d; +} quic_ciphers_t; + +/////////////////////////////////////////////////////////////////////////////// +// QUIC version +/////////////////////////////////////////////////////////////////////////////// + +#define QUIC_NVERSIONS (sizeof(quic_version_vals) / sizeof(quic_version_vals[0])) + +const quic_str_t quic_version_vals[] = { + {0x00000000, (u_char *)"Version Negotiation"}, + {0x00000001, (u_char *)"1"}, + /* Versions QXXX < Q050 are dissected by Wireshark as GQUIC and not as QUIC. + Nonetheless, some implementations report these values in "Version Negotiation" + packets, so decode these fields */ + {0x51303433, (u_char *)"Google Q043"}, + {0x51303434, (u_char *)"Google Q044"}, + {0x51303436, (u_char *)"Google Q046"}, + {0x51303530, (u_char *)"Google Q050"}, + {0x54303530, (u_char *)"Google T050"}, + {0x54303531, (u_char *)"Google T051"}, + {0xfaceb001, (u_char *)"Facebook mvfst (draft-22)"}, + {0xfaceb002, (u_char *)"Facebook mvfst (draft-27)"}, + {0xfaceb00e, (u_char *)"Facebook mvfst (Experimental)"}, + {0xff000004, (u_char *)"draft-04"}, + {0xff000005, (u_char *)"draft-05"}, + {0xff000006, (u_char *)"draft-06"}, + {0xff000007, (u_char *)"draft-07"}, + {0xff000008, (u_char *)"draft-08"}, + {0xff000009, (u_char *)"draft-09"}, + {0xff00000a, (u_char *)"draft-10"}, + {0xff00000b, (u_char *)"draft-11"}, + {0xff00000c, (u_char *)"draft-12"}, + {0xff00000d, (u_char *)"draft-13"}, + {0xff00000e, (u_char *)"draft-14"}, + {0xff00000f, (u_char *)"draft-15"}, + {0xff000010, (u_char *)"draft-16"}, + {0xff000011, (u_char *)"draft-17"}, + {0xff000012, (u_char *)"draft-18"}, + {0xff000013, (u_char *)"draft-19"}, + {0xff000014, (u_char *)"draft-20"}, + {0xff000015, (u_char *)"draft-21"}, + {0xff000016, (u_char *)"draft-22"}, + {0xff000017, (u_char *)"draft-23"}, + {0xff000018, (u_char *)"draft-24"}, + {0xff000019, (u_char *)"draft-25"}, + {0xff00001a, (u_char *)"draft-26"}, + {0xff00001b, (u_char *)"draft-27"}, + {0xff00001c, (u_char *)"draft-28"}, + {0xff00001d, (u_char *)"draft-29"}, + {0xff00001e, (u_char *)"draft-30"}, + {0xff00001f, (u_char *)"draft-31"}, + {0xff000020, (u_char *)"draft-32"}, + {0, NULL}}; + +static int quic_version_is_supported(uint32_t version) +{ + unsigned int i = 0; + for (i = 0; i < QUIC_NVERSIONS; i++) + { + if (quic_version_vals[i].len == version) + { + return 1; + } + } + + return 0; +} + +// Returns the QUIC draft version or 0 if not applicable. +static inline uint8_t quic_draft_version(uint32_t version) +{ + if ((version >> 8) == 0xff0000) + return (uint8_t)version; + + // Facebook mvfst, based on draft -22. + if (version == 0xfaceb001) + return 22; + + // Facebook mvfst, based on draft -27. + if (version == 0xfaceb002 || version == 0xfaceb00e) + return 27; + + // GQUIC Q050, T050 and T051: they are not really based on any drafts, + // but we must return a sensible value + if (version == 0x51303530 || version == 0x54303530 || version == 0x54303531) + return 27; + + /* + * https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15 + * "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; + + return 0; +} + +static inline uint8_t quic_draft_is_max(uint32_t version, uint8_t max_version) +{ + uint8_t draft_version = quic_draft_version(version); + return draft_version && draft_version <= max_version; +} + +/////////////////////////////////////////////////////////////////////////////// +// quic_parse_packet_header() +/////////////////////////////////////////////////////////////////////////////// + +static inline u_char *quic_parse_uint8(const u_char *pos, const u_char *end, uint8_t *out) +{ + if ((size_t)(end - pos) < 1) + { + return NULL; + } + + *out = *pos; + return (u_char *)pos + 1; +} + +static inline u_char *quic_parse_int(const u_char *pos, const u_char *end, uint64_t *out) +{ + u_char *p; + uint64_t value; + int len; + + if (pos >= end) + { + return NULL; + } + + p = (u_char *)pos; + len = 1 << (*p >> 6); + + value = *p++ & 0x3f; + + if ((size_t)(end - p) < (size_t)(len - 1)) + { + return NULL; + } + + while (--len) + { + value = (value << 8) + *p++; + } + + *out = value; + return p; +} + +static inline u_char *quic_parse_uint32(const u_char *pos, const u_char *end, uint32_t *out) +{ + if ((size_t)(end - pos) < sizeof(uint32_t)) + { + return NULL; + } + + *out = ((uint32_t)(pos)[0] << 24 | (pos)[1] << 16 | (pos)[2] << 8 | (pos)[3]); + return (u_char *)pos + sizeof(uint32_t); +} + +static inline u_char *quic_parse_nbytes(const u_char *pos, const u_char *end, size_t len, u_char **out) +{ + if ((size_t)(end - pos) < len) + { + return NULL; + } + + *out = (u_char *)pos; + return (u_char *)pos + len; +} + +static int quic_parse_short_header(quic_dpt_t *dpt, size_t dcid_len) +{ + u_char *p = dpt->pos; + u_char *end = dpt->data + dpt->len; + + if (!(dpt->flags & QUIC_PKT_FIXED_BIT)) + { + LOG_WARN("QUIC fixed bit is not set"); + return -1; + } + + p = quic_parse_nbytes(p, end, dcid_len, &dpt->dcid.data); + if (p == NULL) + { + LOG_WARN("QUIC packet is too small to read dcid"); + return -1; + } + dpt->dcid.len = dcid_len; + + dpt->pos = p; + return 0; +} + +static int quic_parse_long_header(quic_dpt_t *dpt) +{ + uint8_t idlen; + + u_char *p = dpt->pos; + u_char *end = dpt->data + dpt->len; + + // Parser Version + p = quic_parse_uint32(p, end, &dpt->version); + if (p == NULL) + { + LOG_WARN("QUIC packet is too small to read version"); + return -1; + } + + if (!(dpt->flags & QUIC_PKT_FIXED_BIT)) + { + LOG_WARN("QUIC fixed bit is not set"); + return -1; + } + + // Parser DCID + p = quic_parse_uint8(p, end, &idlen); + if (p == NULL) + { + LOG_WARN("QUIC packet is too small to read dcid len"); + return -1; + } + if (idlen > QUIC_MAX_CID_LEN) + { + LOG_WARN("QUIC packet dcid is too long"); + return -1; + } + dpt->dcid.len = idlen; + + p = quic_parse_nbytes(p, end, idlen, &dpt->dcid.data); + if (p == NULL) + { + LOG_WARN("QUIC packet is too small to read dcid"); + return -1; + } + + // Parser SCID + p = quic_parse_uint8(p, end, &idlen); + if (p == NULL) + { + LOG_WARN("QUIC packet is too small to read scid len"); + return -1; + } + if (idlen > QUIC_MAX_CID_LEN) + { + LOG_WARN("QUIC packet scid is too long"); + return -1; + } + + dpt->scid.len = idlen; + + p = quic_parse_nbytes(p, end, idlen, &dpt->scid.data); + if (p == NULL) + { + LOG_WARN("QUIC packet is too small to read scid"); + return -1; + } + + dpt->pos = p; + return 0; +} + +static int quic_parse_long_header_v1(quic_dpt_t *dpt) +{ + uint64_t varint; + + u_char *p = dpt->pos; + u_char *end = (u_char *)dpt->data + dpt->len; + + // ssl_encryption_initial + if (quic_pkt_level_is_initial(dpt->flags)) + { + dpt->level = ssl_encryption_initial; + if (dpt->len < QUIC_MIN_INITIAL_SIZE) + { + LOG_WARN("QUIC UDP datagram is too small for initial packet"); + return -1; + } + + // Parse Token + p = quic_parse_int(p, end, &varint); + if (p == NULL) + { + LOG_WARN("QUIC failed to parse token length"); + return -1; + } + dpt->token.len = varint; + + p = quic_parse_nbytes(p, end, dpt->token.len, &dpt->token.data); + if (p == NULL) + { + LOG_WARN("QUIC packet too small to read token data"); + return -1; + } + } + // ssl_encryption_early_data + else if (quic_pkt_level_is_zrtt(dpt->flags)) + { + dpt->level = ssl_encryption_early_data; + } + // ssl_encryption_handshake + else if (quic_pkt_level_is_handshake(dpt->flags)) + { + dpt->level = ssl_encryption_handshake; + } + else + { + LOG_WARN("QUIC bad packet type"); + return -1; + } + + // Parse Packet Length + p = quic_parse_int(p, end, &varint); + if (p == NULL) + { + LOG_WARN("QUIC bad packet length"); + return -1; + } + dpt->pkt_len = varint; + + if (varint > (uint64_t)((dpt->data + dpt->len) - p)) + { + LOG_WARN("QUIC truncated packet with level %s", quic_pkt_level_to_name(dpt->level)); + return -1; + } + + dpt->pos = p; + dpt->len = p + varint - dpt->data; + + return 0; +} + +static int quic_parse_packet_header(quic_dpt_t *dpt) +{ + if (quic_pkt_header_is_short(dpt->flags)) + { + dpt->header_type = SHORT; + dpt->level = ssl_encryption_application; + + // Parser DCID + if (quic_parse_short_header(dpt, QUIC_MAX_CID_LEN) != 0) + { + return -1; + } + + return 0; + } + + dpt->header_type = LONG; + + // Parser Version, DCID, SCID + if (quic_parse_long_header(dpt) != 0) + { + return -1; + } + + // Check QUIC Version + if (!quic_version_is_supported(dpt->version)) + { + LOG_WARN("QUIC version %08" PRIx32 "unsupport", dpt->version); + return -1; + } + + // Parser Level, Token, Packet Length + if (quic_parse_long_header_v1(dpt) != 0) + { + return -1; + } + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// quic_keys_set_initial_secret() +/////////////////////////////////////////////////////////////////////////////// + +static int hkdf_expand(quic_str_t *out, const EVP_MD *digest, const quic_str_t *prk, const quic_str_t *info) +{ + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) + { + LOG_ERROR("EVP_PKEY_CTX_new_id() failed"); + return -1; + } + + if (EVP_PKEY_derive_init(pctx) <= 0) + { + LOG_ERROR("EVP_PKEY_derive_init() failed"); + goto failed; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) + { + LOG_ERROR("EVP_PKEY_CTX_hkdf_mode() failed"); + goto failed; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) + { + LOG_ERROR("EVP_PKEY_CTX_set_hkdf_md() failed"); + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk->data, prk->len) <= 0) + { + LOG_ERROR("EVP_PKEY_CTX_set1_hkdf_key() failed"); + goto failed; + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info->data, info->len) <= 0) + { + LOG_ERROR("EVP_PKEY_CTX_add1_hkdf_info() failed"); + goto failed; + } + + if (EVP_PKEY_derive(pctx, out->data, &(out->len)) <= 0) + { + LOG_ERROR("EVP_PKEY_derive() failed"); + goto failed; + } + + EVP_PKEY_CTX_free(pctx); + return 0; + +failed: + + EVP_PKEY_CTX_free(pctx); + return -1; +} + +static int quic_hkdf_expand(const EVP_MD *digest, quic_str_t *out, const quic_str_t *label, const quic_str_t *prk) +{ + uint8_t info_buf[20]; + info_buf[0] = 0; + info_buf[1] = out->len; + info_buf[2] = label->len; + + uint8_t *p = (u_char *)memcpy(&info_buf[3], label->data, label->len) + label->len; + *p = '\0'; + + quic_str_t info; + info.len = 2 + 1 + label->len + 1; + info.data = info_buf; + + if (hkdf_expand(out, digest, prk, &info) != 0) + { + return -1; + } + + return 0; +} + +static int hkdf_extract(quic_str_t *out, const EVP_MD *digest, const quic_str_t *secret, const quic_str_t *initial_salt) +{ + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) + { + LOG_ERROR("EVP_PKEY_CTX_new_id() failed"); + return -1; + } + + if (EVP_PKEY_derive_init(pctx) <= 0) + { + LOG_ERROR("EVP_PKEY_derive_init() failed"); + goto failed; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) + { + LOG_ERROR("EVP_PKEY_CTX_hkdf_mode() failed"); + goto failed; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) + { + LOG_ERROR("EVP_PKEY_CTX_set_hkdf_md() failed"); + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret->data, secret->len) <= 0) + { + LOG_ERROR("EVP_PKEY_CTX_set1_hkdf_key() failed"); + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, initial_salt->data, initial_salt->len) <= 0) + { + LOG_ERROR("EVP_PKEY_CTX_set1_hkdf_salt() failed"); + goto failed; + } + + if (EVP_PKEY_derive(pctx, out->data, &(out->len)) <= 0) + { + LOG_ERROR("EVP_PKEY_derive() failed"); + goto failed; + } + + EVP_PKEY_CTX_free(pctx); + return 0; + +failed: + + EVP_PKEY_CTX_free(pctx); + return -1; +} + +static int quic_keys_set_initial_secret(quic_secret_t *client_secret, const quic_str_t *dcid, uint32_t version) +{ + unsigned int i; + const quic_str_t initial_salt_v1 = quic_string( + "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"); + const quic_str_t initial_salt_draft_22 = quic_string( + "\x7f\xbc\xdb\x0e\x7c\x66\xbb\xe9\x19\x3a\x96\xcd\x21\x51\x9e\xbd\x7a\x02\x64\x4a"); + const quic_str_t initial_salt_draft_23 = quic_string( + "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"); + const quic_str_t initial_salt_draft_29 = quic_string( + "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99"); + const quic_str_t initial_salt_draft_q50 = quic_string( + "\x50\x45\x74\xEF\xD0\x66\xFE\x2F\x9D\x94\x5C\xFC\xDB\xD3\xA7\xF0\xD3\xB5\x6B\x45"); + const quic_str_t initial_salt_draft_t50 = quic_string( + "\x7f\xf5\x79\xe5\xac\xd0\x72\x91\x55\x80\x30\x4c\x43\xa2\x36\x7c\x60\x48\x83\x10"); + const quic_str_t initial_salt_draft_t51 = quic_string( + "\x7a\x4e\xde\xf4\xe7\xcc\xee\x5f\xa4\x50\x6c\x19\x12\x4f\xc8\xcc\xda\x6e\x03\x3d"); + + const quic_str_t *initial_salt; + if (version == 0x51303530) + { + initial_salt = &initial_salt_draft_q50; + } + else if (version == 0x54303530) + { + initial_salt = &initial_salt_draft_t50; + } + else if (version == 0x54303531) + { + initial_salt = &initial_salt_draft_t51; + } + else if (quic_draft_is_max(version, 22)) + { + initial_salt = &initial_salt_draft_22; + } + else if (quic_draft_is_max(version, 28)) + { + initial_salt = &initial_salt_draft_23; + } + else if (quic_draft_is_max(version, 32)) + { + initial_salt = &initial_salt_draft_29; + } + else + { + initial_salt = &initial_salt_v1; + } + + /* + * RFC 9001, section 5. Packet Protection + * + * Initial packets use AEAD_AES_128_GCM. The hash function + * for HKDF when deriving initial secrets and keys is SHA-256. + */ + const EVP_MD *digest = EVP_sha256(); + + uint8_t is[SHA256_DIGEST_LENGTH] = {0}; + quic_str_t initial_secret; + initial_secret.data = is; + initial_secret.len = SHA256_DIGEST_LENGTH; + + // Use dcid and initial_salt get initial_secret + if (hkdf_extract(&initial_secret, digest, dcid, initial_salt) != 0) + { + return -1; + } + + struct + { + quic_str_t label; + quic_str_t *key; + quic_str_t *prk; + } seq[] = { + /* labels per RFC 9001, 5.1. Packet Protection Keys */ + {quic_string("tls13 client in"), &client_secret->secret, &initial_secret}, + {quic_string("tls13 quic key"), &client_secret->key, &client_secret->secret}, + {quic_string("tls13 quic iv"), &client_secret->iv, &client_secret->secret}, + {quic_string("tls13 quic hp"), &client_secret->hp, &client_secret->secret}, + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) + { + if (quic_hkdf_expand(digest, seq[i].key, &seq[i].label, seq[i].prk) != 0) + { + return -1; + } + } + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// quic_deprotection_packet() +/////////////////////////////////////////////////////////////////////////////// + +static int quic_deprotection_header(const EVP_CIPHER *cipher, const quic_secret_t *secret, u_char *out, const u_char *in) +{ + int outlen; + u_char zero[QUIC_HP_LEN] = {0}; + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + { + return -1; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, secret->hp.data, in) != 1) + { + LOG_ERROR("EVP_EncryptInit_ex() failed"); + goto failed; + } + + if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, QUIC_HP_LEN)) + { + LOG_ERROR("EVP_EncryptUpdate() failed"); + goto failed; + } + + if (!EVP_EncryptFinal_ex(ctx, out + QUIC_HP_LEN, &outlen)) + { + LOG_ERROR("EVP_EncryptFinal_ex() failed"); + goto failed; + } + + EVP_CIPHER_CTX_free(ctx); + return 0; + +failed: + + EVP_CIPHER_CTX_free(ctx); + return -1; +} + +static uint64_t quic_deprotection_pktnum(u_char **pos, int len, const u_char *mask, uint64_t *largest_pkt_num) +{ + u_char *p; + uint64_t truncated_pn, expected_pn, candidate_pn; + uint64_t pn_nbits, pn_win, pn_hwin, pn_mask; + + pn_nbits = MIN(len * 8, 62); + + p = *pos; + truncated_pn = *p++ ^ *mask++; + + while (--len) + { + truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++); + } + + *pos = p; + + expected_pn = *largest_pkt_num + 1; + pn_win = 1ULL << pn_nbits; + pn_hwin = pn_win / 2; + pn_mask = pn_win - 1; + + candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; + + if ((int64_t)candidate_pn <= (int64_t)(expected_pn - pn_hwin) && candidate_pn < (1ULL << 62) - pn_win) + { + candidate_pn += pn_win; + } + else if (candidate_pn > expected_pn + pn_hwin && candidate_pn >= pn_win) + { + candidate_pn -= pn_win; + } + + *largest_pkt_num = MAX((int64_t)*largest_pkt_num, (int64_t)candidate_pn); + + return candidate_pn; +} + +static void quic_deprotection_nonce(u_char *nonce, size_t len, uint64_t pkt_num) +{ + nonce[len - 4] ^= (pkt_num & 0xff000000) >> 24; + nonce[len - 3] ^= (pkt_num & 0x00ff0000) >> 16; + nonce[len - 2] ^= (pkt_num & 0x0000ff00) >> 8; + nonce[len - 1] ^= (pkt_num & 0x000000ff); +} + +static int quic_deprotection_payload(const EVP_CIPHER *cipher, const quic_secret_t *secret, quic_str_t *out, const u_char *nonce, const quic_str_t *in, const quic_str_t *ad) +{ + int len; + u_char *tag; + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + { + LOG_ERROR("EVP_CIPHER_CTX_new() failed"); + return -1; + } + + if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) + { + EVP_CIPHER_CTX_free(ctx); + LOG_ERROR("EVP_DecryptInit_ex() failed"); + return -1; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, secret->iv.len, NULL) == 0) + { + EVP_CIPHER_CTX_free(ctx); + LOG_ERROR("EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return -1; + } + + if (EVP_DecryptInit_ex(ctx, NULL, NULL, secret->key.data, nonce) != 1) + { + EVP_CIPHER_CTX_free(ctx); + LOG_ERROR("EVP_DecryptInit_ex() failed"); + return -1; + } + + if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) + { + EVP_CIPHER_CTX_free(ctx); + LOG_ERROR("EVP_DecryptUpdate() failed"); + return -1; + } + + if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, in->len - EVP_GCM_TLS_TAG_LEN) != 1) + { + EVP_CIPHER_CTX_free(ctx); + LOG_ERROR("EVP_DecryptUpdate() failed"); + return -1; + } + + out->len = len; + tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) == 0) + { + EVP_CIPHER_CTX_free(ctx); + LOG_ERROR("EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); + return -1; + } + + if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) + { + EVP_CIPHER_CTX_free(ctx); + LOG_ERROR("EVP_DecryptFinal_ex failed"); + return -1; + } + + out->len += len; + EVP_CIPHER_CTX_free(ctx); + + return 0; +} + +static int quic_deprotection_packet(quic_dpt_t *dpt) +{ + u_char *p, *sample; + size_t len; + uint64_t pkt_num, lpn; + int pnl, rc, key_phase; + quic_str_t in, ad; + uint8_t nonce[QUIC_IV_LEN] = {0}, mask[QUIC_HP_LEN] = {0}; + + // Init ciphers, only process the quic package whose level is init, so only use AES_128_GCM_SHA256 + quic_ciphers_t ciphers; + ciphers.c = EVP_aes_128_gcm(); + ciphers.hp = EVP_aes_128_ctr(); + ciphers.d = EVP_sha256(); + + // 使用 client_secret secrets + quic_secret_t *secret = &dpt->client_secret; + + p = dpt->pos; + len = dpt->data + dpt->len - p; + + /* + * RFC 9001, 5.4.2. Header Protection Sample + * 5.4.3. AES-Based Header Protection + * 5.4.4. ChaCha20-Based Header Protection + * + * the Packet Number field is assumed to be 4 bytes long + * AES and ChaCha20 algorithms sample 16 bytes + */ + + if (len < EVP_GCM_TLS_TAG_LEN + 4) + { + LOG_WARN("QUIC payload length %zu too small", len); + return -1; + } + + sample = p + 4; + + /****************************************************** + * header protection + ******************************************************/ + + // Use the ciphers.hp algorithm and the secret.HP to encrypt the data in the sample and store it in the mask + if (quic_deprotection_header(ciphers.hp, secret, mask, sample) != 0) + { + return -1; + } + + // Parse Flags After Decode + dpt->flags ^= mask[0] & quic_pkt_hp_mask(dpt->flags); + + if (quic_pkt_header_is_short(dpt->flags)) + { + key_phase = (dpt->flags & QUIC_PKT_KPHASE) != 0; + + if (key_phase != dpt->key_phase) + { + // TODO + LOG_WARN("key need update !!!!"); + } + } + + lpn = dpt->largest_pkt_num; + + // RFC pn_length = (packet[0] & 0x03) + 1 + // Parser Packet Number length + pnl = (dpt->flags & 0x03) + 1; + + // Parser Packet number + pkt_num = quic_deprotection_pktnum(&p, pnl, &mask[1], &lpn); + dpt->pkt_num = pkt_num; + + /****************************************************** + * packet protection + ******************************************************/ + + in.data = p; + in.len = len - pnl; + + ad.len = p - dpt->data; + ad.data = dpt->plaintext; + + memcpy(ad.data, dpt->data, ad.len); + ad.data[0] = dpt->flags; + + do + { + ad.data[ad.len - pnl] = pkt_num >> (8 * (pnl - 1)) % 256; + } while (--pnl); + + memcpy(nonce, secret->iv.data, secret->iv.len); + quic_deprotection_nonce(nonce, sizeof(nonce), pkt_num); + + dpt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; + dpt->payload.data = dpt->plaintext + ad.len; + + // Parser payload + rc = quic_deprotection_payload(ciphers.c, secret, &dpt->payload, nonce, &in, &ad); + if (rc != 0) + { + return -1; + } + + if (dpt->payload.len == 0) + { + /* + * RFC 9000, 12.4. Frames and Frame Types + * + * An endpoint MUST treat receipt of a packet containing no + * frames as a connection error of type PROTOCOL_VIOLATION. + */ + LOG_WARN("QUIC zero-length packet"); + return -1; + } + + if (dpt->flags & quic_pkt_rb_mask(dpt->flags)) + { + /* + * RFC 9000, Reserved Bits + * + * An endpoint MUST treat receipt of a packet that has + * a non-zero value for these bits, after removing both + * packet and header protection, as a connection error + * of type PROTOCOL_VIOLATION. + */ + LOG_WARN("QUIC reserved bit set in packet"); + return -1; + } + + // Set Largest Packet Number + dpt->largest_pkt_num = lpn; + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// debug +/////////////////////////////////////////////////////////////////////////////// + +static void quic_str_to_hex(u_char *data, size_t len) +{ + for (unsigned int i = 0; i < len; i++) + { + printf("%02x", data[i]); + } + printf("\n"); +} + +static u_char *quic_version_to_str(uint32_t version) +{ + unsigned int i = 0; + for (i = 0; i < QUIC_NVERSIONS; i++) + { + if (quic_version_vals[i].len == version) + { + return quic_version_vals[i].data; + } + } + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// +// PUB API +/////////////////////////////////////////////////////////////////////////////// + +quic_dpt_t *quic_deprotection_new(void) +{ + quic_dpt_t *dpt = (quic_dpt_t *)calloc(1, sizeof(quic_dpt_t)); + + // Allocate the space of length len + 1, instead of allocating the space of length len, mainly for the convenience of debugging printf + dpt->client_secret.secret.len = SHA256_DIGEST_LENGTH; + dpt->client_secret.secret.data = (u_char *)calloc(SHA256_DIGEST_LENGTH + 1, sizeof(u_char)); + + dpt->client_secret.key.len = QUIC_AES_128_KEY_LEN; + dpt->client_secret.key.data = (u_char *)calloc(QUIC_AES_128_KEY_LEN + 1, sizeof(u_char)); + + dpt->client_secret.hp.len = QUIC_AES_128_KEY_LEN; + dpt->client_secret.hp.data = (u_char *)calloc(QUIC_AES_128_KEY_LEN + 1, sizeof(u_char)); + + dpt->client_secret.iv.len = QUIC_IV_LEN; + dpt->client_secret.iv.data = (u_char *)calloc(QUIC_IV_LEN + 1, sizeof(u_char)); + + // NOTE: QUIC_MAX_UDP_PAYLOAD_SIZE is 65527(form Nginx-quice offical) + dpt->plaintext = (u_char *)calloc(QUIC_MAX_UDP_PAYLOAD_SIZE + 1, sizeof(u_char)); + + return dpt; +} + +void quic_deprotection_free(quic_dpt_t *dpt) +{ + if (dpt == NULL) + { + return; + } + + if (dpt->client_secret.secret.data) + { + free(dpt->client_secret.secret.data); + dpt->client_secret.secret.data = NULL; + dpt->client_secret.secret.len = 0; + } + + if (dpt->client_secret.key.data) + { + free(dpt->client_secret.key.data); + dpt->client_secret.key.data = NULL; + dpt->client_secret.key.len = 0; + } + + if (dpt->client_secret.hp.data) + { + free(dpt->client_secret.hp.data); + dpt->client_secret.hp.data = NULL; + dpt->client_secret.hp.len = 0; + } + + if (dpt->client_secret.iv.data) + { + free(dpt->client_secret.iv.data); + dpt->client_secret.iv.data = NULL; + dpt->client_secret.iv.len = 0; + } + + if (dpt->plaintext) + { + free(dpt->plaintext); + dpt->plaintext = NULL; + } + + free(dpt); + dpt = NULL; +} + +void quic_deprotection_dump(quic_dpt_t *dpt) +{ + printf("QUIC version : %08" PRIx32 " %s\n", dpt->version, quic_version_to_str(dpt->version)); + printf("QUIC type : %s\n", dpt->header_type == LONG ? "long" : "short"); + printf("QUIC level : %s\n", quic_pkt_level_to_name(dpt->level)); + printf("QUIC flags : %x\n", dpt->flags); + printf("QUIC num : %" PRIu64 "\n", dpt->pkt_num); + printf("QUIC len : %" PRIu64 "\n", dpt->pkt_len); + printf("QUIC larg_num : %" PRIu64 "\n", dpt->largest_pkt_num); + + printf("QUIC dcid : %zu, ", dpt->dcid.len); + quic_str_to_hex(dpt->dcid.data, dpt->dcid.len); + + printf("QUIC scid : %zu, ", dpt->scid.len); + quic_str_to_hex(dpt->scid.data, dpt->scid.len); + + printf("QUIC token : %zu, ", dpt->token.len); + quic_str_to_hex(dpt->token.data, dpt->token.len); + + printf("QUIC hp : %zu, ", dpt->client_secret.hp.len); + quic_str_to_hex(dpt->client_secret.hp.data, dpt->client_secret.hp.len); + + printf("QUIC iv : %zu, ", dpt->client_secret.iv.len); + quic_str_to_hex(dpt->client_secret.iv.data, dpt->client_secret.iv.len); + + printf("QUIC key : %zu, ", dpt->client_secret.key.len); + quic_str_to_hex(dpt->client_secret.key.data, dpt->client_secret.key.len); + + printf("QUIC secretn : %zu, ", dpt->client_secret.secret.len); + quic_str_to_hex(dpt->client_secret.secret.data, dpt->client_secret.secret.len); + + printf("QUIC plaintex : %zu, ", strlen((char *)dpt->plaintext)); + quic_str_to_hex(dpt->plaintext, strlen((char *)dpt->plaintext)); + + printf("QUIC payload : %zu, ", dpt->payload.len); + quic_str_to_hex(dpt->payload.data, dpt->payload.len); +} + +int quic_deprotection(quic_dpt_t *dpt, const u_char *payload, size_t payload_len) +{ + dpt->data = (u_char *)payload; + dpt->len = payload_len; + dpt->pos = (u_char *)dpt->data; + dpt->flags = payload[0]; + dpt->pos++; + dpt->largest_pkt_num = QUIC_UNSET_PN; + + // Parser Level, Version, DCID, SCID, Token, Packet Length + if (quic_parse_packet_header(dpt) == -1) + { + return -1; + } + + if (dpt->level != ssl_encryption_initial) + { + LOG_WARN("QUIC level %s ignoring", quic_pkt_level_to_name(dpt->level)) + return -1; + } + + /* + * 1.Get the initial_salt to be used through pkt_version + * 2.DCID and initial_salt generate initial_secret through SHA-256 + * 3.IN/Key/IV/HP lable of initial_secret and client_secret generates HP/Key/IV key of client_secret + */ + if (quic_keys_set_initial_secret(&dpt->client_secret, &dpt->dcid, dpt->version) == -1) + { + goto failed; + } + + if (dpt->client_secret.key.len == 0) + { + LOG_WARN("QUIC packet no client_secret, ignoring"); + goto failed; + } + + if (quic_deprotection_packet(dpt) == -1) + { + goto failed; + } + + // deprotection data: dpt->payload.data + + return 0; + +failed: + + return -1; +}
\ No newline at end of file diff --git a/decoders/quic/quic_deprotection.h b/decoders/quic/quic_deprotection.h new file mode 100644 index 0000000..525e398 --- /dev/null +++ b/decoders/quic/quic_deprotection.h @@ -0,0 +1,111 @@ +#pragma once +#ifdef __cpluscplus +extern "C" +{ +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/stat.h> +#include <arpa/inet.h> + +#ifdef DEBUG_SWITCH + +#define LOG_DEBUG(format, ...) \ + { \ + fprintf(stdout, format "\n", ##__VA_ARGS__); \ + fflush(stdout); \ + } + +#define LOG_WARN(format, ...) \ + { \ + fprintf(stderr, format "\n", ##__VA_ARGS__); \ + fflush(stderr); \ + } + +#define LOG_ERROR(format, ...) \ + { \ + fprintf(stderr, format "\n", ##__VA_ARGS__); \ + fflush(stderr); \ + } + +#else + +#define LOG_DEBUG(format, ...) +#define LOG_WARN(format, ...) +#define LOG_ERROR(format, ...) + +#endif + +#define QUIC_MAX_UDP_PAYLOAD_SIZE 1460 + +#define quic_string(str) \ + { \ + sizeof(str) - 1, (u_char *)str} + + typedef struct + { + size_t len; + u_char *data; + } quic_str_t; + + typedef struct quic_secret_s + { + quic_str_t secret; + quic_str_t key; + quic_str_t iv; + quic_str_t hp; + } quic_secret_t; + + typedef enum + { + ssl_encryption_initial = 0, + ssl_encryption_early_data = 1, + ssl_encryption_handshake = 2, + ssl_encryption_application = 3, + } ssl_encryption_level_t; + + typedef enum + { + LONG = 0, + SHORT = 1, + } quic_header_type; + + typedef struct + { + quic_secret_t client_secret; + ssl_encryption_level_t level; // QUIC Packet Process Level + quic_header_type header_type; // QUIC Packet Header Type + + uint32_t version; // QUIC Version + uint8_t flags; // QUIC Flags + u_char *data; // QUIC Packet Data + size_t len; // QUIC Packet Length + u_char *pos; // Process Ptr + uint64_t largest_pkt_num; + + quic_str_t dcid; // QUIC DCID + quic_str_t scid; // QUIC SCID + quic_str_t token; // QUIC TOKEN + + size_t pkt_len; + uint64_t pkt_num; // QUIC Packet Number + u_char *plaintext; + quic_str_t payload; // Decrypted data + + unsigned key_phase : 1; + } quic_dpt_t; + + quic_dpt_t *quic_deprotection_new(void); + void quic_deprotection_free(quic_dpt_t *dpt); + void quic_deprotection_dump(quic_dpt_t *dpt); + int quic_deprotection(quic_dpt_t *dpt, const u_char *payload, size_t payload_len); + +#ifdef __cpluscplus +} +#endif diff --git a/decoders/quic/quic_module.c b/decoders/quic/quic_module.c new file mode 100644 index 0000000..e564019 --- /dev/null +++ b/decoders/quic/quic_module.c @@ -0,0 +1,97 @@ +#include <stdio.h> +#include <assert.h> +#include "stellar/session.h" +#include "stellar/module.h" +#include "quic_util.h" +#include "quic_process.h" + +#define QUIC_TOPIC_NAME_CLIENT_HELLO "QUIC_TOPIC_CLIENT_HELLO" +#define QUIC_TOPIC_NAME_ENCRYPTED_PAYLOAD "QUIC_TOPIC_ENCRYPTED_PAYLOAD" + +static int quic_create_topic_nx(struct module_manager *mod_mgr, const char *topic_name) +{ + struct mq_schema *mq_s = module_manager_get_mq_schema(mod_mgr); + assert(mq_s != NULL); + int quic_topic_id = mq_schema_get_topic_id(mq_s, topic_name); + if (quic_topic_id >= 0) + { + return quic_topic_id; + } + int topic_id = mq_schema_create_topic(mq_s, topic_name, quic_on_msg_dispatch, NULL, quic_msg_free_cb, NULL); + return topic_id; +} + +static struct fieldstat_easy *quick_get_fs4_ins(struct module_manager *mod_mgr) +{ + // todo: use stellar global fieldstat4 instance �� + struct fieldstat_easy *fs4 = fieldstat_easy_new(module_manager_get_max_thread_num(mod_mgr), "QUIC", NULL, 0); + fieldstat_easy_enable_auto_output(fs4, "./log/quic.fs4", 1); + return fs4; +} + +int quic_subscribe_client_hello(struct quic *quic_env, quic_on_client_hello_callback *cb, void *args) +{ + assert(quic_env != NULL); + struct module_manager *mod_mgr = quic_env->mod_mgr_ref; + struct mq_schema *mq_s = module_manager_get_mq_schema(mod_mgr); + int quic_topi_id = quic_create_topic_nx(mod_mgr, QUIC_TOPIC_NAME_CLIENT_HELLO); + return mq_schema_subscribe(mq_s, quic_topi_id, (on_msg_cb_func *)((void *)cb), args); +} + +void quic_exit(struct module_manager *mod_mgr, struct module *mod) +{ + if (mod_mgr == NULL || mod == NULL) + { + return; + } + struct quic *quic_env = (struct quic *)module_get_ctx(mod); + fieldstat_easy_free(quic_env->stat.fs4_ins); + FREE(quic_env); + module_free(mod); +} + +struct quic *quic_module_to_quic(struct module *quic_mod) +{ + assert(quic_mod); + return (struct quic *)module_get_ctx(quic_mod); +} + +struct module *quic_init(struct module_manager *mod_mgr) +{ + assert(mod_mgr != NULL); + struct quic *quic_env = (struct quic *)CALLOC(1, sizeof(struct quic)); + quic_load_config(module_manager_get_toml_path(mod_mgr), &quic_env->config); + quic_env->stat.fs4_ins = quick_get_fs4_ins(mod_mgr); + quic_stat_init(&quic_env->stat); + struct module *mod = module_new(QUIC_MODULE_NAME, quic_env); + quic_env->mod_mgr_ref = mod_mgr; + + quic_env->logger_ref = module_manager_get_logger(mod_mgr); + assert(quic_env->logger_ref != NULL); + struct module *sess_mod = module_manager_get_module(mod_mgr, SESSION_MANAGER_MODULE_NAME); + struct session_manager *sess_mgr = module_to_session_manager(sess_mod); + assert(sess_mgr != NULL); + + session_manager_subscribe_udp(sess_mgr, quic_on_udp_msg_cb, quic_env); + quic_env->exdata_id = session_manager_new_session_exdata_index(sess_mgr, QUIC_EXDATA_NAME, quic_exdata_free_cb, quic_env); + quic_env->quic_topic_chlo_id = quic_create_topic_nx(mod_mgr, QUIC_TOPIC_NAME_CLIENT_HELLO); + if (quic_env->quic_topic_chlo_id < 0) + { + STELLAR_LOG_FATAL(quic_env->logger_ref, QUIC_MODULE_NAME, "quic create topic:%s failed", QUIC_TOPIC_NAME_CLIENT_HELLO); + goto fail_exit; + } + quic_env->quic_topic_encrypt_id = quic_create_topic_nx(mod_mgr, QUIC_TOPIC_NAME_ENCRYPTED_PAYLOAD); + if (quic_env->quic_topic_encrypt_id < 0) + { + STELLAR_LOG_FATAL(quic_env->logger_ref, QUIC_MODULE_NAME, "quic create topic:%s failed", QUIC_TOPIC_NAME_ENCRYPTED_PAYLOAD); + goto fail_exit; + } + STELLAR_LOG_FATAL(quic_env->logger_ref, QUIC_MODULE_NAME, + "quic init success, config: chlo_frag_max_size=%u, parse_packet_max=%u", + quic_env->config.chlo_frag_max_size, quic_env->config.parse_packet_max); + return mod; + +fail_exit: + quic_exit(mod_mgr, mod); + return NULL; +}
\ No newline at end of file diff --git a/decoders/quic/quic_process.c b/decoders/quic/quic_process.c new file mode 100644 index 0000000..a1ee869 --- /dev/null +++ b/decoders/quic/quic_process.c @@ -0,0 +1,386 @@ +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <sys/time.h> +#include <arpa/inet.h> +#include "quic_process.h" +#include "quic_decoder.h" +#include "quic_deprotection.h" +#include "toml/toml.h" + +static void quic_set_default_config(struct quic_config *config) +{ + config->chlo_frag_max_size = QUIC_CHLO_FRAG_MAX_SIZE; + config->parse_packet_max = QUIC_PARSE_FIRST_N_PACKETS; +} + +int quic_load_config(const char *file, struct quic_config *config) +{ + int ret = -1; + char errbuf[200]; + const char *ptr; + FILE *fp = NULL; + toml_table_t *root = NULL; + toml_table_t *sub = NULL; + + memset(config, 0, sizeof(struct quic_config)); + quic_set_default_config(config); + + fp = fopen(file, "r"); + if (fp == NULL) + { + goto error_out; + } + root = toml_parse_file(fp, errbuf, sizeof(errbuf)); + if (root == NULL) + { + goto error_out; + } + + sub = toml_table_in(root, "quic"); + if (sub == NULL) + { + goto error_out; + } + + ptr = toml_raw_in(sub, "chlo_frag_max_size"); + if (ptr != NULL) + { + config->chlo_frag_max_size = (uint32_t)atoi(ptr); + } + ptr = toml_raw_in(sub, "parse_packet_max"); + if (ptr != NULL) + { + config->parse_packet_max = (uint32_t)atoi(ptr); + } +error_out: + if (root) + { + toml_free(root); + } + if (fp) + { + fclose(fp); + } + return ret; +} + +int quic_stat_init(struct quic_stat *qstat) +{ + qstat->fs4_session_cnt_id = fieldstat_easy_register_counter(qstat->fs4_ins, "QUIC_SESS_CNT"); + qstat->fs4_packet_cnt_id = fieldstat_easy_register_counter(qstat->fs4_ins, "QUIC_PKT_CNT"); + qstat->fs4_packet_size_id = fieldstat_easy_register_counter(qstat->fs4_ins, "QUIC_PKT_SIZE"); + qstat->fs4_quic_version_id = fieldstat_easy_register_counter(qstat->fs4_ins, "QUIC_VERSION"); + return 0; +} + +void quic_stat_update_cnt(struct quic_stat *qstat, int thread_idx, enum quic_stat_type type, long long value) +{ + switch (type) + { + case QUIC_STAT_SESSION_CNT: + fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_session_cnt_id, NULL, 0, value); + break; + case QUIC_STAT_PACKET_CNT: + fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_packet_cnt_id, NULL, 0, value); + break; + case QUIC_STAT_PACKET_SIZE: + fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_packet_size_id, NULL, 0, value); + break; + case QUIC_STAT_VERSION: + fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_quic_version_id, NULL, 0, value); + break; + default: + break; + } +} + +void quic_stat_update_version(struct quic_stat *qstat, int thread_idx, uint32_t quic_version, long long value) +{ + struct field version_tag = {}; + char version_str[128] = {0}; + quic_version_to_readable_string(quic_version, version_str, sizeof(version_str)); + version_tag.key = "ver"; + version_tag.type = FIELD_VALUE_CSTRING; + version_tag.value_str = version_str; + fieldstat_easy_counter_incrby(qstat->fs4_ins, thread_idx, qstat->fs4_quic_version_id, &version_tag, 1, value); +} + +int quic_get_topic_id(const struct quic *quic_env, enum quic_topic_type topic_type) +{ + switch (topic_type) + { + case QUIC_TOPIC_CLIENT_HELLO: + return quic_env->quic_topic_chlo_id; + case QUIC_TOPIC_ENCRYPTED_PAYLOAD: + return quic_env->quic_topic_encrypt_id; + default: + assert(0); + return -1; + } + return -1; +} + +static struct quic_message *quic_create_message(enum quic_topic_type topic_type, struct session *sess, struct quic_exdata *quic_ext, void *data) +{ + struct quic_message *qmsg = (struct quic_message *)CALLOC(1, sizeof(struct quic_message)); +#ifndef NDEBUG + qmsg->magic = QUIC_MSG_HDR_MAGIC; +#endif + qmsg->topic_type = topic_type; + qmsg->quic_ext = quic_ext; + qmsg->sess_ref = sess; + qmsg->topic_data = data; + return qmsg; +} + +static void free_quicinfo(struct quic_info *quic_info) +{ + FREE(quic_info->chlo.sni); + quic_info->chlo.sni_len = 0; + FREE(quic_info->chlo.user_agent); + quic_info->chlo.user_agent_len = 0; +} + +void quic_msg_free_cb(void *msg, void *msg_free_arg UNUSED) +{ + assert(msg != NULL); + struct quic_message *qmsg = (struct quic_message *)msg; + assert(QUIC_MSG_HDR_MAGIC == qmsg->magic); + if (qmsg->quic_ext) + { + free_quicinfo(&qmsg->quic_ext->quic_info); + } + FREE(msg); +} + +void quic_session_mq_publish_message_safe(const struct quic *quic_env, struct quic_message *qmsg) +{ + struct mq_runtime *mq_rt = module_manager_get_mq_runtime(quic_env->mod_mgr_ref); + int ret = mq_runtime_publish_message(mq_rt, quic_get_topic_id(quic_env, qmsg->topic_type), qmsg); + assert(ret >= 0); + if (ret < 0) + { + STELLAR_LOG_FATAL(quic_env->logger_ref, QUIC_MODULE_NAME, "quic mq_runtime_publish_message fail, ret=%d", ret); + quic_msg_free_cb(qmsg, NULL); + } + return; +} + +struct quic_exdata *quic_exdata_new(void) +{ + struct quic_exdata *quic_ext = (struct quic_exdata *)CALLOC(1, sizeof(struct quic_exdata)); + return quic_ext; +} + +static struct quic_exdata *quic_session_new(int thread_idx, struct session *sess, struct quic *quic_env, const char *payload, uint16_t payload_len) +{ + struct quic_exdata *quic_ext = quic_exdata_new(); + session_set_exdata(sess, quic_env->exdata_id, quic_ext); + int payload_offset = 0; + enum QUIC_VERSION_T qver = is_quic_protocol(payload, payload_len, &payload_offset); + if (QUIC_VERSION_UNKNOWN == qver) + { + quic_ext->ignore_session = 1; + return quic_ext; + } + quic_stat_update_cnt(&quic_env->stat, thread_idx, QUIC_STAT_SESSION_CNT, 1); + return quic_ext; +} + +void quic_on_udp_msg_cb(struct session *sess, enum session_state state UNUSED, struct packet *pkt, void *plugin_env) +{ + struct quic *quic_env = (struct quic *)plugin_env; + struct quic_exdata *quic_ext = (struct quic_exdata *)session_get_exdata(sess, quic_env->exdata_id); + uint16_t payload_len = packet_get_payload_len(pkt); + const char *payload = packet_get_payload_data(pkt); + if (NULL == payload || payload_len == 0) + { + return; + } + int thread_idx = module_manager_get_thread_id(quic_env->mod_mgr_ref); + if (NULL == quic_ext) + { + quic_ext = quic_session_new(thread_idx, sess, quic_env, payload, payload_len); + assert(quic_ext != NULL); + } + + if (quic_ext->ignore_session) // quic path + { + return; + } + // int thread_seq = module_manager_get_thread_id(quic_env->mod_mgr_ref); + quic_analyze_entry(sess, quic_env, quic_ext, payload, payload_len); + return; +} + +void quic_exdata_free_cb(int idx UNUSED, void *ex_ptr, void *arg UNUSED) +{ + struct quic_exdata *quic_ext = (struct quic_exdata *)ex_ptr; + free_quicinfo(&quic_ext->quic_info); + FREE(quic_ext->quic_buf.buffer); + FREE(ex_ptr); +} + +void quic_on_msg_dispatch(int topic_id UNUSED, void *msg, on_msg_cb_func *on_msg_cb, + void *on_msg_cb_arg, void *dispatch_arg UNUSED) +{ + assert(msg != NULL && on_msg_cb != NULL); + struct quic_message *qmsg = (struct quic_message *)msg; + switch (qmsg->topic_type) + { + case QUIC_TOPIC_CLIENT_HELLO: + { + quic_on_client_hello_callback *on_chlo_cb = (quic_on_client_hello_callback *)((void *)on_msg_cb); + on_chlo_cb(qmsg->sess_ref, qmsg->quic_ext->quic_info.version, (struct quic_client_hello *)qmsg->topic_data, on_msg_cb_arg); + } + break; + // case QUIC_TOPIC_ENCRYPTED_PAYLOAD: + // { + // quic_on_encrypted_payload_callback *on_encrypt_cb = (quic_on_encrypted_payload_callback *)((void *)on_msg_cb); + // on_encrypt_cb(qmsg->sess_ref, (struct quic_encrypted_payload *)qmsg->topic_data, on_msg_cb_arg); + // } + break; + default: + break; + } +} + +static void quic_chlo_chunk_assemble(struct quic_buffer *qbuf, unsigned char *new_payload, int new_len) +{ + if (NULL == new_payload || new_len <= 0) + { + return; + } + if (NULL == qbuf->buffer) + { + qbuf->buffer = (unsigned char *)calloc(1, QUIC_CHLO_FRAG_MAX_SIZE); + qbuf->max_size = QUIC_CHLO_FRAG_MAX_SIZE; + } + int max_copy_len = MIN((int)(qbuf->max_size - qbuf->datalen), new_len); + memcpy(qbuf->buffer + qbuf->datalen, new_payload, max_copy_len); + qbuf->datalen += max_copy_len; +} + +static enum PARSE_RESULT quic_decrypting_payload(struct quic_exdata *qcontext, unsigned char *udp_payload, int udp_payload_len, uint32_t chlo_frag_max_size) +{ + struct quic_buffer *qbuf = &qcontext->quic_buf; + unsigned char *quic_decrypted_payload; + int ret, quic_decrypted_payload_len; + quic_dpt_t *dpt = quic_deprotection_new(); + + if (quic_deprotection(dpt, (const uint8_t *)udp_payload, udp_payload_len) < 0) + { + goto err_exit; + } + + if (qbuf->datalen > 0) + { // some fragment exist + quic_chlo_chunk_assemble(qbuf, dpt->payload.data, dpt->payload.len); + quic_decrypted_payload = qbuf->buffer; + quic_decrypted_payload_len = qbuf->datalen; + } + else + { + quic_decrypted_payload = dpt->payload.data; + quic_decrypted_payload_len = dpt->payload.len; + } + + ret = qk_chlo_is_complete((enum QUIC_VERSION_T)qcontext->quic_info.version, quic_decrypted_payload, quic_decrypted_payload_len); + if (0 == ret) + { + if (NULL == qbuf->buffer || 0 == qbuf->datalen) + { + quic_chlo_chunk_assemble(qbuf, dpt->payload.data, dpt->payload.len); + } + goto fun_exit; + } + parse_quic_decrypted_payload(&qcontext->quic_info, quic_decrypted_payload, quic_decrypted_payload_len, chlo_frag_max_size); + +fun_exit: + quic_deprotection_free(dpt); + return PARSE_RESULT_VERSION; + +err_exit: + quic_deprotection_free(dpt); + return PARSE_RESULT_UNKNOWN; +} + +static enum PARSE_RESULT quic_parse_all_version(struct quic_exdata *qcontext, const char *payload, int payload_len, uint32_t chlo_frag_max_size) +{ + struct quic_info *quic_info = &qcontext->quic_info; + int payload_offset = 0; + enum QUIC_VERSION_T quic_version = QUIC_VERSION_UNKNOWN; + + if (payload == NULL || payload_len <= 0) + { + return PARSE_RESULT_UNKNOWN; + } + quic_version = is_quic_protocol(payload, payload_len, &payload_offset); + if (quic_version == QUIC_VERSION_UNKNOWN) + { + return PARSE_RESULT_UNKNOWN; + } + quic_info->version = (uint32_t)quic_version; + + if (quic_version >= GQUIC_VERSION_Q001 && quic_version <= GQUIC_VERSION_Q048) + { + if (payload_len > payload_offset) + { + return (enum PARSE_RESULT)parse_quic_uncryption_payload(quic_info, (const unsigned char *)payload + payload_offset, payload_len - payload_offset); + } + return PARSE_RESULT_VERSION; + } + else if ((quic_version >= MVFST_VERSION_00 && quic_version <= MVFST_VERSION_0F) || + (quic_version >= GQUIC_VERSION_Q049 && quic_version <= GQUIC_VERSION_Q059) || + (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 quic_decrypting_payload(qcontext, (unsigned char *)payload, payload_len, chlo_frag_max_size); + } + else + { + return PARSE_RESULT_VERSION; + } + + return PARSE_RESULT_VERSION; +} + +void quic_analyze_entry(struct session *sess, struct quic *quic_env, struct quic_exdata *qcontext, const char *payload, size_t payload_len) +{ + int thread_idx = module_manager_get_thread_id(quic_env->mod_mgr_ref); + struct quic_message *qmsg; + qcontext->parse_pkt_num++; + qcontext->parse_payload_size += payload_len; + if (qcontext->parse_payload_size > (unsigned int)QUIC_CHLO_FRAG_MAX_SIZE || qcontext->parse_pkt_num > QUIC_PARSE_FIRST_N_PACKETS) + { + if (0 == qcontext->pub_chlo_msg_flag && qcontext->quic_info.version != QUIC_VERSION_UNKNOWN) + { + qmsg = quic_create_message(QUIC_TOPIC_CLIENT_HELLO, sess, qcontext, &qcontext->quic_info.chlo); + quic_session_mq_publish_message_safe(quic_env, qmsg); + qcontext->pub_chlo_msg_flag = 1; + } + } + else + { + quic_parse_all_version(qcontext, payload, payload_len, quic_env->config.chlo_frag_max_size); + if (PARSE_RESULT_UNKNOWN == qcontext->quic_info.version) + { + qcontext->ignore_session = 1; + return; + } + if (qcontext->quic_info.chlo.sni != NULL) + { + qmsg = quic_create_message(QUIC_TOPIC_CLIENT_HELLO, sess, qcontext, &qcontext->quic_info.chlo); + quic_session_mq_publish_message_safe(quic_env, qmsg); + qcontext->pub_chlo_msg_flag = 1; + quic_stat_update_version(&quic_env->stat, thread_idx, qcontext->quic_info.version, 1); + } + } + quic_stat_update_cnt(&quic_env->stat, thread_idx, QUIC_STAT_PACKET_CNT, 1); + quic_stat_update_cnt(&quic_env->stat, thread_idx, QUIC_STAT_PACKET_SIZE, payload_len); + return; +}
\ No newline at end of file diff --git a/decoders/quic/quic_process.h b/decoders/quic/quic_process.h new file mode 100644 index 0000000..ed24aed --- /dev/null +++ b/decoders/quic/quic_process.h @@ -0,0 +1,116 @@ +#pragma once +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdbool.h> +#include "quic_decoder.h" +#include "quic_util.h" +#include "stellar/quic.h" +#include "stellar/session.h" +#include "fieldstat/fieldstat_easy.h" + +#define QUIC_TOPIC_NAME "QUIC_TOPIC" +#define QUIC_MODULE_NAME "QUIC" +#define QUIC_EXDATA_NAME "QUIC_EXDATA" + +#define FALSE 0x00 +#define TRUE 0x01 +#define MAYBE 0x02 + +#define SUPPORT_QUIC_PORT_NUM 128 + +#define QUIC_HALF_CLOSE 0x01 +#define QUIC_WHOLE_CLOSE 0x02 +#define QUIC_DATA 0x03 +#define QUIC_KEY 1 +#define QUIC_RETURN_NORM 0x60 +#define QUIC_RETURN_UNNORM 0x61 +#define QUIC_RETURN_RESET_BUFFER 0x62 +#define QUIC_RETURN_DROPME 0x63 +#define MAX_REGION_NUM 15 +#define REGION_NAME_LEN 32 + +enum quic_topic_type +{ + QUIC_TOPIC_CLIENT_HELLO = 1, + QUIC_TOPIC_ENCRYPTED_PAYLOAD, +}; + +struct quic_buffer +{ + unsigned char *buffer; + size_t max_size; + size_t datalen; +}; + +struct quic_message +{ +#ifndef NDEBUG +#define QUIC_MSG_HDR_MAGIC 0x51554943 // ASCII: "QUIC", for sanity check + unsigned int magic; +#endif + struct session *sess_ref; + struct quic_exdata *quic_ext; + enum quic_topic_type topic_type; // use type but not id, because id is dynamically get from mq manager + void *topic_data; // refer to quic_client_hello or quic_encrypted_payload +}; + +struct quic_exdata +{ + uint8_t ignore_session; // not quic protocol, ignore subsequent packets of this session + uint8_t pub_chlo_msg_flag; + unsigned int parse_payload_size; + unsigned int parse_pkt_num; + struct quic_info quic_info; + struct quic_buffer quic_buf; // for client hello fragment +}; + +#define QUIC_CHLO_FRAG_MAX_SIZE (4096) +#define QUIC_PARSE_FIRST_N_PACKETS (8) +struct quic_config +{ + uint32_t chlo_frag_max_size; + uint32_t parse_packet_max; +}; + +enum quic_stat_type +{ + QUIC_STAT_SESSION_CNT = 0, + QUIC_STAT_PACKET_CNT, + QUIC_STAT_PACKET_SIZE, + QUIC_STAT_VERSION, + QUIC_STAT_MAX, +}; + +struct quic_stat +{ + struct fieldstat_easy *fs4_ins; + int fs4_session_cnt_id; + int fs4_packet_cnt_id; + int fs4_packet_size_id; + int fs4_quic_version_id; +}; + +struct quic +{ + struct quic_config config; + struct module_manager *mod_mgr_ref; + struct logger *logger_ref; + int exdata_id; + int udp_topic_id; // as subscriber + int quic_topic_chlo_id; // as quic publisher + int quic_topic_encrypt_id; // as quic publisher + struct quic_stat stat; +}; +int quic_load_config(const char *file, struct quic_config *config); +int quic_stat_init(struct quic_stat *qstat); +void quic_on_udp_msg_cb(struct session *sess, enum session_state state, struct packet *pkt, void *plugin_env); +void quic_exdata_free_cb(int idx UNUSED, void *ex_ptr, void *arg); +void quic_before_session_free_cb(struct session *sess, void *args); +void quic_on_msg_dispatch(int topic_id UNUSED, void *msg, on_msg_cb_func *on_msg_cb, + void *on_msg_cb_arg, void *dispatch_arg); +void quic_msg_free_cb(void *msg, void *msg_free_arg); +void quic_analyze_entry(struct session *sess, struct quic *g_quic_plug_env, struct quic_exdata *context, const char *payload, size_t payload_len); +void quic_session_mq_publish_message_safe(const struct quic *quic_env, struct quic_message *qmsg_pr); +void quic_stat_update_cnt(struct quic_stat *qstat, int thread_idx, enum quic_stat_type type, long long value); +void quic_stat_update_version(struct quic_stat *qstat, int thread_idx, uint32_t quic_version, long long value);
\ No newline at end of file diff --git a/decoders/quic/quic_util.h b/decoders/quic/quic_util.h new file mode 100644 index 0000000..0b75852 --- /dev/null +++ b/decoders/quic/quic_util.h @@ -0,0 +1,28 @@ +#pragma once +#include <stddef.h> +#include <stdlib.h> + +#ifndef CALLOC +#define CALLOC(nmemb, size) calloc(nmemb, size) +#endif +#ifndef REALLOC +#define REALLOC(ptr, newsize) realloc(ptr, newsize) +#endif +#ifndef FREE +#define FREE(p) \ + { \ + if (p) \ + { \ + free((void *)p); \ + } \ + p = NULL; \ + } +#endif + +#ifndef UNUSED +#define UNUSED __attribute__((unused)) +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif
\ No newline at end of file diff --git a/decoders/quic/version.map b/decoders/quic/version.map new file mode 100644 index 0000000..4eb803c --- /dev/null +++ b/decoders/quic/version.map @@ -0,0 +1,10 @@ +VERS_3.0{ +global: + extern "C" { + quic_init; + quic_exit; + quic_version_to_readable_string; + quic_subscribe_client_hello*; + }; + local: *; +}; |
