From 72fdac731293225d1c25ec2932f958b29c4b173b Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Wed, 25 Oct 2023 10:43:56 +0200 Subject: [PATCH 01/23] QUIC: Extract more QUIC flow details --- README.md | 16 +- include/ipfixprobe/ipfix-elements.hpp | 19 +- process/quic.cpp | 232 ++++++++++++++-- process/quic.hpp | 108 ++++++- process/quic_parser.cpp | 386 ++++++++++++++++++++++---- process/quic_parser.hpp | 84 +++++- process/stats.hpp | 3 + 7 files changed, 753 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 875a504d..88cf5d29 100644 --- a/README.md +++ b/README.md @@ -614,9 +614,19 @@ List of fields exported together with basic flow fields on interface by WG plugi List of fields exported together with basic flow fields on interface by quic plugin. -| Output field | Type | Description | -|:------------------:|:------:|:-------------------------------:| -| QUIC_SNI | string | Decrypted server name | +| Output field | Type | Description | +|:-------------------:|:------:|:-----------------------------------------------------------------------:| +| QUIC_SNI | string | Decrypted server name | +| QUIC_USER_AGENT | string | Decrypted user agent | +| QUIC_VERSION | uint32 | QUIC version extracted from long header packets | +| QUIC_CLIENT_VERSION | uint32 | QUIC version from the Initial packet with the TLS Client Hello | +| QUIC_TOKEN_LENGTH | uint64 | Token length from Initial and Retry packets | +| QUIC_OCCID | string | Source Connection ID from Initial packet with the TLS Client Hello | +| QUIC_OSCID | string | Destination Connection ID from Initial packet with the TLS Client Hello | +| QUIC_SCID | string | Source Connection ID from long header packets other than before. | +| QUIC_RETRY_SCID | string | Source Connection ID from Retry packet | +| QUIC_MULTIPLEXED | uint8 | > 0 if multiplexed (at least two QUIC_OSCIDs or SNIs) | +| QUIC_ZERO_RTT | uint8 | Number of 0-RTT packets in flow. | ### ICMP diff --git a/include/ipfixprobe/ipfix-elements.hpp b/include/ipfixprobe/ipfix-elements.hpp index 0b15d402..167579e7 100644 --- a/include/ipfixprobe/ipfix-elements.hpp +++ b/include/ipfixprobe/ipfix-elements.hpp @@ -240,6 +240,15 @@ namespace ipxp { #define QUIC_SNI(F) F(8057, 890, -1, nullptr) #define QUIC_USER_AGENT(F) F(8057, 891, -1, nullptr) #define QUIC_VERSION(F) F(8057, 892, 4, nullptr) +#define QUIC_CLIENT_VERSION(F) F(8057, 893, 4, nullptr) +#define QUIC_TOKEN_LENGTH(F) F(8057, 894, 8, nullptr) +#define QUIC_OCCID(F) F(8057, 895, -1, nullptr) +#define QUIC_OSCID(F) F(8057, 896, -1, nullptr) +#define QUIC_SCID(F) F(8057, 897, -1, nullptr) +#define QUIC_RETRY_SCID(F) F(8057, 898, -1, nullptr) +#define QUIC_MULTIPLEXED(F) F(8057, 899, 1, nullptr) +#define QUIC_ZERO_RTT(F) F(8057, 900, 1, nullptr) + #define OSQUERY_PROGRAM_NAME(F) F(8057, 852, -1, nullptr) #define OSQUERY_USERNAME(F) F(8057, 853, -1, nullptr) @@ -494,7 +503,15 @@ namespace ipxp { #define IPFIX_QUIC_TEMPLATE(F) \ F(QUIC_SNI) \ F(QUIC_USER_AGENT) \ - F(QUIC_VERSION) + F(QUIC_VERSION) \ + F(QUIC_CLIENT_VERSION) \ + F(QUIC_TOKEN_LENGTH) \ + F(QUIC_OCCID) \ + F(QUIC_OSCID) \ + F(QUIC_SCID) \ + F(QUIC_RETRY_SCID) \ + F(QUIC_MULTIPLEXED) \ + F(QUIC_ZERO_RTT) #define IPFIX_OSQUERY_TEMPLATE(F) \ F(OSQUERY_PROGRAM_NAME) \ diff --git a/process/quic.cpp b/process/quic.cpp index e2fb770b..4673f7bf 100644 --- a/process/quic.cpp +++ b/process/quic.cpp @@ -33,7 +33,6 @@ __attribute__((constructor)) static void register_this_plugin() QUICPlugin::QUICPlugin() { - quic_ptr = nullptr; } QUICPlugin::~QUICPlugin() @@ -46,10 +45,7 @@ void QUICPlugin::init(const char *params) void QUICPlugin::close() { - if (quic_ptr != nullptr) { - delete quic_ptr; - } - quic_ptr = nullptr; + } ProcessPlugin *QUICPlugin::copy() @@ -57,18 +53,197 @@ ProcessPlugin *QUICPlugin::copy() return new QUICPlugin(*this); } -bool QUICPlugin::process_quic(RecordExtQUIC *quic_data, const Packet &pkt) + + +void QUICPlugin::set_stored_cid_fields(RecordExtQUIC *quic_data, RecordExtQUIC *ext) { + if ((ext != nullptr) && (ext->dir_dport != 0)) { + if (ext->dir_dport == quic_data->server_port) { + // to server + quic_data->scid_length = ext->dir_dcid_length; + memcpy(quic_data->scid, ext->dir_dcid, quic_data->scid_length); + quic_data->occid_length = ext->dir_scid_length; + memcpy(quic_data->occid, ext->dir_scid, quic_data->occid_length); + } else { + // from server + quic_data->scid_length = ext->dir_scid_length; + memcpy(quic_data->scid, ext->dir_scid, quic_data->scid_length); + quic_data->occid_length = ext->dir_dcid_length; + memcpy(quic_data->occid, ext->dir_dcid, quic_data->occid_length); + } + ext->dir_dport = 0; + } +} + + +void QUICPlugin::set_cid_fields(RecordExtQUIC *quic_data, QUICParser *process_quic, int toServer, + RecordExtQUIC *ext, const Packet &pkt ) { + switch (toServer) { + case 1: + process_quic->quic_get_dcid(quic_data->scid); + process_quic->quic_get_dcid_len(quic_data->scid_length); + + process_quic->quic_get_scid(quic_data->occid); + process_quic->quic_get_scid_len(quic_data->occid_length); + + set_stored_cid_fields(quic_data, ext); + break; + case 0: + process_quic->quic_get_dcid(quic_data->occid); + process_quic->quic_get_dcid_len(quic_data->occid_length); + + process_quic->quic_get_scid(quic_data->scid); + process_quic->quic_get_scid_len(quic_data->scid_length); + + set_stored_cid_fields(quic_data, ext); + break; + case -1: + default: + // no direction information, store for future use + process_quic->quic_get_scid(quic_data->dir_scid); + process_quic->quic_get_scid_len(quic_data->dir_scid_length); + process_quic->quic_get_dcid(quic_data->dir_dcid); + process_quic->quic_get_dcid_len(quic_data->dir_dcid_length); + quic_data->dir_dport = pkt.dst_port; + break; + } +} + + +int QUICPlugin::get_direction_to_server_and_set_port(QUICParser *process_quic, RecordExtQUIC *quic_data, + uint16_t parsed_port, const Packet &pkt, RecordExtQUIC *ext) { + int toServer = get_direction_to_server(parsed_port, pkt, ext); + if ((toServer != -1) && (quic_data->server_port ==0)) { + quic_data->server_port = process_quic->quic_get_server_port(); + } + return toServer; +} + +int QUICPlugin::get_direction_to_server(uint16_t parsed_port, const Packet &pkt, RecordExtQUIC *ext) +{ + if (parsed_port != 0) { + return pkt.dst_port == parsed_port; + } else if (((ext != nullptr) && (ext->server_port != 0))) { + return pkt.dst_port == ext->server_port; + } + return -1; +} + +void QUICPlugin::set_client_hello_fields(QUICParser *process_quic, RecordExtQUIC *quic_data, const Packet &pkt, + RecordExtQUIC *ext) { + + process_quic->quic_get_token_length(quic_data->quic_token_length); + char dcid[MAX_CID_LEN] = { 0 }; + uint8_t dcid_len = 0; + // since this this is a client hello the dcid must be set + process_quic->quic_get_dcid(dcid); + process_quic->quic_get_dcid_len(dcid_len); + + + + + if ((quic_data->quic_token_length != QUICParser::QUIC_CONSTANTS::QUIC_UNUSED_VARIABLE_LENGTH_INT) && + (quic_data->quic_token_length > 0) && + ((quic_data->retry_scid_length == dcid_len) || + (ext != nullptr) && (ext->retry_scid_length == dcid_len)) && + ((strncmp(quic_data->retry_scid, dcid, std::min(quic_data->retry_scid_length, dcid_len)) == 0) || + ((ext != nullptr) && (strncmp(ext->retry_scid, dcid, std::min(ext->retry_scid_length, dcid_len))) == 0) ) ) { + // Retry case: We already have all information from the previous CH. + + } else { + // MULTIPLEXING detection + char oscid[MAX_CID_LEN] = { 0 }; + uint8_t oscid_len = 0; + process_quic->quic_get_dcid(oscid); + process_quic->quic_get_dcid_len(oscid_len); + + char sni[BUFF_SIZE] = { 0 }; + process_quic->quic_get_sni(sni); + + if (( (oscid_len == quic_data->oscid_length) && + (quic_data->oscid_length != 0) && + (strncmp(oscid, quic_data->oscid, oscid_len) == 0) && + (strncmp(quic_data->sni, sni, BUFF_SIZE )) == 0) || + ((ext == nullptr))) { + // Repeated Initial or new Initial/QUIC flow + quic_data->server_port = process_quic->quic_get_server_port(); + + process_quic->quic_get_sni(quic_data->sni); + process_quic->quic_get_user_agent(quic_data->user_agent); + + process_quic->quic_get_dcid(quic_data->oscid); + process_quic->quic_get_dcid_len(quic_data->oscid_length); + + process_quic->quic_get_scid(quic_data->occid); + process_quic->quic_get_scid_len(quic_data->occid_length); + + // Set client version to extract difference in compatible version negotiation: RFC9368 + process_quic->quic_get_version(quic_data->quic_client_version); + } else { + if (quic_data->quic_multiplexed < 0xFF) { + quic_data->quic_multiplexed += 1; + } + } + } +} + + +int QUICPlugin::process_quic(RecordExtQUIC *quic_data, Flow &rec, const Packet &pkt) { QUICParser process_quic; - if (!process_quic.quic_start(pkt)) { - return false; - } else { - process_quic.quic_get_sni(quic_data->sni); - process_quic.quic_get_user_agent(quic_data->user_agent); - process_quic.quic_get_version(quic_data->quic_version); - return true; + // Test for QUIC packet in UDP payload + if(process_quic.quic_check_quic_long_header_packet(pkt) ) { + + process_quic.quic_get_version(quic_data->quic_version); + if (quic_data->quic_version == QUICParser::QUIC_VERSION::version_negotiation) { + return FLOW_FLUSH; + } + + RecordExtQUIC *ext = (RecordExtQUIC *) rec.get_extension(RecordExtQUIC::REGISTERED_ID); + // Simple version, more advanced information is available after Initial parsing + int toServer = get_direction_to_server_and_set_port(&process_quic, quic_data, process_quic.quic_get_server_port(), pkt, ext); + + uint8_t packets = 0; + process_quic.quic_get_packets(packets); + if (packets & QUICParser::PACKET_TYPE_FLAG::F_ZERO_RTT) { + uint8_t zero_rtt_pkts = process_quic.quic_get_zero_rtt(); + + if ((uint16_t) zero_rtt_pkts + (uint16_t)quic_data->quic_zero_rtt > 0xFF) { + quic_data->quic_zero_rtt = 0xFF; + } else { + quic_data->quic_zero_rtt += zero_rtt_pkts; + } + } + uint8_t parsed_initial = 0; + + switch (process_quic.quic_get_packet_type()) { + case QUICParser::PACKET_TYPE::INITIAL: + process_quic.quic_get_parsed_initial(parsed_initial); + if (parsed_initial) { + // Successful CH parsing + set_client_hello_fields(&process_quic, quic_data, pkt, ext); + break; + } + // Update accounting for information from CH, SH. + toServer = get_direction_to_server_and_set_port(&process_quic, quic_data, process_quic.quic_get_server_port(), pkt, ext); + // fallthrough to set cids + case QUICParser::PACKET_TYPE::ZERO_RTT: + case QUICParser::PACKET_TYPE::HANDSHAKE: + // -1 sets stores intermediately. + set_cid_fields(quic_data, &process_quic, toServer, ext, pkt); + break; + case QUICParser::PACKET_TYPE::RETRY: + // Additionally set token len + process_quic.quic_get_scid(quic_data->retry_scid); + process_quic.quic_get_scid_len(quic_data->retry_scid_length); + process_quic.quic_get_token_length(quic_data->quic_token_length); + set_cid_fields(quic_data, &process_quic, toServer, ext, pkt); + break; + } + + return QUIC_DETECTED; } + return QUIC_NOT_DETECTED; } // QUICPlugin::process_quic int QUICPlugin::pre_create(Packet &pkt) @@ -78,8 +253,7 @@ int QUICPlugin::pre_create(Packet &pkt) int QUICPlugin::post_create(Flow &rec, const Packet &pkt) { - add_quic(rec, pkt); - return 0; + return add_quic(rec, pkt); } int QUICPlugin::pre_update(Flow &rec, Packet &pkt) @@ -95,20 +269,32 @@ int QUICPlugin::post_update(Flow &rec, const Packet &pkt) return 0; } - add_quic(rec, pkt); - return 0; + return add_quic(rec, pkt); } -void QUICPlugin::add_quic(Flow &rec, const Packet &pkt) +int QUICPlugin::add_quic(Flow &rec, const Packet &pkt) { - if (quic_ptr == nullptr) { - quic_ptr = new RecordExtQUIC(); + RecordExtQUIC *q_ptr = (RecordExtQUIC *) rec.get_extension(RecordExtQUIC::REGISTERED_ID); + bool new_qptr = false; + if (q_ptr == nullptr) { + new_qptr = true; + q_ptr = new RecordExtQUIC(); } - if (process_quic(quic_ptr, pkt)) { - rec.add_extension(quic_ptr); - quic_ptr = nullptr; + int ret = process_quic(q_ptr, rec, pkt); + // Test if QUIC extension is not set + if (new_qptr && (ret == QUIC_DETECTED)) { + rec.add_extension(q_ptr); + } + if (new_qptr && (ret == QUIC_NOT_DETECTED)) { + // If still no record delete q_ptr + delete q_ptr; + } + // Correct if QUIC has already been detected + if (!new_qptr && (ret == QUIC_NOT_DETECTED)) { + return QUIC_DETECTED; } + return ret; } void QUICPlugin::finish(bool print_stats) diff --git a/process/quic.hpp b/process/quic.hpp index e0fb98cf..0c7dab03 100644 --- a/process/quic.hpp +++ b/process/quic.hpp @@ -27,27 +27,78 @@ namespace ipxp { -#define QUIC_UNIREC_TEMPLATE "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION" +#define QUIC_UNIREC_TEMPLATE "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION,QUIC_CLIENT_VERSION,QUIC_TOKEN_LENGTH,QUIC_OCCID,QUIC_OSCID,QUIC_SCID,QUIC_RETRY_SCID,QUIC_MULTIPLEXED,QUIC_ZERO_RTT" UR_FIELDS( string QUIC_SNI, string QUIC_USER_AGENT, - uint32 QUIC_VERSION + uint32 QUIC_VERSION, + uint32 QUIC_CLIENT_VERSION, + uint64 QUIC_TOKEN_LENGTH, + bytes QUIC_OCCID, + bytes QUIC_OSCID, + bytes QUIC_SCID, + bytes QUIC_RETRY_SCID, + uint8 QUIC_MULTIPLEXED, + uint8 QUIC_ZERO_RTT ) /** * \brief Flow record extension header for storing parsed QUIC packets. */ +#define MAX_CID_LEN 20 +#define QUIC_DETECTED 0 +#define QUIC_NOT_DETECTED 2 + struct RecordExtQUIC : public RecordExt { static int REGISTERED_ID; char sni[BUFF_SIZE] = { 0 }; char user_agent[BUFF_SIZE] = { 0 }; uint32_t quic_version; + uint32_t quic_client_version; + uint64_t quic_token_length; + // We use a char as a buffer. + uint8_t occid_length; + uint8_t oscid_length; + uint8_t scid_length; + uint8_t dir_scid_length; + uint8_t dir_dcid_length; + uint8_t retry_scid_length; + char occid[MAX_CID_LEN] = { 0 }; + char oscid[MAX_CID_LEN] = { 0 }; + char scid[MAX_CID_LEN] = { 0 }; + char retry_scid[MAX_CID_LEN] = { 0 }; + // Intermediate storage when direction is not clear + char dir_scid[MAX_CID_LEN] = { 0 }; + char dir_dcid[MAX_CID_LEN] = { 0 }; + uint16_t dir_dport; + uint16_t server_port; + + uint8_t quic_multiplexed; + uint8_t quic_zero_rtt; RecordExtQUIC() : RecordExt(REGISTERED_ID) { sni[0] = 0; user_agent[0] = 0; quic_version = 0; + quic_client_version = 0; + occid_length = 0; + oscid_length = 0; + scid_length = 0; + retry_scid_length = 0; + occid[0] = 0; + oscid[0] = 0; + scid[0] = 0; + retry_scid[0] = 0; + dir_dcid[0] = 0; + dir_scid[0] = 0; + dir_dcid_length=0; + dir_scid_length=0; + server_port = 0; + dir_dport = 0; + quic_token_length = QUICParser::QUIC_CONSTANTS::QUIC_UNUSED_VARIABLE_LENGTH_INT; + quic_multiplexed = 0; + quic_zero_rtt = 0; } #ifdef WITH_NEMEA @@ -56,6 +107,14 @@ struct RecordExtQUIC : public RecordExt { ur_set_string(tmplt, record, F_QUIC_SNI, sni); ur_set_string(tmplt, record, F_QUIC_USER_AGENT, user_agent); ur_set(tmplt, record, F_QUIC_VERSION, quic_version); + ur_set(tmplt, record, F_QUIC_CLIENT_VERSION, quic_client_version); + ur_set(tmplt, record, F_QUIC_TOKEN_LENGTH, quic_token_length); + ur_set_var(tmplt, record, F_QUIC_OCCID, occid, occid_length); + ur_set_var(tmplt, record, F_QUIC_OSCID, oscid, oscid_length); + ur_set_var(tmplt, record, F_QUIC_SCID, scid, scid_length); + ur_set_var(tmplt, record, F_QUIC_RETRY_SCID, retry_scid, retry_scid_length); + ur_set(tmplt, record, F_QUIC_MULTIPLEXED, quic_multiplexed); + ur_set(tmplt, record, F_QUIC_ZERO_RTT, quic_zero_rtt); } const char *get_unirec_tmplt() const @@ -70,9 +129,15 @@ struct RecordExtQUIC : public RecordExt { uint16_t len_sni = strlen(sni); uint16_t len_user_agent = strlen(user_agent); uint16_t len_version = sizeof(quic_version); + uint16_t len_client_version = sizeof(quic_client_version); + uint16_t len_token_length = sizeof(quic_token_length); + uint16_t len_multiplexed = sizeof(quic_multiplexed); + uint16_t len_zero_rtt = sizeof(quic_zero_rtt); int pos = 0; - if ((len_sni + 3) + (len_user_agent + 3) + len_version > size) { + if ((len_sni + 3) + (len_user_agent + 3) + len_version + + len_client_version + len_token_length + len_multiplexed + len_zero_rtt + + (scid_length + 3) + (occid_length + 3) + (oscid_length + 3) + (retry_scid_length + 3) > size) { return -1; } @@ -80,6 +145,24 @@ struct RecordExtQUIC : public RecordExt { pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) user_agent, len_user_agent); *(uint32_t *) (buffer + pos) = htonl(quic_version); pos += len_version; + *(uint32_t *) (buffer + pos) = htonl(quic_client_version); + pos += len_client_version; + *(uint64_t *) (buffer + pos) = htobe64(quic_token_length); + pos += len_token_length; + // original client connection ID + pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) occid, occid_length); + // original server connection id + pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) oscid, oscid_length); + // server connection id + pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) scid, scid_length); + // retry server connection id + pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) retry_scid, retry_scid_length); + + *(uint8_t *) (buffer + pos) = quic_multiplexed; + pos += 1; + + *(uint8_t *) (buffer + pos) = quic_zero_rtt; + pos += 1; return pos; } @@ -98,7 +181,13 @@ struct RecordExtQUIC : public RecordExt { std::ostringstream out; out << "quicsni=\"" << sni << "\"" << "quicuseragent=\"" << user_agent << "\"" << "quicversion=\"" << - quic_version << "\""; + quic_version << "\"" + << "quicclientversion=\"" << quic_client_version << "\"" + << "quicoccidlength=\"" << occid_length << "\"" << "quicoccid=\"" << occid << "\"" + << "quicoscidlength=\"" << oscid_length << "\"" << "quicoscid=\"" << oscid << "\"" + << "quicscidlength=\"" << scid_length << "\"" << "quicscid=\"" << scid << "\"" + << "quicmultiplexed=\"" << quic_multiplexed << "\"" + << "quiczerortt=\"" << quic_zero_rtt << "\""; return out.str(); } }; @@ -125,11 +214,18 @@ class QUICPlugin : public ProcessPlugin int post_create(Flow &rec, const Packet &pkt); int pre_update(Flow &rec, Packet &pkt); int post_update(Flow &rec, const Packet &pkt); - void add_quic(Flow &rec, const Packet &pkt); + int add_quic(Flow &rec, const Packet &pkt); void finish(bool print_stats); private: - bool process_quic(RecordExtQUIC *, const Packet&); + int process_quic(RecordExtQUIC *, Flow &rec, const Packet&); + void set_stored_cid_fields(RecordExtQUIC *quic_data, RecordExtQUIC *ext); + void set_cid_fields(RecordExtQUIC *quic_data, QUICParser *process_quic, int toServer, + RecordExtQUIC *ext, const Packet &pkt ); + int get_direction_to_server(uint16_t parsed_port, const Packet &pkt, RecordExtQUIC *ext); + int get_direction_to_server_and_set_port(QUICParser *process_quic, RecordExtQUIC *quic_data, uint16_t parsed_port, const Packet &pkt, RecordExtQUIC *ext); + void set_client_hello_fields(QUICParser *process_quic, RecordExtQUIC *quic_data, const Packet &pkt, + RecordExtQUIC *ext ); int parsed_initial; RecordExtQUIC *quic_ptr; }; diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index 5ec7b4ee..054ca3ad 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -38,12 +38,30 @@ QUICParser::QUICParser() payload_len = 0; dcid = nullptr; + dcid_len = 0; + scid = nullptr; + scid_len = 0; pkn = nullptr; sample = nullptr; salt = nullptr; final_payload = nullptr; parsed_initial = 0; is_version2 = false; + packet_type = UNKNOWN; + packets = 0; + token_length = QUIC_UNUSED_VARIABLE_LENGTH_INT; + zero_rtt = 0; + server_port = 0; +} + +uint8_t QUICParser::quic_get_packet_type() +{ + return packet_type; +} + +uint8_t QUICParser::quic_get_zero_rtt() +{ + return zero_rtt; } void QUICParser::quic_get_version(uint32_t& version_toset) @@ -52,6 +70,51 @@ void QUICParser::quic_get_version(uint32_t& version_toset) return; } +void QUICParser::quic_get_packets(uint8_t& packets_toset) +{ + packets_toset = packets; + return; +} + +void QUICParser::quic_get_token_length(uint64_t& token_len_toset) +{ + token_len_toset = token_length; + return; +} + +uint16_t QUICParser::quic_get_server_port() { + return server_port; +} + +void QUICParser::quic_get_parsed_initial(uint8_t& to_set) { + to_set = parsed_initial; + return; +} + +void QUICParser::quic_get_dcid_len(uint8_t& scid_length_toset) +{ + scid_length_toset = dcid_len; + return; +} + +void QUICParser::quic_get_scid_len(uint8_t& scid_length_toset) +{ + scid_length_toset = scid_len; + return; +} + +void QUICParser::quic_get_dcid(char* in) +{ + memcpy(in, dcid, dcid_len); + return; +} + +void QUICParser::quic_get_scid(char* in) +{ + memcpy(in, scid, scid_len); + return; +} + void QUICParser::quic_get_sni(char* in) { memcpy(in, sni, BUFF_SIZE); @@ -568,7 +631,7 @@ bool QUICParser::quic_encrypt_sample(uint8_t* plaintext) return true; } -bool QUICParser::quic_decrypt_header(const Packet& pkt) +bool QUICParser::quic_decrypt_initial_header(const uint8_t* payload_pointer, uint64_t offset) { uint8_t plaintext[SAMPLE_LENGTH]; uint8_t mask[5] = {0}; @@ -605,13 +668,13 @@ bool QUICParser::quic_decrypt_header(const Packet& pkt) // payload payload = payload + pkn_len; payload_len = payload_len - pkn_len; - header_len = payload - pkt.payload; + header_len = payload - payload_pointer; if (header_len > MAX_HEADER_LEN) { DEBUG_MSG("Header length too long\n"); return false; } - memcpy(tmp_header_mem, pkt.payload, header_len); + memcpy(tmp_header_mem, payload_pointer, header_len); header = tmp_header_mem; header[0] = first_byte; @@ -630,7 +693,7 @@ bool QUICParser::quic_decrypt_header(const Packet& pkt) initial_secrets.iv + sizeof(initial_secrets.iv) - 8, pntoh64(initial_secrets.iv + sizeof(initial_secrets.iv) - 8) ^ packet_number); return true; -} // QUICPlugin::quic_decrypt_header +} // QUICPlugin::quic_decrypt_initial_header bool QUICParser::quic_decrypt_payload() { @@ -833,48 +896,185 @@ void QUICParser::quic_initialze_arrays() memset(tmp_header_mem, 0, MAX_HEADER_LEN); } +bool QUICParser::quic_check_long_header(uint8_t packet0) +{ + // We test for 1 in the fist position = long header + // We ignore the QUIC bit, as it might be greased + // https://datatracker.ietf.org/doc/html/rfc9287 + return (packet0 & 0x80) == 0x80; + +} + bool QUICParser::quic_check_initial(uint8_t packet0) { - // version 1 (header form:long header(1) | fixed bit:fixed(1) | long packet type:initial(00) --> - // 1100 --> C) - if ((packet0 & 0xF0) == 0xC0) { - is_version2 = false; + + // The fixed bit, might be greased. We assume greasing for all packets + // RFC 9287 + // version 1 (header form:long header(1) | fixed bit:fixed(1/0) | long packet type:initial(00) --> + // 1000 --> 8) + if ((packet0 & 0xB0) == 0x80) { return true; } - // version 2 (header form:long header(1) | fixed bit:fixed(1) | long packet type:initial(01) --> - // 1101 --> D) - else if ((packet0 & 0xF0) == 0xD0) { - is_version2 = true; + // version 2 (header form:long header(1) | fixed bit:fixed(1)/0 | long packet type:initial(01) --> + // 1001 --> 9) + else if ( is_version2 && ((packet0 & 0xB0) == 0x90)) { return true; } else { return false; } } -bool QUICParser::quic_initial_checks(const Packet& pkt) + +bool QUICParser::quic_check_min_initial_size(const Packet&pkt) { - // Port check, Initial packet check and UDP check - if (pkt.ip_proto != 17 || !quic_check_initial(pkt.payload[0]) || pkt.dst_port != 443) { - DEBUG_MSG("Packet is not Initial or does not contains LONG HEADER or is not on port 443\n"); - return false; + if (pkt.payload_len < QUIC_MIN_PACKET_LENGTH) { + return false; + } + return true; +} + + +uint32_t read_uint32_t(const uint8_t *ptr, uint8_t offset) +{ + uint32_t val; + memcpy(&val, ptr + offset, sizeof(uint32_t)); + return val; +} + +bool QUICParser::quic_check_supported_version(const uint32_t version) { + switch (version) { + case q_version2_draft00: + is_version2 = true; + return true; + case q_version2_newest: + is_version2 = true; + return true; + case older_version: + case faceebook1: + case faceebook2: + case facebook_experimental: + case force_ver_neg_pattern: + case version_negotiation: + case quic_newest: + return true; + } + return false; +} + +bool QUICParser::quic_long_header_packet(const Packet& pkt) +{ + // UDP check, Initial packet check, QUIC min long header size, QUIC version check, + if (pkt.ip_proto != 17 || !quic_check_long_header(pkt.payload[0]) || !(quic_check_min_initial_size(pkt)) || !(quic_check_supported_version(ntohl(read_uint32_t(pkt.payload, 1)))) ) { + DEBUG_MSG("Packet is not Initial or does not contains LONG HEADER or is not long enough or is not a supported QUIC version\n"); + return false; } return true; } -bool QUICParser::quic_parse_header(const Packet& pkt) +bool QUICParser::quic_parse_initial_header(const Packet& pkt, const uint8_t* payload_pointer, + const uint8_t* payload_end, uint64_t& offset) { - const uint8_t* payload_pointer = pkt.payload; - uint64_t offset = 0; + token_length = quic_get_variable_length(payload_pointer, offset); + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } + offset += token_length; + + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } + + payload_len = quic_get_variable_length(payload_pointer, offset); + if (payload_len > CURRENT_BUFFER_SIZE) { + return false; + } + + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } - const uint8_t* payload_end = payload_pointer + pkt.payload_len; + pkn = (payload_pointer + offset); + + payload = (payload_pointer + offset); + + // This should not cause an offset. + // offset += sizeof(uint8_t) * 4; + sample = (payload_pointer + offset + 4*sizeof(uint8_t) ); + + if (!quic_check_pointer_pos((payload_pointer + offset + 4*sizeof(uint8_t)), payload_end)) { + return false; + } + return true; +} + +void QUICParser::quic_parse_packet_type(uint8_t packet0) { + + if (version == version_negotiation) { + packets |= F_VERSION_NEGOTIATION; + packet_type = VERSION_NEGOTIATION; + return; + } + + packet_type = (packet0 & 0b00110000)>> 4; + if (!is_version2) { + switch (packet_type) { + case 0b00: + packets |= F_INITIAL; + break; + case 0b01: + packets |= F_ZERO_RTT; + break; + case 0b10: + packets |= F_HANDSHAKE; + break; + case 0b11: + packets |= F_RETRY; + break; + } + } + if (is_version2) { + switch (packet_type) { + case 0b01: + packet_type = INITIAL; + packets |= F_INITIAL; + break; + case 0b10: + packet_type = ZERO_RTT; + packets |= F_ZERO_RTT; + break; + case 0b11: + packet_type = HANDSHAKE; + packets |= F_HANDSHAKE; + break; + case 0b00: + packet_type = RETRY; + packets |= F_RETRY; + break; + } + } +} + +bool QUICParser::quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* payload_pointer, uint8_t* payload_end) +{ + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } quic_h1 = (quic_first_ver_dcidlen*) (payload_pointer + offset); + if (!quic_check_long_header(quic_h1->first_byte)) { + // If not long header packet -> short header packet. Do not analyze. + return false; + } + if (!quic_obtain_version()) { DEBUG_MSG("Error, version not supported\n"); return false; } + quic_parse_packet_type(quic_h1->first_byte); + + offset += sizeof(quic_first_ver_dcidlen); if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { @@ -883,6 +1083,7 @@ bool QUICParser::quic_parse_header(const Packet& pkt) if (quic_h1->dcid_len != 0) { dcid = (payload_pointer + offset); + dcid_len = quic_h1->dcid_len; offset += quic_h1->dcid_len; } @@ -899,6 +1100,8 @@ bool QUICParser::quic_parse_header(const Packet& pkt) } if (quic_h2->scid_len != 0) { + scid = (payload_pointer + offset); + scid_len = quic_h2->scid_len; offset += quic_h2->scid_len; } @@ -906,57 +1109,134 @@ bool QUICParser::quic_parse_header(const Packet& pkt) return false; } - uint64_t token_length = quic_get_variable_length(payload_pointer, offset); + return true; - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; - } +} - offset += token_length; +bool QUICParser::quic_parse_headers(const Packet& pkt, bool forceInitialParsing) +{ + uint8_t* pkt_payload_pointer = (uint8_t *)pkt.payload; + uint8_t* payload_pointer = pkt_payload_pointer; + uint64_t offset = 0; - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; - } + packets = 0; - payload_len = quic_get_variable_length(payload_pointer, offset); - if (payload_len > CURRENT_BUFFER_SIZE) { - return false; + uint8_t* pkt_payload_end = payload_pointer + pkt.payload_len; + + // Handle coalesced packets + // 7 because (1B QUIC LH, 4B Version, 1 B SCID LEN, 1B DCID LEN) + uint64_t stored_payload_len; + while(payload_pointer + offset + QUIC_MIN_PACKET_LENGTH <= pkt.payload + pkt.payload_len) { + payload_pointer = pkt_payload_pointer + offset; + + if (!quic_parse_header(pkt, offset, pkt_payload_pointer, pkt_payload_end)) { + break; + } + + switch (packet_type) { + case ZERO_RTT: + if (zero_rtt < 0xFF) { + zero_rtt +=1; + } + break; + case HANDSHAKE: + payload_len = quic_get_variable_length(pkt_payload_pointer, offset); + if (payload_len > CURRENT_BUFFER_SIZE) { + return false; + } + offset += payload_len; + break; + case INITIAL: + if (!quic_parse_initial_header(pkt, pkt_payload_pointer, pkt_payload_end, offset)) { + return false; + } + stored_payload_len = payload_len; + if (!parsed_initial) { + // Not yet parsed a CH, try to parse as CH + quic_parse_initial(pkt, pkt_payload_pointer, offset); + } + offset += stored_payload_len; + break; + case RETRY: + // 16 - Integrity tag + token_length = pkt_payload_end - payload_pointer - offset - 16; + if (!quic_check_pointer_pos((pkt_payload_pointer + offset), pkt_payload_end)) { + return false; + } + offset += token_length; + if (!quic_check_pointer_pos((pkt_payload_pointer + offset), pkt_payload_end)) { + return false; + } + break; + } + + if (!quic_set_server_port(pkt)) { + DEBUG_MSG("Error, extracting server port"); + return false; + } + + if (packet_type == RETRY) { + break; + } } - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; + // Update packet type to most specific, i.e., Initial + if (packets & F_INITIAL) { + packet_type = INITIAL; } - pkn = (payload_pointer + offset); - payload = (payload_pointer + offset); + return packets; +} // QUICPlugin::quic_parse_data - offset += sizeof(uint8_t) * 4; - sample = (payload_pointer + offset); +bool QUICParser::quic_set_server_port(const Packet& pkt) { - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; + tls_handshake hs = tls_parser.tls_get_handshake(); + + switch (packet_type) { + case INITIAL: + if (hs.type == 1) { + server_port = pkt.dst_port; + } else if (hs.type == 2) { + server_port = pkt.src_port; + } + // e.g. ACKs do not reveal direction + break; + case RETRY: + server_port = pkt.src_port; + break; + case ZERO_RTT: + server_port = pkt.dst_port; + break; + case HANDSHAKE: + // Does not reveal the direction + break; } + return true; +} - return true; -} // QUICPlugin::quic_parse_data +bool QUICParser::quic_check_quic_long_header_packet(const Packet& pkt) { -bool QUICParser::quic_start(const Packet& pkt) -{ - if (!quic_initial_checks(pkt)) { + if (!quic_long_header_packet(pkt)) { return false; } quic_initialze_arrays(); - if (!quic_parse_header(pkt)) { - DEBUG_MSG("Error, parsing header failed\n"); + if (!quic_parse_headers(pkt, false)) { return false; } + return true; +} + + +bool QUICParser::quic_parse_initial(const Packet& pkt, const uint8_t* payload_pointer, uint64_t offset) +{ + if (!quic_create_initial_secrets()) { DEBUG_MSG("Error, creation of initial secrets failed (client side)\n"); return false; } - if (!quic_decrypt_header(pkt)) { + if (!quic_decrypt_initial_header(payload_pointer, offset)) { DEBUG_MSG("Error, header decryption failed (client side)\n"); return false; } @@ -972,6 +1252,16 @@ bool QUICParser::quic_start(const Packet& pkt) DEBUG_MSG("SNI and User Agent Extraction failed\n"); return false; } + + // 1 if CH or SH parsed + parsed_initial = 1; + + // According to RFC 9000 the server port will not change. + if (!quic_set_server_port(pkt)) { + DEBUG_MSG("Error, extracting server port"); + return false; + } + return true; } } // namespace ipxp diff --git a/process/quic_parser.hpp b/process/quic_parser.hpp index 23d3e156..944b0044 100644 --- a/process/quic_parser.hpp +++ b/process/quic_parser.hpp @@ -40,6 +40,10 @@ #define MAX_HEADER_LEN 67 + 100 #define BUFF_SIZE 255 #define CURRENT_BUFFER_SIZE 1500 +// 8 because (1B QUIC LH, 4B Version, 1 B SCID LEN, 1B DCID LEN, Payload/Retry Token/Supported Version >= 1 B) +#define QUIC_MIN_PACKET_LENGTH 8 + + namespace ipxp { typedef struct __attribute__((packed)) quic_first_ver_dcidlen { @@ -86,23 +90,13 @@ class QUICParser { = sizeof("tls13 client in") + sizeof(uint16_t) + sizeof(uint8_t) + sizeof(uint8_t) }; - enum QUIC_VERSION { - older_version = 0xff0000, - faceebook1 = 0xfaceb001, - faceebook2 = 0xfaceb002, - facebook_experimental = 0xfaceb00e, - q_version2_draft00 = 0xff020000, - q_version2_newest = 0x709a50c4, - force_ver_neg_pattern = 0x0a0a0a0a, - version_negotiation = 0x00000000, - quic_newest = 0x00000001 - }; + bool quic_initial_checks(const Packet&); void quic_initialze_arrays(); bool quic_check_initial(uint8_t); - bool quic_parse_header(const Packet&); + bool quic_check_long_header(uint8_t); bool quic_create_initial_secrets(); - bool quic_decrypt_header(const Packet&); + bool quic_decrypt_initial_header(const uint8_t* payload_pointer, uint64_t offset); bool quic_decrypt_payload(); bool quic_reassemble_frames(); bool quic_parse_tls(); @@ -120,6 +114,13 @@ class QUICParser { bool quic_check_version(uint32_t, uint8_t); bool quic_check_pointer_pos(const uint8_t*, const uint8_t*); bool quic_obtain_tls_data(TLSData&); + bool quic_set_server_port(const Packet& pkt); + bool quic_check_min_initial_size(const Packet&pkt); + bool quic_check_supported_version(const uint32_t version); + bool quic_parser_tls_and_set_server_port(const Packet& pkt); + bool quic_parse_initial_header(const Packet& pkt, const uint8_t* payload_pointer, + const uint8_t* payload_end, uint64_t& offset); + void quic_parse_packet_type(uint8_t packet0); Initial_Secrets initial_secrets; @@ -134,16 +135,25 @@ class QUICParser { uint16_t header_len; uint64_t payload_len; + uint8_t packet_type; const uint8_t* dcid; + uint8_t dcid_len; + const uint8_t* scid; + uint8_t scid_len; const uint8_t* pkn; const uint8_t* sample; uint32_t version; + uint64_t token_length; uint8_t decrypted_payload[CURRENT_BUFFER_SIZE]; uint8_t assembled_payload[CURRENT_BUFFER_SIZE]; uint8_t tmp_header_mem[MAX_HEADER_LEN]; uint8_t* final_payload; + uint8_t zero_rtt; + int parsed_initial; + uint16_t server_port; + bool direction_to_server; bool is_version2; @@ -154,12 +164,58 @@ class QUICParser { uint16_t quic_crypto_len; TLSParser tls_parser; + uint8_t packets; + public: + enum PACKET_TYPE { + INITIAL = 0b00, + ZERO_RTT = 0b01, + HANDSHAKE = 0b10, + RETRY = 0b11, + VERSION_NEGOTIATION = 0b111, + UNKNOWN = 0xFF + }; + enum PACKET_TYPE_FLAG { + F_INITIAL = 0b00000001, + F_ZERO_RTT = 0b00000010, + F_HANDSHAKE = 0b00000100, + F_RETRY = 0b00001000, + F_VERSION_NEGOTIATION = 0b00010000 + }; + enum QUIC_CONSTANTS { + QUIC_UNUSED_VARIABLE_LENGTH_INT = 0xFFFFFFFFFFFFFFFF + }; + enum QUIC_VERSION { + older_version = 0xff0000, + faceebook1 = 0xfaceb001, + faceebook2 = 0xfaceb002, + facebook_experimental = 0xfaceb00e, + q_version2_draft00 = 0xff020000, + q_version2_newest = 0x709a50c4, + force_ver_neg_pattern = 0x0a0a0a0a, + version_negotiation = 0x00000000, + quic_newest = 0x00000001 + }; QUICParser(); - bool quic_start(const Packet&); + uint8_t quic_get_zero_rtt(); + bool quic_parse_initial(const Packet&, const uint8_t* payload_end, uint64_t offset); void quic_get_sni(char* in); void quic_get_user_agent(char* in); void quic_get_version(uint32_t&); + void quic_get_token_length(uint64_t&); + void quic_get_dcid(char *in); + void quic_get_scid(char *in); + void quic_get_scid_len(uint8_t&); + void quic_get_dcid_len(uint8_t&); + void quic_get_parsed_initial(uint8_t&); + void quic_get_packets(uint8_t&); + uint8_t quic_get_packet_type(); + uint16_t quic_get_server_port(); + bool quic_check_quic_long_header_packet(const Packet& pkt); + bool quic_parse_headers(const Packet&, bool forceInitialParsing); + bool quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* payload_pointer, uint8_t* payload_end); + bool quic_long_header_packet(const Packet& pkt); + }; } // namespace ipxp diff --git a/process/stats.hpp b/process/stats.hpp index 1a599d41..b3ff2882 100644 --- a/process/stats.hpp +++ b/process/stats.hpp @@ -31,7 +31,10 @@ #ifndef IPXP_PROCESS_STATS_HPP #define IPXP_PROCESS_STATS_HPP +#include #include +#include +#include #include #include From bd964ef7b52d76bdaaef98c2204821aa7275b53f Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:56:13 +0100 Subject: [PATCH 02/23] Fix IPFIX ID collision of QUIC_ZERO_RTT. --- include/ipfixprobe/ipfix-elements.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ipfixprobe/ipfix-elements.hpp b/include/ipfixprobe/ipfix-elements.hpp index 167579e7..455cdbaf 100644 --- a/include/ipfixprobe/ipfix-elements.hpp +++ b/include/ipfixprobe/ipfix-elements.hpp @@ -247,7 +247,7 @@ namespace ipxp { #define QUIC_SCID(F) F(8057, 897, -1, nullptr) #define QUIC_RETRY_SCID(F) F(8057, 898, -1, nullptr) #define QUIC_MULTIPLEXED(F) F(8057, 899, 1, nullptr) -#define QUIC_ZERO_RTT(F) F(8057, 900, 1, nullptr) +#define QUIC_ZERO_RTT(F) F(8057, 889, 1, nullptr) #define OSQUERY_PROGRAM_NAME(F) F(8057, 852, -1, nullptr) From d0bbe9f39e45b5541b982d2037dba77362db3efa Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Tue, 31 Oct 2023 16:01:05 +0100 Subject: [PATCH 03/23] VN: Add QUIC extension if FLOW_FLUSH. Version = VN is not an error. --- process/quic.cpp | 2 +- process/quic_parser.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/process/quic.cpp b/process/quic.cpp index 4673f7bf..b6660cc1 100644 --- a/process/quic.cpp +++ b/process/quic.cpp @@ -283,7 +283,7 @@ int QUICPlugin::add_quic(Flow &rec, const Packet &pkt) int ret = process_quic(q_ptr, rec, pkt); // Test if QUIC extension is not set - if (new_qptr && (ret == QUIC_DETECTED)) { + if (new_qptr && ((ret == QUIC_DETECTED) || (ret == FLOW_FLUSH))) { rec.add_extension(q_ptr); } if (new_qptr && (ret == QUIC_NOT_DETECTED)) { diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index 054ca3ad..ee649802 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -315,7 +315,6 @@ bool QUICParser::quic_obtain_version() if (version == version_negotiation) { DEBUG_MSG("Error, version negotiation\n"); - return false; } else if (!is_version2 && version == quic_newest) { salt = handshake_salt_v1; } else if (!is_version2 && quic_check_version(version, 9)) { From 4ba98964c95befad8918427affa774fd000a553b Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Tue, 31 Oct 2023 19:06:47 +0100 Subject: [PATCH 04/23] Use correct type for CID fields. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 88cf5d29..ec1c0391 100644 --- a/README.md +++ b/README.md @@ -621,10 +621,10 @@ List of fields exported together with basic flow fields on interface by quic plu | QUIC_VERSION | uint32 | QUIC version extracted from long header packets | | QUIC_CLIENT_VERSION | uint32 | QUIC version from the Initial packet with the TLS Client Hello | | QUIC_TOKEN_LENGTH | uint64 | Token length from Initial and Retry packets | -| QUIC_OCCID | string | Source Connection ID from Initial packet with the TLS Client Hello | -| QUIC_OSCID | string | Destination Connection ID from Initial packet with the TLS Client Hello | -| QUIC_SCID | string | Source Connection ID from long header packets other than before. | -| QUIC_RETRY_SCID | string | Source Connection ID from Retry packet | +| QUIC_OCCID | bytes | Source Connection ID from Initial packet with the TLS Client Hello | +| QUIC_OSCID | bytes | Destination Connection ID from Initial packet with the TLS Client Hello | +| QUIC_SCID | bytes | Source Connection ID from long header packets other than before. | +| QUIC_RETRY_SCID | bytes | Source Connection ID from Retry packet | | QUIC_MULTIPLEXED | uint8 | > 0 if multiplexed (at least two QUIC_OSCIDs or SNIs) | | QUIC_ZERO_RTT | uint8 | Number of 0-RTT packets in flow. | From 1c4f8b8249d85a52513f623ebe2a051dee97a743 Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:12:09 +0100 Subject: [PATCH 05/23] 0-RTT collect client CID. But no other CIDs or version. --- process/quic.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/process/quic.cpp b/process/quic.cpp index b6660cc1..0c1677d1 100644 --- a/process/quic.cpp +++ b/process/quic.cpp @@ -194,7 +194,19 @@ int QUICPlugin::process_quic(RecordExtQUIC *quic_data, Flow &rec, const Packet & // Test for QUIC packet in UDP payload if(process_quic.quic_check_quic_long_header_packet(pkt) ) { - process_quic.quic_get_version(quic_data->quic_version); + uint32_t version; + process_quic.quic_get_version(version); + + uint8_t packets = 0; + process_quic.quic_get_packets(packets); + + // A 0-RTT carries the same QUIC version as the Client Initial Hello. + // 0-RTT and compatible version negotiation is not defined. + // We ignore those cases because it might be defined in the future + if ((process_quic.quic_get_packet_type() != QUICParser::PACKET_TYPE::ZERO_RTT)) { + quic_data->quic_version = version; + } + if (quic_data->quic_version == QUICParser::QUIC_VERSION::version_negotiation) { return FLOW_FLUSH; } @@ -203,8 +215,7 @@ int QUICPlugin::process_quic(RecordExtQUIC *quic_data, Flow &rec, const Packet & // Simple version, more advanced information is available after Initial parsing int toServer = get_direction_to_server_and_set_port(&process_quic, quic_data, process_quic.quic_get_server_port(), pkt, ext); - uint8_t packets = 0; - process_quic.quic_get_packets(packets); + if (packets & QUICParser::PACKET_TYPE_FLAG::F_ZERO_RTT) { uint8_t zero_rtt_pkts = process_quic.quic_get_zero_rtt(); @@ -227,7 +238,6 @@ int QUICPlugin::process_quic(RecordExtQUIC *quic_data, Flow &rec, const Packet & // Update accounting for information from CH, SH. toServer = get_direction_to_server_and_set_port(&process_quic, quic_data, process_quic.quic_get_server_port(), pkt, ext); // fallthrough to set cids - case QUICParser::PACKET_TYPE::ZERO_RTT: case QUICParser::PACKET_TYPE::HANDSHAKE: // -1 sets stores intermediately. set_cid_fields(quic_data, &process_quic, toServer, ext, pkt); @@ -239,6 +249,11 @@ int QUICPlugin::process_quic(RecordExtQUIC *quic_data, Flow &rec, const Packet & process_quic.quic_get_token_length(quic_data->quic_token_length); set_cid_fields(quic_data, &process_quic, toServer, ext, pkt); break; + case QUICParser::PACKET_TYPE::ZERO_RTT: + // Connection IDs are identical to Client Initial CH. The DCID might be OSCID at first and change to SCID later. We ignore the DCID. + process_quic.quic_get_scid(quic_data->occid); + process_quic.quic_get_scid_len(quic_data->occid_length); + break; } return QUIC_DETECTED; From 6825aa40eb7b0dfb0243e30dae323846431dc8d6 Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Thu, 2 Nov 2023 12:18:06 +0100 Subject: [PATCH 06/23] Add quic packet type information for each datagram. --- include/ipfixprobe/ipfix-elements.hpp | 4 +++- process/quic.cpp | 8 ++++++++ process/quic.hpp | 29 ++++++++++++++++++++++----- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/include/ipfixprobe/ipfix-elements.hpp b/include/ipfixprobe/ipfix-elements.hpp index 455cdbaf..025411ae 100644 --- a/include/ipfixprobe/ipfix-elements.hpp +++ b/include/ipfixprobe/ipfix-elements.hpp @@ -248,6 +248,7 @@ namespace ipxp { #define QUIC_RETRY_SCID(F) F(8057, 898, -1, nullptr) #define QUIC_MULTIPLEXED(F) F(8057, 899, 1, nullptr) #define QUIC_ZERO_RTT(F) F(8057, 889, 1, nullptr) +#define QUIC_PACKETS(F) F(8057, 888, -1, nullptr) #define OSQUERY_PROGRAM_NAME(F) F(8057, 852, -1, nullptr) @@ -511,7 +512,8 @@ namespace ipxp { F(QUIC_SCID) \ F(QUIC_RETRY_SCID) \ F(QUIC_MULTIPLEXED) \ - F(QUIC_ZERO_RTT) + F(QUIC_ZERO_RTT) \ + F(QUIC_PACKETS) #define IPFIX_OSQUERY_TEMPLATE(F) \ F(OSQUERY_PROGRAM_NAME) \ diff --git a/process/quic.cpp b/process/quic.cpp index 0c1677d1..5eb72bad 100644 --- a/process/quic.cpp +++ b/process/quic.cpp @@ -200,6 +200,14 @@ int QUICPlugin::process_quic(RecordExtQUIC *quic_data, Flow &rec, const Packet & uint8_t packets = 0; process_quic.quic_get_packets(packets); + + // Store all QUIC packet types contained in each packet + uint8_t pos = rec.src_packets + rec.dst_packets -1; + if (pos < QUIC_MAX_ELEMCOUNT) { + quic_data->pkt_types[pos] = packets; + quic_data->last_pkt_type = pos; + } + // A 0-RTT carries the same QUIC version as the Client Initial Hello. // 0-RTT and compatible version negotiation is not defined. // We ignore those cases because it might be defined in the future diff --git a/process/quic.hpp b/process/quic.hpp index 0c7dab03..bf76363d 100644 --- a/process/quic.hpp +++ b/process/quic.hpp @@ -22,12 +22,13 @@ #include "quic_parser.hpp" #include +#include #include #include namespace ipxp { -#define QUIC_UNIREC_TEMPLATE "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION,QUIC_CLIENT_VERSION,QUIC_TOKEN_LENGTH,QUIC_OCCID,QUIC_OSCID,QUIC_SCID,QUIC_RETRY_SCID,QUIC_MULTIPLEXED,QUIC_ZERO_RTT" +#define QUIC_UNIREC_TEMPLATE "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION,QUIC_CLIENT_VERSION,QUIC_TOKEN_LENGTH,QUIC_OCCID,QUIC_OSCID,QUIC_SCID,QUIC_RETRY_SCID,QUIC_MULTIPLEXED,QUIC_ZERO_RTT,QUIC_PACKETS" UR_FIELDS( string QUIC_SNI, string QUIC_USER_AGENT, @@ -39,15 +40,18 @@ UR_FIELDS( bytes QUIC_SCID, bytes QUIC_RETRY_SCID, uint8 QUIC_MULTIPLEXED, - uint8 QUIC_ZERO_RTT + uint8 QUIC_ZERO_RTT, + uint8* QUIC_PACKETS ) /** * \brief Flow record extension header for storing parsed QUIC packets. */ +#define QUIC_MAX_ELEMCOUNT 30 #define MAX_CID_LEN 20 #define QUIC_DETECTED 0 #define QUIC_NOT_DETECTED 2 +#define QUIC_PKT_FIELD_ID 888 struct RecordExtQUIC : public RecordExt { static int REGISTERED_ID; @@ -75,6 +79,8 @@ struct RecordExtQUIC : public RecordExt { uint8_t quic_multiplexed; uint8_t quic_zero_rtt; + uint8_t pkt_types[QUIC_MAX_ELEMCOUNT]; + uint8_t last_pkt_type; RecordExtQUIC() : RecordExt(REGISTERED_ID) { @@ -99,6 +105,8 @@ struct RecordExtQUIC : public RecordExt { quic_token_length = QUICParser::QUIC_CONSTANTS::QUIC_UNUSED_VARIABLE_LENGTH_INT; quic_multiplexed = 0; quic_zero_rtt = 0; + memset(pkt_types, 0, sizeof(pkt_types)); + last_pkt_type = 0; } #ifdef WITH_NEMEA @@ -115,6 +123,10 @@ struct RecordExtQUIC : public RecordExt { ur_set_var(tmplt, record, F_QUIC_RETRY_SCID, retry_scid, retry_scid_length); ur_set(tmplt, record, F_QUIC_MULTIPLEXED, quic_multiplexed); ur_set(tmplt, record, F_QUIC_ZERO_RTT, quic_zero_rtt); + ur_array_allocate(tmplt, record, F_QUIC_PACKETS, last_pkt_type+1); + for (int i = 0; i < last_pkt_type+1; i++) { + ur_array_set(tmplt, record, F_QUIC_PACKETS, i, pkt_types[i]); + } } const char *get_unirec_tmplt() const @@ -126,6 +138,8 @@ struct RecordExtQUIC : public RecordExt { virtual int fill_ipfix(uint8_t *buffer, int size) { + IpfixBasicList basiclist; + basiclist.hdrEnterpriseNum = IpfixBasicList::CesnetPEM; uint16_t len_sni = strlen(sni); uint16_t len_user_agent = strlen(user_agent); uint16_t len_version = sizeof(quic_version); @@ -133,11 +147,13 @@ struct RecordExtQUIC : public RecordExt { uint16_t len_token_length = sizeof(quic_token_length); uint16_t len_multiplexed = sizeof(quic_multiplexed); uint16_t len_zero_rtt = sizeof(quic_zero_rtt); - int pos = 0; + uint16_t pkt_types_len = sizeof(pkt_types[0])*(last_pkt_type+1) + basiclist.HeaderSize() ; + uint32_t pos = 0; if ((len_sni + 3) + (len_user_agent + 3) + len_version + len_client_version + len_token_length + len_multiplexed + len_zero_rtt + - (scid_length + 3) + (occid_length + 3) + (oscid_length + 3) + (retry_scid_length + 3) > size) { + (scid_length + 3) + (occid_length + 3) + (oscid_length + 3) + (retry_scid_length + 3) + + pkt_types_len > size) { return -1; } @@ -163,7 +179,10 @@ struct RecordExtQUIC : public RecordExt { *(uint8_t *) (buffer + pos) = quic_zero_rtt; pos += 1; - return pos; + // Packet types + pos += basiclist.FillBuffer(buffer + pos, pkt_types, (uint16_t) last_pkt_type + 1, (uint16_t) QUIC_PKT_FIELD_ID); + + return pos; } const char **get_ipfix_tmplt() const From 85e0975e95e58b53c68a25282a6e2bd7c7d6fd5d Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Thu, 2 Nov 2023 12:19:19 +0100 Subject: [PATCH 07/23] Enforce CID lengths are within the defined spec. Parse packet type last. --- process/quic_parser.cpp | 13 ++++++++++--- process/quic_parser.hpp | 3 +-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index ee649802..0d7921c5 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -1071,9 +1071,6 @@ bool QUICParser::quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* return false; } - quic_parse_packet_type(quic_h1->first_byte); - - offset += sizeof(quic_first_ver_dcidlen); if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { @@ -1081,6 +1078,10 @@ bool QUICParser::quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* } if (quic_h1->dcid_len != 0) { + if (quic_h1->dcid_len>MAX_CID_LEN) { + DEBUG_MSG("Recieved DCID longer than supported. dcid_len=%d \n", dcid_len); + return false; + } dcid = (payload_pointer + offset); dcid_len = quic_h1->dcid_len; offset += quic_h1->dcid_len; @@ -1099,6 +1100,10 @@ bool QUICParser::quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* } if (quic_h2->scid_len != 0) { + if (quic_h2->scid_len>MAX_CID_LEN) { + DEBUG_MSG("Recieved SCID longer than supported. scid_len=%d \n", scid_len); + return false; + } scid = (payload_pointer + offset); scid_len = quic_h2->scid_len; offset += quic_h2->scid_len; @@ -1108,6 +1113,8 @@ bool QUICParser::quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* return false; } + quic_parse_packet_type(quic_h1->first_byte); + return true; } diff --git a/process/quic_parser.hpp b/process/quic_parser.hpp index 944b0044..dd0f8592 100644 --- a/process/quic_parser.hpp +++ b/process/quic_parser.hpp @@ -42,8 +42,7 @@ #define CURRENT_BUFFER_SIZE 1500 // 8 because (1B QUIC LH, 4B Version, 1 B SCID LEN, 1B DCID LEN, Payload/Retry Token/Supported Version >= 1 B) #define QUIC_MIN_PACKET_LENGTH 8 - - +#define MAX_CID_LEN 20 namespace ipxp { typedef struct __attribute__((packed)) quic_first_ver_dcidlen { From c2dfa2a5fb0d3c45b7517c6c68db7378941ac074 Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Thu, 2 Nov 2023 12:41:55 +0100 Subject: [PATCH 08/23] Export detected quic server port. --- include/ipfixprobe/ipfix-elements.hpp | 2 ++ process/quic.hpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/ipfixprobe/ipfix-elements.hpp b/include/ipfixprobe/ipfix-elements.hpp index 025411ae..0e69cefb 100644 --- a/include/ipfixprobe/ipfix-elements.hpp +++ b/include/ipfixprobe/ipfix-elements.hpp @@ -248,6 +248,7 @@ namespace ipxp { #define QUIC_RETRY_SCID(F) F(8057, 898, -1, nullptr) #define QUIC_MULTIPLEXED(F) F(8057, 899, 1, nullptr) #define QUIC_ZERO_RTT(F) F(8057, 889, 1, nullptr) +#define QUIC_SERVER_PORT(F) F(8057, 887, 2, nullptr) #define QUIC_PACKETS(F) F(8057, 888, -1, nullptr) @@ -513,6 +514,7 @@ namespace ipxp { F(QUIC_RETRY_SCID) \ F(QUIC_MULTIPLEXED) \ F(QUIC_ZERO_RTT) \ + F(QUIC_SERVER_PORT) \ F(QUIC_PACKETS) #define IPFIX_OSQUERY_TEMPLATE(F) \ diff --git a/process/quic.hpp b/process/quic.hpp index bf76363d..cc336eec 100644 --- a/process/quic.hpp +++ b/process/quic.hpp @@ -28,7 +28,7 @@ namespace ipxp { -#define QUIC_UNIREC_TEMPLATE "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION,QUIC_CLIENT_VERSION,QUIC_TOKEN_LENGTH,QUIC_OCCID,QUIC_OSCID,QUIC_SCID,QUIC_RETRY_SCID,QUIC_MULTIPLEXED,QUIC_ZERO_RTT,QUIC_PACKETS" +#define QUIC_UNIREC_TEMPLATE "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION,QUIC_CLIENT_VERSION,QUIC_TOKEN_LENGTH,QUIC_OCCID,QUIC_OSCID,QUIC_SCID,QUIC_RETRY_SCID,QUIC_MULTIPLEXED,QUIC_ZERO_RTT,QUIC_SERVER_PORT,QUIC_PACKETS" UR_FIELDS( string QUIC_SNI, string QUIC_USER_AGENT, @@ -41,6 +41,7 @@ UR_FIELDS( bytes QUIC_RETRY_SCID, uint8 QUIC_MULTIPLEXED, uint8 QUIC_ZERO_RTT, + uint16 QUIC_SERVER_PORT, uint8* QUIC_PACKETS ) @@ -123,6 +124,7 @@ struct RecordExtQUIC : public RecordExt { ur_set_var(tmplt, record, F_QUIC_RETRY_SCID, retry_scid, retry_scid_length); ur_set(tmplt, record, F_QUIC_MULTIPLEXED, quic_multiplexed); ur_set(tmplt, record, F_QUIC_ZERO_RTT, quic_zero_rtt); + ur_set(tmplt, record, F_QUIC_SERVER_PORT, server_port); ur_array_allocate(tmplt, record, F_QUIC_PACKETS, last_pkt_type+1); for (int i = 0; i < last_pkt_type+1; i++) { ur_array_set(tmplt, record, F_QUIC_PACKETS, i, pkt_types[i]); @@ -148,12 +150,14 @@ struct RecordExtQUIC : public RecordExt { uint16_t len_multiplexed = sizeof(quic_multiplexed); uint16_t len_zero_rtt = sizeof(quic_zero_rtt); uint16_t pkt_types_len = sizeof(pkt_types[0])*(last_pkt_type+1) + basiclist.HeaderSize() ; + uint16_t len_server_port = sizeof(server_port); uint32_t pos = 0; + if ((len_sni + 3) + (len_user_agent + 3) + len_version + len_client_version + len_token_length + len_multiplexed + len_zero_rtt + (scid_length + 3) + (occid_length + 3) + (oscid_length + 3) + (retry_scid_length + 3) + - pkt_types_len > size) { + len_server_port + pkt_types_len > size) { return -1; } @@ -179,6 +183,8 @@ struct RecordExtQUIC : public RecordExt { *(uint8_t *) (buffer + pos) = quic_zero_rtt; pos += 1; + *(uint16_t *) (buffer + pos) = htons(server_port); + pos += len_server_port; // Packet types pos += basiclist.FillBuffer(buffer + pos, pkt_types, (uint16_t) last_pkt_type + 1, (uint16_t) QUIC_PKT_FIELD_ID); From 1fab916019227ca8de486a3283274c832999255f Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:17:47 +0100 Subject: [PATCH 09/23] vn: Add server port before flushing flow --- process/quic.cpp | 9 ++++----- process/quic_parser.cpp | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/process/quic.cpp b/process/quic.cpp index 5eb72bad..fa200506 100644 --- a/process/quic.cpp +++ b/process/quic.cpp @@ -215,15 +215,10 @@ int QUICPlugin::process_quic(RecordExtQUIC *quic_data, Flow &rec, const Packet & quic_data->quic_version = version; } - if (quic_data->quic_version == QUICParser::QUIC_VERSION::version_negotiation) { - return FLOW_FLUSH; - } - RecordExtQUIC *ext = (RecordExtQUIC *) rec.get_extension(RecordExtQUIC::REGISTERED_ID); // Simple version, more advanced information is available after Initial parsing int toServer = get_direction_to_server_and_set_port(&process_quic, quic_data, process_quic.quic_get_server_port(), pkt, ext); - if (packets & QUICParser::PACKET_TYPE_FLAG::F_ZERO_RTT) { uint8_t zero_rtt_pkts = process_quic.quic_get_zero_rtt(); @@ -235,6 +230,10 @@ int QUICPlugin::process_quic(RecordExtQUIC *quic_data, Flow &rec, const Packet & } uint8_t parsed_initial = 0; + if (version == QUICParser::QUIC_VERSION::version_negotiation) { + return FLOW_FLUSH; + } + switch (process_quic.quic_get_packet_type()) { case QUICParser::PACKET_TYPE::INITIAL: process_quic.quic_get_parsed_initial(parsed_initial); diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index 0d7921c5..a65bd8b5 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -1204,10 +1204,12 @@ bool QUICParser::quic_set_server_port(const Packet& pkt) { if (hs.type == 1) { server_port = pkt.dst_port; } else if (hs.type == 2) { + // Won't be reached, since we don't supply the OCCID to quic_parser server_port = pkt.src_port; } // e.g. ACKs do not reveal direction break; + case VERSION_NEGOTIATION: case RETRY: server_port = pkt.src_port; break; From f6ae3222bbdc33881fa396c7bae15069c0b5bbff Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:21:41 +0100 Subject: [PATCH 10/23] quic versions: Support all versions triggering vns. --- process/quic_parser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index a65bd8b5..7a349960 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -952,11 +952,13 @@ bool QUICParser::quic_check_supported_version(const uint32_t version) { case faceebook1: case faceebook2: case facebook_experimental: - case force_ver_neg_pattern: case version_negotiation: case quic_newest: return true; } + if ((version & 0x0f0f0f0f) == force_ver_neg_pattern) { + return true + } return false; } From fa271e9cfab3865bca0463273be8c4bd936ad152 Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:36:57 +0100 Subject: [PATCH 11/23] Fix processing of version negotiation eliciting version. --- process/quic_parser.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index 7a349960..41eb999a 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -247,6 +247,11 @@ uint8_t QUICParser::quic_draft_version(uint32_t version) if ((version >> 8) == older_version) { return (uint8_t) version; } + // This exists since version 29, but is still present in RFC9000. + if (version & 0x0F0F0F0F == force_ver_neg_pattern){ + // Version 1 + return 35; + } switch (version) { // older mvfst version, but still used, based on draft 22, but salt 21 used case (faceebook1): @@ -255,9 +260,6 @@ uint8_t QUICParser::quic_draft_version(uint32_t version) case faceebook2: case facebook_experimental: return 27; - case (force_ver_neg_pattern & 0x0F0F0F0F): - return 29; - // version 2 draft 00 case q_version2_draft00: // newest @@ -329,6 +331,8 @@ bool QUICParser::quic_obtain_version() salt = handshake_salt_draft_23; } else if (!is_version2 && quic_check_version(version, 32)) { salt = handshake_salt_draft_29; + else if (!is_version2 && quic_check_version(version, 35)) { + salt = handshake_salt_v1; } else if (is_version2 && quic_check_version(version, 100)) { salt = handshake_salt_v2; } else { From c77c89f2814956e6a2442abb6436918179665796 Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:31:28 +0100 Subject: [PATCH 12/23] tested and improved quic module This include the following improvements: - Fix order of processing CID information - Fix detection of QUIC sni - Store DCID from first Initial and Retry packet for decryption, in case the connection does not begin with an Initial incl. the TLS client hello. - Version negotiation: extract CID fields - Scan for QUIC packets in the entire flow. Before we only checked the first packet. But this is problematic when an unknown QUIC version is used in the first packet. - Add all known QUIC versions and respective salts (Adds Facebook versions, picoquic and QUICv2 salts) - Add extraction of TLS extensions in Client Hello. This includes the extraction of the TLS extension type, length and payload. - Increase QUIC token size. - Update authors and date - Update README - Code cleanup, refector get_API --- README.md | 32 +- include/ipfixprobe/ipfix-elements.hpp | 10 +- process/quic.cpp | 582 +++++++++++++++++--------- process/quic.hpp | 551 ++++++++++++++---------- process/quic_parser.cpp | 538 ++++++++++++++++-------- process/quic_parser.hpp | 115 ++++- 6 files changed, 1220 insertions(+), 608 deletions(-) diff --git a/README.md b/README.md index ec1c0391..b24bb7c9 100644 --- a/README.md +++ b/README.md @@ -614,19 +614,25 @@ List of fields exported together with basic flow fields on interface by WG plugi List of fields exported together with basic flow fields on interface by quic plugin. -| Output field | Type | Description | -|:-------------------:|:------:|:-----------------------------------------------------------------------:| -| QUIC_SNI | string | Decrypted server name | -| QUIC_USER_AGENT | string | Decrypted user agent | -| QUIC_VERSION | uint32 | QUIC version extracted from long header packets | -| QUIC_CLIENT_VERSION | uint32 | QUIC version from the Initial packet with the TLS Client Hello | -| QUIC_TOKEN_LENGTH | uint64 | Token length from Initial and Retry packets | -| QUIC_OCCID | bytes | Source Connection ID from Initial packet with the TLS Client Hello | -| QUIC_OSCID | bytes | Destination Connection ID from Initial packet with the TLS Client Hello | -| QUIC_SCID | bytes | Source Connection ID from long header packets other than before. | -| QUIC_RETRY_SCID | bytes | Source Connection ID from Retry packet | -| QUIC_MULTIPLEXED | uint8 | > 0 if multiplexed (at least two QUIC_OSCIDs or SNIs) | -| QUIC_ZERO_RTT | uint8 | Number of 0-RTT packets in flow. | +| Output field | Type | Description | +|:-------------------:|:--------:|:------------------------------------------------------------------------:| +| QUIC_SNI | string | Decrypted server name | +| QUIC_USER_AGENT | string | Decrypted user agent | +| QUIC_VERSION | uint32 | QUIC version from first server long header packets | +| QUIC_CLIENT_VERSION | uint32 | QUIC version from first client long header packet | +| QUIC_TOKEN_LENGTH | uint64 | Token length from Initial and Retry packets | +| QUIC_OCCID | bytes | Source Connection ID from first client packet | +| QUIC_OSCID | bytes | Destination Connection ID from first client packet | +| QUIC_SCID | bytes | Source Connection ID from first server packet | +| QUIC_RETRY_SCID | bytes | Source Connection ID from Retry packet | +| QUIC_MULTIPLEXED | uint8 | > 0 if multiplexed (at least two different QUIC_OSCIDs or SNIs) | +| QUIC_ZERO_RTT | uint8 | Number of 0-RTT packets in flow. | +| QUIC_SERVER_PORT | uint16 | TODO Server Port determined by packet type and TLS message | +| QUIC_PACKETS | uint8\* | QUIC long header packet type (v1 encoded), version negotiation, QUIC bit | +| QUIC_CH_PARSED | uint8 | >0 if TLS Client Hello parsed without errors | +| QUIC_TLS_EXT_TYPE | uint16\* | TLS extensions in the TLS Client Hello | +| QUIC_TLS_EXT_LEN | uint16\* | Length of each TLS extension | +| QUIC_TLS_EXT | string | Payload of each TLS extension | ### ICMP diff --git a/include/ipfixprobe/ipfix-elements.hpp b/include/ipfixprobe/ipfix-elements.hpp index 0e69cefb..12460912 100644 --- a/include/ipfixprobe/ipfix-elements.hpp +++ b/include/ipfixprobe/ipfix-elements.hpp @@ -250,6 +250,10 @@ namespace ipxp { #define QUIC_ZERO_RTT(F) F(8057, 889, 1, nullptr) #define QUIC_SERVER_PORT(F) F(8057, 887, 2, nullptr) #define QUIC_PACKETS(F) F(8057, 888, -1, nullptr) +#define QUIC_CH_PARSED(F) F(8057, 886, 1, nullptr) +#define QUIC_TLS_EXT_TYPE(F) F(8057, 885, -1, nullptr) +#define QUIC_TLS_EXT_LEN(F) F(8057, 884, -1, nullptr) +#define QUIC_TLS_EXT(F) F(8057, 883, -1, nullptr) #define OSQUERY_PROGRAM_NAME(F) F(8057, 852, -1, nullptr) @@ -515,7 +519,11 @@ namespace ipxp { F(QUIC_MULTIPLEXED) \ F(QUIC_ZERO_RTT) \ F(QUIC_SERVER_PORT) \ - F(QUIC_PACKETS) + F(QUIC_PACKETS) \ + F(QUIC_CH_PARSED) \ + F(QUIC_TLS_EXT_TYPE) \ + F(QUIC_TLS_EXT_LEN) \ + F(QUIC_TLS_EXT) #define IPFIX_OSQUERY_TEMPLATE(F) \ F(OSQUERY_PROGRAM_NAME) \ diff --git a/process/quic.cpp b/process/quic.cpp index fa200506..cdc64c99 100644 --- a/process/quic.cpp +++ b/process/quic.cpp @@ -7,15 +7,14 @@ * \brief Plugin for enriching flows for quic data. * \author Andrej Lukacovic lukacan1@fit.cvut.cz * \author Karel Hynek - * \date 2022 + * \author Jonas Mücke + * \date 2023 */ - #ifdef WITH_NEMEA -# include +#include #endif - #include "quic.hpp" namespace ipxp { @@ -23,161 +22,298 @@ int RecordExtQUIC::REGISTERED_ID = -1; __attribute__((constructor)) static void register_this_plugin() { - static PluginRecord rec = PluginRecord("quic", [](){ - return new QUICPlugin(); - }); + static PluginRecord rec = PluginRecord("quic", []() { return new QUICPlugin(); }); - register_plugin(&rec); - RecordExtQUIC::REGISTERED_ID = register_extension(); + register_plugin(&rec); + RecordExtQUIC::REGISTERED_ID = register_extension(); } -QUICPlugin::QUICPlugin() -{ -} +QUICPlugin::QUICPlugin() {} QUICPlugin::~QUICPlugin() { - close(); + close(); } -void QUICPlugin::init(const char *params) -{ } +void QUICPlugin::init(const char* params) {} -void QUICPlugin::close() -{ +void QUICPlugin::close() {} +ProcessPlugin* QUICPlugin::copy() +{ + return new QUICPlugin(*this); } -ProcessPlugin *QUICPlugin::copy() +void QUICPlugin::set_cid_if_unset( + bool& set_flag, + uint8_t& src_id_length, + char* src_id, + uint8_t& dst_id_length, + char* dst_id) { - return new QUICPlugin(*this); + if (!set_flag) { + dst_id_length = src_id_length; + memcpy(dst_id, src_id, src_id_length); + set_flag = true; + } } - - -void QUICPlugin::set_stored_cid_fields(RecordExtQUIC *quic_data, RecordExtQUIC *ext) { - if ((ext != nullptr) && (ext->dir_dport != 0)) { - if (ext->dir_dport == quic_data->server_port) { +void QUICPlugin::set_stored_cid_fields(RecordExtQUIC* quic_data, bool new_quic_flow) +{ + if (!new_quic_flow & (quic_data->server_port != 0) & (quic_data->dir_dport != 0)) { + if (quic_data->dir_dport == quic_data->server_port) { // to server - quic_data->scid_length = ext->dir_dcid_length; - memcpy(quic_data->scid, ext->dir_dcid, quic_data->scid_length); - quic_data->occid_length = ext->dir_scid_length; - memcpy(quic_data->occid, ext->dir_scid, quic_data->occid_length); + // Check for CH to make sure it is a server chosen CID + if (quic_data->client_hello_seen & quic_data->packet_from_server_seen) { + set_cid_if_unset( + quic_data->scid_set, + quic_data->dir_dcid_length, + quic_data->dir_dcid, + quic_data->scid_length, + quic_data->scid); + } + set_cid_if_unset( + quic_data->occid_set, + quic_data->dir_scid_length, + quic_data->dir_scid, + quic_data->occid_length, + quic_data->occid); } else { // from server - quic_data->scid_length = ext->dir_scid_length; - memcpy(quic_data->scid, ext->dir_scid, quic_data->scid_length); - quic_data->occid_length = ext->dir_dcid_length; - memcpy(quic_data->occid, ext->dir_dcid, quic_data->occid_length); + set_cid_if_unset( + quic_data->scid_set, + quic_data->dir_scid_length, + quic_data->dir_scid, + quic_data->scid_length, + quic_data->scid); + set_cid_if_unset( + quic_data->occid_set, + quic_data->dir_dcid_length, + quic_data->dir_dcid, + quic_data->occid_length, + quic_data->occid); + } + quic_data->dir_dport = 0; + + if (quic_data->dir_dport2 != 0) { + if (quic_data->dir_dport2 == quic_data->server_port) { + // to server + // Check for CH to make sure it is a server chosen CID + if (quic_data->client_hello_seen & quic_data->packet_from_server_seen) { + set_cid_if_unset( + quic_data->scid_set, + quic_data->dir_dcid_length2, + quic_data->dir_dcid2, + quic_data->scid_length, + quic_data->scid); + } + set_cid_if_unset( + quic_data->occid_set, + quic_data->dir_scid_length2, + quic_data->dir_scid2, + quic_data->occid_length, + quic_data->occid); + } else { + // from server + set_cid_if_unset( + quic_data->scid_set, + quic_data->dir_scid_length2, + quic_data->dir_scid2, + quic_data->scid_length, + quic_data->scid); + set_cid_if_unset( + quic_data->occid_set, + quic_data->dir_dcid_length2, + quic_data->dir_dcid2, + quic_data->occid_length, + quic_data->occid); + } + quic_data->dir_dport2 = 0; } - ext->dir_dport = 0; } } +void QUICPlugin::set_cid_fields( + RecordExtQUIC* quic_data, + Flow& rec, + QUICParser* process_quic, + int toServer, + bool new_quic_flow, + const Packet& pkt) +{ + uint8_t packets = get_packets_from_server(process_quic->quic_get_server_port(), rec); -void QUICPlugin::set_cid_fields(RecordExtQUIC *quic_data, QUICParser *process_quic, int toServer, - RecordExtQUIC *ext, const Packet &pkt ) { switch (toServer) { - case 1: + case 1: + set_stored_cid_fields(quic_data, new_quic_flow); + + if ((!quic_data->scid_set) && (quic_data->packet_from_server_seen) + && (packets - 1 != quic_data->cnt_retry_packets)) { process_quic->quic_get_dcid(quic_data->scid); process_quic->quic_get_dcid_len(quic_data->scid_length); + quic_data->scid_set = true; + } + if (!quic_data->occid_set) { process_quic->quic_get_scid(quic_data->occid); process_quic->quic_get_scid_len(quic_data->occid_length); + quic_data->occid_set = true; + } + break; + case 0: + set_stored_cid_fields(quic_data, new_quic_flow); - set_stored_cid_fields(quic_data, ext); - break; - case 0: + if (!quic_data->occid_set) { process_quic->quic_get_dcid(quic_data->occid); process_quic->quic_get_dcid_len(quic_data->occid_length); + quic_data->occid_set = true; + } + if ((!quic_data->scid_set) & quic_data->packet_from_server_seen) { process_quic->quic_get_scid(quic_data->scid); process_quic->quic_get_scid_len(quic_data->scid_length); + quic_data->scid_set = true; + } - set_stored_cid_fields(quic_data, ext); - break; - case -1: - default: - // no direction information, store for future use + break; + case -1: + default: + // no direction information, store for future use + // We store only information from the first packet per direction + if ((quic_data->dir_dport != 0) && (quic_data->dir_dport2 == 0) + && (quic_data->dir_dport != pkt.dst_port)) { + // conditions first is set, second is unset, other direction than first. + // Store in secondary dataset + process_quic->quic_get_scid(quic_data->dir_scid2); + process_quic->quic_get_scid_len(quic_data->dir_scid_length2); + process_quic->quic_get_dcid(quic_data->dir_dcid2); + process_quic->quic_get_dcid_len(quic_data->dir_dcid_length2); + quic_data->dir_dport2 = pkt.dst_port; + } + + if (quic_data->dir_dport == 0) { process_quic->quic_get_scid(quic_data->dir_scid); process_quic->quic_get_scid_len(quic_data->dir_scid_length); process_quic->quic_get_dcid(quic_data->dir_dcid); process_quic->quic_get_dcid_len(quic_data->dir_dcid_length); quic_data->dir_dport = pkt.dst_port; - break; + } + break; } } - -int QUICPlugin::get_direction_to_server_and_set_port(QUICParser *process_quic, RecordExtQUIC *quic_data, - uint16_t parsed_port, const Packet &pkt, RecordExtQUIC *ext) { - int toServer = get_direction_to_server(parsed_port, pkt, ext); - if ((toServer != -1) && (quic_data->server_port ==0)) { +int QUICPlugin::get_direction_to_server_and_set_port( + QUICParser* process_quic, + RecordExtQUIC* quic_data, + uint16_t parsed_port, + const Packet& pkt, + bool new_quic_flow) +{ + int toServer = get_direction_to_server(parsed_port, quic_data, pkt, new_quic_flow); + if ((toServer != -1) && (quic_data->server_port == 0)) { quic_data->server_port = process_quic->quic_get_server_port(); } + if (toServer == 0) { + quic_data->packet_from_server_seen = true; + } return toServer; } -int QUICPlugin::get_direction_to_server(uint16_t parsed_port, const Packet &pkt, RecordExtQUIC *ext) +int QUICPlugin::get_direction_to_server( + uint16_t parsed_port, + RecordExtQUIC* quic_data, + const Packet& pkt, + bool new_quic_flow) { if (parsed_port != 0) { return pkt.dst_port == parsed_port; - } else if (((ext != nullptr) && (ext->server_port != 0))) { - return pkt.dst_port == ext->server_port; + } else if ((!new_quic_flow) & (quic_data->server_port != 0)) { + return pkt.dst_port == quic_data->server_port; } return -1; } -void QUICPlugin::set_client_hello_fields(QUICParser *process_quic, RecordExtQUIC *quic_data, const Packet &pkt, - RecordExtQUIC *ext) { +uint8_t QUICPlugin::get_packets_from_server(uint16_t server_port, Flow& rec) +{ + uint8_t packets = 0; + if (server_port == rec.src_port) { + packets = rec.src_packets; + } else { + packets = rec.dst_packets; + } + return packets; +} +void QUICPlugin::set_client_hello_fields( + QUICParser* process_quic, + Flow& rec, + RecordExtQUIC* quic_data, + const Packet& pkt, + bool new_quic_flow) +{ process_quic->quic_get_token_length(quic_data->quic_token_length); - char dcid[MAX_CID_LEN] = { 0 }; + char dcid[MAX_CID_LEN] = {0}; uint8_t dcid_len = 0; - // since this this is a client hello the dcid must be set + // since this is a client hello the dcid must be set process_quic->quic_get_dcid(dcid); process_quic->quic_get_dcid_len(dcid_len); - - - - if ((quic_data->quic_token_length != QUICParser::QUIC_CONSTANTS::QUIC_UNUSED_VARIABLE_LENGTH_INT) && - (quic_data->quic_token_length > 0) && - ((quic_data->retry_scid_length == dcid_len) || - (ext != nullptr) && (ext->retry_scid_length == dcid_len)) && - ((strncmp(quic_data->retry_scid, dcid, std::min(quic_data->retry_scid_length, dcid_len)) == 0) || - ((ext != nullptr) && (strncmp(ext->retry_scid, dcid, std::min(ext->retry_scid_length, dcid_len))) == 0) ) ) { - // Retry case: We already have all information from the previous CH. + if ((quic_data->quic_token_length + != QUICParser::QUIC_CONSTANTS::QUIC_UNUSED_VARIABLE_LENGTH_INT) + && (quic_data->quic_token_length > 0) + && ((quic_data->retry_scid_length == dcid_len) + || (!new_quic_flow) && (quic_data->retry_scid_length == dcid_len)) + && ((strncmp(quic_data->retry_scid, dcid, std::min(quic_data->retry_scid_length, dcid_len)) + == 0) + || ((!new_quic_flow) + && (strncmp( + quic_data->retry_scid, + dcid, + std::min(quic_data->retry_scid_length, dcid_len))) + == 0))) { + // CH after Retry case: We already have all information from the previous CH. } else { // MULTIPLEXING detection - char oscid[MAX_CID_LEN] = { 0 }; + char oscid[MAX_CID_LEN] = {0}; uint8_t oscid_len = 0; process_quic->quic_get_dcid(oscid); process_quic->quic_get_dcid_len(oscid_len); - char sni[BUFF_SIZE] = { 0 }; + char sni[BUFF_SIZE] = {0}; process_quic->quic_get_sni(sni); - if (( (oscid_len == quic_data->oscid_length) && - (quic_data->oscid_length != 0) && - (strncmp(oscid, quic_data->oscid, oscid_len) == 0) && - (strncmp(quic_data->sni, sni, BUFF_SIZE )) == 0) || - ((ext == nullptr))) { + if ((new_quic_flow) || (!quic_data->client_hello_seen) + || ((quic_data->client_hello_seen) + && ((strncmp(oscid, quic_data->oscid, oscid_len) == 0) || + // Case that the first response message from server is received and the client + // now uses that SCID as DCID. + (quic_data->packet_from_server_seen && oscid_len == (quic_data->scid_length) + && (strncmp(oscid, quic_data->scid, oscid_len) == 0))) + && (strncmp(quic_data->sni, sni, BUFF_SIZE) == 0))) { // Repeated Initial or new Initial/QUIC flow quic_data->server_port = process_quic->quic_get_server_port(); process_quic->quic_get_sni(quic_data->sni); process_quic->quic_get_user_agent(quic_data->user_agent); - process_quic->quic_get_dcid(quic_data->oscid); - process_quic->quic_get_dcid_len(quic_data->oscid_length); + if (!quic_data->oscid_set) { + process_quic->quic_get_dcid(quic_data->oscid); + process_quic->quic_get_dcid_len(quic_data->oscid_length); + quic_data->oscid_set = true; + } - process_quic->quic_get_scid(quic_data->occid); - process_quic->quic_get_scid_len(quic_data->occid_length); + if (!quic_data->occid_set) { + process_quic->quic_get_scid(quic_data->occid); + process_quic->quic_get_scid_len(quic_data->occid_length); + quic_data->occid_set = true; + } // Set client version to extract difference in compatible version negotiation: RFC9368 - process_quic->quic_get_version(quic_data->quic_client_version); + if (!quic_data->client_version_set) { + process_quic->quic_get_version(quic_data->quic_client_version); + quic_data->client_version_set = true; + } } else { if (quic_data->quic_multiplexed < 0xFF) { quic_data->quic_multiplexed += 1; @@ -186,144 +322,216 @@ void QUICPlugin::set_client_hello_fields(QUICParser *process_quic, RecordExtQUIC } } - -int QUICPlugin::process_quic(RecordExtQUIC *quic_data, Flow &rec, const Packet &pkt) +void QUICPlugin::set_packet_type(RecordExtQUIC* quic_data, Flow& rec, uint8_t packets) { - QUICParser process_quic; - - // Test for QUIC packet in UDP payload - if(process_quic.quic_check_quic_long_header_packet(pkt) ) { - - uint32_t version; - process_quic.quic_get_version(version); - - uint8_t packets = 0; - process_quic.quic_get_packets(packets); - - - // Store all QUIC packet types contained in each packet - uint8_t pos = rec.src_packets + rec.dst_packets -1; - if (pos < QUIC_MAX_ELEMCOUNT) { - quic_data->pkt_types[pos] = packets; - quic_data->last_pkt_type = pos; - } + uint32_t pos = rec.src_packets + rec.dst_packets - 1; + if (pos < QUIC_MAX_ELEMCOUNT) { + quic_data->pkt_types[pos] = packets; + quic_data->last_pkt_type = pos; + } +} - // A 0-RTT carries the same QUIC version as the Client Initial Hello. - // 0-RTT and compatible version negotiation is not defined. - // We ignore those cases because it might be defined in the future - if ((process_quic.quic_get_packet_type() != QUICParser::PACKET_TYPE::ZERO_RTT)) { +int QUICPlugin::process_quic( + RecordExtQUIC* quic_data, + Flow& rec, + const Packet& pkt, + bool new_quic_flow) +{ + QUICParser process_quic; + + // Test for QUIC LH packet in UDP payload + if (process_quic.quic_check_quic_long_header_packet( + pkt, + quic_data->initial_dcid, + quic_data->initial_dcid_length)) { + uint32_t version; + process_quic.quic_get_version(version); + + // Get kinds of packet included in datagram + uint8_t packets = 0; + process_quic.quic_get_packets(packets); + + // Store all QUIC packet types contained in each packet + set_packet_type(quic_data, rec, packets); + + // A 0-RTT carries the same QUIC version as the Client Initial Hello. + // 0-RTT and compatible version negotiation is not defined. + // We ignore those cases because it might be defined in the future + if ((packets & QUICParser::PACKET_TYPE_FLAG::F_ZERO_RTT) == 0) { quic_data->quic_version = version; - } + } - RecordExtQUIC *ext = (RecordExtQUIC *) rec.get_extension(RecordExtQUIC::REGISTERED_ID); - // Simple version, more advanced information is available after Initial parsing - int toServer = get_direction_to_server_and_set_port(&process_quic, quic_data, process_quic.quic_get_server_port(), pkt, ext); + // Simple version, more advanced information is available after Initial parsing + int toServer = get_direction_to_server_and_set_port( + &process_quic, + quic_data, + process_quic.quic_get_server_port(), + pkt, + new_quic_flow); if (packets & QUICParser::PACKET_TYPE_FLAG::F_ZERO_RTT) { - uint8_t zero_rtt_pkts = process_quic.quic_get_zero_rtt(); + uint8_t zero_rtt_pkts = 0; + process_quic.quic_get_zero_rtt(zero_rtt_pkts); - if ((uint16_t) zero_rtt_pkts + (uint16_t)quic_data->quic_zero_rtt > 0xFF) { + if ((uint16_t) zero_rtt_pkts + (uint16_t) quic_data->quic_zero_rtt > 0xFF) { quic_data->quic_zero_rtt = 0xFF; } else { quic_data->quic_zero_rtt += zero_rtt_pkts; } } - uint8_t parsed_initial = 0; - - if (version == QUICParser::QUIC_VERSION::version_negotiation) { - return FLOW_FLUSH; - } - - switch (process_quic.quic_get_packet_type()) { - case QUICParser::PACKET_TYPE::INITIAL: - process_quic.quic_get_parsed_initial(parsed_initial); - if (parsed_initial) { - // Successful CH parsing - set_client_hello_fields(&process_quic, quic_data, pkt, ext); - break; - } - // Update accounting for information from CH, SH. - toServer = get_direction_to_server_and_set_port(&process_quic, quic_data, process_quic.quic_get_server_port(), pkt, ext); - // fallthrough to set cids - case QUICParser::PACKET_TYPE::HANDSHAKE: - // -1 sets stores intermediately. - set_cid_fields(quic_data, &process_quic, toServer, ext, pkt); - break; - case QUICParser::PACKET_TYPE::RETRY: - // Additionally set token len - process_quic.quic_get_scid(quic_data->retry_scid); - process_quic.quic_get_scid_len(quic_data->retry_scid_length); - process_quic.quic_get_token_length(quic_data->quic_token_length); - set_cid_fields(quic_data, &process_quic, toServer, ext, pkt); - break; - case QUICParser::PACKET_TYPE::ZERO_RTT: - // Connection IDs are identical to Client Initial CH. The DCID might be OSCID at first and change to SCID later. We ignore the DCID. - process_quic.quic_get_scid(quic_data->occid); - process_quic.quic_get_scid_len(quic_data->occid_length); - break; - } - - return QUIC_DETECTED; - } - return QUIC_NOT_DETECTED; + uint8_t parsed_initial = 0; + + if (version == QUICParser::QUIC_VERSION::version_negotiation) { + set_cid_fields(quic_data, rec, &process_quic, toServer, new_quic_flow, pkt); + return FLOW_FLUSH; + } + + // export if parsed CH + quic_data->parsed_ch |= process_quic.quic_get_parsed_ch(); + + switch (process_quic.quic_get_packet_type()) { + case QUICParser::PACKET_TYPE::INITIAL: + process_quic.quic_get_parsed_initial(parsed_initial); + // Store DCID from first observed Initial packet. This is used in the crypto operations. + // Check length works because the first Initial must have a non-zero DCID. + if (quic_data->initial_dcid_length == 0) { + process_quic.quic_get_dcid_len(quic_data->initial_dcid_length); + process_quic.quic_get_dcid(quic_data->initial_dcid); + // Once established it can only be changed by a retry packet. + } + + if (parsed_initial && (process_quic.quic_get_tls_hs_type() == 1)) { + // Successful CH parsing + set_stored_cid_fields(quic_data, new_quic_flow); + set_client_hello_fields(&process_quic, rec, quic_data, pkt, new_quic_flow); + quic_data->client_hello_seen = true; + + if (!quic_data->tls_ext_type_set) { + process_quic.quic_get_tls_ext_type(quic_data->tls_ext_type); + process_quic.quic_get_tls_ext_type_len(quic_data->tls_ext_type_len); + quic_data->tls_ext_type_set = true; + } + + if (!quic_data->tls_ext_len_set) { + process_quic.quic_get_tls_extension_lengths(quic_data->tls_ext_len); + process_quic.quic_get_tls_extension_lengths_len(quic_data->tls_ext_len_len); + quic_data->tls_ext_len_set = true; + } + + if (!quic_data->tls_ext_set) { + process_quic.quic_get_tls_ext(quic_data->tls_ext); + process_quic.quic_get_tls_ext_len(quic_data->tls_ext_length); + quic_data->tls_ext_set = true; + } + break; + } + // Update accounting for information from CH, SH. + toServer = get_direction_to_server_and_set_port( + &process_quic, + quic_data, + process_quic.quic_get_server_port(), + pkt, + new_quic_flow); + // fallthrough to set cids + case QUICParser::PACKET_TYPE::HANDSHAKE: + // -1 sets stores intermediately. + set_cid_fields(quic_data, rec, &process_quic, toServer, new_quic_flow, pkt); + break; + case QUICParser::PACKET_TYPE::RETRY: + quic_data->cnt_retry_packets += 1; + // Additionally set token len + process_quic.quic_get_scid(quic_data->retry_scid); + process_quic.quic_get_scid_len(quic_data->retry_scid_length); + // Update DCID for decryption + process_quic.quic_get_dcid_len(quic_data->initial_dcid_length); + process_quic.quic_get_scid(quic_data->initial_dcid); + + process_quic.quic_get_token_length(quic_data->quic_token_length); + + if (!quic_data->occid_set) { + process_quic.quic_get_dcid(quic_data->occid); + process_quic.quic_get_dcid_len(quic_data->occid_length); + quic_data->occid_set = true; + } + + break; + case QUICParser::PACKET_TYPE::ZERO_RTT: + // Connection IDs are identical to Client Initial CH. The DCID might be OSCID at first + // and change to SCID later. We ignore the DCID. + if (!quic_data->occid_set) { + process_quic.quic_get_scid(quic_data->occid); + process_quic.quic_get_scid_len(quic_data->occid_length); + quic_data->occid_set = true; + } + break; + } + + return QUIC_DETECTED; + } else { + // Even if no QUIC detected store packets, which will only include the QUIC bit. + uint8_t packets = 0; + process_quic.quic_get_packets(packets); + set_packet_type(quic_data, rec, packets); + } + return QUIC_NOT_DETECTED; } // QUICPlugin::process_quic -int QUICPlugin::pre_create(Packet &pkt) +int QUICPlugin::pre_create(Packet& pkt) { - return 0; + return 0; } -int QUICPlugin::post_create(Flow &rec, const Packet &pkt) +int QUICPlugin::post_create(Flow& rec, const Packet& pkt) { - return add_quic(rec, pkt); + return add_quic(rec, pkt); } -int QUICPlugin::pre_update(Flow &rec, Packet &pkt) +int QUICPlugin::pre_update(Flow& rec, Packet& pkt) { - return 0; + return 0; } -int QUICPlugin::post_update(Flow &rec, const Packet &pkt) +int QUICPlugin::post_update(Flow& rec, const Packet& pkt) { - RecordExtQUIC *ext = (RecordExtQUIC *) rec.get_extension(RecordExtQUIC::REGISTERED_ID); - - if (ext == nullptr) { - return 0; - } + // RecordExtQUIC *ext = (RecordExtQUIC *) rec.get_extension(RecordExtQUIC::REGISTERED_ID); + // + // if (ext == nullptr) { + // return 0; + // } - return add_quic(rec, pkt); + return add_quic(rec, pkt); } -int QUICPlugin::add_quic(Flow &rec, const Packet &pkt) +int QUICPlugin::add_quic(Flow& rec, const Packet& pkt) { - RecordExtQUIC *q_ptr = (RecordExtQUIC *) rec.get_extension(RecordExtQUIC::REGISTERED_ID); - bool new_qptr = false; - if (q_ptr == nullptr) { - new_qptr = true; - q_ptr = new RecordExtQUIC(); - } - - int ret = process_quic(q_ptr, rec, pkt); - // Test if QUIC extension is not set - if (new_qptr && ((ret == QUIC_DETECTED) || (ret == FLOW_FLUSH))) { - rec.add_extension(q_ptr); - } - if (new_qptr && (ret == QUIC_NOT_DETECTED)) { - // If still no record delete q_ptr - delete q_ptr; - } - // Correct if QUIC has already been detected - if (!new_qptr && (ret == QUIC_NOT_DETECTED)) { - return QUIC_DETECTED; - } - return ret; + RecordExtQUIC* q_ptr = (RecordExtQUIC*) rec.get_extension(RecordExtQUIC::REGISTERED_ID); + bool new_qptr = false; + if (q_ptr == nullptr) { + new_qptr = true; + q_ptr = new RecordExtQUIC(); + } + + int ret = process_quic(q_ptr, rec, pkt, new_qptr); + // Test if QUIC extension is not set + if (new_qptr && ((ret == QUIC_DETECTED) || (ret == FLOW_FLUSH))) { + rec.add_extension(q_ptr); + } + if (new_qptr && (ret == QUIC_NOT_DETECTED)) { + // If still no record delete q_ptr + delete q_ptr; + } + // Correct if QUIC has already been detected + if (!new_qptr && (ret == QUIC_NOT_DETECTED)) { + return QUIC_DETECTED; + } + return ret; } void QUICPlugin::finish(bool print_stats) { - if (print_stats) { - std::cout << "QUIC plugin stats:" << std::endl; - std::cout << " Parsed SNI: " << parsed_initial << std::endl; - } -} + if (print_stats) { + std::cout << "QUIC plugin stats:" << std::endl; + std::cout << " Parsed SNI: " << parsed_initial << std::endl; + } } +} // namespace ipxp diff --git a/process/quic.hpp b/process/quic.hpp index cc336eec..ff71a650 100644 --- a/process/quic.hpp +++ b/process/quic.hpp @@ -7,44 +7,46 @@ * \brief Plugin for enriching flows for quic data. * \author Andrej Lukacovic lukacan1@fit.cvut.cz * \author Karel Hynek - * \date 2022 + * \author Jonas Mücke + * \date 2023 */ - #ifndef IPXP_PROCESS_QUIC_HPP #define IPXP_PROCESS_QUIC_HPP - #ifdef WITH_NEMEA -# include "fields.h" +#include "fields.h" #endif - #include "quic_parser.hpp" -#include #include #include +#include #include - namespace ipxp { -#define QUIC_UNIREC_TEMPLATE "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION,QUIC_CLIENT_VERSION,QUIC_TOKEN_LENGTH,QUIC_OCCID,QUIC_OSCID,QUIC_SCID,QUIC_RETRY_SCID,QUIC_MULTIPLEXED,QUIC_ZERO_RTT,QUIC_SERVER_PORT,QUIC_PACKETS" +#define QUIC_UNIREC_TEMPLATE \ + "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION,QUIC_CLIENT_VERSION,QUIC_TOKEN_LENGTH,QUIC_OCCID,QUIC_" \ + "OSCID,QUIC_SCID,QUIC_RETRY_SCID,QUIC_MULTIPLEXED,QUIC_ZERO_RTT,QUIC_SERVER_PORT,QUIC_" \ + "PACKETS,QUIC_CH_PARSED,QUIC_TLS_EXT_TYPE,QUIC_TLS_EXT_LEN,QUIC_TLS_EXT" UR_FIELDS( - string QUIC_SNI, - string QUIC_USER_AGENT, - uint32 QUIC_VERSION, - uint32 QUIC_CLIENT_VERSION, - uint64 QUIC_TOKEN_LENGTH, - bytes QUIC_OCCID, - bytes QUIC_OSCID, - bytes QUIC_SCID, - bytes QUIC_RETRY_SCID, - uint8 QUIC_MULTIPLEXED, - uint8 QUIC_ZERO_RTT, - uint16 QUIC_SERVER_PORT, - uint8* QUIC_PACKETS -) - + string QUIC_SNI, + string QUIC_USER_AGENT, + uint32 QUIC_VERSION, + uint32 QUIC_CLIENT_VERSION, + uint64 QUIC_TOKEN_LENGTH, + bytes QUIC_OCCID, + bytes QUIC_OSCID, + bytes QUIC_SCID, + bytes QUIC_RETRY_SCID, + uint8 QUIC_MULTIPLEXED, + uint8 QUIC_ZERO_RTT, + uint16 QUIC_SERVER_PORT, + uint8* QUIC_PACKETS, + uint8 QUIC_CH_PARSED, + uint16* QUIC_TLS_EXT_TYPE, + uint16* QUIC_TLS_EXT_LEN, + bytes QUIC_TLS_EXT) /** * \brief Flow record extension header for storing parsed QUIC packets. */ @@ -53,206 +55,335 @@ UR_FIELDS( #define QUIC_DETECTED 0 #define QUIC_NOT_DETECTED 2 #define QUIC_PKT_FIELD_ID 888 +#define QUIC_TLS_EXT_TYPE_FIELD_ID 885 +#define QUIC_TLS_EXT_LEN_FIELD_ID 884 struct RecordExtQUIC : public RecordExt { - static int REGISTERED_ID; - char sni[BUFF_SIZE] = { 0 }; - char user_agent[BUFF_SIZE] = { 0 }; - uint32_t quic_version; - uint32_t quic_client_version; - uint64_t quic_token_length; - // We use a char as a buffer. - uint8_t occid_length; - uint8_t oscid_length; - uint8_t scid_length; - uint8_t dir_scid_length; - uint8_t dir_dcid_length; - uint8_t retry_scid_length; - char occid[MAX_CID_LEN] = { 0 }; - char oscid[MAX_CID_LEN] = { 0 }; - char scid[MAX_CID_LEN] = { 0 }; - char retry_scid[MAX_CID_LEN] = { 0 }; - // Intermediate storage when direction is not clear - char dir_scid[MAX_CID_LEN] = { 0 }; - char dir_dcid[MAX_CID_LEN] = { 0 }; - uint16_t dir_dport; - uint16_t server_port; - - uint8_t quic_multiplexed; - uint8_t quic_zero_rtt; - uint8_t pkt_types[QUIC_MAX_ELEMCOUNT]; - uint8_t last_pkt_type; - - RecordExtQUIC() : RecordExt(REGISTERED_ID) - { - sni[0] = 0; - user_agent[0] = 0; - quic_version = 0; - quic_client_version = 0; - occid_length = 0; - oscid_length = 0; - scid_length = 0; - retry_scid_length = 0; - occid[0] = 0; - oscid[0] = 0; - scid[0] = 0; - retry_scid[0] = 0; - dir_dcid[0] = 0; - dir_scid[0] = 0; - dir_dcid_length=0; - dir_scid_length=0; - server_port = 0; - dir_dport = 0; - quic_token_length = QUICParser::QUIC_CONSTANTS::QUIC_UNUSED_VARIABLE_LENGTH_INT; - quic_multiplexed = 0; - quic_zero_rtt = 0; - memset(pkt_types, 0, sizeof(pkt_types)); - last_pkt_type = 0; - } - - #ifdef WITH_NEMEA - virtual void fill_unirec(ur_template_t *tmplt, void *record) - { - ur_set_string(tmplt, record, F_QUIC_SNI, sni); - ur_set_string(tmplt, record, F_QUIC_USER_AGENT, user_agent); - ur_set(tmplt, record, F_QUIC_VERSION, quic_version); - ur_set(tmplt, record, F_QUIC_CLIENT_VERSION, quic_client_version); - ur_set(tmplt, record, F_QUIC_TOKEN_LENGTH, quic_token_length); - ur_set_var(tmplt, record, F_QUIC_OCCID, occid, occid_length); - ur_set_var(tmplt, record, F_QUIC_OSCID, oscid, oscid_length); - ur_set_var(tmplt, record, F_QUIC_SCID, scid, scid_length); - ur_set_var(tmplt, record, F_QUIC_RETRY_SCID, retry_scid, retry_scid_length); - ur_set(tmplt, record, F_QUIC_MULTIPLEXED, quic_multiplexed); - ur_set(tmplt, record, F_QUIC_ZERO_RTT, quic_zero_rtt); - ur_set(tmplt, record, F_QUIC_SERVER_PORT, server_port); - ur_array_allocate(tmplt, record, F_QUIC_PACKETS, last_pkt_type+1); - for (int i = 0; i < last_pkt_type+1; i++) { - ur_array_set(tmplt, record, F_QUIC_PACKETS, i, pkt_types[i]); - } - } - - const char *get_unirec_tmplt() const - { - return QUIC_UNIREC_TEMPLATE; - } - - #endif // ifdef WITH_NEMEA - - virtual int fill_ipfix(uint8_t *buffer, int size) - { - IpfixBasicList basiclist; - basiclist.hdrEnterpriseNum = IpfixBasicList::CesnetPEM; - uint16_t len_sni = strlen(sni); - uint16_t len_user_agent = strlen(user_agent); - uint16_t len_version = sizeof(quic_version); - uint16_t len_client_version = sizeof(quic_client_version); - uint16_t len_token_length = sizeof(quic_token_length); - uint16_t len_multiplexed = sizeof(quic_multiplexed); - uint16_t len_zero_rtt = sizeof(quic_zero_rtt); - uint16_t pkt_types_len = sizeof(pkt_types[0])*(last_pkt_type+1) + basiclist.HeaderSize() ; - uint16_t len_server_port = sizeof(server_port); - uint32_t pos = 0; - - - if ((len_sni + 3) + (len_user_agent + 3) + len_version + - len_client_version + len_token_length + len_multiplexed + len_zero_rtt + - (scid_length + 3) + (occid_length + 3) + (oscid_length + 3) + (retry_scid_length + 3) + - len_server_port + pkt_types_len > size) { - return -1; - } - - pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) sni, len_sni); - pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) user_agent, len_user_agent); - *(uint32_t *) (buffer + pos) = htonl(quic_version); - pos += len_version; - *(uint32_t *) (buffer + pos) = htonl(quic_client_version); - pos += len_client_version; - *(uint64_t *) (buffer + pos) = htobe64(quic_token_length); - pos += len_token_length; - // original client connection ID - pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) occid, occid_length); - // original server connection id - pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) oscid, oscid_length); - // server connection id - pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) scid, scid_length); - // retry server connection id - pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) retry_scid, retry_scid_length); - - *(uint8_t *) (buffer + pos) = quic_multiplexed; - pos += 1; - - *(uint8_t *) (buffer + pos) = quic_zero_rtt; - pos += 1; - *(uint16_t *) (buffer + pos) = htons(server_port); - pos += len_server_port; - // Packet types - pos += basiclist.FillBuffer(buffer + pos, pkt_types, (uint16_t) last_pkt_type + 1, (uint16_t) QUIC_PKT_FIELD_ID); - - return pos; - } - - const char **get_ipfix_tmplt() const - { - static const char *ipfix_template[] = { - IPFIX_QUIC_TEMPLATE(IPFIX_FIELD_NAMES) - nullptr - }; - - return ipfix_template; - } - - std::string get_text() const - { - std::ostringstream out; - - out << "quicsni=\"" << sni << "\"" << "quicuseragent=\"" << user_agent << "\"" << "quicversion=\"" << - quic_version << "\"" - << "quicclientversion=\"" << quic_client_version << "\"" - << "quicoccidlength=\"" << occid_length << "\"" << "quicoccid=\"" << occid << "\"" - << "quicoscidlength=\"" << oscid_length << "\"" << "quicoscid=\"" << oscid << "\"" - << "quicscidlength=\"" << scid_length << "\"" << "quicscid=\"" << scid << "\"" - << "quicmultiplexed=\"" << quic_multiplexed << "\"" - << "quiczerortt=\"" << quic_zero_rtt << "\""; - return out.str(); - } + static int REGISTERED_ID; + char sni[BUFF_SIZE] = {0}; + char user_agent[BUFF_SIZE] = {0}; + uint32_t quic_version; + uint32_t quic_client_version; + uint64_t quic_token_length; + // We use a char as a buffer. + uint8_t occid_length; + uint8_t oscid_length; + uint8_t scid_length; + uint8_t initial_dcid_length; + uint8_t dir_scid_length; + uint8_t dir_dcid_length; + uint8_t dir_scid_length2; + uint8_t dir_dcid_length2; + uint8_t retry_scid_length; + char occid[MAX_CID_LEN] = {0}; + char oscid[MAX_CID_LEN] = {0}; + char scid[MAX_CID_LEN] = {0}; + char initial_dcid[MAX_CID_LEN] = {0}; + char retry_scid[MAX_CID_LEN] = {0}; + // Intermediate storage when direction is not clear + char dir_scid[MAX_CID_LEN] = {0}; + char dir_dcid[MAX_CID_LEN] = {0}; + char dir_scid2[MAX_CID_LEN] = {0}; + char dir_dcid2[MAX_CID_LEN] = {0}; + uint16_t dir_dport; + uint16_t dir_dport2; + uint16_t server_port; + uint8_t cnt_retry_packets; + + uint8_t quic_multiplexed; + uint8_t quic_zero_rtt; + uint8_t pkt_types[QUIC_MAX_ELEMCOUNT]; + + uint16_t tls_ext_type[MAX_QUIC_TLS_EXT_LEN]; + uint16_t tls_ext_type_len; + bool tls_ext_type_set; + + uint16_t tls_ext_len[MAX_QUIC_TLS_EXT_LEN]; + uint8_t tls_ext_len_len; + bool tls_ext_len_set; + + char tls_ext[CURRENT_BUFFER_SIZE]; + uint16_t tls_ext_length; + bool tls_ext_set; + + uint8_t last_pkt_type; + + uint8_t parsed_ch; + + // Flags to ease decisions + bool occid_set; + bool oscid_set; + bool scid_set; + + bool client_version_set; + bool client_hello_seen; + bool packet_from_server_seen; + + RecordExtQUIC() + : RecordExt(REGISTERED_ID) + { + sni[0] = 0; + user_agent[0] = 0; + quic_version = 0; + quic_client_version = 0; + occid_length = 0; + oscid_length = 0; + scid_length = 0; + retry_scid_length = 0; + occid[0] = 0; + oscid[0] = 0; + scid[0] = 0; + retry_scid[0] = 0; + dir_dcid[0] = 0; + dir_scid[0] = 0; + dir_dcid_length = 0; + dir_scid_length = 0; + dir_dcid2[0] = 0; + dir_scid2[0] = 0; + dir_dcid_length2 = 0; + dir_scid_length2 = 0; + server_port = 0; + dir_dport = 0; + dir_dport2 = 0; + quic_token_length = QUICParser::QUIC_CONSTANTS::QUIC_UNUSED_VARIABLE_LENGTH_INT; + quic_multiplexed = 0; + quic_zero_rtt = 0; + memset(pkt_types, 0, sizeof(pkt_types)); + + memset(tls_ext_type, 0, sizeof(tls_ext_type)); + tls_ext_type_len = 0; + tls_ext_type_set = false; + + memset(tls_ext_len, 0, sizeof(tls_ext_len)); + tls_ext_len_len = 0; + tls_ext_len_set = false; + + memset(tls_ext, 0, sizeof(tls_ext)); + tls_ext_length = 0; + tls_ext_set = false; + + last_pkt_type = 0; + initial_dcid[0] = 0; + initial_dcid_length = 0; + parsed_ch = 0; + + occid_set = false; + oscid_set = false; + scid_set = false; + client_version_set = false; + cnt_retry_packets = 0; + + client_hello_seen = false; + packet_from_server_seen = false; + } + +#ifdef WITH_NEMEA + virtual void fill_unirec(ur_template_t* tmplt, void* record) + { + ur_set_string(tmplt, record, F_QUIC_SNI, sni); + ur_set_string(tmplt, record, F_QUIC_USER_AGENT, user_agent); + ur_set(tmplt, record, F_QUIC_VERSION, quic_version); + ur_set(tmplt, record, F_QUIC_CLIENT_VERSION, quic_client_version); + ur_set(tmplt, record, F_QUIC_TOKEN_LENGTH, quic_token_length); + ur_set_var(tmplt, record, F_QUIC_OCCID, occid, occid_length); + ur_set_var(tmplt, record, F_QUIC_OSCID, oscid, oscid_length); + ur_set_var(tmplt, record, F_QUIC_SCID, scid, scid_length); + ur_set_var(tmplt, record, F_QUIC_RETRY_SCID, retry_scid, retry_scid_length); + ur_set(tmplt, record, F_QUIC_MULTIPLEXED, quic_multiplexed); + ur_set(tmplt, record, F_QUIC_ZERO_RTT, quic_zero_rtt); + ur_set(tmplt, record, F_QUIC_SERVER_PORT, server_port); + ur_array_allocate(tmplt, record, F_QUIC_PACKETS, last_pkt_type + 1); + for (int i = 0; i < last_pkt_type + 1; i++) { + ur_array_set(tmplt, record, F_QUIC_PACKETS, i, pkt_types[i]); + } + ur_set(tmplt, record, F_QUIC_CH_PARSED, parsed_ch); + ur_array_allocate(tmplt, record, F_QUIC_TLS_EXT_TYPE, tls_ext_type_len); + for (int i = 0; i < tls_ext_type_len; i++) { + ur_array_set(tmplt, record, F_QUIC_TLS_EXT_TYPE, i, tls_ext_type[i]); + } + ur_array_allocate(tmplt, record, F_QUIC_TLS_EXT_LEN, tls_ext_len_len); + for (int i = 0; i < tls_ext_len_len; i++) { + ur_array_set(tmplt, record, F_QUIC_TLS_EXT_LEN, i, tls_ext_len[i]); + } + ur_set_var(tmplt, record, F_QUIC_TLS_EXT, tls_ext, tls_ext_length); + } + + const char* get_unirec_tmplt() const { return QUIC_UNIREC_TEMPLATE; } + +#endif // ifdef WITH_NEMEA + + virtual int fill_ipfix(uint8_t* buffer, int size) + { + IpfixBasicList basiclist; + basiclist.hdrEnterpriseNum = IpfixBasicList::CesnetPEM; + uint16_t len_sni = strlen(sni); + uint16_t len_user_agent = strlen(user_agent); + uint16_t len_version = sizeof(quic_version); + uint16_t len_client_version = sizeof(quic_client_version); + uint16_t len_token_length = sizeof(quic_token_length); + uint16_t len_multiplexed = sizeof(quic_multiplexed); + uint16_t len_zero_rtt = sizeof(quic_zero_rtt); + uint16_t pkt_types_len + = sizeof(pkt_types[0]) * (last_pkt_type + 1) + basiclist.HeaderSize(); + uint16_t len_server_port = sizeof(server_port); + uint16_t len_parsed_ch = sizeof(parsed_ch); + + uint16_t len_tls_ext_type + = sizeof(tls_ext_type[0]) * (tls_ext_type_len) + basiclist.HeaderSize(); + uint16_t len_tls_len = sizeof(tls_ext_len[0]) * (tls_ext_len_len) + basiclist.HeaderSize(); + uint16_t len_tls_ext = tls_ext_length + 3; + + uint32_t pos = 0; + + if ((len_sni + 3) + (len_user_agent + 3) + len_version + len_client_version + + len_token_length + len_multiplexed + len_zero_rtt + (scid_length + 3) + + (occid_length + 3) + (oscid_length + 3) + (retry_scid_length + 3) + + len_server_port + pkt_types_len + len_parsed_ch + len_tls_ext_type + len_tls_len + + len_tls_ext + > size) { + return -1; + } + + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) sni, len_sni); + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) user_agent, len_user_agent); + *(uint32_t*) (buffer + pos) = htonl(quic_version); + pos += len_version; + *(uint32_t*) (buffer + pos) = htonl(quic_client_version); + pos += len_client_version; + *(uint64_t*) (buffer + pos) = htobe64(quic_token_length); + pos += len_token_length; + // original client connection ID + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) occid, occid_length); + // original server connection id + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) oscid, oscid_length); + // server connection id + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) scid, scid_length); + // retry server connection id + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) retry_scid, retry_scid_length); + + *(uint8_t*) (buffer + pos) = quic_multiplexed; + pos += 1; + + *(uint8_t*) (buffer + pos) = quic_zero_rtt; + pos += 1; + *(uint16_t*) (buffer + pos) = htons(server_port); + pos += len_server_port; + // Packet types + pos += basiclist.FillBuffer( + buffer + pos, + pkt_types, + (uint16_t) last_pkt_type + 1, + (uint16_t) QUIC_PKT_FIELD_ID); + + *(uint8_t*) (buffer + pos) = parsed_ch; + pos += 1; + + pos += basiclist.FillBuffer( + buffer + pos, + tls_ext_type, + (uint16_t) tls_ext_type_len, + (uint16_t) QUIC_TLS_EXT_TYPE_FIELD_ID); + pos += basiclist.FillBuffer( + buffer + pos, + tls_ext_len, + (uint16_t) tls_ext_len_len, + (uint16_t) QUIC_TLS_EXT_LEN_FIELD_ID); + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) tls_ext, tls_ext_length); + + return pos; + } + + const char** get_ipfix_tmplt() const + { + static const char* ipfix_template[] = {IPFIX_QUIC_TEMPLATE(IPFIX_FIELD_NAMES) nullptr}; + + return ipfix_template; + } + + std::string get_text() const + { + std::ostringstream out; + + out << "quicsni=\"" << sni << "\"" + << "quicuseragent=\"" << user_agent << "\"" + << "quicversion=\"" << quic_version << "\"" + << "quicclientversion=\"" << quic_client_version << "\"" + << "quicoccidlength=\"" << occid_length << "\"" + << "quicoccid=\"" << occid << "\"" + << "quicoscidlength=\"" << oscid_length << "\"" + << "quicoscid=\"" << oscid << "\"" + << "quicscidlength=\"" << scid_length << "\"" + << "quicscid=\"" << scid << "\"" + << "quicmultiplexed=\"" << quic_multiplexed << "\"" + << "quiczerortt=\"" << quic_zero_rtt << "\"" + << "quic_parsed_ch=\"" << parsed_ch << "\""; + return out.str(); + } }; /** * \brief Flow cache plugin for parsing QUIC packets. */ -class QUICPlugin : public ProcessPlugin -{ +class QUICPlugin : public ProcessPlugin { public: - QUICPlugin(); - ~QUICPlugin(); - void init(const char *params); - void close(); - RecordExt *get_ext() const { return new RecordExtQUIC(); } + QUICPlugin(); + ~QUICPlugin(); + void init(const char* params); + void close(); + RecordExt* get_ext() const { return new RecordExtQUIC(); } - OptionsParser *get_parser() const { return new OptionsParser("quic", "Parse QUIC traffic"); } + OptionsParser* get_parser() const { return new OptionsParser("quic", "Parse QUIC traffic"); } - std::string get_name() const { return "quic"; } + std::string get_name() const { return "quic"; } - ProcessPlugin *copy(); + ProcessPlugin* copy(); - int pre_create(Packet &pkt); - int post_create(Flow &rec, const Packet &pkt); - int pre_update(Flow &rec, Packet &pkt); - int post_update(Flow &rec, const Packet &pkt); - int add_quic(Flow &rec, const Packet &pkt); - void finish(bool print_stats); + int pre_create(Packet& pkt); + int post_create(Flow& rec, const Packet& pkt); + int pre_update(Flow& rec, Packet& pkt); + int post_update(Flow& rec, const Packet& pkt); + int add_quic(Flow& rec, const Packet& pkt); + void finish(bool print_stats); + void set_packet_type(RecordExtQUIC* quic_data, Flow& rec, uint8_t packets); private: - int process_quic(RecordExtQUIC *, Flow &rec, const Packet&); - void set_stored_cid_fields(RecordExtQUIC *quic_data, RecordExtQUIC *ext); - void set_cid_fields(RecordExtQUIC *quic_data, QUICParser *process_quic, int toServer, - RecordExtQUIC *ext, const Packet &pkt ); - int get_direction_to_server(uint16_t parsed_port, const Packet &pkt, RecordExtQUIC *ext); - int get_direction_to_server_and_set_port(QUICParser *process_quic, RecordExtQUIC *quic_data, uint16_t parsed_port, const Packet &pkt, RecordExtQUIC *ext); - void set_client_hello_fields(QUICParser *process_quic, RecordExtQUIC *quic_data, const Packet &pkt, - RecordExtQUIC *ext ); - int parsed_initial; - RecordExtQUIC *quic_ptr; + int process_quic(RecordExtQUIC*, Flow& rec, const Packet&, bool new_quic_flow); + void set_stored_cid_fields(RecordExtQUIC* quic_data, bool new_quic_flow); + void set_cid_fields( + RecordExtQUIC* quic_data, + Flow& rec, + QUICParser* process_quic, + int toServer, + bool new_quic_flow, + const Packet& pkt); + int get_direction_to_server( + uint16_t parsed_port, + RecordExtQUIC* quic_data, + const Packet& pkt, + bool new_quic_flow); + int get_direction_to_server_and_set_port( + QUICParser* process_quic, + RecordExtQUIC* quic_data, + uint16_t parsed_port, + const Packet& pkt, + bool new_quic_flow); + void set_client_hello_fields( + QUICParser* process_quic, + Flow& rec, + RecordExtQUIC* quic_data, + const Packet& pkt, + bool new_quic_flow); + void set_cid_if_unset +( + bool& set_flag, + uint8_t& src_id_length, + char* src_id, + uint8_t& dst_id_length, + char* dst_id); + uint8_t get_packets_from_server(uint16_t server_port, Flow& rec); + + int parsed_initial; + RecordExtQUIC* quic_ptr; }; -} +} // namespace ipxp #endif /* IPXP_PROCESS_QUIC_HPP */ diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index 41eb999a..a729ea0d 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -7,7 +7,8 @@ * \brief Class for parsing quic traffic. * \author Andrej Lukacovic lukacan1@fit.cvut.cz * \author Karel Hynek - * \date 2022 + * \author Jonas Mücke + * \date 2023 */ #include "quic_parser.hpp" @@ -39,6 +40,8 @@ QUICParser::QUICParser() dcid = nullptr; dcid_len = 0; + initial_dcid = nullptr; + initial_dcid_len = 0; scid = nullptr; scid_len = 0; pkn = nullptr; @@ -52,6 +55,49 @@ QUICParser::QUICParser() token_length = QUIC_UNUSED_VARIABLE_LENGTH_INT; zero_rtt = 0; server_port = 0; + pkn_len = 0; + tls_hs_type = 0; + parsed_client_hello = false; + + memset(quic_tls_ext_type, 0, sizeof(quic_tls_ext_type)); + quic_tls_ext_type_pos = 0; + + memset(quic_tls_extension_lengths, 0, sizeof(quic_tls_extension_lengths)); + quic_tls_extension_lengths_pos = 0; + + memset(quic_tls_ext, 0, sizeof(quic_tls_ext)); + quic_tls_ext_pos = 0; +} +void QUICParser::quic_get_tls_ext_type(uint16_t* tls_ext_type_toset) +{ + // *2 since 16 bit instead of 8 + memcpy(tls_ext_type_toset, quic_tls_ext_type, quic_tls_ext_type_pos * 2); +} + +void QUICParser::quic_get_tls_ext_type_len(uint16_t& tls_ext_type_len_toset) +{ + tls_ext_type_len_toset = quic_tls_ext_type_pos; +} + +void QUICParser::quic_get_tls_ext(char* in) +{ + memcpy(in, quic_tls_ext, quic_tls_ext_pos); + return; +} + +void QUICParser::quic_get_tls_ext_len(uint16_t& tls_ext_len_toset) +{ + tls_ext_len_toset = quic_tls_ext_pos; +} + +void QUICParser::quic_get_tls_extension_lengths(uint16_t* tls_extensions_len) +{ + memcpy(tls_extensions_len, quic_tls_extension_lengths, quic_tls_extension_lengths_pos * 2); +} + +void QUICParser::quic_get_tls_extension_lengths_len(uint8_t& tls_extensions_length_len_toset) +{ + tls_extensions_length_len_toset = quic_tls_extension_lengths_pos; } uint8_t QUICParser::quic_get_packet_type() @@ -59,9 +105,22 @@ uint8_t QUICParser::quic_get_packet_type() return packet_type; } -uint8_t QUICParser::quic_get_zero_rtt() +uint8_t QUICParser::quic_get_parsed_ch() +{ + if (parsed_client_hello) { + return 1; + } + return 0; +} + +uint8_t QUICParser::quic_get_tls_hs_type() +{ + return tls_hs_type; +} + +void QUICParser::quic_get_zero_rtt(uint8_t& zero_rtt_toset) { - return zero_rtt; + zero_rtt_toset = zero_rtt; } void QUICParser::quic_get_version(uint32_t& version_toset) @@ -82,11 +141,13 @@ void QUICParser::quic_get_token_length(uint64_t& token_len_toset) return; } -uint16_t QUICParser::quic_get_server_port() { +uint16_t QUICParser::quic_get_server_port() +{ return server_port; } -void QUICParser::quic_get_parsed_initial(uint8_t& to_set) { +void QUICParser::quic_get_parsed_initial(uint8_t& to_set) +{ to_set = parsed_initial; return; } @@ -103,6 +164,12 @@ void QUICParser::quic_get_scid_len(uint8_t& scid_length_toset) return; } +void QUICParser::quic_get_tls_extensions(char* in) +{ + memcpy(in, quic_tls_ext, quic_tls_ext_pos); + return; +} + void QUICParser::quic_get_dcid(char* in) { memcpy(in, dcid, dcid_len); @@ -184,17 +251,41 @@ uint64_t QUICParser::quic_get_variable_length(const uint8_t* start, uint64_t& of bool QUICParser::quic_obtain_tls_data(TLSData& payload) { + quic_tls_extension_lengths_pos = 0; + quic_tls_ext_type_pos = 0; + quic_tls_ext_pos = 0; while (payload.start + sizeof(tls_ext) <= payload.end) { tls_ext* ext = (tls_ext*) payload.start; uint16_t type = ntohs(ext->type); uint16_t length = ntohs(ext->length); + // Store extension type + if (quic_tls_ext_type_pos < MAX_QUIC_TLS_EXT_LEN) { + quic_tls_ext_type[quic_tls_ext_type_pos] = type; + quic_tls_ext_type_pos += 1; + } + + // Store extension type length + if (quic_tls_extension_lengths_pos < MAX_QUIC_TLS_EXT_LEN) { + quic_tls_extension_lengths[quic_tls_extension_lengths_pos] = length; + quic_tls_extension_lengths_pos += 1; + } + + // payload.start += sizeof(tls_ext); if (payload.start + length > payload.end) { break; } + // Save value payload except for length + uint16_t len_of_ext = length; + if (quic_tls_ext_pos + len_of_ext < CURRENT_BUFFER_SIZE) { + memcpy(quic_tls_ext + quic_tls_ext_pos, payload.start, len_of_ext); + quic_tls_ext_pos += len_of_ext; + } + + // Legacy extract specific fields if (type == TLS_EXT_SERVER_NAME && length != 0) { tls_parser.tls_get_server_name(payload, sni, BUFF_SIZE); } else if ( @@ -235,9 +326,9 @@ bool QUICParser::quic_parse_tls() if (!tls_parser.tls_check_ext_len(payload)) { return false; } - if (!quic_obtain_tls_data(payload)) { - return false; - } + // If no parameters were extracted. We also accept the QUIC connection. (no error check here) + quic_obtain_tls_data(payload); + return true; } // QUICPlugin::quic_parse_tls @@ -245,27 +336,80 @@ uint8_t QUICParser::quic_draft_version(uint32_t version) { // this is IETF implementation, older version used if ((version >> 8) == older_version) { - return (uint8_t) version; + uint8_t draftversion = (uint8_t) version & 0xff; + if (draftversion >= 1 && draftversion <=34) { + return (uint8_t) version; + } } // This exists since version 29, but is still present in RFC9000. - if (version & 0x0F0F0F0F == force_ver_neg_pattern){ + if ((version & 0x0F0F0F0F) == force_ver_neg_pattern) { // Version 1 return 35; + } else if ((version & 0xffffff00) == quant) { + return 35; + } else if ((version >> 8) == quic_go) { + return 35; + } + if ((version >> 8) == quicly) { + return true; + } + if ((version >> 4) == ms_quic) { + return true; + } + if ((version >> 4) == ethz) { + return true; + } + if ((version >> 4) == telecom_italia) { + return true; + } + if ((version >> 4) == moz_quic) { + return true; + } + if ((version >> 4) == tencent_quic) { + return true; + } + if ((version >> 4) == quinn_noise) { + return true; + } + if ((version >> 4) == quic_over_scion) { + return true; } switch (version) { + case version_negotiation: + // TODO verify: We return a value that has no salt assigned. + return 1; // older mvfst version, but still used, based on draft 22, but salt 21 used + case (facebook_mvfst_old): + return 20; case (faceebook1): return 22; // more used atm, salt 23 used case faceebook2: + // 3 and 4 use default salt 23 according to mvfst: + // https://github.com/facebook/mvfst/blob/e89b990eaec5787a7dca7750362ea530e7703bdf/quic/handshake/HandshakeLayer.cpp#L27 + case facebook3: + case facebook4: case facebook_experimental: + case facebook_experimental2: + case facebook_experimental3: + case facebook_mvfst_alias: + case facebook_mvfst_alias2: return 27; // version 2 draft 00 + case quic_newest: + return 35; + case picoquic1: + case picoquic2: + return 36; case q_version2_draft00: // newest case q_version2_newest: + is_version2 = true; return 100; - + case q_version2: + is_version2 = true; + return 101; + case facebook_v1_alias: default: return 255; } @@ -311,9 +455,16 @@ bool QUICParser::quic_obtain_version() static const uint8_t handshake_salt_v1[SALT_LENGTH] = {0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}; - static const uint8_t handshake_salt_v2[SALT_LENGTH] + static const uint8_t handshake_salt_v2_provisional[SALT_LENGTH] = {0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}; + static const uint8_t handshake_salt_v2[SALT_LENGTH] + = {0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, + 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}; + // picoquic + static const uint8_t handshake_salt_picoquic_internal[SALT_LENGTH] + = {0x30, 0x67, 0x16, 0xd7, 0x63, 0x75, 0xd5, 0x55, 0x4b, 0x2f, + 0x60, 0x5e, 0xef, 0x78, 0xd8, 0x33, 0x3d, 0xc1, 0xca, 0x36}; if (version == version_negotiation) { DEBUG_MSG("Error, version negotiation\n"); @@ -331,9 +482,13 @@ bool QUICParser::quic_obtain_version() salt = handshake_salt_draft_23; } else if (!is_version2 && quic_check_version(version, 32)) { salt = handshake_salt_draft_29; - else if (!is_version2 && quic_check_version(version, 35)) { + } else if (!is_version2 && quic_check_version(version, 35)) { salt = handshake_salt_v1; + }else if (!is_version2 && quic_check_version(version, 36)) { + salt = handshake_salt_picoquic_internal; } else if (is_version2 && quic_check_version(version, 100)) { + salt = handshake_salt_v2_provisional; + } else if (is_version2 && quic_check_version(version, 101)) { salt = handshake_salt_v2; } else { DEBUG_MSG("Error, version not supported\n"); @@ -513,6 +668,12 @@ bool QUICParser::quic_derive_secrets(uint8_t* secret) bool QUICParser::quic_create_initial_secrets() { + // Set DCID if not set by previous packet + if (initial_dcid_len == 0) { + initial_dcid_len = dcid_len; + initial_dcid = (uint8_t*) dcid; + } + uint8_t extracted_secret[HASH_SHA2_256_LENGTH] = {0}; size_t extr_len = HASH_SHA2_256_LENGTH; @@ -546,7 +707,7 @@ bool QUICParser::quic_create_initial_secrets() EVP_PKEY_CTX_free(pctx); return false; } - if (1 != EVP_PKEY_CTX_set1_hkdf_key(pctx, dcid, quic_h1->dcid_len)) { + if (1 != EVP_PKEY_CTX_set1_hkdf_key(pctx, initial_dcid, initial_dcid_len)) { DEBUG_MSG("Error, key initialization failed(Extract)\n"); EVP_PKEY_CTX_free(pctx); return false; @@ -641,7 +802,6 @@ bool QUICParser::quic_decrypt_initial_header(const uint8_t* payload_pointer, uin uint8_t full_pkn[4] = {0}; uint8_t first_byte = 0; uint32_t packet_number = 0; - uint8_t pkn_len; // https://www.rfc-editor.org/rfc/rfc9001.html#name-header-protection-applicati @@ -901,83 +1061,70 @@ void QUICParser::quic_initialze_arrays() bool QUICParser::quic_check_long_header(uint8_t packet0) { - // We test for 1 in the fist position = long header - // We ignore the QUIC bit, as it might be greased - // https://datatracker.ietf.org/doc/html/rfc9287 - return (packet0 & 0x80) == 0x80; - + // We test for 1 in the fist position = long header + // We ignore the QUIC bit, as it might be greased + // https://datatracker.ietf.org/doc/html/rfc9287 + return (packet0 & 0x80) == 0x80; } bool QUICParser::quic_check_initial(uint8_t packet0) { - // The fixed bit, might be greased. We assume greasing for all packets // RFC 9287 - // version 1 (header form:long header(1) | fixed bit:fixed(1/0) | long packet type:initial(00) --> - // 1000 --> 8) + // version 1 (header form:long header(1) | fixed bit:fixed(1/0) | long packet type:initial(00) + // --> 1000 --> 8) if ((packet0 & 0xB0) == 0x80) { return true; } - // version 2 (header form:long header(1) | fixed bit:fixed(1)/0 | long packet type:initial(01) --> - // 1001 --> 9) - else if ( is_version2 && ((packet0 & 0xB0) == 0x90)) { + // version 2 (header form:long header(1) | fixed bit:fixed(1)/0 | long packet type:initial(01) + // --> 1001 --> 9) + else if (is_version2 && ((packet0 & 0xB0) == 0x90)) { return true; } else { return false; } } - -bool QUICParser::quic_check_min_initial_size(const Packet&pkt) +bool QUICParser::quic_check_min_initial_size(const Packet& pkt) { if (pkt.payload_len < QUIC_MIN_PACKET_LENGTH) { - return false; - } - return true; + return false; + } + return true; } - -uint32_t read_uint32_t(const uint8_t *ptr, uint8_t offset) +uint32_t read_uint32_t(const uint8_t* ptr, uint8_t offset) { - uint32_t val; - memcpy(&val, ptr + offset, sizeof(uint32_t)); - return val; + uint32_t val; + memcpy(&val, ptr + offset, sizeof(uint32_t)); + return val; } -bool QUICParser::quic_check_supported_version(const uint32_t version) { - switch (version) { - case q_version2_draft00: - is_version2 = true; - return true; - case q_version2_newest: - is_version2 = true; - return true; - case older_version: - case faceebook1: - case faceebook2: - case facebook_experimental: - case version_negotiation: - case quic_newest: - return true; - } - if ((version & 0x0f0f0f0f) == force_ver_neg_pattern) { - return true - } - return false; +bool QUICParser::quic_check_supported_version(const uint32_t version) +{ + uint8_t draft_version = quic_draft_version(version); + return (draft_version > 0) && (draft_version < 255) ; } bool QUICParser::quic_long_header_packet(const Packet& pkt) { // UDP check, Initial packet check, QUIC min long header size, QUIC version check, - if (pkt.ip_proto != 17 || !quic_check_long_header(pkt.payload[0]) || !(quic_check_min_initial_size(pkt)) || !(quic_check_supported_version(ntohl(read_uint32_t(pkt.payload, 1)))) ) { - DEBUG_MSG("Packet is not Initial or does not contains LONG HEADER or is not long enough or is not a supported QUIC version\n"); - return false; + if (pkt.ip_proto != 17 || !quic_check_long_header(pkt.payload[0]) + || !(quic_check_min_initial_size(pkt)) + || !(quic_check_supported_version(ntohl(read_uint32_t(pkt.payload, 1))))) { + DEBUG_MSG( + "Packet is not Initial or does not contains LONG HEADER or is not long enough or is " + "not a supported QUIC version\n"); + return false; } return true; } -bool QUICParser::quic_parse_initial_header(const Packet& pkt, const uint8_t* payload_pointer, - const uint8_t* payload_end, uint64_t& offset) +bool QUICParser::quic_parse_initial_header( + const Packet& pkt, + const uint8_t* payload_pointer, + const uint8_t* payload_end, + uint64_t& offset) { token_length = quic_get_variable_length(payload_pointer, offset); if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { @@ -985,81 +1132,94 @@ bool QUICParser::quic_parse_initial_header(const Packet& pkt, const uint8_t* pa } offset += token_length; - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; - } + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } - payload_len = quic_get_variable_length(payload_pointer, offset); - if (payload_len > CURRENT_BUFFER_SIZE) { - return false; - } + payload_len = quic_get_variable_length(payload_pointer, offset); + if (payload_len > CURRENT_BUFFER_SIZE) { + return false; + } - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; - } + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } - pkn = (payload_pointer + offset); + pkn = (payload_pointer + offset); - payload = (payload_pointer + offset); + payload = (payload_pointer + offset); // This should not cause an offset. // offset += sizeof(uint8_t) * 4; - sample = (payload_pointer + offset + 4*sizeof(uint8_t) ); + sample = (payload_pointer + offset + 4 * sizeof(uint8_t)); - if (!quic_check_pointer_pos((payload_pointer + offset + 4*sizeof(uint8_t)), payload_end)) { - return false; - } - return true; + if (!quic_check_pointer_pos((payload_pointer + offset + 4 * sizeof(uint8_t)), payload_end)) { + return false; + } + return true; } -void QUICParser::quic_parse_packet_type(uint8_t packet0) { +void QUICParser::quic_parse_quic_bit(uint8_t packet0) +{ + // Contains value of the first included QUIC bit (in the case of coalesced packets) + // Always the second msb. + // Note: no meaning if in Version negotiation. + + packets |= (packet0 & QUIC_BIT) << 1; +} +void QUICParser::quic_parse_packet_type(uint8_t packet0) +{ if (version == version_negotiation) { packets |= F_VERSION_NEGOTIATION; packet_type = VERSION_NEGOTIATION; return; } - packet_type = (packet0 & 0b00110000)>> 4; + packet_type = (packet0 & 0b00110000) >> 4; if (!is_version2) { switch (packet_type) { - case 0b00: - packets |= F_INITIAL; - break; - case 0b01: - packets |= F_ZERO_RTT; - break; - case 0b10: - packets |= F_HANDSHAKE; - break; - case 0b11: - packets |= F_RETRY; - break; + case 0b00: + packets |= F_INITIAL; + break; + case 0b01: + packets |= F_ZERO_RTT; + break; + case 0b10: + packets |= F_HANDSHAKE; + break; + case 0b11: + packets |= F_RETRY; + break; } } if (is_version2) { switch (packet_type) { - case 0b01: - packet_type = INITIAL; - packets |= F_INITIAL; - break; - case 0b10: - packet_type = ZERO_RTT; - packets |= F_ZERO_RTT; - break; - case 0b11: - packet_type = HANDSHAKE; - packets |= F_HANDSHAKE; - break; - case 0b00: - packet_type = RETRY; - packets |= F_RETRY; - break; + case 0b01: + packet_type = INITIAL; + packets |= F_INITIAL; + break; + case 0b10: + packet_type = ZERO_RTT; + packets |= F_ZERO_RTT; + break; + case 0b11: + packet_type = HANDSHAKE; + packets |= F_HANDSHAKE; + break; + case 0b00: + packet_type = RETRY; + packets |= F_RETRY; + break; } } } -bool QUICParser::quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* payload_pointer, uint8_t* payload_end) +bool QUICParser::quic_parse_header( + const Packet& pkt, + uint64_t& offset, + uint8_t* payload_pointer, + uint8_t* payload_end) { if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { return false; @@ -1084,7 +1244,7 @@ bool QUICParser::quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* } if (quic_h1->dcid_len != 0) { - if (quic_h1->dcid_len>MAX_CID_LEN) { + if (quic_h1->dcid_len > MAX_CID_LEN) { DEBUG_MSG("Recieved DCID longer than supported. dcid_len=%d \n", dcid_len); return false; } @@ -1106,8 +1266,8 @@ bool QUICParser::quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* } if (quic_h2->scid_len != 0) { - if (quic_h2->scid_len>MAX_CID_LEN) { - DEBUG_MSG("Recieved SCID longer than supported. scid_len=%d \n", scid_len); + if (quic_h2->scid_len > MAX_CID_LEN) { + DEBUG_MSG("Recieved SCID longer than supported. scid_len=%d \n", scid_len); return false; } scid = (payload_pointer + offset); @@ -1122,23 +1282,20 @@ bool QUICParser::quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* quic_parse_packet_type(quic_h1->first_byte); return true; - } bool QUICParser::quic_parse_headers(const Packet& pkt, bool forceInitialParsing) { - uint8_t* pkt_payload_pointer = (uint8_t *)pkt.payload; + uint8_t* pkt_payload_pointer = (uint8_t*) pkt.payload; uint8_t* payload_pointer = pkt_payload_pointer; uint64_t offset = 0; - packets = 0; - uint8_t* pkt_payload_end = payload_pointer + pkt.payload_len; // Handle coalesced packets // 7 because (1B QUIC LH, 4B Version, 1 B SCID LEN, 1B DCID LEN) uint64_t stored_payload_len; - while(payload_pointer + offset + QUIC_MIN_PACKET_LENGTH <= pkt.payload + pkt.payload_len) { + while (pkt.payload + offset + QUIC_MIN_PACKET_LENGTH <= pkt.payload + pkt.payload_len) { payload_pointer = pkt_payload_pointer + offset; if (!quic_parse_header(pkt, offset, pkt_payload_pointer, pkt_payload_end)) { @@ -1146,40 +1303,55 @@ bool QUICParser::quic_parse_headers(const Packet& pkt, bool forceInitialParsing) } switch (packet_type) { - case ZERO_RTT: - if (zero_rtt < 0xFF) { - zero_rtt +=1; - } - break; - case HANDSHAKE: - payload_len = quic_get_variable_length(pkt_payload_pointer, offset); - if (payload_len > CURRENT_BUFFER_SIZE) { - return false; - } - offset += payload_len; - break; - case INITIAL: - if (!quic_parse_initial_header(pkt, pkt_payload_pointer, pkt_payload_end, offset)) { - return false; - } - stored_payload_len = payload_len; + case ZERO_RTT: + payload_len = quic_get_variable_length(pkt_payload_pointer, offset); + if (zero_rtt < 0xFF) { + zero_rtt += 1; + } + offset += payload_len; + break; + case HANDSHAKE: + payload_len = quic_get_variable_length(pkt_payload_pointer, offset); + if (payload_len > CURRENT_BUFFER_SIZE) { + return false; + } + offset += payload_len; + break; + case INITIAL: + if (!quic_parse_initial_header(pkt, pkt_payload_pointer, pkt_payload_end, offset)) { + return false; + } + stored_payload_len = payload_len; + if (!parsed_initial) { + // Not yet parsed a CH, try to parse as CH with inherited DCID + quic_parse_initial(pkt, pkt_payload_pointer, offset); + // If still not parsed, try with DCID from current packet. + // Session resumption is such a case. if (!parsed_initial) { - // Not yet parsed a CH, try to parse as CH + quic_tls_extension_lengths_pos = 0; + initial_dcid_len = dcid_len; + initial_dcid = (uint8_t*) dcid; + // Increment by tag_len, since subsequent function is not stateless. + payload_len += 16; + // Undo another side effect + payload = payload - pkn_len; + payload_len += pkn_len; quic_parse_initial(pkt, pkt_payload_pointer, offset); } - offset += stored_payload_len; - break; - case RETRY: - // 16 - Integrity tag - token_length = pkt_payload_end - payload_pointer - offset - 16; - if (!quic_check_pointer_pos((pkt_payload_pointer + offset), pkt_payload_end)) { - return false; - } - offset += token_length; - if (!quic_check_pointer_pos((pkt_payload_pointer + offset), pkt_payload_end)) { - return false; - } - break; + } + offset += stored_payload_len; + break; + case RETRY: + // 16 - Integrity tag + token_length = pkt_payload_end - payload_pointer - offset - 16; + if (!quic_check_pointer_pos((pkt_payload_pointer + offset), pkt_payload_end)) { + return false; + } + offset += token_length; + if (!quic_check_pointer_pos((pkt_payload_pointer + offset), pkt_payload_end)) { + return false; + } + break; } if (!quic_set_server_port(pkt)) { @@ -1197,39 +1369,47 @@ bool QUICParser::quic_parse_headers(const Packet& pkt, bool forceInitialParsing) packet_type = INITIAL; } - return packets; } // QUICPlugin::quic_parse_data -bool QUICParser::quic_set_server_port(const Packet& pkt) { - +bool QUICParser::quic_set_server_port(const Packet& pkt) +{ tls_handshake hs = tls_parser.tls_get_handshake(); switch (packet_type) { - case INITIAL: - if (hs.type == 1) { - server_port = pkt.dst_port; - } else if (hs.type == 2) { - // Won't be reached, since we don't supply the OCCID to quic_parser - server_port = pkt.src_port; - } - // e.g. ACKs do not reveal direction - break; - case VERSION_NEGOTIATION: - case RETRY: - server_port = pkt.src_port; - break; - case ZERO_RTT: + case INITIAL: + tls_hs_type = hs.type; + if (hs.type == 1) { server_port = pkt.dst_port; - break; - case HANDSHAKE: - // Does not reveal the direction - break; + } else if (hs.type == 2) { + // Won't be reached, since we don't supply the OCCID to quic_parser + server_port = pkt.src_port; + } + // e.g. ACKs do not reveal direction + break; + case VERSION_NEGOTIATION: + case RETRY: + server_port = pkt.src_port; + break; + case ZERO_RTT: + server_port = pkt.dst_port; + break; + case HANDSHAKE: + // Does not reveal the direction + break; } - return true; + return true; } -bool QUICParser::quic_check_quic_long_header_packet(const Packet& pkt) { +bool QUICParser::quic_check_quic_long_header_packet( + const Packet& pkt, + char* initial_packet_dcid, + uint8_t& initial_packet_dcid_length) +{ + initial_dcid_len = initial_packet_dcid_length; + initial_dcid = (uint8_t*) initial_packet_dcid; + + quic_parse_quic_bit(pkt.payload[0]); if (!quic_long_header_packet(pkt)) { return false; @@ -1242,10 +1422,11 @@ bool QUICParser::quic_check_quic_long_header_packet(const Packet& pkt) { return true; } - -bool QUICParser::quic_parse_initial(const Packet& pkt, const uint8_t* payload_pointer, uint64_t offset) +bool QUICParser::quic_parse_initial( + const Packet& pkt, + const uint8_t* payload_pointer, + uint64_t offset) { - if (!quic_create_initial_secrets()) { DEBUG_MSG("Error, creation of initial secrets failed (client side)\n"); return false; @@ -1272,10 +1453,15 @@ bool QUICParser::quic_parse_initial(const Packet& pkt, const uint8_t* payload_po // According to RFC 9000 the server port will not change. if (!quic_set_server_port(pkt)) { - DEBUG_MSG("Error, extracting server port"); - return false; + DEBUG_MSG("Error, extracting server port"); + return false; + } + + if (tls_hs_type == TLS_HANDSHAKE_CLIENT_HELLO) { + parsed_client_hello = true; } return true; } + } // namespace ipxp diff --git a/process/quic_parser.hpp b/process/quic_parser.hpp index dd0f8592..3eb614ea 100644 --- a/process/quic_parser.hpp +++ b/process/quic_parser.hpp @@ -7,7 +7,8 @@ * \brief Class for parsing quic traffic. * \author Andrej Lukacovic lukacan1@fit.cvut.cz * \author Karel Hynek - * \date 2022 + * \author Jonas Mücke + * \date 2023 */ #include "tls_parser.hpp" @@ -34,15 +35,20 @@ // first byte (1) + version (4) + dcid length (1) + dcid (20) + scid length (1) + scid (20) + // token length (variable so max is 8) + token (idk) + length (max 8) + pkt number (4) -// cant figure out if token length has any boundaries, teoretically 8 byte version of token length +// cant figure out if token length has any boundaries, theoretically 8 byte version of token length // means 2^64 as max length -// 67 (header basic data) + 100 (max token length) -#define MAX_HEADER_LEN 67 + 100 +// 67 (header basic data) + 256 (max token length) +// TODO(jmuecke) I increased the token length, because I observered token_lengths up to 128. Revisit +// if larger tokens are common. +#define MAX_HEADER_LEN 67 + 256 #define BUFF_SIZE 255 #define CURRENT_BUFFER_SIZE 1500 -// 8 because (1B QUIC LH, 4B Version, 1 B SCID LEN, 1B DCID LEN, Payload/Retry Token/Supported Version >= 1 B) +// 8 because (1B QUIC LH, 4B Version, 1 B SCID LEN, 1B DCID LEN, Payload/Retry Token/Supported +// Version >= 1 B) #define QUIC_MIN_PACKET_LENGTH 8 #define MAX_CID_LEN 20 +#define QUIC_BIT 0b01000000 +#define MAX_QUIC_TLS_EXT_LEN 30 namespace ipxp { typedef struct __attribute__((packed)) quic_first_ver_dcidlen { @@ -89,7 +95,6 @@ class QUICParser { = sizeof("tls13 client in") + sizeof(uint16_t) + sizeof(uint8_t) + sizeof(uint8_t) }; - bool quic_initial_checks(const Packet&); void quic_initialze_arrays(); bool quic_check_initial(uint8_t); @@ -108,17 +113,21 @@ class QUICParser { void quic_skip_connection_close2(uint8_t*, uint64_t&); void quic_copy_crypto(uint8_t*, uint64_t&); bool quic_encrypt_sample(uint8_t*); + uint8_t quic_draft_version(uint32_t); uint64_t quic_get_variable_length(const uint8_t*, uint64_t&); bool quic_check_version(uint32_t, uint8_t); bool quic_check_pointer_pos(const uint8_t*, const uint8_t*); bool quic_obtain_tls_data(TLSData&); bool quic_set_server_port(const Packet& pkt); - bool quic_check_min_initial_size(const Packet&pkt); + bool quic_check_min_initial_size(const Packet& pkt); bool quic_check_supported_version(const uint32_t version); bool quic_parser_tls_and_set_server_port(const Packet& pkt); - bool quic_parse_initial_header(const Packet& pkt, const uint8_t* payload_pointer, - const uint8_t* payload_end, uint64_t& offset); + bool quic_parse_initial_header( + const Packet& pkt, + const uint8_t* payload_pointer, + const uint8_t* payload_end, + uint64_t& offset); void quic_parse_packet_type(uint8_t packet0); Initial_Secrets initial_secrets; @@ -137,6 +146,8 @@ class QUICParser { uint8_t packet_type; const uint8_t* dcid; uint8_t dcid_len; + uint8_t* initial_dcid; + uint8_t initial_dcid_len; const uint8_t* scid; uint8_t scid_len; const uint8_t* pkn; @@ -144,17 +155,30 @@ class QUICParser { uint32_t version; uint64_t token_length; + uint8_t pkn_len; + uint8_t decrypted_payload[CURRENT_BUFFER_SIZE]; uint8_t assembled_payload[CURRENT_BUFFER_SIZE]; uint8_t tmp_header_mem[MAX_HEADER_LEN]; uint8_t* final_payload; uint8_t zero_rtt; + uint16_t quic_tls_ext_type[MAX_QUIC_TLS_EXT_LEN]; + uint8_t quic_tls_ext_type_pos; + + uint16_t quic_tls_extension_lengths[MAX_QUIC_TLS_EXT_LEN]; + uint8_t quic_tls_extension_lengths_pos; + + char quic_tls_ext[CURRENT_BUFFER_SIZE]; + uint16_t quic_tls_ext_pos; + int parsed_initial; + bool parsed_client_hello; uint16_t server_port; bool direction_to_server; bool is_version2; + uint8_t tls_hs_type; char sni[BUFF_SIZE] = {0}; char user_agent[BUFF_SIZE] = {0}; @@ -179,42 +203,91 @@ class QUICParser { F_ZERO_RTT = 0b00000010, F_HANDSHAKE = 0b00000100, F_RETRY = 0b00001000, - F_VERSION_NEGOTIATION = 0b00010000 - }; - enum QUIC_CONSTANTS { - QUIC_UNUSED_VARIABLE_LENGTH_INT = 0xFFFFFFFFFFFFFFFF + F_VERSION_NEGOTIATION = 0b00010000, + // We store the QUIC bit in the first bit of QUIC_PACKETS + // The following enum should not be used, unless for extraction + F_QUIC_BIT = 0b10000000 }; + enum QUIC_CONSTANTS { QUIC_UNUSED_VARIABLE_LENGTH_INT = 0xFFFFFFFFFFFFFFFF }; enum QUIC_VERSION { - older_version = 0xff0000, + // Full versions faceebook1 = 0xfaceb001, faceebook2 = 0xfaceb002, + facebook3 = 0xfaceb00d, + facebook4 = 0xfaceb00f, facebook_experimental = 0xfaceb00e, + facebook_experimental2 = 0xfaceb011, + facebook_experimental3 = 0xfaceb013, + facebook_mvfst_old = 0xfaceb000, + facebook_mvfst_alias = 0xfaceb010, + facebook_mvfst_alias2 = 0xfaceb012, + facebook_v1_alias = 0xfaceb003, q_version2_draft00 = 0xff020000, q_version2_newest = 0x709a50c4, - force_ver_neg_pattern = 0x0a0a0a0a, + q_version2 = 0x6b3343cf, version_negotiation = 0x00000000, - quic_newest = 0x00000001 + quic_newest = 0x00000001, + picoquic1 = 0x50435130, + picoquic2 = 0x50435131, + // Patterns + force_ver_neg_pattern = 0x0a0a0a0a, + quant = 0x45474700, + older_version = 0xff0000, + quic_go = 0x51474f, + // unknown handshake salt TODO use version 1 as default? + quicly = 0x91c170, + // https://github.com/microsoft/msquic/blob/d33bc56d5e11db52e2b34ae152ea598fd6e935c0/src/core/packet.c#L461 + // But version is different + ms_quic = 0xabcd000, + + ethz = 0xf0f0f0f, + telecom_italia = 0xf0f0f1f0, + + moz_quic = 0xf123f0c, + + tencent_quic = 0x0700700, + + quinn_noise = 0xf0f0f2f, + + quic_over_scion = 0x5c10000 }; QUICParser(); - uint8_t quic_get_zero_rtt(); + void quic_get_zero_rtt(uint8_t& zero_rtt_toset); bool quic_parse_initial(const Packet&, const uint8_t* payload_end, uint64_t offset); void quic_get_sni(char* in); void quic_get_user_agent(char* in); void quic_get_version(uint32_t&); void quic_get_token_length(uint64_t&); - void quic_get_dcid(char *in); - void quic_get_scid(char *in); + void quic_get_dcid(char* in); + void quic_get_scid(char* in); void quic_get_scid_len(uint8_t&); void quic_get_dcid_len(uint8_t&); void quic_get_parsed_initial(uint8_t&); void quic_get_packets(uint8_t&); + uint8_t quic_get_parsed_ch(); uint8_t quic_get_packet_type(); uint16_t quic_get_server_port(); - bool quic_check_quic_long_header_packet(const Packet& pkt); + bool quic_check_quic_long_header_packet( + const Packet& pkt, + char* initial_packet_dcid, + uint8_t& initial_packet_dcid_length); bool quic_parse_headers(const Packet&, bool forceInitialParsing); - bool quic_parse_header(const Packet &pkt, uint64_t& offset, uint8_t* payload_pointer, uint8_t* payload_end); + bool quic_parse_header( + const Packet& pkt, + uint64_t& offset, + uint8_t* payload_pointer, + uint8_t* payload_end); bool quic_long_header_packet(const Packet& pkt); + uint8_t quic_get_tls_hs_type(); + void quic_get_tls_ext_len(uint16_t& tls_ext_len_toset); + void quic_get_tls_ext(char* in); + void quic_get_tls_ext_type_len(uint16_t& tls_ext_type_len_toset); + void quic_get_tls_ext_type(uint16_t* tls_ext_type_toset); + void quic_parse_quic_bit(uint8_t packet0); + void quic_get_tls_extension_lengths(uint16_t* tls_extensions_len); + void quic_get_tls_extension_lengths_len(uint8_t& tls_extensions_length_len_toset); + void quic_get_tls_extensions(char* in); }; } // namespace ipxp From 53ba8f735fab5fe09bc942281d92195caba8a605 Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Tue, 23 Jan 2024 14:47:59 +0100 Subject: [PATCH 13/23] Restrict TLS extraction to alpn and quic_transport parameters --- README.md | 41 +++++++++++++++++++++-------------------- configure.ac | 8 ++++++++ process/quic_parser.cpp | 17 +++++++++++++---- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b24bb7c9..6e1ed48f 100644 --- a/README.md +++ b/README.md @@ -613,26 +613,27 @@ List of fields exported together with basic flow fields on interface by WG plugi ### QUIC List of fields exported together with basic flow fields on interface by quic plugin. - -| Output field | Type | Description | -|:-------------------:|:--------:|:------------------------------------------------------------------------:| -| QUIC_SNI | string | Decrypted server name | -| QUIC_USER_AGENT | string | Decrypted user agent | -| QUIC_VERSION | uint32 | QUIC version from first server long header packets | -| QUIC_CLIENT_VERSION | uint32 | QUIC version from first client long header packet | -| QUIC_TOKEN_LENGTH | uint64 | Token length from Initial and Retry packets | -| QUIC_OCCID | bytes | Source Connection ID from first client packet | -| QUIC_OSCID | bytes | Destination Connection ID from first client packet | -| QUIC_SCID | bytes | Source Connection ID from first server packet | -| QUIC_RETRY_SCID | bytes | Source Connection ID from Retry packet | -| QUIC_MULTIPLEXED | uint8 | > 0 if multiplexed (at least two different QUIC_OSCIDs or SNIs) | -| QUIC_ZERO_RTT | uint8 | Number of 0-RTT packets in flow. | -| QUIC_SERVER_PORT | uint16 | TODO Server Port determined by packet type and TLS message | -| QUIC_PACKETS | uint8\* | QUIC long header packet type (v1 encoded), version negotiation, QUIC bit | -| QUIC_CH_PARSED | uint8 | >0 if TLS Client Hello parsed without errors | -| QUIC_TLS_EXT_TYPE | uint16\* | TLS extensions in the TLS Client Hello | -| QUIC_TLS_EXT_LEN | uint16\* | Length of each TLS extension | -| QUIC_TLS_EXT | string | Payload of each TLS extension | +`-with-quic-ch-full-tls-ext` enables extraction of all TLS extensions in the Client Hello. + +| Output field | Type | Description | +|:-------------------:|:--------:|:---------------------------------------------------------------------------------------------:| +| QUIC_SNI | string | Decrypted server name | +| QUIC_USER_AGENT | string | Decrypted user agent | +| QUIC_VERSION | uint32 | QUIC version from first server long header packets | +| QUIC_CLIENT_VERSION | uint32 | QUIC version from first client long header packet | +| QUIC_TOKEN_LENGTH | uint64 | Token length from Initial and Retry packets | +| QUIC_OCCID | bytes | Source Connection ID from first client packet | +| QUIC_OSCID | bytes | Destination Connection ID from first client packet | +| QUIC_SCID | bytes | Source Connection ID from first server packet | +| QUIC_RETRY_SCID | bytes | Source Connection ID from Retry packet | +| QUIC_MULTIPLEXED | uint8 | > 0 if multiplexed (at least two different QUIC_OSCIDs or SNIs) | +| QUIC_ZERO_RTT | uint8 | Number of 0-RTT packets in flow. | +| QUIC_SERVER_PORT | uint16 | TODO Server Port determined by packet type and TLS message | +| QUIC_PACKETS | uint8\* | QUIC long header packet type (v1 encoded), version negotiation, QUIC bit | +| QUIC_CH_PARSED | uint8 | >0 if TLS Client Hello parsed without errors | +| QUIC_TLS_EXT_TYPE | uint16\* | TLS extensions in the TLS Client Hello | +| QUIC_TLS_EXT_LEN | uint16\* | Length of each TLS extension | +| QUIC_TLS_EXT | string | Payload of all/application_layer_protocol_negotiation and quic_transport params TLS extension | ### ICMP diff --git a/configure.ac b/configure.ac index a46cb3a1..6760262b 100644 --- a/configure.ac +++ b/configure.ac @@ -159,6 +159,12 @@ if [[ -z "$WITH_QUIC_TRUE" ]]; then AC_DEFINE([WITH_QUIC], [1], [Define to 1 if compile with quic plugin]) fi +AC_ARG_WITH([quic-ch-full-tls-ext], + AC_HELP_STRING([--with-quic-ch-full-tls-ext],[Extract all QUIC TLS payloads from the first client hello.]), + [ + CPPFLAGS="$CPPFLAGS -DQUIC_CH_FULL_TLS_EXT" + ] +) AM_CONDITIONAL(OS_CYGWIN, test x${host_os} = xcygwin) @@ -402,6 +408,8 @@ AC_ARG_WITH([msects], ) + + AM_CONDITIONAL(MAKE_RPMS, test x$RPMBUILD != x) AM_CONDITIONAL(MAKE_DEB, test x$DEBUILD != x) diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index a729ea0d..d375538f 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -279,10 +279,19 @@ bool QUICParser::quic_obtain_tls_data(TLSData& payload) } // Save value payload except for length - uint16_t len_of_ext = length; - if (quic_tls_ext_pos + len_of_ext < CURRENT_BUFFER_SIZE) { - memcpy(quic_tls_ext + quic_tls_ext_pos, payload.start, len_of_ext); - quic_tls_ext_pos += len_of_ext; + if (quic_tls_ext_pos + length < CURRENT_BUFFER_SIZE) { + #ifndef QUIC_CH_FULL_TLS_EXT + if (type == TLS_EXT_ALPN + || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS_V1 + || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS + || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS_V2) { + #endif + memcpy(quic_tls_ext + quic_tls_ext_pos, payload.start, length); + quic_tls_ext_pos += length; + #ifndef QUIC_CH_FULL_TLS_EXT + } + #endif + } // Legacy extract specific fields From 0ecfefbe25504c62bf17768c1a252f6dc4522c99 Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:10:12 +0100 Subject: [PATCH 14/23] Fix text output --- process/quic.hpp | 54 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/process/quic.hpp b/process/quic.hpp index ff71a650..31fe82dc 100644 --- a/process/quic.hpp +++ b/process/quic.hpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace ipxp { #define QUIC_UNIREC_TEMPLATE \ @@ -308,15 +309,47 @@ struct RecordExtQUIC : public RecordExt { << "quicuseragent=\"" << user_agent << "\"" << "quicversion=\"" << quic_version << "\"" << "quicclientversion=\"" << quic_client_version << "\"" - << "quicoccidlength=\"" << occid_length << "\"" - << "quicoccid=\"" << occid << "\"" - << "quicoscidlength=\"" << oscid_length << "\"" - << "quicoscid=\"" << oscid << "\"" - << "quicscidlength=\"" << scid_length << "\"" - << "quicscid=\"" << scid << "\"" - << "quicmultiplexed=\"" << quic_multiplexed << "\"" - << "quiczerortt=\"" << quic_zero_rtt << "\"" - << "quic_parsed_ch=\"" << parsed_ch << "\""; + << "quicoccidlength=\"" << (int) occid_length << "\""; + out << "quicoccid=\""; + for (int i = 0; i < occid_length; i++) { + out << std::hex << (occid[i] & 0xff); + } + out << "\"" + << "quicoscidlength=\"" << std::dec << (int) oscid_length << "\""; + out << "quicoscid=\""; + for (int i = 0; i < oscid_length; i++) { + out << std::hex << (oscid[i] & 0xff); + } + out << "\"" + << "quicscidlength=\"" << std::dec << (int) scid_length << "\""; + out << "quicscid=\""; + for (int i = 0; i < scid_length; i++) { + out << std::hex << (scid[i] & 0xff); + } + out << "\"" + << "quicmultiplexed=\"" << std::dec << (int) quic_multiplexed << "\"" + << "quiczerortt=\"" << (int) quic_zero_rtt << "\"" + << "quicparsedch=\"" << (int) parsed_ch << "\""; + out << "quictlsexttype=("; + for (int i = 0; i < tls_ext_type_len; i++) { + out << std::dec << (uint16_t) tls_ext_type[i]; + if (i != tls_ext_type_len - 1) { + out << ","; + } + } + out << ")quictlsextlen=("; + for (int i = 0; i < tls_ext_len_len; i++) { + out << std::dec << (uint16_t) tls_ext_len[i]; + if (i != tls_ext_len_len - 1) { + out << ","; + } + } + out << ")quictlsext=\""; + for (int i = 0; i < tls_ext_length; i++) { + out << std::hex << std::setw(2) << std::setfill('0')<< (uint16_t) tls_ext[i]; + } + out << "\""; + return out.str(); } }; @@ -373,8 +406,7 @@ class QUICPlugin : public ProcessPlugin { RecordExtQUIC* quic_data, const Packet& pkt, bool new_quic_flow); - void set_cid_if_unset -( + void set_cid_if_unset( bool& set_flag, uint8_t& src_id_length, char* src_id, From 531c54a62562cd2f02a92019adfda68106d4651b Mon Sep 17 00:00:00 2001 From: Karel Hynek Date: Mon, 12 Feb 2024 10:35:17 +0100 Subject: [PATCH 15/23] QUIC - Fix required output IPFIX buffer size --- process/quic.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process/quic.hpp b/process/quic.hpp index 31fe82dc..432e21e3 100644 --- a/process/quic.hpp +++ b/process/quic.hpp @@ -240,7 +240,7 @@ struct RecordExtQUIC : public RecordExt { + len_token_length + len_multiplexed + len_zero_rtt + (scid_length + 3) + (occid_length + 3) + (oscid_length + 3) + (retry_scid_length + 3) + len_server_port + pkt_types_len + len_parsed_ch + len_tls_ext_type + len_tls_len - + len_tls_ext + + len_tls_ext + 3 * basiclist.HeaderSize() > size) { return -1; } From 13c522afae42f79dd2bfba2149aac785bb881c91 Mon Sep 17 00:00:00 2001 From: Karel Hynek Date: Mon, 12 Feb 2024 10:40:01 +0100 Subject: [PATCH 16/23] QUIC - Fix payload len underflow, when smaller than expected --- process/quic_parser.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index d375538f..206ef968 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -840,6 +840,10 @@ bool QUICParser::quic_decrypt_initial_header(const uint8_t* payload_pointer, uin // payload payload = payload + pkn_len; payload_len = payload_len - pkn_len; + if (payload_len > CURRENT_BUFFER_SIZE ) { + DEBUG_MSG("Payload length underflow\n"); + return false; + } header_len = payload - payload_pointer; if (header_len > MAX_HEADER_LEN) { DEBUG_MSG("Header length too long\n"); @@ -874,8 +878,8 @@ bool QUICParser::quic_decrypt_payload() /* Input is --> "header || ciphertext (buffer) || auth tag (16 bytes)" */ - if (payload_len <= 16) { - DEBUG_MSG("Payload decryption error, ciphertext too short\n"); + if (payload_len <= 16 || payload_len > CURRENT_BUFFER_SIZE) { + DEBUG_MSG("Payload decryption error, ciphertext too short or long\n"); return false; } // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-34#section-5.3 From 131613ce0ff130d0337fc9e70d0241f17fa36357 Mon Sep 17 00:00:00 2001 From: Karel Hynek Date: Mon, 12 Feb 2024 11:05:34 +0100 Subject: [PATCH 17/23] QUIC - Avoid source buffer overflow in crypto frame copy --- process/quic_parser.cpp | 21 +++++++++++++++------ process/quic_parser.hpp | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index 206ef968..4e9e6a0b 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -1000,14 +1000,23 @@ inline void QUICParser::quic_skip_connection_close2(uint8_t* start, uint64_t& of return; } -inline void QUICParser::quic_copy_crypto(uint8_t* start, uint64_t& offset) +inline void QUICParser::quic_copy_crypto(uint8_t* start, const uint8_t* end, uint64_t& offset) { offset += 1; - uint16_t frame_offset = quic_get_variable_length(start, offset); - uint16_t frame_length = quic_get_variable_length(start, offset); + uint32_t frame_offset = quic_get_variable_length(start, offset); + uint32_t frame_length = quic_get_variable_length(start, offset); - frame_offset = std::min(frame_offset, (uint16_t) (CURRENT_BUFFER_SIZE - 1)); - frame_length = std::min((uint16_t) (CURRENT_BUFFER_SIZE - 1 - frame_offset), frame_length); + if(end < (start + offset)) { + //avoid source buffer overflow + quic_crypto_len += frame_length; + offset += frame_length; + return; + } + + frame_offset = std::min(frame_offset, (uint32_t) (CURRENT_BUFFER_SIZE - 1)); + frame_length = std::min((uint32_t) (CURRENT_BUFFER_SIZE - 1 - frame_offset), frame_length); + // avoid memory overlap in memcpy when not enought space in source buffer + frame_length = std::min(frame_length, (uint32_t)(end - (start + offset))); memcpy(assembled_payload + frame_offset, start + offset, frame_length); if (frame_offset < quic_crypto_start) { @@ -1036,7 +1045,7 @@ bool QUICParser::quic_reassemble_frames() // https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types // only those frames can occure in initial packets if (quic_check_frame_type(current, CRYPTO)) { - quic_copy_crypto(decrypted_payload, offset); + quic_copy_crypto(decrypted_payload, payload_end, offset); } else if (quic_check_frame_type(current, ACK1)) { quic_skip_ack1(decrypted_payload, offset); } else if (quic_check_frame_type(current, ACK2)) { diff --git a/process/quic_parser.hpp b/process/quic_parser.hpp index 3eb614ea..fc2d5b57 100644 --- a/process/quic_parser.hpp +++ b/process/quic_parser.hpp @@ -111,7 +111,7 @@ class QUICParser { void quic_skip_ack2(uint8_t*, uint64_t&); void quic_skip_connection_close1(uint8_t*, uint64_t&); void quic_skip_connection_close2(uint8_t*, uint64_t&); - void quic_copy_crypto(uint8_t*, uint64_t&); + void quic_copy_crypto(uint8_t*, const uint8_t*, uint64_t&); bool quic_encrypt_sample(uint8_t*); uint8_t quic_draft_version(uint32_t); From f7b6f44df8cf66e2ce73721372bcc8e281604b9e Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:28:13 +0100 Subject: [PATCH 18/23] Return proper QUIC version. We assume QUIC version 1 if we have no knowledge about the spoken QUIC version. --- process/quic_parser.cpp | 56 ++++++++++++++++++++--------------------- process/quic_parser.hpp | 16 ++++++------ 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index 4e9e6a0b..d787a8a2 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -343,46 +343,44 @@ bool QUICParser::quic_parse_tls() uint8_t QUICParser::quic_draft_version(uint32_t version) { + // Calculate potential draft version + uint8_t draftversion = (uint8_t) version & 0xff; // this is IETF implementation, older version used if ((version >> 8) == older_version) { - uint8_t draftversion = (uint8_t) version & 0xff; if (draftversion >= 1 && draftversion <=34) { return (uint8_t) version; - } - } + } } // This exists since version 29, but is still present in RFC9000. if ((version & 0x0F0F0F0F) == force_ver_neg_pattern) { // Version 1 return 35; - } else if ((version & 0xffffff00) == quant) { - return 35; - } else if ((version >> 8) == quic_go) { - return 35; - } - if ((version >> 8) == quicly) { - return true; - } - if ((version >> 4) == ms_quic) { - return true; - } - if ((version >> 4) == ethz) { - return true; - } - if ((version >> 4) == telecom_italia) { - return true; } - if ((version >> 4) == moz_quic) { - return true; - } - if ((version >> 4) == tencent_quic) { - return true; - } - if ((version >> 4) == quinn_noise) { - return true; + + // Without further knowledge we assume QUIC version 1. + + // Last Nybble zero + switch (version & 0xfffffff0) { + case ms_quic: + return 29; + case ethz: + case telecom_italia: + case tencent_quic: + case quinn_noise: + case quic_over_scion: + return 35; + case moz_quic: + return 14; } - if ((version >> 4) == quic_over_scion) { - return true; + + // Last Byte zero + switch (version & 0xffffff00 ) { + case quant: + return draftversion; + case quic_go: + case quicly: + return 35; } + switch (version) { case version_negotiation: // TODO verify: We return a value that has no salt assigned. diff --git a/process/quic_parser.hpp b/process/quic_parser.hpp index fc2d5b57..8e7b364b 100644 --- a/process/quic_parser.hpp +++ b/process/quic_parser.hpp @@ -233,23 +233,23 @@ class QUICParser { force_ver_neg_pattern = 0x0a0a0a0a, quant = 0x45474700, older_version = 0xff0000, - quic_go = 0x51474f, + quic_go = 0x51474f00, // unknown handshake salt TODO use version 1 as default? - quicly = 0x91c170, + quicly = 0x91c17000, // https://github.com/microsoft/msquic/blob/d33bc56d5e11db52e2b34ae152ea598fd6e935c0/src/core/packet.c#L461 // But version is different - ms_quic = 0xabcd000, + ms_quic = 0xabcd0000, - ethz = 0xf0f0f0f, + ethz = 0xf0f0f0f0, telecom_italia = 0xf0f0f1f0, - moz_quic = 0xf123f0c, + moz_quic = 0xf123f0c0, - tencent_quic = 0x0700700, + tencent_quic = 0x07007000, - quinn_noise = 0xf0f0f2f, + quinn_noise = 0xf0f0f2f0, - quic_over_scion = 0x5c10000 + quic_over_scion = 0x5c100000 }; QUICParser(); void quic_get_zero_rtt(uint8_t& zero_rtt_toset); From 3cfa8a7811dbe436db939055be4e545f7bbbba14 Mon Sep 17 00:00:00 2001 From: Karel Hynek Date: Thu, 15 Feb 2024 13:42:17 +0100 Subject: [PATCH 19/23] QUIC - Fix IPFIX IDs for basic list elements --- include/ipfixprobe/ipfix-elements.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/ipfixprobe/ipfix-elements.hpp b/include/ipfixprobe/ipfix-elements.hpp index 12460912..4961372f 100644 --- a/include/ipfixprobe/ipfix-elements.hpp +++ b/include/ipfixprobe/ipfix-elements.hpp @@ -249,10 +249,10 @@ namespace ipxp { #define QUIC_MULTIPLEXED(F) F(8057, 899, 1, nullptr) #define QUIC_ZERO_RTT(F) F(8057, 889, 1, nullptr) #define QUIC_SERVER_PORT(F) F(8057, 887, 2, nullptr) -#define QUIC_PACKETS(F) F(8057, 888, -1, nullptr) +#define QUIC_PACKETS(F) F(0, 291, -1, nullptr) // BASIC LIST -- FIELD IS e8057id888 (uint16*) #define QUIC_CH_PARSED(F) F(8057, 886, 1, nullptr) -#define QUIC_TLS_EXT_TYPE(F) F(8057, 885, -1, nullptr) -#define QUIC_TLS_EXT_LEN(F) F(8057, 884, -1, nullptr) +#define QUIC_TLS_EXT_TYPE(F) F(0, 291, -1, nullptr) // BASIC LIST -- FIELD IS e8057id885 (uint16*) +#define QUIC_TLS_EXT_LEN(F) F(0, 291, -1, nullptr) // BASIC LIST -- FIELD IS e8057id884 (uint16*) #define QUIC_TLS_EXT(F) F(8057, 883, -1, nullptr) From 71683abe38b1bdc9c59024f8ceac34726b3a1288 Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Tue, 20 Feb 2024 14:34:53 +0100 Subject: [PATCH 20/23] Change payload_len only if previously modified. --- process/quic_parser.cpp | 11 +++++++---- process/quic_parser.hpp | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index d787a8a2..7a573fa5 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -37,6 +37,7 @@ QUICParser::QUICParser() header_len = 0; payload_len = 0; + payload_len_offset = 0; dcid = nullptr; dcid_len = 0; @@ -884,6 +885,8 @@ bool QUICParser::quic_decrypt_payload() // "These cipher suites have a 16-byte authentication tag and produce an output 16 bytes larger // than their input." adjust length because last 16 bytes are authentication tag payload_len -= 16; + payload_len_offset = 16; + memcpy(&atag, &payload[payload_len], 16); EVP_CIPHER_CTX* ctx; @@ -1349,11 +1352,11 @@ bool QUICParser::quic_parse_headers(const Packet& pkt, bool forceInitialParsing) // Session resumption is such a case. if (!parsed_initial) { quic_tls_extension_lengths_pos = 0; - initial_dcid_len = dcid_len; - initial_dcid = (uint8_t*) dcid; + // len = 0 forces reading DCID from current packet + initial_dcid_len = 0; // Increment by tag_len, since subsequent function is not stateless. - payload_len += 16; - // Undo another side effect + payload_len += payload_len_offset; + // Undo side effect from QUICParser::quic_decrypt_initial_header payload = payload - pkn_len; payload_len += pkn_len; quic_parse_initial(pkt, pkt_payload_pointer, offset); diff --git a/process/quic_parser.hpp b/process/quic_parser.hpp index 8e7b364b..01fe112c 100644 --- a/process/quic_parser.hpp +++ b/process/quic_parser.hpp @@ -142,6 +142,7 @@ class QUICParser { uint16_t header_len; uint64_t payload_len; + uint8_t payload_len_offset; uint8_t packet_type; const uint8_t* dcid; From b3cdfb7470a065ca8e07e6205a6b7a4f9269207f Mon Sep 17 00:00:00 2001 From: Pavel Siska Date: Wed, 3 Apr 2024 13:03:43 +0200 Subject: [PATCH 21/23] quic - fix code format --- process/quic.cpp | 6 ------ process/quic.hpp | 11 ++++++++--- process/quic_parser.cpp | 35 ++++++++++++++++------------------- process/quic_parser.hpp | 18 +++--------------- 4 files changed, 27 insertions(+), 43 deletions(-) diff --git a/process/quic.cpp b/process/quic.cpp index cdc64c99..48775fcf 100644 --- a/process/quic.cpp +++ b/process/quic.cpp @@ -493,12 +493,6 @@ int QUICPlugin::pre_update(Flow& rec, Packet& pkt) int QUICPlugin::post_update(Flow& rec, const Packet& pkt) { - // RecordExtQUIC *ext = (RecordExtQUIC *) rec.get_extension(RecordExtQUIC::REGISTERED_ID); - // - // if (ext == nullptr) { - // return 0; - // } - return add_quic(rec, pkt); } diff --git a/process/quic.hpp b/process/quic.hpp index 432e21e3..44db8551 100644 --- a/process/quic.hpp +++ b/process/quic.hpp @@ -19,17 +19,19 @@ #endif #include "quic_parser.hpp" +#include #include #include #include #include -#include namespace ipxp { + #define QUIC_UNIREC_TEMPLATE \ "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION,QUIC_CLIENT_VERSION,QUIC_TOKEN_LENGTH,QUIC_OCCID,QUIC_" \ "OSCID,QUIC_SCID,QUIC_RETRY_SCID,QUIC_MULTIPLEXED,QUIC_ZERO_RTT,QUIC_SERVER_PORT,QUIC_" \ "PACKETS,QUIC_CH_PARSED,QUIC_TLS_EXT_TYPE,QUIC_TLS_EXT_LEN,QUIC_TLS_EXT" + UR_FIELDS( string QUIC_SNI, string QUIC_USER_AGENT, @@ -48,6 +50,7 @@ UR_FIELDS( uint16* QUIC_TLS_EXT_TYPE, uint16* QUIC_TLS_EXT_LEN, bytes QUIC_TLS_EXT) + /** * \brief Flow record extension header for storing parsed QUIC packets. */ @@ -312,7 +315,7 @@ struct RecordExtQUIC : public RecordExt { << "quicoccidlength=\"" << (int) occid_length << "\""; out << "quicoccid=\""; for (int i = 0; i < occid_length; i++) { - out << std::hex << (occid[i] & 0xff); + out << std::hex << (occid[i] & 0xff); } out << "\"" << "quicoscidlength=\"" << std::dec << (int) oscid_length << "\""; @@ -346,7 +349,7 @@ struct RecordExtQUIC : public RecordExt { } out << ")quictlsext=\""; for (int i = 0; i < tls_ext_length; i++) { - out << std::hex << std::setw(2) << std::setfill('0')<< (uint16_t) tls_ext[i]; + out << std::hex << std::setw(2) << std::setfill('0') << (uint16_t) tls_ext[i]; } out << "\""; @@ -417,5 +420,7 @@ class QUICPlugin : public ProcessPlugin { int parsed_initial; RecordExtQUIC* quic_ptr; }; + } // namespace ipxp + #endif /* IPXP_PROCESS_QUIC_HPP */ diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index 7a573fa5..2bc8554c 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -13,8 +13,6 @@ #include "quic_parser.hpp" -// #include "quic_variable_length.cpp" - #ifdef DEBUG_QUIC #define DEBUG_MSG(format, ...) fprintf(stderr, format, ##__VA_ARGS__) #else @@ -281,18 +279,16 @@ bool QUICParser::quic_obtain_tls_data(TLSData& payload) // Save value payload except for length if (quic_tls_ext_pos + length < CURRENT_BUFFER_SIZE) { - #ifndef QUIC_CH_FULL_TLS_EXT - if (type == TLS_EXT_ALPN - || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS_V1 +#ifndef QUIC_CH_FULL_TLS_EXT + if (type == TLS_EXT_ALPN || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS_V1 || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS_V2) { - #endif +#endif memcpy(quic_tls_ext + quic_tls_ext_pos, payload.start, length); quic_tls_ext_pos += length; - #ifndef QUIC_CH_FULL_TLS_EXT +#ifndef QUIC_CH_FULL_TLS_EXT } - #endif - +#endif } // Legacy extract specific fields @@ -348,9 +344,10 @@ uint8_t QUICParser::quic_draft_version(uint32_t version) uint8_t draftversion = (uint8_t) version & 0xff; // this is IETF implementation, older version used if ((version >> 8) == older_version) { - if (draftversion >= 1 && draftversion <=34) { + if (draftversion >= 1 && draftversion <= 34) { return (uint8_t) version; - } } + } + } // This exists since version 29, but is still present in RFC9000. if ((version & 0x0F0F0F0F) == force_ver_neg_pattern) { // Version 1 @@ -374,7 +371,7 @@ uint8_t QUICParser::quic_draft_version(uint32_t version) } // Last Byte zero - switch (version & 0xffffff00 ) { + switch (version & 0xffffff00) { case quant: return draftversion; case quic_go: @@ -492,8 +489,8 @@ bool QUICParser::quic_obtain_version() salt = handshake_salt_draft_29; } else if (!is_version2 && quic_check_version(version, 35)) { salt = handshake_salt_v1; - }else if (!is_version2 && quic_check_version(version, 36)) { - salt = handshake_salt_picoquic_internal; + } else if (!is_version2 && quic_check_version(version, 36)) { + salt = handshake_salt_picoquic_internal; } else if (is_version2 && quic_check_version(version, 100)) { salt = handshake_salt_v2_provisional; } else if (is_version2 && quic_check_version(version, 101)) { @@ -839,7 +836,7 @@ bool QUICParser::quic_decrypt_initial_header(const uint8_t* payload_pointer, uin // payload payload = payload + pkn_len; payload_len = payload_len - pkn_len; - if (payload_len > CURRENT_BUFFER_SIZE ) { + if (payload_len > CURRENT_BUFFER_SIZE) { DEBUG_MSG("Payload length underflow\n"); return false; } @@ -1007,8 +1004,8 @@ inline void QUICParser::quic_copy_crypto(uint8_t* start, const uint8_t* end, uin uint32_t frame_offset = quic_get_variable_length(start, offset); uint32_t frame_length = quic_get_variable_length(start, offset); - if(end < (start + offset)) { - //avoid source buffer overflow + if (end < (start + offset)) { + // avoid source buffer overflow quic_crypto_len += frame_length; offset += frame_length; return; @@ -1017,7 +1014,7 @@ inline void QUICParser::quic_copy_crypto(uint8_t* start, const uint8_t* end, uin frame_offset = std::min(frame_offset, (uint32_t) (CURRENT_BUFFER_SIZE - 1)); frame_length = std::min((uint32_t) (CURRENT_BUFFER_SIZE - 1 - frame_offset), frame_length); // avoid memory overlap in memcpy when not enought space in source buffer - frame_length = std::min(frame_length, (uint32_t)(end - (start + offset))); + frame_length = std::min(frame_length, (uint32_t) (end - (start + offset))); memcpy(assembled_payload + frame_offset, start + offset, frame_length); if (frame_offset < quic_crypto_start) { @@ -1126,7 +1123,7 @@ uint32_t read_uint32_t(const uint8_t* ptr, uint8_t offset) bool QUICParser::quic_check_supported_version(const uint32_t version) { uint8_t draft_version = quic_draft_version(version); - return (draft_version > 0) && (draft_version < 255) ; + return (draft_version > 0) && (draft_version < 255); } bool QUICParser::quic_long_header_packet(const Packet& pkt) diff --git a/process/quic_parser.hpp b/process/quic_parser.hpp index 01fe112c..629e9d48 100644 --- a/process/quic_parser.hpp +++ b/process/quic_parser.hpp @@ -51,6 +51,7 @@ #define MAX_QUIC_TLS_EXT_LEN 30 namespace ipxp { + typedef struct __attribute__((packed)) quic_first_ver_dcidlen { uint8_t first_byte; uint32_t version; @@ -252,6 +253,7 @@ class QUICParser { quic_over_scion = 0x5c100000 }; + QUICParser(); void quic_get_zero_rtt(uint8_t& zero_rtt_toset); bool quic_parse_initial(const Packet&, const uint8_t* payload_end, uint64_t offset); @@ -290,6 +292,7 @@ class QUICParser { void quic_get_tls_extension_lengths_len(uint8_t& tls_extensions_length_len_toset); void quic_get_tls_extensions(char* in); }; + } // namespace ipxp // known versions @@ -322,18 +325,3 @@ class QUICParser { * 0xfaceb011 -- MVFST_EXPERIMENTAL2 * 0xfaceb013 -- MVFST_EXPERIMENTAL3 */ - -// google salts - -/*static const uint8_t hanshake_salt_draft_q50[SALT_LENGTH] = { - * 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, - * 0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45 - * }; - * static const uint8_t hanshake_salt_draft_t50[SALT_LENGTH] = { - * 0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80, - * 0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10 - * }; - * static const uint8_t hanshake_salt_draft_t51[SALT_LENGTH] = { - * 0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50, - * 0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d - * };*/ From 8a1e37005f34a47571928256bff16bec3960c654 Mon Sep 17 00:00:00 2001 From: jmuecke <48099698+jmuecke@users.noreply.github.com> Date: Tue, 16 Apr 2024 22:06:16 +0200 Subject: [PATCH 22/23] Only act on first retry packet --- process/quic.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/process/quic.cpp b/process/quic.cpp index 48775fcf..8f8e9da1 100644 --- a/process/quic.cpp +++ b/process/quic.cpp @@ -439,14 +439,21 @@ int QUICPlugin::process_quic( break; case QUICParser::PACKET_TYPE::RETRY: quic_data->cnt_retry_packets += 1; - // Additionally set token len - process_quic.quic_get_scid(quic_data->retry_scid); - process_quic.quic_get_scid_len(quic_data->retry_scid_length); - // Update DCID for decryption - process_quic.quic_get_dcid_len(quic_data->initial_dcid_length); - process_quic.quic_get_scid(quic_data->initial_dcid); - - process_quic.quic_get_token_length(quic_data->quic_token_length); + /* + * A client MUST accept and process at most one Retry packet for each connection + * attempt. After the client has received and processed an Initial or Retry packet from + * the server, it MUST discard any subsequent Retry packets that it receives. + */ + if (quic_data->cnt_retry_packets == 1) { + // Additionally set token len + process_quic.quic_get_scid(quic_data->retry_scid); + process_quic.quic_get_scid_len(quic_data->retry_scid_length); + // Update DCID for decryption + process_quic.quic_get_dcid_len(quic_data->initial_dcid_length); + process_quic.quic_get_scid(quic_data->initial_dcid); + + process_quic.quic_get_token_length(quic_data->quic_token_length); + } if (!quic_data->occid_set) { process_quic.quic_get_dcid(quic_data->occid); From 24234d11ada0d4b1db6b705c469166b0d8afd127 Mon Sep 17 00:00:00 2001 From: Pavel Siska Date: Mon, 22 Apr 2024 12:27:17 +0200 Subject: [PATCH 23/23] test - update reference file of quic test --- tests/functional/reference/quic | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/reference/quic b/tests/functional/reference/quic index e1d99b34..8e1592b6 100644 --- a/tests/functional/reference/quic +++ b/tests/functional/reference/quic @@ -1,2 +1,2 @@ -2a00:1450:4014:800::2004,2001:718:2:62::2,1378,0,0,2021-08-01T13:25:53.513510,2021-08-01T13:25:53.513510,c4:b2:39:14:28:3f,a8:5e:45:b5:9c:d6,1,0,4278190109,443,58181,0,17,0,0,"www.google.com","Chrome/92.0.4515.107 Windows NT 10.0; Win64; x64" -ipaddr DST_IP,ipaddr SRC_IP,uint64 BYTES,uint64 BYTES_REV,uint64 LINK_BIT_FIELD,time TIME_FIRST,time TIME_LAST,macaddr DST_MAC,macaddr SRC_MAC,uint32 PACKETS,uint32 PACKETS_REV,uint32 QUIC_VERSION,uint16 DST_PORT,uint16 SRC_PORT,uint8 DIR_BIT_FIELD,uint8 PROTOCOL,uint8 TCP_FLAGS,uint8 TCP_FLAGS_REV,string QUIC_SNI,string QUIC_USER_AGENT +2a00:1450:4014:800::2004,2001:718:2:62::2,1378,0,0,55,2021-08-01T13:25:53.513510,2021-08-01T13:25:53.513510,c4:b2:39:14:28:3f,a8:5e:45:b5:9c:d6,1,0,4278190109,4278190109,443,443,58181,0,17,1,0,0,0,0,,d3e8e06049a4f6b2,[129],,,"www.google.com",00060568332d3239010480007530030245c0040480f000000504806000000604806000000704806000000802406409024067200480010000712702646a0f007129304368726f6d652f39322e302e343531352e3130372057696e646f7773204e542031302e303b2057696e36343b207836348000475204ff00001de6116524f56220fb079a3feec6b3af6c,"Chrome/92.0.4515.107 Windows NT 10.0; Win64; x64",[19|8|8|20|38|2|0|3|131|3|8|316],[0|10|16|13|51|45|42|43|65445|27|17513|41] +ipaddr DST_IP,ipaddr SRC_IP,uint64 BYTES,uint64 BYTES_REV,uint64 LINK_BIT_FIELD,uint64 QUIC_TOKEN_LENGTH,time TIME_FIRST,time TIME_LAST,macaddr DST_MAC,macaddr SRC_MAC,uint32 PACKETS,uint32 PACKETS_REV,uint32 QUIC_CLIENT_VERSION,uint32 QUIC_VERSION,uint16 DST_PORT,uint16 QUIC_SERVER_PORT,uint16 SRC_PORT,uint8 DIR_BIT_FIELD,uint8 PROTOCOL,uint8 QUIC_CH_PARSED,uint8 QUIC_MULTIPLEXED,uint8 QUIC_ZERO_RTT,uint8 TCP_FLAGS,uint8 TCP_FLAGS_REV,bytes QUIC_OCCID,bytes QUIC_OSCID,uint8* QUIC_PACKETS,bytes QUIC_RETRY_SCID,bytes QUIC_SCID,string QUIC_SNI,bytes QUIC_TLS_EXT,string QUIC_USER_AGENT,uint16* QUIC_TLS_EXT_LEN,uint16* QUIC_TLS_EXT_TYPE