diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
commit | e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch) | |
tree | 68cb5ef9081156392f1dd62a00c6ccc1451b93df /epan/dissectors/packet-quic.c | |
parent | Initial commit. (diff) | |
download | wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip |
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'epan/dissectors/packet-quic.c')
-rw-r--r-- | epan/dissectors/packet-quic.c | 5796 |
1 files changed, 5796 insertions, 0 deletions
diff --git a/epan/dissectors/packet-quic.c b/epan/dissectors/packet-quic.c new file mode 100644 index 00000000..a41ce9cc --- /dev/null +++ b/epan/dissectors/packet-quic.c @@ -0,0 +1,5796 @@ +/* packet-quic.c + * Routines for QUIC (IETF) dissection + * Copyright 2017, Alexis La Goutte <alexis.lagoutte at gmail dot com> + * Copyright 2018 Peter Wu <peter@lekensteyn.nl> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * See https://quicwg.org + * RFC9000 QUIC: A UDP-Based Multiplexed and Secure Transport + * RFC9001 Using TLS to Secure QUIC + * RFC8889 Version-Independent Properties of QUIC + * RFC9221 An Unreliable Datagram Extension to QUIC + * RFC9369 QUIC Version 2 + * RFC9368 Compatible Version Negotiation for QUIC + * + * Extension: + * https://tools.ietf.org/html/draft-ferrieuxhamchaoui-quic-lossbits-03 + * https://tools.ietf.org/html/draft-huitema-quic-ts-02 + * https://tools.ietf.org/html/draft-ietf-quic-ack-frequency-07 (and also draft-04/05) + * https://tools.ietf.org/html/draft-deconinck-quic-multipath-06 + * https://tools.ietf.org/html/draft-banks-quic-cibir-01 + * https://tools.ietf.org/html/draft-ietf-quic-multipath-05 (and also draft-04) + + * + * Currently supported QUIC version(s): draft-21, draft-22, draft-23, draft-24, + * draft-25, draft-26, draft-27, draft-28, draft-29, draft-30, draft-31, draft-32, + * draft-33, draft-34, v1, v2 + * For a table of supported QUIC versions per Wireshark version, see + * https://github.com/quicwg/base-drafts/wiki/Tools#wireshark + * + * Decryption is supported via TLS 1.3 secrets in the "TLS Key Log File", + * configured either at the TLS Protocol preferences, or embedded in a pcapng + * file. Sample captures and secrets can be found at: + * https://gitlab.com/wireshark/wireshark/-/issues/13881 + * + * Limitations: + * - STREAM offsets larger than 32-bit are unsupported. + * - STREAM with sizes larger than 32 bit are unsupported. STREAM sizes can be + * up to 62 bit in QUIC, but the TVB and reassembly API is limited to 32 bit. + * - Out-of-order and overlapping STREAM frame data is not handled. + * - "Follow QUIC Stream" doesn't work with STREAM IDs larger than 32 bit + */ + +#include <config.h> + +#include <epan/packet.h> +#include <epan/expert.h> +#include <epan/proto_data.h> +#include <epan/to_str.h> +#include "packet-tls-utils.h" +#include "packet-tls.h" +#include "packet-tcp.h" /* used for STREAM reassembly. */ +#include "packet-quic.h" +#include <epan/reassemble.h> +#include <epan/prefs.h> +#include <wsutil/pint.h> + +#include <epan/tap.h> +#include <epan/follow.h> +#include <epan/addr_resolv.h> + +/* Prototypes */ +void proto_reg_handoff_quic(void); +void proto_register_quic(void); + +static int quic_follow_tap = -1; + +/* Initialize the protocol and registered fields */ +static int proto_quic = -1; +static int hf_quic_connection_number = -1; +static int hf_quic_packet_length = -1; +static int hf_quic_header_form = -1; +static int hf_quic_long_packet_type = -1; +static int hf_quic_long_packet_type_v2 = -1; +static int hf_quic_long_reserved = -1; +static int hf_quic_packet_number_length = -1; +static int hf_quic_dcid = -1; +static int hf_quic_scid = -1; +static int hf_quic_dcil = -1; +static int hf_quic_scil = -1; +static int hf_quic_token_length = -1; +static int hf_quic_token = -1; +static int hf_quic_length = -1; +static int hf_quic_packet_number = -1; +static int hf_quic_version = -1; +static int hf_quic_supported_version = -1; +static int hf_quic_vn_unused = -1; +static int hf_quic_short = -1; +static int hf_quic_fixed_bit = -1; +static int hf_quic_spin_bit = -1; +static int hf_quic_short_reserved = -1; +static int hf_quic_q_bit = -1; +static int hf_quic_l_bit = -1; +static int hf_quic_key_phase = -1; +static int hf_quic_payload = -1; +static int hf_quic_protected_payload = -1; +static int hf_quic_remaining_payload = -1; +static int hf_quic_odcil = -1; +static int hf_quic_odcid = -1; +static int hf_quic_retry_token = -1; +static int hf_quic_retry_integrity_tag = -1; + +static int hf_quic_frame = -1; +static int hf_quic_frame_type = -1; + +static int hf_quic_padding_length = -1; +static int hf_quic_ack_largest_acknowledged = -1; +static int hf_quic_ack_ack_delay = -1; +static int hf_quic_ack_ack_range_count = -1; +static int hf_quic_ack_first_ack_range = -1; +static int hf_quic_ack_gap = -1; +static int hf_quic_ack_ack_range = -1; +static int hf_quic_ack_ect0_count = -1; +static int hf_quic_ack_ect1_count = -1; +static int hf_quic_ack_ecn_ce_count = -1; +static int hf_quic_rsts_stream_id = -1; +static int hf_quic_rsts_application_error_code = -1; +static int hf_quic_rsts_final_size = -1; +static int hf_quic_ss_stream_id = -1; +static int hf_quic_ss_application_error_code = -1; +static int hf_quic_crypto_offset = -1; +static int hf_quic_crypto_length = -1; +static int hf_quic_crypto_crypto_data = -1; +static int hf_quic_nt_length = -1; +static int hf_quic_nt_token = -1; +static int hf_quic_stream_fin = -1; +static int hf_quic_stream_len = -1; +static int hf_quic_stream_off = -1; +static int hf_quic_stream_stream_id = -1; +static int hf_quic_stream_initiator = -1; +static int hf_quic_stream_direction = -1; +static int hf_quic_stream_offset = -1; +static int hf_quic_stream_length = -1; +static int hf_quic_stream_data = -1; +static int hf_quic_md_maximum_data = -1; +static int hf_quic_msd_stream_id = -1; +static int hf_quic_msd_maximum_stream_data = -1; +static int hf_quic_ms_max_streams = -1; +static int hf_quic_db_stream_data_limit = -1; +static int hf_quic_sdb_stream_id = -1; +static int hf_quic_sdb_stream_data_limit = -1; +static int hf_quic_sb_stream_limit = -1; +static int hf_quic_nci_retire_prior_to = -1; +static int hf_quic_nci_sequence = -1; +static int hf_quic_nci_connection_id_length = -1; +static int hf_quic_nci_connection_id = -1; +static int hf_quic_nci_stateless_reset_token = -1; +static int hf_quic_rci_sequence = -1; +static int hf_quic_path_challenge_data = -1; +static int hf_quic_path_response_data = -1; +static int hf_quic_cc_error_code = -1; +static int hf_quic_cc_error_code_app = -1; +static int hf_quic_cc_error_code_tls_alert = -1; +static int hf_quic_cc_frame_type = -1; +static int hf_quic_cc_reason_phrase_length = -1; +static int hf_quic_cc_reason_phrase = -1; +static int hf_quic_dg_length = -1; +static int hf_quic_dg = -1; +static int hf_quic_af_sequence_number = -1; +static int hf_quic_af_ack_eliciting_threshold = -1; +static int hf_quic_af_request_max_ack_delay = -1; +static int hf_quic_af_reordering_threshold = -1; +//static int hf_quic_af_ignore_order = -1; +//static int hf_quic_af_ignore_ce = -1; +static int hf_quic_ts = -1; +static int hf_quic_unpredictable_bits = -1; +static int hf_quic_stateless_reset_token = -1; +static int hf_quic_reassembled_in = -1; +static int hf_quic_reassembled_length = -1; +static int hf_quic_reassembled_data = -1; +static int hf_quic_fragments = -1; +static int hf_quic_fragment = -1; +static int hf_quic_fragment_overlap = -1; +static int hf_quic_fragment_overlap_conflict = -1; +static int hf_quic_fragment_multiple_tails = -1; +static int hf_quic_fragment_too_long_fragment = -1; +static int hf_quic_fragment_error = -1; +static int hf_quic_fragment_count = -1; + +static int hf_quic_crypto_reassembled_in = -1; +static int hf_quic_crypto_fragments = -1; +static int hf_quic_crypto_fragment = -1; +static int hf_quic_crypto_fragment_count = -1; + +static int hf_quic_mp_add_address_first_byte = -1; +static int hf_quic_mp_add_address_reserved = -1; +static int hf_quic_mp_add_address_port_present = -1; +static int hf_quic_mp_add_address_ip_version = -1; +static int hf_quic_mp_add_address_id = -1; +static int hf_quic_mp_add_address_sq_number = -1; +static int hf_quic_mp_add_address_interface_type = -1; +static int hf_quic_mp_add_address_ip_address = -1; +static int hf_quic_mp_add_address_ip_address_v6 = -1; +static int hf_quic_mp_add_address_port = -1; +static int hf_quic_mp_uniflow_id = -1; +static int hf_quic_mp_receiving_uniflows = -1; +static int hf_quic_mp_active_sending_uniflows = -1; +static int hf_quic_mp_add_local_address_id = -1; +static int hf_quic_mp_uniflow_info_section = -1; +static int hf_quic_mp_receiving_uniflow_info_section = -1; +static int hf_quic_mp_active_sending_uniflows_info_section = -1; + +/* multipath*/ +static int hf_quic_mp_ack_dcid_sequence_number = -1; +static int hf_quic_mp_pa_dcid_sequence_number = -1; +static int hf_quic_mp_ps_dcid_sequence_number = -1; +static int hf_quic_mp_ps_path_status_sequence_number = -1; +static int hf_quic_mp_ps_path_status = -1; + +static expert_field ei_quic_connection_unknown = EI_INIT; +static expert_field ei_quic_ft_unknown = EI_INIT; +static expert_field ei_quic_decryption_failed = EI_INIT; +static expert_field ei_quic_protocol_violation = EI_INIT; +static expert_field ei_quic_bad_retry = EI_INIT; +static expert_field ei_quic_coalesced_padding_data = EI_INIT; +static expert_field ei_quic_retransmission = EI_INIT; +static expert_field ei_quic_overlap = EI_INIT; +static expert_field ei_quic_data_after_forcing_vn = EI_INIT; + +static gint ett_quic = -1; +static gint ett_quic_af = -1; +static gint ett_quic_short_header = -1; +static gint ett_quic_connection_info = -1; +static gint ett_quic_ft = -1; +static gint ett_quic_ftflags = -1; +static gint ett_quic_ftid = -1; +static gint ett_quic_fragments = -1; +static gint ett_quic_fragment = -1; +static gint ett_quic_crypto_fragments = -1; +static gint ett_quic_crypto_fragment = -1; + +static dissector_handle_t quic_handle; +static dissector_handle_t tls13_handshake_handle; + +static dissector_table_t quic_proto_dissector_table; + +/* Fields for showing reassembly results for fragments of QUIC stream data. */ +static const fragment_items quic_stream_fragment_items = { + &ett_quic_fragment, + &ett_quic_fragments, + &hf_quic_fragments, + &hf_quic_fragment, + &hf_quic_fragment_overlap, + &hf_quic_fragment_overlap_conflict, + &hf_quic_fragment_multiple_tails, + &hf_quic_fragment_too_long_fragment, + &hf_quic_fragment_error, + &hf_quic_fragment_count, + &hf_quic_reassembled_in, + &hf_quic_reassembled_length, + &hf_quic_reassembled_data, + "Fragments" +}; + +/* Fields for showing reassembly results for fragments of QUIC crypto packets. */ +static const fragment_items quic_crypto_fragment_items = { + &ett_quic_crypto_fragment, + &ett_quic_crypto_fragments, + &hf_quic_crypto_fragments, + &hf_quic_crypto_fragment, + &hf_quic_fragment_overlap, /* We can reuse the error fields. */ + &hf_quic_fragment_overlap_conflict, + &hf_quic_fragment_multiple_tails, + &hf_quic_fragment_too_long_fragment, + &hf_quic_fragment_error, + &hf_quic_crypto_fragment_count, + &hf_quic_crypto_reassembled_in, + NULL, /* length, redundant */ + NULL, /* data, redundant */ + "Fragments" +}; + +/* + * PROTECTED PAYLOAD DECRYPTION (done in first pass) + * + * Long packet types always use a single cipher depending on packet type. + * Short packet types always use 1-RTT secrets for packet protection (pp). + * + * Considerations: + * - QUIC packets might appear out-of-order (short packets before handshake + * message is captured), lost or retransmitted/duplicated. + * - During live capture, keys might not be immediately be available. 1-RTT + * client keys will be ready while client processes Server Hello (Handshake). + * 1-RTT server keys will be ready while server creates Handshake message in + * response to Initial Handshake. + * - So delay cipher creation until first short packet is received. + * + * Required input from TLS dissector: TLS-Exporter 0-RTT/1-RTT secrets and + * cipher/hash algorithms. + * + * QUIC payload decryption requires proper reconstruction of the packet number + * which requires proper header decryption. The different states are: + * + * Packet type Packet number space Secrets + * Long: Initial Initial Initial secrets + * Long: Handshake Handshake Handshake + * Long: 0-RTT 0/1-RTT (appdata) 0-RTT + * Short header 0/1-RTT (appdata) 1-RTT (KP0 / KP1) + * + * Important to note is that Short Header decryption requires TWO ciphers (one + * for each key phase), but that header protection uses only KP0. Total state + * needed for each peer (client and server): + * - 3 packet number spaces: Initial, Handshake, 0/1-RTT (appdata). + * - 4 header protection ciphers: initial, 0-RTT, HS, 1-RTT. + * - 5 payload protection ciphers: initial, 0-RTT, HS, 1-RTT (KP0), 1-RTT (KP1). + * + * The multipath draft features introduces separate appdata number spaces for + * each Destination Connection ID. + */ + +/* Loss bits feature: https://tools.ietf.org/html/draft-ferrieuxhamchaoui-quic-lossbits-03 + "The use of the loss bits is negotiated using a transport parameter. + [..] + When loss_bits parameter is present, the peer is allowed to use + reserved bits in the short packet header as loss bits if the peer + sends loss_bits=1. + When loss_bits is set to 1, the sender will use reserved bits as loss + bits if the peer includes the loss_bits transport parameter. + [..] + Unlike the reserved (R) bits, the loss (Q and L) bits are not + protected. When sending loss bits has been negotiated, the first + byte of the header protection mask used to protect short packet + headers has its five most significant bits masked out instead of + three. +*/ + +typedef struct quic_decrypt_result { + const guchar *error; /**< Error message or NULL for success. */ + const guint8 *data; /**< Decrypted result on success (file-scoped). */ + guint data_len; /**< Size of decrypted data. */ +} quic_decrypt_result_t; + +/** QUIC decryption context. */ + + +typedef struct quic_hp_cipher { + gcry_cipher_hd_t hp_cipher; /**< Header protection cipher. */ +} quic_hp_cipher; +typedef struct quic_pp_cipher { + gcry_cipher_hd_t pp_cipher; /**< Packet protection cipher. */ + guint8 pp_iv[TLS13_AEAD_NONCE_LENGTH]; +} quic_pp_cipher; +typedef struct quic_ciphers { + quic_hp_cipher hp_cipher; + quic_pp_cipher pp_cipher; +} quic_ciphers; + +/** + * Packet protection state for an endpoint. + */ +typedef struct quic_pp_state { + guint8 *next_secret; /**< Next application traffic secret. */ + quic_pp_cipher pp_ciphers[2]; /**< PP cipher for Key Phase 0/1 */ + quic_hp_cipher hp_cipher; /**< HP cipher for both Key Phases; it does not change after KeyUpdate */ + guint64 changed_in_pkn; /**< Packet number where key change occurred. */ + bool key_phase : 1; /**< Current key phase. */ +} quic_pp_state_t; + +/** Singly-linked list of Connection IDs. */ +typedef struct quic_cid_item quic_cid_item_t; +struct quic_cid_item { + struct quic_cid_item *next; + quic_cid_t data; +}; + +/** + * CRYPTO stream state. + * + */ +typedef struct _quic_crypto_state { + guint64 max_contiguous_offset; + guint8 encryption_level; /**< AKA packet type */ + wmem_tree_t *multisegment_pdus; + wmem_map_t *retrans_offsets; +} quic_crypto_state; + +/** + * Per-STREAM state, identified by QUIC Stream ID. + * + * Assume that every QUIC Short Header packet has no STREAM frames that overlap + * each other in the same QUIC packet (identified by "frame_num"). Thus, the + * Stream ID and offset uniquely identifies the STREAM Frame info in per packet. + */ +typedef struct _quic_stream_state { + guint64 stream_id; + wmem_tree_t *multisegment_pdus; + void *subdissector_private; +} quic_stream_state; + +/** + * Data used to allow "Follow QUIC Stream" functionality + */ +typedef struct _quic_follow_stream { + guint32 num; + guint64 stream_id; +} quic_follow_stream; + +typedef struct quic_follow_tap_data { + tvbuff_t *tvb; + guint64 stream_id; + gboolean from_server; +} quic_follow_tap_data_t; + +/** + * State for a single QUIC connection, identified by one or more Destination + * Connection IDs (DCID). + */ +typedef struct quic_info_data quic_info_data_t; +struct quic_info_data { + guint32 number; /** Similar to "udp.stream", but for identifying QUIC connections across migrations. */ + guint32 version; + address server_address; + guint16 server_port; + bool skip_decryption : 1; /**< Set to 1 if no keys are available. */ + bool client_dcid_set : 1; /**< Set to 1 if client_dcid_initial is set. */ + bool client_loss_bits_recv : 1; /**< The client is able to read loss bits info */ + bool client_loss_bits_send : 1; /**< The client wants to send loss bits info */ + bool server_loss_bits_recv : 1; /**< The server is able to read loss bits info */ + bool server_loss_bits_send : 1; /**< The server wants to send loss bits info */ + bool client_multipath : 1; /**< The client supports multipath */ + bool server_multipath : 1; /**< The server supports multipath */ + bool client_grease_quic_bit : 1; /**< The client supports greasing the Fixed (QUIC) bit */ + bool server_grease_quic_bit : 1; /**< The server supports greasing the Fixed (QUIC) bit */ + int hash_algo; /**< Libgcrypt hash algorithm for key derivation. */ + int cipher_algo; /**< Cipher algorithm for packet number and packet encryption. */ + int cipher_mode; /**< Cipher mode for packet encryption. */ + quic_ciphers client_initial_ciphers; + quic_ciphers server_initial_ciphers; + quic_ciphers client_0rtt_ciphers; + quic_ciphers client_handshake_ciphers; + quic_ciphers server_handshake_ciphers; + quic_pp_state_t client_pp; + quic_pp_state_t server_pp; + guint64 max_client_pkn[3]; /**< Packet number spaces for Initial, Handshake and appdata. */ + guint64 max_server_pkn[3]; + wmem_map_t *max_client_mp_pkn; /**< Appdata packet number spaces for multipath, by sequence number. */ + wmem_map_t *max_server_mp_pkn; + quic_cid_item_t client_cids; /**< SCID of client from first Initial Packet. */ + quic_cid_item_t server_cids; /**< SCID of server from first Retry/Handshake. */ + quic_cid_t client_dcid_initial; /**< DCID from Initial Packet. */ + dissector_handle_t app_handle; /**< Application protocol handle (NULL if unknown). */ + wmem_map_t *client_streams; /**< Map from Stream ID -> STREAM info (guint64 -> quic_stream_state), sent by the client. */ + wmem_map_t *server_streams; /**< Map from Stream ID -> STREAM info (guint64 -> quic_stream_state), sent by the server. */ + wmem_list_t *streams_list; /**< Ordered list of QUIC Stream ID in this connection (both directions). Used by "Follow QUIC Stream" functionality */ + wmem_map_t *streams_map; /**< Map pinfo->num --> First stream in that frame (guint -> quic_follow_stream). Used by "Follow QUIC Stream" functionality */ + wmem_map_t *client_crypto; + wmem_map_t *server_crypto; + gquic_info_data_t *gquic_info; /**< GQUIC info for >Q050 flows. */ + quic_info_data_t *prev; /**< The previous QUIC connection multiplexed on the same network 5-tuple. Used by checking Stateless Reset tokens */ +}; + +typedef struct _quic_crypto_info { + const guint64 packet_number; /**< Reconstructed full packet number. */ + guint64 crypto_offset; /**< 62-bit stream offset. */ + guint32 offset; /**< Offset within the stream (different for reassembled data). */ + gboolean from_server; +} quic_crypto_info; + +/** Per-packet information about QUIC, populated on the first pass. */ +struct quic_packet_info { + struct quic_packet_info *next; + guint64 packet_number; /**< Reconstructed full packet number. */ + quic_decrypt_result_t decryption; + guint8 pkn_len; /**< Length of PKN (1/2/3/4) or unknown (0). */ + guint8 first_byte; /**< Decrypted flag byte, valid only if pkn_len is non-zero. */ + guint8 packet_type; + bool retry_integrity_failure : 1; + bool retry_integrity_success : 1; +}; +typedef struct quic_packet_info quic_packet_info_t; + +/** A UDP datagram contains one or more QUIC packets. */ +typedef struct quic_datagram { + quic_info_data_t *conn; + quic_packet_info_t first_packet; + uint64_t seq_num; /**< Sequence number of the connection ID */ + bool from_server : 1; + bool stateless_reset : 1; +} quic_datagram; + +/** + * Maps CID (quic_cid_t *) to a QUIC Connection (quic_info_data_t *). + * This assumes that the CIDs are not shared between two different connections + * (potentially with different versions) as that would break dissection. + * + * These mappings are authoritative. For example, Initial.SCID is stored in + * quic_client_connections while Retry.SCID is stored in + * quic_server_connections. Retry.DCID should normally correspond to an entry in + * quic_client_connections. + */ +static wmem_map_t *quic_client_connections, *quic_server_connections; +static wmem_map_t *quic_initial_connections; /* Initial.DCID -> connection */ +static wmem_list_t *quic_connections; /* All unique connections. */ +static guint32 quic_cid_lengths; /* Bitmap of CID lengths. */ +static guint quic_connections_count; + +static gboolean +quic_multipath_negotiated(quic_info_data_t *conn); + +/* Returns the QUIC draft version or 0 if not applicable. */ +static inline guint8 quic_draft_version(guint32 version) { + /* IETF Draft versions */ + if ((version >> 8) == 0xff0000) { + return (guint8) version; + } + /* Facebook mvfst, based on draft -22. */ + if (version == 0xfaceb001) { + return 22; + } + /* Facebook mvfst, based on draft -27. */ + if (version == 0xfaceb002 || version == 0xfaceb00e) { + return 27; + } + /* GQUIC Q050, T050 and T051: they are not really based on any drafts, + * but we must return a sensible value */ + if (version == 0x51303530 || + version == 0x54303530 || + version == 0x54303531) { + return 27; + } + /* https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15 + "Versions that follow the pattern 0x?a?a?a?a are reserved for use in + forcing version negotiation to be exercised" + We can't return a correct draft version because we don't have a real + version here! That means that we can't decode any data and we can dissect + only the cleartext header. + Let's return v1 (any other numbers should be fine, anyway) to only allow + the dissection of the (expected) long header */ + if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) { + return 34; + } + /* QUIC (final?) constants for v1 are defined in draft-33, but draft-34 is the + final draft version */ + if (version == 0x00000001) { + return 34; + } + /* QUIC Version 2 */ + if (version == 0x6b3343cf) { + return 100; + } + return 0; +} + +static inline gboolean is_quic_v2(guint32 version) { + return version == 0x6b3343cf; +} + +static inline gboolean is_quic_draft_max(guint32 version, guint8 max_version) { + guint8 draft_version = quic_draft_version(version); + return draft_version && draft_version <= max_version; +} + +const range_string quic_version_vals[] = { + { 0x00000000, 0x00000000, "Version Negotiation" }, + { 0x00000001, 0x00000001, "1" }, + { 0x45474700, 0x454747ff, "Quant" }, + { 0x50435130, 0x50435131, "Picoquic internal" }, + { 0x50524f58, 0x50524f58, "Proxied QUIC (PROX)" }, + /* Versions QXXX < Q050 are dissected by Wireshark as GQUIC and not as QUIC. + Nonetheless, some implementations report these values in "Version Negotiation" + packets, so decode these fields */ + { 0x51303433, 0x51303433, "Google Q043" }, + { 0x51303434, 0x51303434, "Google Q044" }, + { 0x51303436, 0x51303436, "Google Q046" }, + { 0x51303530, 0x51303530, "Google Q050" }, + { 0x51474f00, 0x51474fff, "QGO (QUIC GO)" }, + { 0x54303530, 0x54303530, "Google T050" }, + { 0x54303531, 0x54303531, "Google T051" }, + { 0x91c17000, 0x91c170ff, "Quicly" }, + { 0xabcd0000, 0xabcd000f, "MsQuic" }, + { 0xf0f0f0f0, 0xf0f0f0ff, "ETH Zürich (Measurability experiments)" }, + { 0xf0f0f1f0, 0xf0f0f1ff, "Telecom Italia (Measurability experiments)" }, + { 0xf123f0c0, 0xf123f0cf, "MozQuic" }, + { 0xfaceb001, 0xfaceb001, "Facebook mvfst (draft-22)" }, + { 0xfaceb002, 0xfaceb002, "Facebook mvfst (draft-27)" }, + { 0xfaceb003, 0xfaceb00d, "Facebook mvfst" }, + { 0xfaceb00e, 0xfaceb00e, "Facebook mvfst (Experimental)" }, + { 0xfaceb00f, 0xfaceb00f, "Facebook mvfst" }, + { 0xff000004, 0xff000004, "draft-04" }, + { 0xff000005, 0xff000005, "draft-05" }, + { 0xff000006, 0xff000006, "draft-06" }, + { 0xff000007, 0xff000007, "draft-07" }, + { 0xff000008, 0xff000008, "draft-08" }, + { 0xff000009, 0xff000009, "draft-09" }, + { 0xff00000a, 0xff00000a, "draft-10" }, + { 0xff00000b, 0xff00000b, "draft-11" }, + { 0xff00000c, 0xff00000c, "draft-12" }, + { 0xff00000d, 0xff00000d, "draft-13" }, + { 0xff00000e, 0xff00000e, "draft-14" }, + { 0xff00000f, 0xff00000f, "draft-15" }, + { 0xff000010, 0xff000010, "draft-16" }, + { 0xff000011, 0xff000011, "draft-17" }, + { 0xff000012, 0xff000012, "draft-18" }, + { 0xff000013, 0xff000013, "draft-19" }, + { 0xff000014, 0xff000014, "draft-20" }, + { 0xff000015, 0xff000015, "draft-21" }, + { 0xff000016, 0xff000016, "draft-22" }, + { 0xff000017, 0xff000017, "draft-23" }, + { 0xff000018, 0xff000018, "draft-24" }, + { 0xff000019, 0xff000019, "draft-25" }, + { 0xff00001a, 0xff00001a, "draft-26" }, + { 0xff00001b, 0xff00001b, "draft-27" }, + { 0xff00001c, 0xff00001c, "draft-28" }, + { 0xff00001d, 0xff00001d, "draft-29" }, + { 0xff00001e, 0xff00001e, "draft-30" }, + { 0xff00001f, 0xff00001f, "draft-31" }, + { 0xff000020, 0xff000020, "draft-32" }, + { 0xff000021, 0xff000021, "draft-33" }, + { 0xff000022, 0xff000022, "draft-34" }, + /* QUICv2 */ + { 0xff020000, 0xff020000, "v2-draft-00" }, /* Never used; not really supported */ + { 0x709A50C4, 0x709A50C4, "v2-draft-01" }, /* Never used; not really supported */ + { 0x6b3343cf, 0x6b3343cf, "2" }, + { 0, 0, NULL } +}; + +static const value_string quic_short_long_header_vals[] = { + { 0, "Short Header" }, + { 1, "Long Header" }, + { 0, NULL } +}; + +#define SH_KP 0x04 + +/* Note that these values are "internal-value" used by Wireshark only. + Real wire-format values depends on QUIC version */ +#define QUIC_LPT_INITIAL 0x0 +#define QUIC_LPT_0RTT 0x1 +#define QUIC_LPT_HANDSHAKE 0x2 +#define QUIC_LPT_RETRY 0x3 +#define QUIC_LPT_VER_NEG 0xfe /* Version Negotiation packets don't have any real packet type */ +#define QUIC_SHORT_PACKET 0xff /* dummy value that is definitely not LPT */ + +static const value_string quic_v1_long_packet_type_vals[] = { + { 0x00, "Initial" }, + { 0x03, "Retry" }, + { 0x02, "Handshake" }, + { 0x01, "0-RTT" }, + /* Version Negotiation packets never use this mapping, so no need to add QUIC_LPT_VER_NEG */ + { 0, NULL } +}; +static const value_string quic_v2_long_packet_type_vals[] = { + { 0x00, "Retry" }, + { 0x01, "Initial" }, + { 0x02, "0-RTT" }, + { 0x03, "Handshake" }, + /* Version Negotiation packets never use this mapping, so no need to add QUIC_LPT_VER_NEG */ + { 0, NULL } +}; + +/* https://github.com/quicwg/base-drafts/wiki/Temporary-IANA-Registry#quic-frame-types */ +#define FT_PADDING 0x00 +#define FT_PING 0x01 +#define FT_ACK 0x02 +#define FT_ACK_ECN 0x03 +#define FT_RESET_STREAM 0x04 +#define FT_STOP_SENDING 0x05 +#define FT_CRYPTO 0x06 +#define FT_NEW_TOKEN 0x07 +#define FT_STREAM_8 0x08 +#define FT_STREAM_9 0x09 +#define FT_STREAM_A 0x0a +#define FT_STREAM_B 0x0b +#define FT_STREAM_C 0x0c +#define FT_STREAM_D 0x0d +#define FT_STREAM_E 0x0e +#define FT_STREAM_F 0x0f +#define FT_MAX_DATA 0x10 +#define FT_MAX_STREAM_DATA 0x11 +#define FT_MAX_STREAMS_BIDI 0x12 +#define FT_MAX_STREAMS_UNI 0x13 +#define FT_DATA_BLOCKED 0x14 +#define FT_STREAM_DATA_BLOCKED 0x15 +#define FT_STREAMS_BLOCKED_BIDI 0x16 +#define FT_STREAMS_BLOCKED_UNI 0x17 +#define FT_NEW_CONNECTION_ID 0x18 +#define FT_RETIRE_CONNECTION_ID 0x19 +#define FT_PATH_CHALLENGE 0x1a +#define FT_PATH_RESPONSE 0x1b +#define FT_IMMEDIATE_ACK 0x1f +#define FT_CONNECTION_CLOSE_TPT 0x1c +#define FT_CONNECTION_CLOSE_APP 0x1d +#define FT_HANDSHAKE_DONE 0x1e +#define FT_DATAGRAM 0x30 +#define FT_MP_NEW_CONNECTION_ID 0x40 +#define FT_MP_RETIRE_CONNECTION_ID 0x41 +#define FT_MP_ACK 0x42 +#define FT_MP_ACK_ECN 0x43 +#define FT_ADD_ADDRESS 0x44 +#define FT_REMOVE_ADDRESS 0x45 +#define FT_UNIFLOWS 0x46 +#define FT_DATAGRAM_LENGTH 0x31 +#define FT_IMMEDIATE_ACK_DRAFT05 0xac /* ack-frequency-draft-05 */ +#define FT_ACK_FREQUENCY 0xaf +#define FT_ACK_MP_DRAFT04 0xbaba00 /* multipath-draft-04 */ +#define FT_ACK_MP_ECN_DRAFT04 0xbaba01 /* multipath-draft-04 */ +#define FT_PATH_ABANDON_DRAFT04 0xbaba05 /* multipath-draft-04 */ +#define FT_PATH_STATUS_DRAFT04 0xbaba06 /* multipath-draft-04 */ +#define FT_ACK_MP 0x15228c00 +#define FT_ACK_MP_ECN 0x15228c01 +#define FT_PATH_ABANDON 0x15228c05 +#define FT_PATH_STATUS 0x15228c06 /* multipath-draft-05 */ +#define FT_PATH_STANDBY 0x15228c07 /* multipath-draft-06 */ +#define FT_PATH_AVAILABLE 0x15228c08 /* multipath-draft-06 */ +#define FT_TIME_STAMP 0x02F5 + +static const range_string quic_frame_type_vals[] = { + { 0x00, 0x00, "PADDING" }, + { 0x01, 0x01, "PING" }, + { 0x02, 0x03, "ACK" }, + { 0x04, 0x04, "RESET_STREAM" }, + { 0x05, 0x05, "STOP_SENDING" }, + { 0x06, 0x06, "CRYPTO" }, + { 0x07, 0x07, "NEW_TOKEN" }, + { 0x08, 0x0f, "STREAM" }, + { 0x10, 0x10, "MAX_DATA" }, + { 0x11, 0x11, "MAX_STREAM_DATA" }, + { 0x12, 0x12, "MAX_STREAMS (BIDI)" }, + { 0x13, 0x13, "MAX_STREAMS (UNI)" }, + { 0x14, 0x14, "DATA_BLOCKED" }, + { 0x15, 0x15, "STREAM_DATA_BLOCKED" }, + { 0x16, 0x16, "STREAMS_BLOCKED (BIDI)" }, + { 0x16, 0x17, "STREAMS_BLOCKED (UNI)" }, + { 0x18, 0x18, "NEW_CONNECTION_ID" }, + { 0x19, 0x19, "RETIRE_CONNECTION_ID" }, + { 0x1a, 0x1a, "PATH_CHALLENGE" }, + { 0x1b, 0x1b, "PATH_RESPONSE" }, + { 0x1c, 0x1c, "CONNECTION_CLOSE (Transport)" }, + { 0x1d, 0x1d, "CONNECTION_CLOSE (Application)" }, + { 0x1e, 0x1e, "HANDSHAKE_DONE" }, + { 0x1f, 0x1f, "IMMEDIATE_ACK" }, + { 0x30, 0x31, "DATAGRAM" }, + { 0x40, 0x40, "MP_NEW_CONNECTION_ID" }, + { 0x41, 0x41, "MP_RETIRE_CONNECTION_ID" }, + { 0x42, 0x43, "MP_ACK" }, + { 0x44, 0x44, "ADD_ADDRESS" }, + { 0x45, 0x45, "REMOVE_ADDRESS" }, + { 0x46, 0x46, "UNIFLOWS" }, + { 0xac, 0xac, "IMMEDIATE_ACK (draft05)" }, /* ack-frequency-draft-05 */ + { 0xaf, 0xaf, "ACK_FREQUENCY" }, + { 0x02f5, 0x02f5, "TIME_STAMP" }, + { 0xbaba00, 0xbaba01, "ACK_MP" }, /* multipath-draft-04 */ + { 0xbaba05, 0xbaba05, "PATH_ABANDON" }, /* multipath-draft-04 */ + { 0xbaba06, 0xbaba06, "PATH_STATUS" }, /* multipath-draft-04 */ + { 0x15228c00, 0x15228c01, "ACK_MP" }, /* >= multipath-draft-05 */ + { 0x15228c05, 0x15228c05, "PATH_ABANDON" }, /* >= multipath-draft-05 */ + { 0x15228c06, 0x15228c06, "PATH_STATUS" }, /* = multipath-draft-05 */ + { 0x15228c07, 0x15228c07, "PATH_STANDBY" }, /* >= multipath-draft-06 */ + { 0x15228c08, 0x15228c08, "PATH_AVAILABLE" }, /* >= multipath-draft-06 */ + { 0, 0, NULL }, +}; + + +/* >= draft-08 */ +#define FTFLAGS_STREAM_FIN 0x01 +#define FTFLAGS_STREAM_LEN 0x02 +#define FTFLAGS_STREAM_OFF 0x04 + +#define FTFLAGS_STREAM_INITIATOR 0x01 +#define FTFLAGS_STREAM_DIRECTION 0x02 + +static const range_string quic_transport_error_code_vals[] = { + /* 0x00 - 0x3f Assigned via Standards Action or IESG Review policies. */ + { 0x0000, 0x0000, "NO_ERROR" }, + { 0x0001, 0x0001, "INTERNAL_ERROR" }, + { 0x0002, 0x0002, "CONNECTION_REFUSED" }, + { 0x0003, 0x0003, "FLOW_CONTROL_ERROR" }, + { 0x0004, 0x0004, "STREAM_ID_ERROR" }, + { 0x0005, 0x0005, "STREAM_STATE_ERROR" }, + { 0x0006, 0x0006, "FINAL_SIZE_ERROR" }, + { 0x0007, 0x0007, "FRAME_ENCODING_ERROR" }, + { 0x0008, 0x0008, "TRANSPORT_PARAMETER_ERROR" }, + { 0x0009, 0x0009, "CONNECTION_ID_LIMIT_ERROR" }, + { 0x000a, 0x000a, "PROTOCOL_VIOLATION" }, + { 0x000b, 0x000b, "INVALID_TOKEN" }, + { 0x000c, 0x000c, "APPLICATION_ERROR" }, + { 0x000d, 0x000d, "CRYPTO_BUFFER_EXCEEDED" }, + { 0x000e, 0x000e, "KEY_UPDATE_ERROR" }, + { 0x000f, 0x000f, "AEAD_LIMIT_REACHED" }, + { 0x0010, 0x0010, "NO_VIABLE_PATH" }, + { 0x0011, 0x0011, "VERSION_NEGOTIATION_ERROR" }, + { 0x0100, 0x01ff, "CRYPTO_ERROR" }, + /* 0x40 - 0x3fff Assigned via Specification Required policy. */ + { 0, 0, NULL } +}; + +static const value_string quic_packet_number_lengths[] = { + { 0, "1 bytes" }, + { 1, "2 bytes" }, + { 2, "3 bytes" }, + { 3, "4 bytes" }, + { 0, NULL } +}; + +static const val64_string quic_frame_id_initiator[] = { + { 0, "Client-initiated" }, + { 1, "Server-initiated" }, + { 0, NULL } +}; + +static const val64_string quic_frame_id_direction[] = { + { 0, "Bidirectional" }, + { 1, "Unidirectional" }, + { 0, NULL } +}; + +static const val64_string quic_mp_path_status[] = { + { 1, "Standby" }, + { 2, "Available" }, + { 0, NULL } +}; + + +static void +quic_extract_header(tvbuff_t *tvb, guint8 *long_packet_type, guint32 *version, + quic_cid_t *dcid, quic_cid_t *scid); + +static int +quic_get_long_packet_type(guint8 first_byte, guint32 version) +{ + /* Up to V1 */ + if (!is_quic_v2(version)) { + if ((first_byte & 0x30) >> 4 == 0) + return QUIC_LPT_INITIAL; + if ((first_byte & 0x30) >> 4 == 1) + return QUIC_LPT_0RTT; + if ((first_byte & 0x30) >> 4 == 2) + return QUIC_LPT_HANDSHAKE; + return QUIC_LPT_RETRY; + } else { + if ((first_byte & 0x30) >> 4 == 0) + return QUIC_LPT_RETRY; + if ((first_byte & 0x30) >> 4 == 1) + return QUIC_LPT_INITIAL; + if ((first_byte & 0x30) >> 4 == 2) + return QUIC_LPT_0RTT; + return QUIC_LPT_HANDSHAKE; + } +} + +static void +quic_streams_add(packet_info *pinfo, quic_info_data_t *quic_info, guint64 stream_id); + +static void +quic_hp_cipher_reset(quic_hp_cipher *hp_cipher) +{ + gcry_cipher_close(hp_cipher->hp_cipher); + memset(hp_cipher, 0, sizeof(*hp_cipher)); +} +static void +quic_pp_cipher_reset(quic_pp_cipher *pp_cipher) +{ + gcry_cipher_close(pp_cipher->pp_cipher); + memset(pp_cipher, 0, sizeof(*pp_cipher)); +} +static void +quic_ciphers_reset(quic_ciphers *ciphers) +{ + quic_hp_cipher_reset(&ciphers->hp_cipher); + quic_pp_cipher_reset(&ciphers->pp_cipher); +} + +static gboolean +quic_is_hp_cipher_initialized(quic_hp_cipher *hp_cipher) +{ + return hp_cipher && hp_cipher->hp_cipher; +} +static gboolean +quic_is_pp_cipher_initialized(quic_pp_cipher *pp_cipher) +{ + return pp_cipher && pp_cipher->pp_cipher; +} +static gboolean +quic_are_ciphers_initialized(quic_ciphers *ciphers) +{ + return ciphers && + quic_is_hp_cipher_initialized(&ciphers->hp_cipher) && + quic_is_pp_cipher_initialized(&ciphers->pp_cipher); +} + +/* Inspired from ngtcp2 */ +static guint64 quic_pkt_adjust_pkt_num(guint64 max_pkt_num, guint64 pkt_num, + size_t n) { + guint64 k = max_pkt_num == G_MAXUINT64 ? max_pkt_num : max_pkt_num + 1; + guint64 u = k & ~((G_GUINT64_CONSTANT(1) << n) - 1); + guint64 a = u | pkt_num; + guint64 b = (u + (G_GUINT64_CONSTANT(1) << n)) | pkt_num; + guint64 a1 = k < a ? a - k : k - a; + guint64 b1 = k < b ? b - k : k - b; + + if (a1 < b1) { + return a; + } + return b; +} + +/** + * Given a header protection cipher, a buffer and the packet number offset, + * return the unmasked first byte and packet number. + * If the loss bits feature is enabled, the protected bits in the first byte + * are fewer than usual: 3 instead of 5 (on short headers only) + */ +static gboolean +quic_decrypt_header(tvbuff_t *tvb, guint pn_offset, quic_hp_cipher *hp_cipher, int hp_cipher_algo, + guint8 *first_byte, guint32 *pn, gboolean loss_bits_negotiated) +{ + if (!hp_cipher->hp_cipher) { + // need to know the cipher. + return FALSE; + } + gcry_cipher_hd_t h = hp_cipher->hp_cipher; + + // Sample is always 16 bytes and starts after PKN (assuming length 4). + // https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.2 + guint8 sample[16]; + tvb_memcpy(tvb, sample, pn_offset + 4, 16); + + guint8 mask[5] = { 0 }; + switch (hp_cipher_algo) { + case GCRY_CIPHER_AES128: + case GCRY_CIPHER_AES256: + /* Encrypt in-place with AES-ECB and extract the mask. */ + if (gcry_cipher_encrypt(h, sample, sizeof(sample), NULL, 0)) { + return FALSE; + } + memcpy(mask, sample, sizeof(mask)); + break; + case GCRY_CIPHER_CHACHA20: + /* If Gcrypt receives a 16 byte IV, it will assume the buffer to be + * counter || nonce (in little endian), as desired. */ + if (gcry_cipher_setiv(h, sample, 16)) { + return FALSE; + } + /* Apply ChaCha20, encrypt in-place five zero bytes. */ + if (gcry_cipher_encrypt(h, mask, sizeof(mask), NULL, 0)) { + return FALSE; + } + break; + default: + return FALSE; + } + + // https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.1 + guint8 packet0 = tvb_get_guint8(tvb, 0); + if ((packet0 & 0x80) == 0x80) { + // Long header: 4 bits masked + packet0 ^= mask[0] & 0x0f; + } else { + // Short header + if (loss_bits_negotiated == FALSE) { + // Standard mask: 5 bits masked + packet0 ^= mask[0] & 0x1F; + } else { + // https://tools.ietf.org/html/draft-ferrieuxhamchaoui-quic-lossbits-03#section-5.3 + packet0 ^= mask[0] & 0x07; + } + } + guint pkn_len = (packet0 & 0x03) + 1; + + guint8 pkn_bytes[4]; + tvb_memcpy(tvb, pkn_bytes, pn_offset, pkn_len); + guint32 pkt_pkn = 0; + for (guint i = 0; i < pkn_len; i++) { + pkt_pkn |= (pkn_bytes[i] ^ mask[1 + i]) << (8 * (pkn_len - 1 - i)); + } + *first_byte = packet0; + *pn = pkt_pkn; + return TRUE; +} + +/** + * Retrieve the maximum valid packet number space for a peer. + */ +static guint64 * +quic_max_packet_number(quic_info_data_t *quic_info, uint64_t seq_num, gboolean from_server, guint8 first_byte) +{ + int pkn_space; + if ((first_byte & 0x80) && quic_get_long_packet_type(first_byte, quic_info->version) == QUIC_LPT_INITIAL) { + // Long header, Initial + pkn_space = 0; + } else if ((first_byte & 0x80) && quic_get_long_packet_type(first_byte, quic_info->version) == QUIC_LPT_HANDSHAKE) { + // Long header, Handshake + pkn_space = 1; + } else { + // Long header (0-RTT) or Short Header (1-RTT appdata). + pkn_space = 2; + } + if (quic_multipath_negotiated(quic_info) && seq_num > 0) { + /* The multipath draft states that key negotiation must + * happen before 2^32 CID sequence numbers are used, so + * possibly we could get away with using GUINT_TO_POINTER + * and saving some memory here. + */ + wmem_map_t **mp_pkn_map; + if (from_server) { + if (quic_info->max_server_mp_pkn == NULL) { + quic_info->max_server_mp_pkn = wmem_map_new(wmem_file_scope(), wmem_int64_hash, g_int64_equal); + } + mp_pkn_map = &quic_info->max_server_mp_pkn; + } else { + if (quic_info->max_client_mp_pkn == NULL) { + quic_info->max_client_mp_pkn = wmem_map_new(wmem_file_scope(), wmem_int64_hash, g_int64_equal); + } + mp_pkn_map = &quic_info->max_client_mp_pkn; + } + uint64_t *pkt_num = wmem_map_lookup(*mp_pkn_map, &seq_num); + if (pkt_num == NULL) { + uint64_t *seq_num_p = wmem_new(wmem_file_scope(), uint64_t); + *seq_num_p = seq_num; + pkt_num = wmem_new0(wmem_file_scope(), uint64_t); + wmem_map_insert(*mp_pkn_map, seq_num_p, pkt_num); + } + return pkt_num; + } else { + if (from_server) { + return &quic_info->max_server_pkn[pkn_space]; + } else { + return &quic_info->max_client_pkn[pkn_space]; + } + } +} + +/** + * Calculate the full packet number and store it for later use. + */ +static void +quic_set_full_packet_number(quic_info_data_t *quic_info, quic_packet_info_t *quic_packet, + uint64_t seq_num, gboolean from_server, + guint8 first_byte, guint32 pkn32) +{ + guint pkn_len = (first_byte & 3) + 1; + guint64 pkn_full; + guint64 max_pn = *quic_max_packet_number(quic_info, seq_num, from_server, first_byte); + + /* Sequential first pass, try to reconstruct full packet number. */ + pkn_full = quic_pkt_adjust_pkt_num(max_pn, pkn32, 8 * pkn_len); + quic_packet->pkn_len = pkn_len; + quic_packet->packet_number = pkn_full; +} + +static const char * +cid_to_string(wmem_allocator_t *pool, const quic_cid_t *cid) +{ + if (cid->len == 0) { + return "(none)"; + } + char *str = (char *)wmem_alloc0(pool, 2 * cid->len + 1); + bytes_to_hexstr(str, cid->cid, cid->len); + return str; +} + +/* QUIC Connection tracking. {{{ */ +static guint +quic_connection_hash(gconstpointer key) +{ + const quic_cid_t *cid = (const quic_cid_t *)key; + + return wmem_strong_hash((const guint8 *)cid->cid, cid->len); +} + +/* Note this function intentionally does not consider the reset token. */ +static gboolean +quic_connection_equal(gconstpointer a, gconstpointer b) +{ + const quic_cid_t *cid1 = (const quic_cid_t *)a; + const quic_cid_t *cid2 = (const quic_cid_t *)b; + + return cid1->len == cid2->len && !memcmp(cid1->cid, cid2->cid, cid1->len); +} + +static gboolean +quic_cids_has_match(const quic_cid_item_t *items, quic_cid_t *raw_cid) +{ + while (items) { + const quic_cid_t *cid = &items->data; + // "raw_cid" potentially has some trailing data that is not part of the + // actual CID, so accept any prefix match against "cid". + // Note that this explicitly matches an empty CID. + if (raw_cid->len >= cid->len && !memcmp(raw_cid->cid, cid->cid, cid->len)) { + raw_cid->seq_num = cid->seq_num; + return TRUE; + } + items = items->next; + } + return FALSE; +} + +static void +quic_cids_insert(quic_cid_t *cid, quic_info_data_t *conn, gboolean from_server) +{ + wmem_map_t *connections = from_server ? quic_server_connections : quic_client_connections; + // Replace any previous CID key with the new one. + wmem_map_remove(connections, cid); + wmem_map_insert(connections, cid, conn); + G_STATIC_ASSERT(QUIC_MAX_CID_LENGTH <= 8 * sizeof(quic_cid_lengths)); + quic_cid_lengths |= (1ULL << cid->len); +} + +static inline gboolean +quic_cids_is_known_length(const quic_cid_t *cid) +{ + return (quic_cid_lengths & (1ULL << cid->len)) != 0; +} + +/** + * Returns the most recent QUIC connection for the current UDP stream. This may + * return NULL after connection migration if the new UDP association was not + * properly linked via a match based on the Connection ID. + * + * There may be more than one QUIC connection multiplexed on the same UDP + * 5-tuple; previous connections can be found by looking at the ->prev pointer. + * Per RFC 9000, multiplexed connections with zero-length CIDs will fail. + */ +static quic_info_data_t * +quic_connection_from_conv(packet_info *pinfo) +{ + conversation_t *conv = find_conversation_pinfo(pinfo, 0); + if (conv) { + return (quic_info_data_t *)conversation_get_proto_data(conv, proto_quic); + } + return NULL; +} + +/** + * Tries to lookup a matching connection (if Connection ID is NULL, the + * most recent connection on the network 5-tuple is returned, if any). + * If connection is found, "from_server" is set accordingly. + */ +static quic_info_data_t * +quic_connection_find_dcid(packet_info *pinfo, quic_cid_t *dcid, gboolean *from_server) +{ + /* https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-5.2 + * + * "If the packet has a Destination Connection ID corresponding to an + * existing connection, QUIC processes that packet accordingly." + * "If the Destination Connection ID is zero length and the packet matches + * the address/port tuple of a connection where the host did not require + * connection IDs, QUIC processes the packet as part of that connection." + */ + quic_info_data_t *conn = NULL; + const quic_cid_t *original_dcid; + gboolean check_ports = FALSE; + + if (dcid && dcid->len > 0) { + // Optimization: avoid lookup for invalid CIDs. + if (!quic_cids_is_known_length(dcid)) { + return NULL; + } + if (wmem_map_lookup_extended(quic_client_connections, dcid, (const void**)&original_dcid, (void**)&conn)) { + // DCID recognized by client, so it was from server. + *from_server = TRUE; + // On collision (both client and server choose the same CID), check + // the port to learn about the side. + // This is required for supporting draft -10 which has a single CID. + check_ports = !!wmem_map_lookup(quic_server_connections, dcid); + // Copy the other information, like sequence number (for multipath). + *dcid = *original_dcid; + } else { + if (wmem_map_lookup_extended(quic_server_connections, dcid, (const void**)&original_dcid, (void**)&conn)) { + // DCID recognized by server, so it was from client. + *from_server = FALSE; + // Copy the other information, like sequence number. + *dcid = *original_dcid; + } + } + } else { + conn = quic_connection_from_conv(pinfo); + if (conn) { + check_ports = TRUE; + } + } + + if (check_ports) { + *from_server = conn->server_port == pinfo->srcport && + addresses_equal(&conn->server_address, &pinfo->src); + } + + return conn; +} + +/** + * Try to find a QUIC connection based on DCID. For short header packets, DCID + * will be modified in order to find the actual length. + * DCID can be empty, in that case a connection is looked up by address only. + */ +static quic_info_data_t * +quic_connection_find(packet_info *pinfo, guint8 long_packet_type, + quic_cid_t *dcid, gboolean *from_server) +{ + gboolean is_long_packet = long_packet_type != QUIC_SHORT_PACKET; + quic_info_data_t *conn = NULL; + + if (long_packet_type == QUIC_LPT_0RTT && dcid->len > 0) { + // The 0-RTT packet always matches the SCID/DCID of the Client Initial + conn = (quic_info_data_t *) wmem_map_lookup(quic_initial_connections, dcid); + *from_server = FALSE; + } else { + // Find a connection for Handshake, Version Negotiation and Server Initial packets by + // matching their DCID against the SCIDs of the original Initial packets + // from the peer. For Client Initial packets, match DCID of the first + // Client Initial (these may contain ACK frames). + conn = quic_connection_find_dcid(pinfo, dcid, from_server); + /* Handle cases where we get a second Client Initial packet before a + * Server Initial packet (so this is not recognized by the server yet), + * e.g. the TLS Client Hello is fragmented in more than one frame. + */ + if (long_packet_type == QUIC_LPT_INITIAL && !conn && dcid->len > 0) { + conn = (quic_info_data_t *) wmem_map_lookup(quic_initial_connections, dcid); + if (conn) { + *from_server = FALSE; + } + } + if (long_packet_type == QUIC_LPT_INITIAL && conn && !*from_server && dcid->len > 0 && + !quic_connection_equal(dcid, &conn->client_dcid_initial) && + !quic_cids_has_match(&conn->server_cids, dcid)) { + // If the Initial Packet is from the client, it must either match + // the DCID from the first Client Initial, or the DCID that was + // assigned by the server. Otherwise this must be considered a fresh + // Client Initial, for example after the Version Negotiation packet, + // and the connection must be cleared to avoid decryption failure. + conn = NULL; + } + } + + if (!is_long_packet && !conn) { + // For short packets, first try to find a match based on the address. + // (This is necessary to match a zero-length connection ID - for + // other cases, the second method below also works, and it can vary + // which is faster to try first.) + conn = quic_connection_find_dcid(pinfo, NULL, from_server); + /* Since we don't know the DCID, check all connections multiplexed + * on the same 5-tuple for a match. */ + while (conn) { + if ((*from_server && quic_cids_has_match(&conn->client_cids, dcid)) || + (!*from_server && quic_cids_has_match(&conn->server_cids, dcid))) { + // Connection matches packet. + break; + } + conn = conn->prev; + } + + // No match found so far, potentially connection migration. Length of + // actual DCID is unknown, so just keep decrementing until found. + while (!conn && dcid->len > 1) { + dcid->len--; + if (quic_cids_is_known_length(dcid)) { + conn = quic_connection_find_dcid(pinfo, dcid, from_server); + } + } + if (!conn) { + // No match found, truncate DCID (not really needed, but this + // ensures that debug prints clearly show that DCID is invalid). + dcid->len = 0; + } + } + return conn; +} + +/** Create a new QUIC Connection based on a Client Initial packet. */ +static quic_info_data_t * +quic_connection_create(packet_info *pinfo, guint32 version) +{ + conversation_t *conv; + quic_info_data_t *prev_conn, *conn = NULL; + + conn = wmem_new0(wmem_file_scope(), quic_info_data_t); + wmem_list_append(quic_connections, conn); + conn->number = quic_connections_count++; + conn->version = version; + copy_address_wmem(wmem_file_scope(), &conn->server_address, &pinfo->dst); + conn->server_port = pinfo->destport; + + // For faster lookups without having to check DCID + conv = find_or_create_conversation(pinfo); + // Check for another connection multiplexed on the 5-tuple + prev_conn = conversation_get_proto_data(conv, proto_quic); + if (prev_conn) { + conn->prev = prev_conn; + } + conversation_add_proto_data(conv, proto_quic, conn); + + conv = find_or_create_conversation_by_id(pinfo, CONVERSATION_QUIC, conn->number); + conversation_add_proto_data(conv, proto_quic, conn); + + if (version == 0x51303530 || version == 0x54303530 || version == 0x54303531) { + gquic_info_data_t *gquic_info; + + gquic_info = wmem_new(wmem_file_scope(), gquic_info_data_t); + if (version == 0x51303530) + gquic_info->version = 50; + else if (version == 0x54303530) + gquic_info->version = 150; + else + gquic_info->version = 151; + gquic_info->encoding = ENC_BIG_ENDIAN; + gquic_info->version_valid = TRUE; + gquic_info->server_port = pinfo->destport; + conn->gquic_info = gquic_info; + } + + return conn; +} + +/** Update client/server connection identifiers, assuming the information is + * from the Client Initial. */ +static void +quic_connection_update_initial(quic_info_data_t *conn, const quic_cid_t *scid, const quic_cid_t *dcid) +{ + // Key connection by Client CID (if provided). + if (scid->len) { + memcpy(&conn->client_cids.data, scid, sizeof(quic_cid_t)); + quic_cids_insert(&conn->client_cids.data, conn, FALSE); + } + if (dcid->len > 0) { + // According to the spec, the Initial Packet DCID MUST be at least 8 + // bytes, but non-conforming implementations could exist. + memcpy(&conn->client_dcid_initial, dcid, sizeof(quic_cid_t)); + wmem_map_insert(quic_initial_connections, &conn->client_dcid_initial, conn); + conn->client_dcid_set = TRUE; + } +} + +/** + * Use the new CID as additional identifier for the specified connection and + * remember it for connection tracking. + */ +static void +quic_connection_add_cid(quic_info_data_t *conn, quic_cid_t *new_cid, gboolean from_server) +{ + DISSECTOR_ASSERT(new_cid->len > 0); + quic_cid_item_t *items = from_server ? &conn->server_cids : &conn->client_cids; + + if (quic_cids_has_match(items, new_cid)) { + // CID is already known for this connection. + // XXX: If the same CID is reused with a new sequence number and + // multipath is being used, that's an issue. (Expert info?) + return; + } + + // Insert new CID right after the first known CID (the very first CID cannot + // be overwritten since it might be used as key somewhere else). + quic_cid_item_t *new_item = wmem_new0(wmem_file_scope(), quic_cid_item_t); + new_item->data = *new_cid; + new_item->next = items->next; + items->next = new_item; + + quic_cids_insert(&new_item->data, conn, from_server); +} + +/** Create or update a connection. */ +static void +quic_connection_create_or_update(quic_info_data_t **conn_p, + packet_info *pinfo, guint32 long_packet_type, + guint32 version, const quic_cid_t *scid, + const quic_cid_t *dcid, gboolean from_server) +{ + quic_info_data_t *conn = *conn_p; + + switch (long_packet_type) { + case QUIC_LPT_INITIAL: + if (!from_server) { + if (!conn) { + // The first Initial Packet from the client creates a new connection. + *conn_p = quic_connection_create(pinfo, version); + quic_connection_update_initial(*conn_p, scid, dcid); + } else if (!conn->client_dcid_set && dcid->len) { + // If this client Initial Packet responds to a Retry Packet, + // then remember the new client SCID and initial DCID for the + // new Initial cipher and clear the first server CID such that + // the next server Initial Packet can link the connection with + // that new SCID. + quic_connection_update_initial(conn, scid, dcid); + wmem_map_remove(quic_server_connections, &conn->server_cids.data); + memset(&conn->server_cids, 0, sizeof(quic_cid_t)); + } + break; + } + /* fallthrough */ + case QUIC_LPT_RETRY: + case QUIC_LPT_HANDSHAKE: + // Remember CID from first server Retry/Handshake packet + // (or from the first server Initial packet, since draft -13). + if (from_server && conn) { + if (long_packet_type == QUIC_LPT_RETRY) { + // Retry Packet: the next Initial Packet from the + // client should start a new cryptographic handshake. Erase the + // current "Initial DCID" such that the next client Initial + // packet populates the new value. + wmem_map_remove(quic_initial_connections, &conn->client_dcid_initial); + memset(&conn->client_dcid_initial, 0, sizeof(quic_cid_t)); + conn->client_dcid_set = FALSE; + } + if (conn->server_cids.data.len == 0 && scid->len) { + memcpy(&conn->server_cids.data, scid, sizeof(quic_cid_t)); + quic_cids_insert(&conn->server_cids.data, conn, TRUE); + } + } + break; + } +} + +static void +quic_connection_destroy(gpointer data, gpointer user_data _U_) +{ + quic_info_data_t *conn = (quic_info_data_t *)data; + quic_ciphers_reset(&conn->client_initial_ciphers); + quic_ciphers_reset(&conn->server_initial_ciphers); + quic_ciphers_reset(&conn->client_handshake_ciphers); + quic_ciphers_reset(&conn->server_handshake_ciphers); + + quic_ciphers_reset(&conn->client_0rtt_ciphers); + + quic_hp_cipher_reset(&conn->client_pp.hp_cipher); + quic_pp_cipher_reset(&conn->client_pp.pp_ciphers[0]); + quic_pp_cipher_reset(&conn->client_pp.pp_ciphers[1]); + + quic_hp_cipher_reset(&conn->server_pp.hp_cipher); + quic_pp_cipher_reset(&conn->server_pp.pp_ciphers[0]); + quic_pp_cipher_reset(&conn->server_pp.pp_ciphers[1]); +} +/* QUIC Connection tracking. }}} */ + +/* QUIC Streams tracking and reassembly. {{{ */ +static reassembly_table quic_reassembly_table; + +/** Perform sequence analysis for STREAM frames. */ +static quic_stream_state * +quic_get_stream_state(packet_info *pinfo, quic_info_data_t *quic_info, gboolean from_server, guint64 stream_id) +{ + wmem_map_t **streams_p = from_server ? &quic_info->server_streams : &quic_info->client_streams; + wmem_map_t *streams = *streams_p; + quic_stream_state *stream = NULL; + + if (PINFO_FD_VISITED(pinfo)) { + DISSECTOR_ASSERT(streams); + stream = (quic_stream_state *)wmem_map_lookup(streams, &stream_id); + DISSECTOR_ASSERT(stream); + return stream; + } + + // Initialize per-connection and per-stream state. + if (!streams) { + streams = wmem_map_new(wmem_file_scope(), wmem_int64_hash, g_int64_equal); + *streams_p = streams; + } else { + stream = (quic_stream_state *)wmem_map_lookup(streams, &stream_id); + } + if (!stream) { + stream = wmem_new0(wmem_file_scope(), quic_stream_state); + stream->stream_id = stream_id; + stream->multisegment_pdus = wmem_tree_new(wmem_file_scope()); + wmem_map_insert(streams, &stream->stream_id, stream); + } + return stream; +} + +static void +process_quic_stream(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, + quic_info_data_t *quic_info, quic_stream_info *stream_info) +{ + if (quic_info->app_handle) { + tvbuff_t *next_tvb = tvb_new_subset_remaining(tvb, offset); + // Traverse the STREAM frame tree. + proto_tree *top_tree = proto_tree_get_parent_tree(tree); + top_tree = proto_tree_get_parent_tree(top_tree); + // Subdissectors MUST NOT assume that 'stream_info' remains valid after + // returning. Copying the pointer will result in illegal memory access. + call_dissector_with_data(quic_info->app_handle, next_tvb, pinfo, top_tree, stream_info); + } +} + +/** + * Reassemble stream data within a STREAM frame. + */ +static void +desegment_quic_stream(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, + proto_tree *tree, quic_info_data_t *quic_info, + quic_stream_info *stream_info, + quic_stream_state *stream) +{ + fragment_head *fh; + int last_fragment_len; + gboolean must_desegment; + gboolean called_dissector; + int another_pdu_follows; + int deseg_offset; + struct tcp_multisegment_pdu *msp; + guint32 seq = (guint32)stream_info->stream_offset; + const guint32 nxtseq = seq + (guint32)length; + guint32 reassembly_id = 0; + + // XXX fix the tvb accessors below such that no new tvb is needed. + tvb = tvb_new_subset_length(tvb, 0, offset + length); + +again: + fh = NULL; + last_fragment_len = 0; + must_desegment = FALSE; + called_dissector = FALSE; + another_pdu_follows = 0; + msp = NULL; + + /* + * Initialize these to assume no desegmentation. + * If that's not the case, these will be set appropriately + * by the subdissector. + */ + pinfo->desegment_offset = 0; + pinfo->desegment_len = 0; + + /* + * Initialize this to assume that this segment will just be + * added to the middle of a desegmented chunk of data, so + * that we should show it all as data. + * If that's not the case, it will be set appropriately. + */ + deseg_offset = offset; + + /* Have we seen this PDU before (and is it the start of a multi- + * segment PDU)? + */ + if ((msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32(stream->multisegment_pdus, seq)) && + nxtseq <= msp->nxtpdu) { + // XXX: This also happens the second time through the data for an MSP normally + // TODO show expert info for retransmission? Additional checks may be + // necessary here to tell a retransmission apart from other (normal?) + // conditions. See also similar code in packet-tcp.c. +#if 0 + proto_tree_add_debug_text(tree, "TODO retransmission expert info frame %d stream_id=%" PRIu64 " offset=%d visited=%d reassembly_id=0x%08x", + pinfo->num, stream->stream_id, offset, PINFO_FD_VISITED(pinfo), reassembly_id); +#endif + return; + } + /* Else, find the most previous PDU starting before this sequence number */ + if (!msp && seq > 0) { + msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32_le(stream->multisegment_pdus, seq-1); + /* Unless if we already fully reassembled the msp that covers seq-1 + * and seq is beyond the end of that msp. In that case this segment + * will be the start of a new msp. + */ + if (msp && (msp->flags & MSP_FLAGS_GOT_ALL_SEGMENTS) && + seq >= msp->nxtpdu) { + msp = NULL; + } + } + + { + // A single stream can contain multiple fragments (e.g. for HTTP/3 + // HEADERS and DATA frames). Let's hope that a single stream within a + // QUIC packet does not contain multiple partial fragments, that would + // result in a reassembly ID collision here. If that collision becomes + // an issue, we would have to replace "msp->first_frame" with a new + // field in "msp" that is initialized with "stream_info->stream_offset". +#if 0 + guint64 reassembly_id_data[2]; + reassembly_id_data[0] = stream_info->stream_id; + reassembly_id_data[1] = msp ? msp->first_frame : pinfo->num; + reassembly_id = wmem_strong_hash((const guint8 *)&reassembly_id_data, sizeof(reassembly_id_data)); +#else + // XXX for debug (visibility) purposes, do not use a hash but concatenate + reassembly_id = ((msp ? msp->first_frame : pinfo->num) << 16) | (guint32)stream_info->stream_id; +#endif + } + + if (msp && msp->seq <= seq && msp->nxtpdu > seq) { + int len; + + if (!PINFO_FD_VISITED(pinfo)) { + msp->last_frame=pinfo->num; + msp->last_frame_time=pinfo->abs_ts; + } + + /* OK, this PDU was found, which means the segment continues + * a higher-level PDU and that we must desegment it. + */ + if (msp->flags & MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT) { + /* The dissector asked for the entire segment */ + len = tvb_captured_length_remaining(tvb, offset); + } else { + len = MIN(nxtseq, msp->nxtpdu) - seq; + } + last_fragment_len = len; + + fh = fragment_add(&quic_reassembly_table, tvb, offset, + pinfo, reassembly_id, NULL, + seq - msp->seq, len, + nxtseq < msp->nxtpdu); + if (fh) { + msp->flags |= MSP_FLAGS_GOT_ALL_SEGMENTS; + } + if (!PINFO_FD_VISITED(pinfo) + && msp->flags & MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT) { + msp->flags &= (~MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT); + + /* If we consumed the entire segment there is no + * other pdu starting anywhere inside this segment. + * So update nxtpdu to point at least to the start + * of the next segment. + * (If the subdissector asks for even more data we + * will advance nxtpdu even further later down in + * the code.) + */ + msp->nxtpdu = nxtseq; + } + + if( (msp->nxtpdu < nxtseq) + && (msp->nxtpdu >= seq) + && (len > 0)) { + another_pdu_follows=msp->nxtpdu - seq; + } + } else { + /* This segment was not found in our table, so it doesn't + * contain a continuation of a higher-level PDU. + * Call the normal subdissector. + */ + + stream_info->offset = seq; + process_quic_stream(tvb, offset, pinfo, tree, quic_info, stream_info); + called_dissector = TRUE; + + /* Did the subdissector ask us to desegment some more data + * before it could handle the packet? + * If so we'll have to handle that later. + */ + if (pinfo->desegment_len) { + must_desegment = TRUE; + if (!PINFO_FD_VISITED(pinfo)) { + if (msp) + msp->flags &= ~MSP_FLAGS_GOT_ALL_SEGMENTS; + } + + /* + * Set "deseg_offset" to the offset in "tvb" + * of the first byte of data that the + * subdissector didn't process. + */ + deseg_offset = offset + pinfo->desegment_offset; + } + + /* Either no desegmentation is necessary, or this is + * segment contains the beginning but not the end of + * a higher-level PDU and thus isn't completely + * desegmented. + */ + fh = NULL; + } + + /* is it completely desegmented? */ + if (fh) { + /* + * Yes, we think it is. + * We only call subdissector for the last segment. + * Note that the last segment may include more than what + * we needed. + */ + if (fh->reassembled_in == pinfo->num) { + /* + * OK, this is the last segment. + * Let's call the subdissector with the desegmented data. + */ + + tvbuff_t *next_tvb = tvb_new_chain(tvb, fh->tvb_data); + add_new_data_source(pinfo, next_tvb, "Reassembled QUIC"); + stream_info->offset = seq; + process_quic_stream(next_tvb, 0, pinfo, tree, quic_info, stream_info); + called_dissector = TRUE; + + int old_len = (int)(tvb_reported_length(next_tvb) - last_fragment_len); + if (pinfo->desegment_len && + pinfo->desegment_offset <= old_len) { + /* + * "desegment_len" isn't 0, so it needs more + * data for something - and "desegment_offset" + * is before "old_len", so it needs more data + * to dissect the stuff we thought was + * completely desegmented (as opposed to the + * stuff at the beginning being completely + * desegmented, but the stuff at the end + * being a new higher-level PDU that also + * needs desegmentation). + */ + fragment_set_partial_reassembly(&quic_reassembly_table, + pinfo, reassembly_id, NULL); + + /* Update msp->nxtpdu to point to the new next + * pdu boundary. + */ + if (pinfo->desegment_len == DESEGMENT_ONE_MORE_SEGMENT) { + /* We want reassembly of at least one + * more segment so set the nxtpdu + * boundary to one byte into the next + * segment. + * This means that the next segment + * will complete reassembly even if it + * is only one single byte in length. + * If this is an OoO segment, then increment the MSP end. + */ + msp->nxtpdu = MAX(seq + tvb_reported_length_remaining(tvb, offset), msp->nxtpdu) + 1; + msp->flags |= MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT; +#if 0 + } else if (pinfo->desegment_len == DESEGMENT_UNTIL_FIN) { + tcpd->fwd->flags |= TCP_FLOW_REASSEMBLE_UNTIL_FIN; +#endif + } else { + if (seq + last_fragment_len >= msp->nxtpdu) { + /* This is the segment (overlapping) the end of the MSP. */ + msp->nxtpdu = seq + last_fragment_len + pinfo->desegment_len; + } else { + /* This is a segment before the end of the MSP, so it + * must be an out-of-order segmented that completed the + * MSP. The requested additional data is relative to + * that end. + */ + msp->nxtpdu += pinfo->desegment_len; + } + } + + /* Since we need at least some more data + * there can be no pdu following in the + * tail of this segment. + */ + another_pdu_follows = 0; + offset += last_fragment_len; + seq += last_fragment_len; + if (tvb_captured_length_remaining(tvb, offset) > 0) + goto again; + } else { + proto_item *frag_tree_item; + proto_tree *parent_tree = proto_tree_get_parent(tree); + show_fragment_tree(fh, &quic_stream_fragment_items, + parent_tree, pinfo, next_tvb, &frag_tree_item); + // TODO move tree item if needed. + + if(pinfo->desegment_len) { + if (!PINFO_FD_VISITED(pinfo)) { + must_desegment = TRUE; + if (msp) + msp->flags &= ~MSP_FLAGS_GOT_ALL_SEGMENTS; + } + /* See packet-tcp.h for details about this. */ + deseg_offset = fh->datalen - pinfo->desegment_offset; + deseg_offset = tvb_reported_length(tvb) - deseg_offset; + } + } + } + } + + if (must_desegment) { + + guint32 deseg_seq = seq + (deseg_offset - offset); + + if (!PINFO_FD_VISITED(pinfo)) { + // TODO handle DESEGMENT_UNTIL_FIN if needed, maybe use the FIN bit? + if ((nxtseq - deseg_seq) <= 1024*1024) { + if(pinfo->desegment_len == DESEGMENT_ONE_MORE_SEGMENT) { + /* The subdissector asked to reassemble using the + * entire next segment. + * Just ask reassembly for one more byte + * but set this msp flag so we can pick it up + * above. + */ + msp = pdu_store_sequencenumber_of_next_pdu(pinfo, deseg_seq, + nxtseq+1, stream->multisegment_pdus); + msp->flags |= MSP_FLAGS_REASSEMBLE_ENTIRE_SEGMENT; + } else { + msp = pdu_store_sequencenumber_of_next_pdu(pinfo, + deseg_seq, nxtseq+pinfo->desegment_len, stream->multisegment_pdus); + } + + /* add this segment as the first one for this new pdu */ + fragment_add(&quic_reassembly_table, tvb, deseg_offset, + pinfo, reassembly_id, NULL, + 0, nxtseq - deseg_seq, + nxtseq < msp->nxtpdu); + } + } else { + /* If this is not the first time we have seen the packet, then + * the MSP should already be created. Retrieve it to see if we + * know what later frame the PDU is reassembled in. + */ + if ((msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32(stream->multisegment_pdus, deseg_seq))) { + fh = fragment_get(&quic_reassembly_table, pinfo, reassembly_id, NULL); + } + } + } + + if (!called_dissector || pinfo->desegment_len != 0) { + if (fh != NULL && fh->reassembled_in != 0 && + !(fh->flags & FD_PARTIAL_REASSEMBLY)) { + /* + * We know what frame this PDU is reassembled in; + * let the user know. + */ + proto_item *item = proto_tree_add_uint(tree, hf_quic_reassembled_in, tvb, 0, + 0, fh->reassembled_in); + proto_item_set_generated(item); + } + + /* TODO: Show what's left in the packet as a raw QUIC "segment", like + * packet-tcp.c does here. + */ + } + pinfo->can_desegment = 0; + pinfo->desegment_offset = 0; + pinfo->desegment_len = 0; + + if (another_pdu_follows) { + /* there was another pdu following this one. */ + pinfo->can_desegment = 2; + offset += another_pdu_follows; + seq += another_pdu_follows; + goto again; + } +} + +static void +dissect_quic_stream_payload(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, + proto_tree *tree, quic_info_data_t *quic_info, + quic_stream_info *stream_info, + quic_stream_state *stream) +{ + /* QUIC application data is most likely not properly dissected when + * reassembly is not enabled. Therefore we do not even offer "desegment" + * preference to disable reassembly. + */ + + if (length > 0) { + /* Don't call a subdissector for a zero length segment. It won't + * work for dissection (see #12368), and our methods of determing + * if desegmentation is needed won't work either (#19497). If there + * ever is an app_handle on top of QUIC that needs to be called with + * a zero length segment, revisit this. (Cf. #15159) + */ + pinfo->can_desegment = 2; + desegment_quic_stream(tvb, offset, length, pinfo, tree, quic_info, stream_info, stream); + } +} +/* QUIC Streams tracking and reassembly. }}} */ + +static gboolean quic_crypto_out_of_order = TRUE; + +static reassembly_table quic_crypto_reassembly_table; + +typedef struct _quic_crypto_retrans_key { + guint64 pkt_number; /* QUIC packet number */ + int offset; + guint32 num; /* Frame number in the capture file, pinfo->num */ +} quic_crypto_retrans_key; + +static guint +quic_crypto_retrans_hash(gconstpointer k) +{ + const quic_crypto_retrans_key* key = (const quic_crypto_retrans_key*) k; + +#if 0 + return wmem_strong_hash((const guint8 *)key, sizeof(quic_crypto_retrans_key)); +#endif + guint hash_val; + + /* Most of the time the packet number in the capture file suffices. */ + hash_val = key->num; + + return hash_val; +} + +static gint +quic_crypto_retrans_equal(gconstpointer k1, gconstpointer k2) +{ + const quic_crypto_retrans_key* key1 = (const quic_crypto_retrans_key*) k1; + const quic_crypto_retrans_key* key2 = (const quic_crypto_retrans_key*) k2; + + return (key1->num == key2->num) && + (key1->pkt_number == key2->pkt_number) && + (key1->offset == key2->offset); +} + +static quic_crypto_state * +quic_get_crypto_state(packet_info *pinfo, quic_info_data_t *quic_info, gboolean from_server, const guint8 encryption_level) +{ + wmem_map_t **cryptos_p = from_server ? &quic_info->server_crypto : &quic_info->client_crypto; + wmem_map_t *cryptos = *cryptos_p; + quic_crypto_state *crypto = NULL; + + if (PINFO_FD_VISITED(pinfo)) { + DISSECTOR_ASSERT(cryptos); + crypto = (quic_crypto_state *)wmem_map_lookup(cryptos, GUINT_TO_POINTER(encryption_level)); + DISSECTOR_ASSERT(crypto); + return crypto; + } + + // Initialize per-connection and per-stream state. + if (!cryptos) { + cryptos = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal); + *cryptos_p = cryptos; + } else { + crypto = (quic_crypto_state *)wmem_map_lookup(cryptos, GUINT_TO_POINTER(encryption_level)); + } + if (!crypto) { + crypto = wmem_new0(wmem_file_scope(), quic_crypto_state); + crypto->multisegment_pdus = wmem_tree_new(wmem_file_scope()); + crypto->retrans_offsets = wmem_map_new(wmem_file_scope(), + quic_crypto_retrans_hash, quic_crypto_retrans_equal); + crypto->encryption_level = encryption_level; + wmem_map_insert(cryptos, GUINT_TO_POINTER(encryption_level), crypto); + } + + return crypto; +} + +static void +process_quic_crypto(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, + proto_tree *tree, quic_crypto_info *crypto_info) +{ + + tvbuff_t *next_tvb = tvb_new_subset_length(tvb, offset, length); + col_set_writable(pinfo->cinfo, -1, FALSE); + /* + * Dissect TLS handshake record. The Client/Server Hello (CH/SH) + * are contained in the Initial Packet. 0-RTT keys are ready + * after CH. HS + 1-RTT keys are ready after SH. + * (Note: keys captured from the client might become available + * after capturing the packets due to processing delay.) + * These keys will be loaded in the first HS/0-RTT/1-RTT msg. + */ + call_dissector_with_data(tls13_handshake_handle, next_tvb, pinfo, tree, GUINT_TO_POINTER(crypto_info->offset)); + col_set_writable(pinfo->cinfo, -1, TRUE); +} + +/** + * Reassemble data within a CRYPTO frame. + * + * This always gets handed to the TLS handshake dissector, which does its own + * fragmentation handling, so all we do is the Out Of Order handling. + * RFC 9001 4.1.3 "Sending and Receiving Handshake Messages" + * "TLS is responsible for buffering handshake bytes that have arrived in order. + * QUIC is responsible for buffering handshake bytes that arrive out of order or + * for encryption levels that are not yet ready." + * + * XXX: We are only buffering bytes that arive out of order within an encryption + * level. Buffering for encryption levels that are not yet ready requires + * determining that they are not ready (and they may never be ready from our + * perspective if we don't have the keys.) + */ + +static void +desegment_quic_crypto(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, + proto_tree *tree, quic_info_data_t *quic_info _U_, + quic_crypto_info *crypto_info, + quic_crypto_state *crypto) +{ + fragment_head *fh; + gboolean called_dissector; + gboolean has_gap; + struct tcp_multisegment_pdu *msp; + + /* XXX: There are a few elements in QUIC that can be up to 64 bit + * integers that we're truncating to 32 bit here to re-use current + * code. + */ + + guint32 seq = (guint32)crypto_info->crypto_offset; + const guint32 nxtseq = seq + (guint32)length; + guint32 reassembly_id = 0; + + fh = NULL; + called_dissector = FALSE; + has_gap = FALSE; + msp = NULL; + + /* Look for retransmissions and overlap and discard them, only handing + * new in order bytes to TLS. + * + * It's possible to have multiple QUIC packets in the same capture + * file frame, so to really be assured of no collision we need the + * QUIC connection ID, the QUIC packet number space, the QUIC + * packet number, and the offset within the QUIC packet in addition + * to the frame number in the capture file. + * + * crypto (a quic_crypto_state*) is already unique to the connection + * ID and packet number space, so we need to store the other two + * in its map. + * + * Alternatively we could have the real offset in the capture + * file frame, but we can't easily get that since the tvb is the + * result of decryption. + */ + quic_crypto_retrans_key *tmp_key = wmem_new(pinfo->pool, quic_crypto_retrans_key); + tmp_key->num = pinfo->num; + tmp_key->offset = offset; + tmp_key->pkt_number = crypto_info->packet_number; + + if (!PINFO_FD_VISITED(pinfo)) { + if (crypto_info->crypto_offset + length <= crypto->max_contiguous_offset) { + /* No new data. Remember this. */ + proto_tree_add_expert(tree, pinfo, &ei_quic_retransmission, tvb, offset, length); + guint64* contiguous_offset = wmem_new(wmem_file_scope(), guint64); + *contiguous_offset = crypto->max_contiguous_offset; + quic_crypto_retrans_key *fkey = wmem_new(wmem_file_scope(), quic_crypto_retrans_key); + *fkey = *tmp_key; + wmem_map_insert(crypto->retrans_offsets, fkey, contiguous_offset); + return; + } else if (crypto_info->crypto_offset < crypto->max_contiguous_offset) { + /* XXX: Retrieve the previous data and compare for conflicts? */ + proto_tree_add_expert(tree, pinfo, &ei_quic_overlap, tvb, offset, length); + guint64 overlap = crypto->max_contiguous_offset - crypto_info->crypto_offset; + length -= (int)overlap; + seq = (guint32)(crypto->max_contiguous_offset); + offset += (guint32)(overlap); + /* Store this offset */ + guint64* contiguous_offset = wmem_new(wmem_file_scope(), guint64); + *contiguous_offset = crypto->max_contiguous_offset; + quic_crypto_retrans_key *fkey = wmem_new(wmem_file_scope(), quic_crypto_retrans_key); + *fkey = *tmp_key; + wmem_map_insert(crypto->retrans_offsets, fkey, contiguous_offset); + } + } else { + /* Retrieve any per-frame state about retransmitted and overlapping + * data. + */ + guint64 *contiguous_offset = (guint64 *)wmem_map_lookup(crypto->retrans_offsets, tmp_key); + if (contiguous_offset != NULL) { + if (crypto_info->crypto_offset + length <= *contiguous_offset) { + proto_tree_add_expert(tree, pinfo, &ei_quic_retransmission, tvb, offset, length); + return; + } else if (crypto_info->crypto_offset < *contiguous_offset) { + /* XXX: Retrieve the previous data and compare for conflicts? */ + proto_tree_add_expert(tree, pinfo, &ei_quic_overlap, tvb, offset, length); + guint64 overlap = *contiguous_offset - crypto_info->crypto_offset; + length -= (int)overlap; + seq = (guint32)(*contiguous_offset); + offset += (guint32)(overlap); + } else { + DISSECTOR_ASSERT_NOT_REACHED(); + } + } + } + + /* By doing the above we should not have any retransmissions from in + * order bytes. Retransmission and overlaps in out of order bytes are + * still possible, but those will be handled by adding them to the + * msp fragments. TLS is also going to handle defragmenting (instead + * of returning info about PDU ends via pinfo->desegment_offset and + * pinfo->desegment_len), so we can make this simpler than for payload + * streams or TCP. + * + * Since TLS doesn't set pinfo->desegment_offset and pinfo->desegment_len, + * we can't align our msps to PDU boundaries, and so we can't skip past + * any missing out of order bytes to send TLS later whole received PDUs. + */ + + /* Find the most recent msp that starts before this sequence number. */ + msp = (struct tcp_multisegment_pdu *)wmem_tree_lookup32_le(crypto->multisegment_pdus, seq); + + /* If we already fully reassembled that msp and seq is beyond its end + * (the latter should always be the case since we're discarding + * retransmitted bytes above), this segment isn't part of the msp. + */ + if (msp && (msp->flags & MSP_FLAGS_GOT_ALL_SEGMENTS) && + seq >= msp->nxtpdu) { + msp = NULL; + } + + /* The TCP reassembly functions already use msp->seq as a tiebreaker in + * case we do have more than one OOO reassembly in a given frame, which + * happens with Chrome's "Chaos Protection". + * + * XXX: It would be better to use functions that use the QUIC connection + * instead of addresses and ports, since concurrent connections on the + * same 5 tuple is possible, but using the frame number as well limits + * problems to more unusual encapsulations. + * + * RFC 9000 9. "Connection Migration": "An endpoint MUST NOT initiate + * connection migration before the handshake is confirmed" so we shouldn't + * have to worry about CRYPTO packets for the same connection being + * fragmented on different 5-tuples. (There may be new CRYPTO packets + * with session tickets later, but we should handle that.) + */ + reassembly_id = ((msp ? msp->first_frame : pinfo->num) << 8) | crypto->encryption_level; + + if (!PINFO_FD_VISITED(pinfo)) { + has_gap = crypto->max_contiguous_offset < seq; + + if (!has_gap) { + /* No gap, so either this is a standalone in order + * segment, or it's part of our in progress out of + * order MSP and we need to look at the MSP fragments + * to see what the last contiguous offset is. + * Advance the contiguous offset appropriately. + * + * XXX: A slightly different approach would involve splitting + * the MSP as now done in the TCP dissector. That would send + * any new bytes to TLS sooner and is closer to what RFC 9001 + * recommends. It's less important to do so than in TCP, but + * is a possible future improvement. + */ + if (msp) { + fh = fragment_get(&quic_crypto_reassembly_table, pinfo, reassembly_id, msp); + DISSECTOR_ASSERT(fh); + /* The offsets in the fragment list are relative to msp->seq */ + guint32 max = nxtseq - msp->seq; + for (fragment_item *frag = fh->next; frag; frag = frag->next) { + guint32 frag_end = frag->offset + frag->len; + if (frag->offset <= max && max < frag_end) { + max = frag_end; + } + } + crypto->max_contiguous_offset = max + msp->seq; + } else { + crypto->max_contiguous_offset = nxtseq; + } + } + + /* We always want to hand the entire segment to the TLS dissector. + * So update nxtpdu to point at least to the start of the next segment. + */ + if (msp) { + msp->nxtpdu = MAX(msp->nxtpdu, nxtseq); + } + } + + if (msp && msp->seq <= seq && msp->nxtpdu > seq) { + if (!PINFO_FD_VISITED(pinfo)) { + msp->last_frame=pinfo->num; + msp->last_frame_time=pinfo->abs_ts; + } + + /* OK, this PDU was found, which means the segment continues + * a higher-level PDU and that we must desegment it. + */ + fragment_reset_tot_len(&quic_crypto_reassembly_table, pinfo, reassembly_id, msp, + MAX(nxtseq, msp->nxtpdu) - msp->seq); + + fh = fragment_add(&quic_crypto_reassembly_table, tvb, offset, + pinfo, reassembly_id, msp, + seq - msp->seq, length, + nxtseq < msp->nxtpdu); + if (fh) { + msp->flags |= MSP_FLAGS_GOT_ALL_SEGMENTS; + if (msp->flags & MSP_FLAGS_MISSING_FIRST_SEGMENT) { + msp->first_frame_with_seq = seq; // Overloading this + /* We use "first_frame_with_seq" to mean "the sequence number + * of the fragment that completed the MSP" because many + * CRYPTO frames can be at the same layer, so the normal + * methods of determining the reassembled in fragment don't + * work. (We could store the seq in last_frame instead.) + */ + msp->flags &= (~MSP_FLAGS_MISSING_FIRST_SEGMENT); + } + } + } else if (has_gap) { + /* We need to start a new Out of Order MSP on our first visit. + * We shouldn't get here on a second visit. + */ + if (!PINFO_FD_VISITED(pinfo)) { + msp = pdu_store_sequencenumber_of_next_pdu(pinfo, (guint32)crypto->max_contiguous_offset, nxtseq, crypto->multisegment_pdus); + msp->flags |= MSP_FLAGS_MISSING_FIRST_SEGMENT; + fh = fragment_add(&quic_crypto_reassembly_table, tvb, offset, + pinfo, reassembly_id, msp, + seq - msp->seq, length, + nxtseq < msp->nxtpdu); + } + } else { + /* This segment was not found in our table, so it doesn't + * contain a continuation of a higher-level PDU. + * Call the normal subdissector. + */ + + crypto_info->offset = seq; + process_quic_crypto(tvb, offset, length, pinfo, tree, crypto_info); + called_dissector = TRUE; + } + + /* is it completely desegmented? */ + if (fh) { + /* + * Yes, we think it is. + * We only call TLS for the segment that reassembled it. + */ + if (fh->reassembled_in == pinfo->num && seq == msp->first_frame_with_seq) { + /* + * OK, this is it. + * Let's call the subdissector with the desegmented data. + */ + + tvbuff_t *next_tvb = tvb_new_chain(tvb, fh->tvb_data); + add_new_data_source(pinfo, next_tvb, "Reassembled QUIC CRYPTO"); + proto_item *frag_tree_item; + /* XXX: Should we use the proto_tree_get_root for these? + * There are PADDING and PINGs after the crypto, so maybe not? + */ + show_fragment_tree(fh, &quic_crypto_fragment_items, tree, pinfo, next_tvb, &frag_tree_item); + crypto_info->offset = seq; + process_quic_crypto(next_tvb, 0, tvb_captured_length(next_tvb), pinfo, tree, crypto_info); + called_dissector = TRUE; + } + } + + if (!called_dissector) { + if (fh != NULL && fh->reassembled_in != 0 && + fh->reassembled_in != pinfo->num ) { + /* + * We know what frame this PDU is reassembled in; + * let the user know. + */ + proto_item *item = proto_tree_add_uint(tree, hf_quic_reassembled_in, tvb, 0, + 0, fh->reassembled_in); + proto_item_set_generated(item); + } + } +} + +static void +dissect_quic_crypto_payload(tvbuff_t *tvb, int offset, int length, packet_info *pinfo, + proto_tree *tree, quic_info_data_t *quic_info, + quic_crypto_info *crypto_info, + quic_crypto_state *crypto) +{ + /* Make sure that TLS can also desegment */ + pinfo->can_desegment = 2; + if (quic_crypto_out_of_order) { + desegment_quic_crypto(tvb, offset, length, pinfo, tree, quic_info, crypto_info, crypto); + } else { + crypto_info->offset = (guint32)crypto_info->crypto_offset; + process_quic_crypto(tvb, offset, length, pinfo, tree, crypto_info); + } +} + +void +quic_stream_add_proto_data(packet_info *pinfo, quic_stream_info *stream_info, void *proto_data) +{ + quic_stream_state *stream = quic_get_stream_state(pinfo, stream_info->quic_info, stream_info->from_server, stream_info->stream_id); + stream->subdissector_private = proto_data; +} + +void *quic_stream_get_proto_data(packet_info *pinfo, quic_stream_info *stream_info) +{ + quic_stream_state *stream = quic_get_stream_state(pinfo, stream_info->quic_info, stream_info->from_server, stream_info->stream_id); + return stream->subdissector_private; +} + +static int +dissect_quic_frame_type(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, guint offset, quic_info_data_t *quic_info, const quic_packet_info_t *quic_packet, gboolean from_server) +{ + proto_item *ti_ft, *ti_ftflags, *ti_ftid, *ti; + proto_tree *ft_tree, *ftflags_tree, *ftid_tree; + guint64 frame_type; + gint32 lenft; + guint orig_offset = offset; + + ti_ft = proto_tree_add_item(quic_tree, hf_quic_frame, tvb, offset, 1, ENC_NA); + ft_tree = proto_item_add_subtree(ti_ft, ett_quic_ft); + + ti_ftflags = proto_tree_add_item_ret_varint(ft_tree, hf_quic_frame_type, tvb, offset, -1, ENC_VARINT_QUIC, &frame_type, &lenft); + proto_item_set_text(ti_ft, "%s", rval_to_str_const((guint32)frame_type, quic_frame_type_vals, "Unknown")); + offset += lenft; + + switch(frame_type){ + case FT_PADDING:{ + guint32 pad_len; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", PADDING"); + + /* A padding frame consists of a single zero octet, but for brevity + * sake let's combine multiple zeroes into a single field. */ + pad_len = 1 + tvb_skip_guint8(tvb, offset, tvb_reported_length_remaining(tvb, offset), '\0') - offset; + ti = proto_tree_add_uint(ft_tree, hf_quic_padding_length, tvb, offset, 0, pad_len); + proto_item_set_generated(ti); + proto_item_append_text(ti_ft, " Length: %u", pad_len); + offset += pad_len - 1; + } + break; + case FT_PING:{ + col_append_fstr(pinfo->cinfo, COL_INFO, ", PING"); + } + break; + case FT_ACK: + case FT_ACK_ECN: + case FT_ACK_MP: + case FT_ACK_MP_ECN: + case FT_ACK_MP_DRAFT04: + case FT_ACK_MP_ECN_DRAFT04: + case FT_MP_ACK: + case FT_MP_ACK_ECN:{ + guint64 ack_range_count; + gint32 lenvar; + + switch(frame_type){ + case FT_ACK: + col_append_fstr(pinfo->cinfo, COL_INFO, ", ACK"); + break; + case FT_ACK_ECN: + col_append_fstr(pinfo->cinfo, COL_INFO, ", ACK_ECN"); + break; + case FT_MP_ACK: + col_append_fstr(pinfo->cinfo, COL_INFO, ", MP_ACK"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_uniflow_id, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + break; + case FT_MP_ACK_ECN: + col_append_fstr(pinfo->cinfo, COL_INFO, ", MP_ACK_ECN"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_uniflow_id, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + break; + case FT_ACK_MP: + case FT_ACK_MP_DRAFT04: + col_append_fstr(pinfo->cinfo, COL_INFO, ", ACK_MP"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_ack_dcid_sequence_number, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + break; + case FT_ACK_MP_ECN: + case FT_ACK_MP_ECN_DRAFT04: + col_append_fstr(pinfo->cinfo, COL_INFO, ", ACK_MP_ECN"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_ack_dcid_sequence_number, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + break; + } + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ack_largest_acknowledged, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ack_ack_delay, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ack_ack_range_count, tvb, offset, -1, ENC_VARINT_QUIC, &ack_range_count, &lenvar); + offset += lenvar; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ack_first_ack_range, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + + /* ACK Ranges - Repeated "Ack Range Count" */ + while (ack_range_count) { + + /* Gap To Next Block */ + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ack_gap, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ack_ack_range, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + + ack_range_count--; + } + + /* ECN Counts. */ + if (frame_type == FT_ACK_ECN || frame_type == FT_MP_ACK_ECN || frame_type == FT_ACK_MP_ECN || frame_type == FT_ACK_MP_ECN_DRAFT04 ) { + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ack_ect0_count, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ack_ect1_count, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ack_ecn_ce_count, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + } + } + break; + case FT_RESET_STREAM:{ + guint64 stream_id, error_code; + gint32 len_streamid = 0, len_finalsize = 0, len_error_code = 0; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", RS"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_rsts_stream_id, tvb, offset, -1, ENC_VARINT_QUIC, &stream_id, &len_streamid); + offset += len_streamid; + + proto_item_append_text(ti_ft, " id=%" PRIu64, stream_id); + col_append_fstr(pinfo->cinfo, COL_INFO, "(%" PRIu64 ")", stream_id); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_rsts_application_error_code, tvb, offset, -1, ENC_VARINT_QUIC, &error_code, &len_error_code); + offset += len_error_code; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_rsts_final_size, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_finalsize); + offset += len_finalsize; + + proto_item_append_text(ti_ft, " Error code: %#" PRIx64, error_code); + } + break; + case FT_STOP_SENDING:{ + gint32 len_streamid; + guint64 stream_id, error_code; + gint32 len_error_code = 0; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", SS"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ss_stream_id, tvb, offset, -1, ENC_VARINT_QUIC, &stream_id, &len_streamid); + offset += len_streamid; + + proto_item_append_text(ti_ft, " id=%" PRIu64, stream_id); + col_append_fstr(pinfo->cinfo, COL_INFO, "(%" PRIu64 ")", stream_id); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ss_application_error_code, tvb, offset, -1, ENC_VARINT_QUIC, &error_code, &len_error_code); + offset += len_error_code; + + proto_item_append_text(ti_ft, " Error code: %#" PRIx64, error_code); + } + break; + case FT_CRYPTO: { + guint64 crypto_offset, crypto_length; + gint32 lenvar; + col_append_fstr(pinfo->cinfo, COL_INFO, ", CRYPTO"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_crypto_offset, tvb, offset, -1, ENC_VARINT_QUIC, &crypto_offset, &lenvar); + offset += lenvar; + proto_tree_add_item_ret_varint(ft_tree, hf_quic_crypto_length, tvb, offset, -1, ENC_VARINT_QUIC, &crypto_length, &lenvar); + offset += lenvar; + proto_tree_add_item(ft_tree, hf_quic_crypto_crypto_data, tvb, offset, (guint32)crypto_length, ENC_NA); + quic_crypto_state *crypto = quic_get_crypto_state(pinfo, quic_info, from_server, quic_packet->packet_type); + quic_crypto_info crypto_info = { + .packet_number = quic_packet->packet_number, + .crypto_offset = crypto_offset, + .from_server = from_server, + }; + dissect_quic_crypto_payload(tvb, offset, (int)crypto_length, pinfo, ft_tree, quic_info, &crypto_info, crypto); + offset += (guint32)crypto_length; + } + break; + case FT_NEW_TOKEN: { + guint64 token_length; + gint32 lenvar; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", NT"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_nt_length, tvb, offset, -1, ENC_VARINT_QUIC, &token_length, &lenvar); + offset += lenvar; + + proto_tree_add_item(ft_tree, hf_quic_nt_token, tvb, offset, (guint32)token_length, ENC_NA); + offset += (guint32)token_length; + } + break; + case FT_STREAM_8: + case FT_STREAM_9: + case FT_STREAM_A: + case FT_STREAM_B: + case FT_STREAM_C: + case FT_STREAM_D: + case FT_STREAM_E: + case FT_STREAM_F: { + guint64 stream_id, stream_offset = 0, length; + gint32 lenvar; + + offset -= 1; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", STREAM"); + + ftflags_tree = proto_item_add_subtree(ti_ftflags, ett_quic_ftflags); + proto_tree_add_item(ftflags_tree, hf_quic_stream_fin, tvb, offset, 1, ENC_NA); + proto_tree_add_item(ftflags_tree, hf_quic_stream_len, tvb, offset, 1, ENC_NA); + proto_tree_add_item(ftflags_tree, hf_quic_stream_off, tvb, offset, 1, ENC_NA); + offset += 1; + + ti_ftid = proto_tree_add_item_ret_varint(ft_tree, hf_quic_stream_stream_id, tvb, offset, -1, ENC_VARINT_QUIC, &stream_id, &lenvar); + ftid_tree = proto_item_add_subtree(ti_ftid, ett_quic_ftid); + proto_tree_add_item_ret_varint(ftid_tree, hf_quic_stream_initiator, tvb, offset, -1, ENC_VARINT_QUIC, NULL, NULL); + proto_tree_add_item_ret_varint(ftid_tree, hf_quic_stream_direction, tvb, offset, -1, ENC_VARINT_QUIC, NULL, NULL); + offset += lenvar; + + proto_item_append_text(ti_ft, " id=%" PRIu64, stream_id); + col_append_fstr(pinfo->cinfo, COL_INFO, "(%" PRIu64 ")", stream_id); + + proto_item_append_text(ti_ft, " fin=%d", !!(frame_type & FTFLAGS_STREAM_FIN)); + + if (!PINFO_FD_VISITED(pinfo)) { + quic_streams_add(pinfo, quic_info, stream_id); + } + + if (frame_type & FTFLAGS_STREAM_OFF) { + proto_tree_add_item_ret_varint(ft_tree, hf_quic_stream_offset, tvb, offset, -1, ENC_VARINT_QUIC, &stream_offset, &lenvar); + offset += lenvar; + } + proto_item_append_text(ti_ft, " off=%" PRIu64, stream_offset); + + if (frame_type & FTFLAGS_STREAM_LEN) { + proto_tree_add_item_ret_varint(ft_tree, hf_quic_stream_length, tvb, offset, -1, ENC_VARINT_QUIC, &length, &lenvar); + offset += lenvar; + } else { + length = tvb_reported_length_remaining(tvb, offset); + } + proto_item_append_text(ti_ft, " len=%" PRIu64 " dir=%s origin=%s", length, + val64_to_str_const(!!(stream_id & FTFLAGS_STREAM_DIRECTION), quic_frame_id_direction, "unknown"), + val64_to_str_const(!!(stream_id & FTFLAGS_STREAM_INITIATOR), quic_frame_id_initiator, "unknown")); + + proto_tree_add_item(ft_tree, hf_quic_stream_data, tvb, offset, (int)length, ENC_NA); + if (have_tap_listener(quic_follow_tap)) { + quic_follow_tap_data_t *follow_data = wmem_new0(pinfo->pool, quic_follow_tap_data_t); + + follow_data->tvb = tvb_new_subset_length(tvb, offset, (int)length); + follow_data->stream_id = stream_id; + follow_data->from_server = from_server; + + tap_queue_packet(quic_follow_tap, pinfo, follow_data); + } + quic_stream_state *stream = quic_get_stream_state(pinfo, quic_info, from_server, stream_id); + quic_stream_info stream_info = { + .stream_id = stream_id, + .stream_offset = stream_offset, + .quic_info = quic_info, + .from_server = from_server, + }; + dissect_quic_stream_payload(tvb, offset, (int)length, pinfo, ft_tree, quic_info, &stream_info, stream); + offset += (int)length; + } + break; + case FT_MAX_DATA:{ + gint32 len_maximumdata; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", MD"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_md_maximum_data, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_maximumdata); + offset += len_maximumdata; + } + break; + case FT_MAX_STREAM_DATA:{ + gint32 len_streamid, len_maximumstreamdata; + guint64 stream_id; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", MSD"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_msd_stream_id, tvb, offset, -1, ENC_VARINT_QUIC, &stream_id, &len_streamid); + offset += len_streamid; + + proto_item_append_text(ti_ft, " id=%" PRIu64, stream_id); + col_append_fstr(pinfo->cinfo, COL_INFO, "(%" PRIu64 ")", stream_id); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_msd_maximum_stream_data, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_maximumstreamdata); + offset += len_maximumstreamdata; + } + break; + case FT_MAX_STREAMS_BIDI: + case FT_MAX_STREAMS_UNI:{ + gint32 len_streamid; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", MS"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ms_max_streams, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_streamid); + offset += len_streamid; + } + break; + case FT_DATA_BLOCKED:{ + gint32 len_offset; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", DB"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_db_stream_data_limit, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_offset); + offset += len_offset; + } + break; + case FT_STREAM_DATA_BLOCKED:{ + gint32 len_streamid, len_offset; + guint64 stream_id; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", SDB"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_sdb_stream_id, tvb, offset, -1, ENC_VARINT_QUIC, &stream_id, &len_streamid); + offset += len_streamid; + + proto_item_append_text(ti_ft, " id=%" PRIu64, stream_id); + col_append_fstr(pinfo->cinfo, COL_INFO, "(%" PRIu64 ")", stream_id); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_sdb_stream_data_limit, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_offset); + offset += len_offset; + } + break; + case FT_STREAMS_BLOCKED_BIDI: + case FT_STREAMS_BLOCKED_UNI:{ + gint32 len_streamid; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", SB"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_sb_stream_limit, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_streamid); + offset += len_streamid; + } + break; + case FT_NEW_CONNECTION_ID: + case FT_MP_NEW_CONNECTION_ID:{ + gint32 len_sequence; + gint32 len_retire_prior_to; + uint64_t seq_num; + gint32 nci_length; + gint32 lenvar = 0; + gboolean valid_cid = FALSE; + + switch(frame_type){ + case FT_NEW_CONNECTION_ID: + col_append_fstr(pinfo->cinfo, COL_INFO, ", NCI"); + break; + case FT_MP_NEW_CONNECTION_ID: + col_append_fstr(pinfo->cinfo, COL_INFO, ", MP_NCI"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_uniflow_id, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + break; + } + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_nci_sequence, tvb, offset, -1, ENC_VARINT_QUIC, &seq_num, &len_sequence); + offset += len_sequence; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_nci_retire_prior_to, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_retire_prior_to); + offset += len_retire_prior_to; + + ti = proto_tree_add_item_ret_uint(ft_tree, hf_quic_nci_connection_id_length, tvb, offset, 1, ENC_BIG_ENDIAN, &nci_length); + offset++; + + valid_cid = nci_length >= 1 && nci_length <= QUIC_MAX_CID_LENGTH; + if (!valid_cid) { + expert_add_info_format(pinfo, ti, &ei_quic_protocol_violation, + "Connection ID Length must be between 1 and %d bytes", QUIC_MAX_CID_LENGTH); + } + + proto_tree_add_item(ft_tree, hf_quic_nci_connection_id, tvb, offset, nci_length, ENC_NA); + quic_cid_t cid = {.len=0}; + if (valid_cid && quic_info) { + tvb_memcpy(tvb, cid.cid, offset, nci_length); + cid.len = nci_length; + cid.seq_num = seq_num; + quic_connection_add_cid(quic_info, &cid, from_server); + } + offset += nci_length; + + proto_tree_add_item(ft_tree, hf_quic_nci_stateless_reset_token, tvb, offset, 16, ENC_NA); + if (valid_cid && quic_info) { + quic_add_stateless_reset_token(pinfo, tvb, offset, &cid); + } + offset += 16; + } + break; + case FT_RETIRE_CONNECTION_ID: + case FT_MP_RETIRE_CONNECTION_ID:{ + gint32 len_sequence; + gint32 lenvar; + + switch(frame_type){ + case FT_RETIRE_CONNECTION_ID: + col_append_fstr(pinfo->cinfo, COL_INFO, ", RC"); + break; + case FT_MP_RETIRE_CONNECTION_ID: + col_append_fstr(pinfo->cinfo, COL_INFO, ", MP_RC"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_uniflow_id, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + break; + } + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_rci_sequence, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_sequence); + offset += len_sequence; + } + break; + case FT_PATH_CHALLENGE:{ + col_append_fstr(pinfo->cinfo, COL_INFO, ", PC"); + + proto_tree_add_item(ft_tree, hf_quic_path_challenge_data, tvb, offset, 8, ENC_NA); + offset += 8; + } + break; + case FT_PATH_RESPONSE:{ + col_append_fstr(pinfo->cinfo, COL_INFO, ", PR"); + + proto_tree_add_item(ft_tree, hf_quic_path_response_data, tvb, offset, 8, ENC_NA); + offset += 8; + } + break; + case FT_CONNECTION_CLOSE_TPT: + case FT_CONNECTION_CLOSE_APP: + case FT_PATH_ABANDON_DRAFT04: + case FT_PATH_ABANDON:{ + gint32 len_reasonphrase, len_frametype, len_error_code; + guint64 len_reason = 0; + guint64 error_code; + const char *tls_alert = NULL; + + if (frame_type == FT_PATH_ABANDON_DRAFT04 || frame_type == FT_PATH_ABANDON) { + gint32 lenvar; + col_append_fstr(pinfo->cinfo, COL_INFO, ", PA"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_pa_dcid_sequence_number, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar); + offset += lenvar; + } else { + col_append_fstr(pinfo->cinfo, COL_INFO, ", CC"); + } + if (frame_type == FT_CONNECTION_CLOSE_TPT) { + proto_tree_add_item_ret_varint(ft_tree, hf_quic_cc_error_code, tvb, offset, -1, ENC_VARINT_QUIC, &error_code, &len_error_code); + if ((error_code >> 8) == 1) { // CRYPTO_ERROR (0x1XX) + tls_alert = try_val_to_str(error_code & 0xff, ssl_31_alert_description); + if (tls_alert) { + proto_tree_add_item(ft_tree, hf_quic_cc_error_code_tls_alert, tvb, offset + len_error_code - 1, 1, ENC_BIG_ENDIAN); + } + } + offset += len_error_code; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_cc_frame_type, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_frametype); + offset += len_frametype; + } else { /* FT_CONNECTION_CLOSE_APP) */ + proto_tree_add_item_ret_varint(ft_tree, hf_quic_cc_error_code_app, tvb, offset, -1, ENC_VARINT_QUIC, &error_code, &len_error_code); + offset += len_error_code; + } + + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_cc_reason_phrase_length, tvb, offset, -1, ENC_VARINT_QUIC, &len_reason, &len_reasonphrase); + offset += len_reasonphrase; + + proto_tree_add_item(ft_tree, hf_quic_cc_reason_phrase, tvb, offset, (guint32)len_reason, ENC_ASCII); + offset += (guint32)len_reason; + + // Transport Error codes higher than 0x3fff are for Private Use. + if (frame_type == FT_CONNECTION_CLOSE_TPT && error_code <= 0x3fff) { + proto_item_append_text(ti_ft, " Error code: %s", rval_to_str((guint32)error_code, quic_transport_error_code_vals, "Unknown (%d)")); + } else { + proto_item_append_text(ti_ft, " Error code: %#" PRIx64, error_code); + } + if (tls_alert) { + proto_item_append_text(ti_ft, " (%s)", tls_alert); + } + } + break; + case FT_HANDSHAKE_DONE: + col_append_fstr(pinfo->cinfo, COL_INFO, ", DONE"); + break; + case FT_DATAGRAM: + case FT_DATAGRAM_LENGTH:{ + gint32 dg_length; + guint64 length; + col_append_fstr(pinfo->cinfo, COL_INFO, ", DG"); + if (frame_type == FT_DATAGRAM_LENGTH) { + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_dg_length, tvb, offset, -1, ENC_VARINT_QUIC, &length, &dg_length); + offset += dg_length; + } else { + length = (guint32) tvb_reported_length_remaining(tvb, offset); + } + proto_tree_add_item(ft_tree, hf_quic_dg, tvb, offset, (guint32)length, ENC_NA); + offset += (guint32)length; + } + break; + case FT_IMMEDIATE_ACK_DRAFT05: + case FT_IMMEDIATE_ACK: + col_append_fstr(pinfo->cinfo, COL_INFO, ", IA"); + break; + case FT_ACK_FREQUENCY:{ + gint32 length; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", AF"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_af_sequence_number, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_af_ack_eliciting_threshold, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_af_request_max_ack_delay, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_af_reordering_threshold, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + } + break; + case FT_TIME_STAMP:{ + gint32 length; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", TS"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_ts, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + + } + break; + case FT_ADD_ADDRESS:{ + gint32 length; + guint64 config_bits; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", ADD_ADDRESS"); + + static int * const config_fields[] = { + &hf_quic_mp_add_address_reserved, + &hf_quic_mp_add_address_port_present, + &hf_quic_mp_add_address_ip_version, + NULL + }; + + proto_tree_add_bitmask_ret_uint64(ft_tree, tvb, offset, hf_quic_mp_add_address_first_byte, ett_quic, config_fields, ENC_BIG_ENDIAN, &config_bits); + offset += 1; + + proto_tree_add_item(ft_tree, hf_quic_mp_add_address_id, tvb, offset, 1, ENC_NA); + offset += 1; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_add_address_sq_number, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + + proto_tree_add_item(ft_tree, hf_quic_mp_add_address_interface_type, tvb, offset, 1, ENC_NA); + offset += 1; + + if ((config_bits & 0x06) == 0x06) { + ws_in6_addr addr; + tvb_get_ipv6(tvb, offset, &addr); + proto_tree_add_ipv6(ft_tree, hf_quic_mp_add_address_ip_address_v6, tvb, offset, 16, &addr); + offset += 16; + } else { + guint32 ip_config = tvb_get_ipv4(tvb, offset); + proto_tree_add_ipv4(ft_tree, hf_quic_mp_add_address_ip_address, tvb, offset, 4, ip_config); + offset += 4; + } + + if ((config_bits & 0x10 ) == 0x10) { + proto_tree_add_item(ft_tree, hf_quic_mp_add_address_port, tvb, offset, 2, ENC_NA); + offset += 2; + } + } + break; + case FT_REMOVE_ADDRESS:{ + gint32 length; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", REMOVE_ADDRESS"); + + proto_tree_add_item(ft_tree, hf_quic_mp_add_address_id, tvb, offset, 1, ENC_NA); + offset += 1; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_add_address_sq_number, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + } + break; + case FT_UNIFLOWS:{ + gint32 length; + gint32 len_receiving_uniflows; + gint32 len_active_sending_uniflows; + gint32 len_uniflow_id; + + guint64 ret_receiving_uniflows; + guint64 ret_active_sending_uniflows; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", UNIFLOWS"); + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_add_address_sq_number, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_receiving_uniflows, tvb, offset, -1, ENC_VARINT_QUIC, &ret_receiving_uniflows, &len_receiving_uniflows); + offset += (guint32)len_receiving_uniflows; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_active_sending_uniflows, tvb, offset, -1, ENC_VARINT_QUIC, &ret_active_sending_uniflows, &len_active_sending_uniflows); + offset += (guint32)len_active_sending_uniflows; + + proto_item *receiving_uniflows_ft; + proto_tree *receiving_uniflows_tree; + + receiving_uniflows_ft = proto_tree_add_item(ft_tree, hf_quic_mp_receiving_uniflow_info_section , tvb, offset, 1, ENC_NA); + receiving_uniflows_tree = proto_item_add_subtree(receiving_uniflows_ft, ett_quic_ft); + + for (guint64 i = 0; i < ret_receiving_uniflows; i++) { + proto_item *item_ft; + proto_tree *item_tree; + + item_ft = proto_tree_add_item(receiving_uniflows_tree, hf_quic_mp_uniflow_info_section, tvb, offset, 1, ENC_NA); + item_tree = proto_item_add_subtree(item_ft, ett_quic_ft); + + len_uniflow_id = 0; + + proto_tree_add_item_ret_varint(item_tree, hf_quic_mp_uniflow_id, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_uniflow_id); + offset += (guint32)len_uniflow_id; + + proto_tree_add_item(item_tree, hf_quic_mp_add_local_address_id , tvb, offset, 1, ENC_NA); + offset += 1; + } + + proto_item *active_sending_uniflows_ft; + proto_tree *active_sending_uniflows_tree; + + active_sending_uniflows_ft = proto_tree_add_item(ft_tree, hf_quic_mp_active_sending_uniflows_info_section, tvb, offset, 1, ENC_NA); + active_sending_uniflows_tree = proto_item_add_subtree(active_sending_uniflows_ft, ett_quic_ft); + + for (guint64 i = 0; i < ret_active_sending_uniflows; i++) { + proto_item *item_ft; + proto_tree *item_tree; + + item_ft = proto_tree_add_item(active_sending_uniflows_tree, hf_quic_mp_uniflow_info_section, tvb, offset, 1, ENC_NA); + item_tree = proto_item_add_subtree(item_ft, ett_quic_ft); + + len_uniflow_id = 0; + + proto_tree_add_item_ret_varint(item_tree, hf_quic_mp_uniflow_id, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &len_uniflow_id); + offset += (guint32)len_uniflow_id; + + proto_tree_add_item(item_tree, hf_quic_mp_add_local_address_id , tvb, offset, 1, ENC_NA); + offset += 1; + } + } + break; + case FT_PATH_STATUS_DRAFT04: + case FT_PATH_STATUS: + case FT_PATH_STANDBY: + case FT_PATH_AVAILABLE:{ + gint32 length; + + col_append_fstr(pinfo->cinfo, COL_INFO, ", PS"); + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_ps_dcid_sequence_number, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_ps_path_status_sequence_number, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + + if (frame_type == FT_PATH_STATUS || frame_type == FT_PATH_STATUS_DRAFT04) { + proto_tree_add_item_ret_varint(ft_tree, hf_quic_mp_ps_path_status, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &length); + offset += (guint32)length; + } + } + break; + default: + expert_add_info_format(pinfo, ti_ft, &ei_quic_ft_unknown, "Unknown Frame Type %#" PRIx64, frame_type); + break; + } + + proto_item_set_len(ti_ft, offset - orig_offset); + + return offset; +} + +static gboolean +quic_hp_cipher_init(quic_hp_cipher *hp_cipher, int hash_algo, guint8 key_length, guint8 *secret, guint32 version); +static gboolean +quic_pp_cipher_init(quic_pp_cipher *pp_cipher, int hash_algo, guint8 key_length, guint8 *secret, guint32 version); + +/** + * Given a QUIC message (header + non-empty payload), the actual packet number, + * try to decrypt it using the PP cipher. + * As the header points to the original buffer with an encrypted packet number, + * the (encrypted) packet number length is also included. + * + * The actual packet number must be constructed according to + * https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-12.3 + */ +static void +quic_decrypt_message(quic_pp_cipher *pp_cipher, tvbuff_t *head, guint header_length, + guint8 first_byte, guint pkn_len, guint64 packet_number, quic_decrypt_result_t *result, packet_info *pinfo) +{ + gcry_error_t err; + guint8 *header; + guint8 nonce[TLS13_AEAD_NONCE_LENGTH]; + guint8 *buffer; + guint8 atag[16]; + guint buffer_length; + const guchar **error = &result->error; + quic_datagram *dgram_info; + + dgram_info = (quic_datagram *)p_get_proto_data(wmem_file_scope(), pinfo, proto_quic, 0); + + DISSECTOR_ASSERT(pp_cipher != NULL); + DISSECTOR_ASSERT(pp_cipher->pp_cipher != NULL); + DISSECTOR_ASSERT(pkn_len < header_length); + DISSECTOR_ASSERT(1 <= pkn_len && pkn_len <= 4); + // copy header, but replace encrypted first byte and PKN by plaintext. + header = (guint8 *)tvb_memdup(pinfo->pool, head, 0, header_length); + header[0] = first_byte; + for (guint i = 0; i < pkn_len; i++) { + header[header_length - 1 - i] = (guint8)(packet_number >> (8 * i)); + } + + /* Input is "header || ciphertext (buffer) || auth tag (16 bytes)" */ + buffer_length = tvb_captured_length_remaining(head, header_length + 16); + if (buffer_length == 0) { + *error = "Decryption not possible, ciphertext is too short"; + return; + } + buffer = (guint8 *)tvb_memdup(wmem_file_scope(), head, header_length, buffer_length); + tvb_memcpy(head, atag, header_length + buffer_length, 16); + + memcpy(nonce, pp_cipher->pp_iv, TLS13_AEAD_NONCE_LENGTH); + /* Packet number is left-padded with zeroes and XORed with write_iv */ + phton64(nonce + sizeof(nonce) - 8, pntoh64(nonce + sizeof(nonce) - 8) ^ packet_number); + /* QUIC Multipath draft also uses the lower 32 bits of the CID + * sequence number (which MUST NOT go over 2^32 when multipath + * is used; also, the nonce must be at least 12 bytes.) + */ + if (dgram_info && dgram_info->conn && quic_multipath_negotiated(dgram_info->conn)) { + DISSECTOR_ASSERT_CMPINT(TLS13_AEAD_NONCE_LENGTH, >=, 12); + phton32(nonce + sizeof(nonce) - 12, pntoh32(nonce + sizeof(nonce) - 12) ^ (UINT32_MAX & dgram_info->seq_num)); + } + + gcry_cipher_reset(pp_cipher->pp_cipher); + err = gcry_cipher_setiv(pp_cipher->pp_cipher, nonce, TLS13_AEAD_NONCE_LENGTH); + if (err) { + *error = wmem_strdup_printf(wmem_file_scope(), "Decryption (setiv) failed: %s", gcry_strerror(err)); + return; + } + + /* associated data (A) is the contents of QUIC header */ + err = gcry_cipher_authenticate(pp_cipher->pp_cipher, header, header_length); + if (err) { + *error = wmem_strdup_printf(wmem_file_scope(), "Decryption (authenticate) failed: %s", gcry_strerror(err)); + return; + } + + /* Output ciphertext (C) */ + err = gcry_cipher_decrypt(pp_cipher->pp_cipher, buffer, buffer_length, NULL, 0); + if (err) { + *error = wmem_strdup_printf(wmem_file_scope(), "Decryption (decrypt) failed: %s", gcry_strerror(err)); + return; + } + + err = gcry_cipher_checktag(pp_cipher->pp_cipher, atag, 16); + if (err) { + *error = wmem_strdup_printf(wmem_file_scope(), "Decryption (checktag) failed: %s", gcry_strerror(err)); + return; + } + + result->error = NULL; + result->data = buffer; + result->data_len = buffer_length; +} + +static gboolean +quic_hkdf_expand_label(int hash_algo, guint8 *secret, guint secret_len, const char *label, guint8 *out, guint out_len) +{ + const StringInfo secret_si = { secret, secret_len }; + guchar *out_mem = NULL; + if (tls13_hkdf_expand_label(hash_algo, &secret_si, "tls13 ", label, out_len, &out_mem)) { + memcpy(out, out_mem, out_len); + wmem_free(NULL, out_mem); + return TRUE; + } + return FALSE; +} + +/** + * Compute the client and server initial secrets given Connection ID "cid". + * + * On success TRUE is returned and the two initial secrets are set. + * FALSE is returned on error (see "error" parameter for the reason). + */ +static gboolean +quic_derive_initial_secrets(const quic_cid_t *cid, + guint8 client_initial_secret[HASH_SHA2_256_LENGTH], + guint8 server_initial_secret[HASH_SHA2_256_LENGTH], + guint32 version, + const gchar **error) +{ + /* + * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2 + * + * initial_salt = 0xafbfec289993d24c9e9786f19c6111e04390a899 + * initial_secret = HKDF-Extract(initial_salt, client_dst_connection_id) + * + * client_initial_secret = HKDF-Expand-Label(initial_secret, + * "client in", "", Hash.length) + * server_initial_secret = HKDF-Expand-Label(initial_secret, + * "server in", "", Hash.length) + * + * Hash for handshake packets is SHA-256 (output size 32). + */ + static const guint8 handshake_salt_draft_22[20] = { + 0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0xe9, 0x19, 0x3a, + 0x96, 0xcd, 0x21, 0x51, 0x9e, 0xbd, 0x7a, 0x02, 0x64, 0x4a + }; + static const guint8 handshake_salt_draft_23[20] = { + 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, + 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02, + }; + static const guint8 handshake_salt_draft_29[20] = { + 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, + 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99 + }; + static const guint8 handshake_salt_v1[20] = { + 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, + 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a + }; + static const guint8 hanshake_salt_draft_q50[20] = { + 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, + 0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45 + }; + static const guint8 hanshake_salt_draft_t50[20] = { + 0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80, + 0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10 + }; + static const gint8 hanshake_salt_draft_t51[20] = { + 0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50, + 0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d + }; + static const guint8 handshake_salt_v2[20] = { + 0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, + 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9 + }; + + gcry_error_t err; + guint8 secret[HASH_SHA2_256_LENGTH]; + + if (version == 0x51303530) { + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_q50, sizeof(hanshake_salt_draft_q50), + cid->cid, cid->len, secret); + } else if (version == 0x54303530) { + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t50, sizeof(hanshake_salt_draft_t50), + cid->cid, cid->len, secret); + } else if (version == 0x54303531) { + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t51, sizeof(hanshake_salt_draft_t51), + cid->cid, cid->len, secret); + } else if (is_quic_draft_max(version, 22)) { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_22, sizeof(handshake_salt_draft_22), + cid->cid, cid->len, secret); + } else if (is_quic_draft_max(version, 28)) { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_23, sizeof(handshake_salt_draft_23), + cid->cid, cid->len, secret); + } else if (is_quic_draft_max(version, 32)) { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_29, sizeof(handshake_salt_draft_29), + cid->cid, cid->len, secret); + } else if (is_quic_draft_max(version, 34)) { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_v1, sizeof(handshake_salt_v1), + cid->cid, cid->len, secret); + } else { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_v2, sizeof(handshake_salt_v2), + cid->cid, cid->len, secret); + } + if (err) { + *error = wmem_strdup_printf(wmem_packet_scope(), "Failed to extract secrets: %s", gcry_strerror(err)); + return FALSE; + } + + if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "client in", + client_initial_secret, HASH_SHA2_256_LENGTH)) { + *error = "Key expansion (client) failed"; + return FALSE; + } + + if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "server in", + server_initial_secret, HASH_SHA2_256_LENGTH)) { + *error = "Key expansion (server) failed"; + return FALSE; + } + + *error = NULL; + return TRUE; +} + +/** + * Maps a Packet Protection cipher to the Packet Number protection cipher. + * See https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.3 + */ +static gboolean +quic_get_pn_cipher_algo(int cipher_algo, int *hp_cipher_mode) +{ + switch (cipher_algo) { + case GCRY_CIPHER_AES128: + case GCRY_CIPHER_AES256: + *hp_cipher_mode = GCRY_CIPHER_MODE_ECB; + return TRUE; + case GCRY_CIPHER_CHACHA20: + *hp_cipher_mode = GCRY_CIPHER_MODE_STREAM; + return TRUE; + default: + return FALSE; + } +} + +/* + * (Re)initialize the PNE/PP ciphers using the given cipher algorithm. + * If the optional base secret is given, then its length MUST match the hash + * algorithm output. + */ +static gboolean +quic_hp_cipher_prepare(quic_hp_cipher *hp_cipher, int hash_algo, int cipher_algo, guint8 *secret, const char **error, guint32 version) +{ + /* Clear previous state (if any). */ + quic_hp_cipher_reset(hp_cipher); + + int hp_cipher_mode; + if (!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) { + *error = "Unsupported cipher algorithm"; + return FALSE; + } + + if (gcry_cipher_open(&hp_cipher->hp_cipher, cipher_algo, hp_cipher_mode, 0)) { + quic_hp_cipher_reset(hp_cipher); + *error = "Failed to create HP cipher"; + return FALSE; + } + + if (secret) { + guint cipher_keylen = (guint8) gcry_cipher_get_algo_keylen(cipher_algo); + if (!quic_hp_cipher_init(hp_cipher, hash_algo, cipher_keylen, secret, version)) { + quic_hp_cipher_reset(hp_cipher); + *error = "Failed to derive key material for HP cipher"; + return FALSE; + } + } + + return TRUE; +} +static gboolean +quic_pp_cipher_prepare(quic_pp_cipher *pp_cipher, int hash_algo, int cipher_algo, int cipher_mode, guint8 *secret, const char **error, guint32 version) +{ + /* Clear previous state (if any). */ + quic_pp_cipher_reset(pp_cipher); + + int hp_cipher_mode; + if (!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) { + *error = "Unsupported cipher algorithm"; + return FALSE; + } + + if (gcry_cipher_open(&pp_cipher->pp_cipher, cipher_algo, cipher_mode, 0)) { + quic_pp_cipher_reset(pp_cipher); + *error = "Failed to create PP cipher"; + return FALSE; + } + + if (secret) { + guint cipher_keylen = (guint8) gcry_cipher_get_algo_keylen(cipher_algo); + if (!quic_pp_cipher_init(pp_cipher, hash_algo, cipher_keylen, secret, version)) { + quic_pp_cipher_reset(pp_cipher); + *error = "Failed to derive key material for PP cipher"; + return FALSE; + } + } + + return TRUE; +} +static gboolean +quic_ciphers_prepare(quic_ciphers *ciphers, int hash_algo, int cipher_algo, int cipher_mode, guint8 *secret, const char **error, guint32 version) +{ + return quic_hp_cipher_prepare(&ciphers->hp_cipher, hash_algo, cipher_algo, secret, error, version) && + quic_pp_cipher_prepare(&ciphers->pp_cipher, hash_algo, cipher_algo, cipher_mode, secret, error, version); +} + + +static gboolean +quic_create_initial_decoders(const quic_cid_t *cid, const gchar **error, quic_info_data_t *quic_info) +{ + guint8 client_secret[HASH_SHA2_256_LENGTH]; + guint8 server_secret[HASH_SHA2_256_LENGTH]; + + if (!quic_derive_initial_secrets(cid, client_secret, server_secret, quic_info->version, error)) { + return FALSE; + } + + /* Packet numbers are protected with AES128-CTR, + * initial packets are protected with AEAD_AES_128_GCM. */ + if (!quic_ciphers_prepare(&quic_info->client_initial_ciphers, GCRY_MD_SHA256, + GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, client_secret, error, quic_info->version) || + !quic_ciphers_prepare(&quic_info->server_initial_ciphers, GCRY_MD_SHA256, + GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, server_secret, error, quic_info->version)) { + return FALSE; + } + + return TRUE; +} + +static gboolean +quic_create_0rtt_decoder(guint i, gchar *early_data_secret, guint early_data_secret_len, + quic_ciphers *ciphers, int *cipher_algo, guint32 version) +{ + static const guint16 tls13_ciphers[] = { + 0x1301, /* TLS_AES_128_GCM_SHA256 */ + 0x1302, /* TLS_AES_256_GCM_SHA384 */ + 0x1303, /* TLS_CHACHA20_POLY1305_SHA256 */ + 0x1304, /* TLS_AES_128_CCM_SHA256 */ + 0x1305, /* TLS_AES_128_CCM_8_SHA256 */ + }; + if (i >= G_N_ELEMENTS(tls13_ciphers)) { + // end of list + return FALSE; + } + int cipher_mode = 0, hash_algo = 0; + const char *error_ignored = NULL; + if (tls_get_cipher_info(NULL, tls13_ciphers[i], cipher_algo, &cipher_mode, &hash_algo)) { + guint hash_len = gcry_md_get_algo_dlen(hash_algo); + if (hash_len == early_data_secret_len && quic_ciphers_prepare(ciphers, hash_algo, *cipher_algo, cipher_mode, early_data_secret, &error_ignored, version)) { + return TRUE; + } + } + /* This cipher failed, but there are more to try. */ + quic_ciphers_reset(ciphers); + return TRUE; +} + +static gboolean +quic_create_decoders(packet_info *pinfo, quic_info_data_t *quic_info, quic_ciphers *ciphers, + gboolean from_server, TLSRecordType type, const char **error) +{ + if (!quic_info->hash_algo) { + if (!tls_get_cipher_info(pinfo, 0, &quic_info->cipher_algo, &quic_info->cipher_mode, &quic_info->hash_algo)) { + *error = "Unable to retrieve cipher information"; + return FALSE; + } + } + + guint hash_len = gcry_md_get_algo_dlen(quic_info->hash_algo); + char *secret = (char *)wmem_alloc0(pinfo->pool, hash_len); + + if (!tls13_get_quic_secret(pinfo, from_server, type, hash_len, hash_len, secret)) { + *error = "Secrets are not available"; + return FALSE; + } + + if (!quic_ciphers_prepare(ciphers, quic_info->hash_algo, + quic_info->cipher_algo, quic_info->cipher_mode, secret, error, quic_info->version)) { + return FALSE; + } + + return TRUE; +} + +/** + * Tries to obtain the QUIC application traffic secrets. + */ +static gboolean +quic_get_traffic_secret(packet_info *pinfo, int hash_algo, quic_pp_state_t *pp_state, gboolean from_client) +{ + guint hash_len = gcry_md_get_algo_dlen(hash_algo); + char *secret = (char *)wmem_alloc0(pinfo->pool, hash_len); + if (!tls13_get_quic_secret(pinfo, !from_client, TLS_SECRET_APP, hash_len, hash_len, secret)) { + return FALSE; + } + pp_state->next_secret = (guint8 *)wmem_memdup(wmem_file_scope(), secret, hash_len); + return TRUE; +} + +/** + * Expands the secret (length MUST be the same as the "hash_algo" digest size) + * and initialize cipher with the new key. + */ +static gboolean +quic_hp_cipher_init(quic_hp_cipher *hp_cipher, int hash_algo, guint8 key_length, guint8 *secret, guint32 version) +{ + guchar hp_key[256/8]; + guint hash_len = gcry_md_get_algo_dlen(hash_algo); + char *label = !is_quic_v2(version) ? "quic hp" : "quicv2 hp"; + + if (!quic_hkdf_expand_label(hash_algo, secret, hash_len, label, hp_key, key_length)) { + return FALSE; + } + + return gcry_cipher_setkey(hp_cipher->hp_cipher, hp_key, key_length) == 0; +} +static gboolean +quic_pp_cipher_init(quic_pp_cipher *pp_cipher, int hash_algo, guint8 key_length, guint8 *secret, guint32 version) +{ + guchar write_key[256/8]; /* Maximum key size is for AES256 cipher. */ + guint hash_len = gcry_md_get_algo_dlen(hash_algo); + char *key_label = !is_quic_v2(version) ? "quic key" : "quicv2 key"; + char *iv_label = !is_quic_v2(version) ? "quic iv" : "quicv2 iv"; + + if (key_length > sizeof(write_key)) { + return FALSE; + } + + if (!quic_hkdf_expand_label(hash_algo, secret, hash_len, key_label, write_key, key_length) || + !quic_hkdf_expand_label(hash_algo, secret, hash_len, iv_label, pp_cipher->pp_iv, sizeof(pp_cipher->pp_iv))) { + return FALSE; + } + + return gcry_cipher_setkey(pp_cipher->pp_cipher, write_key, key_length) == 0; +} + + +/** + * Updates the packet protection secret to the next one. + */ +static void +quic_update_key(guint32 version, int hash_algo, quic_pp_state_t *pp_state) +{ + guint hash_len = gcry_md_get_algo_dlen(hash_algo); + const char *label = is_quic_draft_max(version, 23) ? "traffic upd" : (is_quic_draft_max(version, 34) ? "quic ku" : "quicv2 ku"); + gboolean ret = quic_hkdf_expand_label(hash_algo, pp_state->next_secret, hash_len, + label, pp_state->next_secret, hash_len); + /* This must always succeed as our hash algorithm was already validated. */ + DISSECTOR_ASSERT(ret); +} + +/** + * Retrieves the header protection cipher for short header packets and prepares + * the packet protection cipher. The application layer protocol is also queried. + */ +static quic_hp_cipher * +quic_get_1rtt_hp_cipher(packet_info *pinfo, quic_info_data_t *quic_info, gboolean from_server, const char **error) +{ + /* Keys were previously not available. */ + if (quic_info->skip_decryption) { + return NULL; + } + + quic_pp_state_t *client_pp = &quic_info->client_pp; + quic_pp_state_t *server_pp = &quic_info->server_pp; + quic_pp_state_t *pp_state = !from_server ? client_pp : server_pp; + + /* Try to lookup secrets if not available. */ + if (!quic_info->client_pp.next_secret) { + /* Query TLS for the cipher suite. */ + if (!tls_get_cipher_info(pinfo, 0, &quic_info->cipher_algo, &quic_info->cipher_mode, &quic_info->hash_algo)) { + /* We end up here if: + * no previous TLS handshake is found + * the used ciphers are unsupported + * some (unencrypted) padding is misdetected as SH coalesced packet + Because of the third scenario, we can't set quic_info->skip_decryption + to TRUE; otherwise we will stop decrypting the entire session, even if + we are able to. + Unfortunately, this way, we lost the optimization that allows skipping checks + for future packets in case the capture starts in midst of a + connection where the handshake is not present. + Note that even if we have a basic logic to detect unencrypted padding (via + check_dcid_on_coalesced_packet()), there is not a proper way to detect it + other than checking if the decryption successed + */ + *error = "Missing TLS handshake, unsupported ciphers or padding"; + return NULL; + } + + /* XXX: What if this is padding (or anything else) that is falsely + * detected as a SH packet after the TLS handshake in Initial frames + * but before the TLS handshake in the Handshake frames? Then the check + * above won't fail and we will retrieve the wrong TLS information, + * including ALPN. + */ + + /* Retrieve secrets for both the client and server. */ + if (!quic_get_traffic_secret(pinfo, quic_info->hash_algo, client_pp, TRUE) || + !quic_get_traffic_secret(pinfo, quic_info->hash_algo, server_pp, FALSE)) { + quic_info->skip_decryption = TRUE; + *error = "Secrets are not available"; + return NULL; + } + + // Create initial cipher handles for Key Phase 0 using the 1-RTT keys. + if (!quic_hp_cipher_prepare(&client_pp->hp_cipher, quic_info->hash_algo, + quic_info->cipher_algo, client_pp->next_secret, error, quic_info->version) || + !quic_pp_cipher_prepare(&client_pp->pp_ciphers[0], quic_info->hash_algo, + quic_info->cipher_algo, quic_info->cipher_mode, client_pp->next_secret, error, quic_info->version) || + !quic_hp_cipher_prepare(&server_pp->hp_cipher, quic_info->hash_algo, + quic_info->cipher_algo, server_pp->next_secret, error, quic_info->version) || + !quic_pp_cipher_prepare(&server_pp->pp_ciphers[0], quic_info->hash_algo, + quic_info->cipher_algo, quic_info->cipher_mode, server_pp->next_secret, error, quic_info->version)) { + quic_info->skip_decryption = TRUE; + return NULL; + } + // Rotate the 1-RTT key for the client and server for the next key update. + quic_update_key(quic_info->version, quic_info->hash_algo, client_pp); + quic_update_key(quic_info->version, quic_info->hash_algo, server_pp); + + // For efficiency, look up the application layer protocol once. The + // handshake must have been completed before, so ALPN is known. + const char *proto_name = tls_get_alpn(pinfo); + if (proto_name) { + quic_info->app_handle = dissector_get_string_handle(quic_proto_dissector_table, proto_name); + // If no specific handle is found, alias "h3-*" to "h3" and "doq-*" to "doq" + if (!quic_info->app_handle) { + if (g_str_has_prefix(proto_name, "h3-")) { + quic_info->app_handle = dissector_get_string_handle(quic_proto_dissector_table, "h3"); + } else if (g_str_has_prefix(proto_name, "doq-")) { + quic_info->app_handle = dissector_get_string_handle(quic_proto_dissector_table, "doq"); + } + } + } + } + + // Note: Header Protect cipher does not change after Key Update. + return &pp_state->hp_cipher; +} + +/** + * Tries to construct the appropriate cipher for the current key phase. + * See also "PROTECTED PAYLOAD DECRYPTION" comment on top of this file. + */ +static quic_pp_cipher * +quic_get_pp_cipher(gboolean key_phase, quic_info_data_t *quic_info, gboolean from_server) +{ + const char *error = NULL; + gboolean success = FALSE; + + /* Keys were previously not available. */ + if (quic_info->skip_decryption) { + return NULL; + } + + quic_pp_state_t *client_pp = &quic_info->client_pp; + quic_pp_state_t *server_pp = &quic_info->server_pp; + quic_pp_state_t *pp_state = !from_server ? client_pp : server_pp; + + /* + * If the key phase changed, try to decrypt the packet using the new cipher. + * If that fails, then it is either a malicious packet or out-of-order. + * In that case, try the previous cipher (unless it is the very first KP1). + * '!!' is due to key_phase being a signed bitfield, it forces -1 into 1. + */ + if (key_phase != !!pp_state->key_phase) { + quic_pp_cipher new_cipher; + + memset(&new_cipher, 0, sizeof(new_cipher)); + if (!quic_pp_cipher_prepare(&new_cipher, quic_info->hash_algo, + quic_info->cipher_algo, quic_info->cipher_mode, pp_state->next_secret, &error, quic_info->version)) { + /* This should never be reached, if the parameters were wrong + * before, then it should have set "skip_decryption". */ + REPORT_DISSECTOR_BUG("quic_pp_cipher_prepare unexpectedly failed: %s", error); + return NULL; + } + + // TODO verify decryption before switching keys. + success = TRUE; + + if (success) { + /* Verified the cipher, use it from now on and rotate the key. */ + /* Note that HP cipher is not touched. + https://tools.ietf.org/html/draft-ietf-quic-tls-32#section-5.4 + "The same header protection key is used for the duration of the + connection, with the value not changing after a key update" */ + quic_pp_cipher_reset(&pp_state->pp_ciphers[key_phase]); + pp_state->pp_ciphers[key_phase] = new_cipher; + quic_update_key(quic_info->version, quic_info->hash_algo, pp_state); + + pp_state->key_phase = key_phase; + //pp_state->changed_in_pkn = pkn; + + return &pp_state->pp_ciphers[key_phase]; + } else { + // TODO fallback to previous cipher + return NULL; + } + } + + return &pp_state->pp_ciphers[key_phase]; +} + +/** + * Process (protected) payload, adding the encrypted payload to the tree. If + * decryption is possible, frame dissection is also attempted. + * + * The given offset must correspond to the end of the QUIC header and begin of + * the (protected) payload. Dissected frames are appended to "tree" and expert + * info is attached to "ti" (the field with the encrypted payload). + */ +static void +quic_process_payload(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_item *ti, guint offset, + quic_info_data_t *quic_info, quic_packet_info_t *quic_packet, gboolean from_server, + quic_pp_cipher *pp_cipher, guint8 first_byte, guint pkn_len) +{ + quic_decrypt_result_t *decryption = &quic_packet->decryption; + + /* + * If no decryption error has occurred yet, try decryption on the first + * pass and store the result for later use. + */ + if (!PINFO_FD_VISITED(pinfo)) { + if (!quic_packet->decryption.error && quic_is_pp_cipher_initialized(pp_cipher)) { + quic_decrypt_message(pp_cipher, tvb, offset, first_byte, pkn_len, quic_packet->packet_number, &quic_packet->decryption, pinfo); + } + } + + if (decryption->error) { + expert_add_info_format(pinfo, ti, &ei_quic_decryption_failed, + "Decryption failed: %s", decryption->error); + } else if (decryption->data_len) { + tvbuff_t *decrypted_tvb = tvb_new_child_real_data(tvb, decryption->data, + decryption->data_len, decryption->data_len); + add_new_data_source(pinfo, decrypted_tvb, "Decrypted QUIC"); + + guint decrypted_offset = 0; + while (tvb_reported_length_remaining(decrypted_tvb, decrypted_offset) > 0) { + if (quic_info->version == 0x51303530 || quic_info->version == 0x54303530 || quic_info->version == 0x54303531) { + decrypted_offset = dissect_gquic_frame_type(decrypted_tvb, pinfo, tree, decrypted_offset, pkn_len, quic_info->gquic_info); + } else { + decrypted_offset = dissect_quic_frame_type(decrypted_tvb, pinfo, tree, decrypted_offset, quic_info, quic_packet, from_server); + } + } + } else if (quic_info->skip_decryption) { + expert_add_info_format(pinfo, ti, &ei_quic_decryption_failed, + "Decryption skipped because keys are not available."); + } +} + +static void +quic_verify_retry_token(tvbuff_t *tvb, quic_packet_info_t *quic_packet, const quic_cid_t *odcid, guint32 version) +{ + /* + * Verify the Retry Integrity Tag using the fixed key from + * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.8 + */ + static const guint8 key_v1[] = { + 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, + 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e + }; + static const guint8 nonce_v1[] = { + 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb + }; + static const guint8 key_draft_29[] = { + 0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0, + 0x57, 0x28, 0x15, 0x5a, 0x6c, 0xb9, 0x6b, 0xe1 + }; + static const guint8 key_v2[] = { + 0x8f, 0xb4, 0xb0, 0x1b, 0x56, 0xac, 0x48, 0xe2, + 0x60, 0xfb, 0xcb, 0xce, 0xad, 0x7c, 0xcc, 0x92 + }; + static const guint8 nonce_draft_29[] = { + 0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21, 0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c + }; + static const guint8 key_draft_25[] = { + 0x4d, 0x32, 0xec, 0xdb, 0x2a, 0x21, 0x33, 0xc8, + 0x41, 0xe4, 0x04, 0x3d, 0xf2, 0x7d, 0x44, 0x30, + }; + static const guint8 nonce_draft_25[] = { + 0x4d, 0x16, 0x11, 0xd0, 0x55, 0x13, 0xa5, 0x52, 0xc5, 0x87, 0xd5, 0x75, + }; + static const guint8 nonce_v2[] = { + 0xd8, 0x69, 0x69, 0xbc, 0x2d, 0x7c, 0x6d, 0x99, 0x90, 0xef, 0xb0, 0x4a + }; + gcry_cipher_hd_t h = NULL; + gcry_error_t err; + gint pseudo_packet_tail_length = tvb_reported_length(tvb) - 16; + + DISSECTOR_ASSERT(pseudo_packet_tail_length > 0); + + err = gcry_cipher_open(&h, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, 0); + DISSECTOR_ASSERT_HINT(err == 0, "create cipher"); + if (is_quic_draft_max(version, 28)) { + err = gcry_cipher_setkey(h, key_draft_25, sizeof(key_draft_25)); + } else if (is_quic_draft_max(version, 32)) { + err = gcry_cipher_setkey(h, key_draft_29, sizeof(key_draft_29)); + } else if (is_quic_draft_max(version, 34)) { + err = gcry_cipher_setkey(h, key_v1, sizeof(key_v1)); + } else { + err = gcry_cipher_setkey(h, key_v2, sizeof(key_v2)); + } + DISSECTOR_ASSERT_HINT(err == 0, "set key"); + if (is_quic_draft_max(version, 28)) { + err = gcry_cipher_setiv(h, nonce_draft_25, sizeof(nonce_draft_25)); + } else if (is_quic_draft_max(version, 32)) { + err = gcry_cipher_setiv(h, nonce_draft_29, sizeof(nonce_draft_29)); + } else if (is_quic_draft_max(version, 34)) { + err = gcry_cipher_setiv(h, nonce_v1, sizeof(nonce_v1)); + } else { + err = gcry_cipher_setiv(h, nonce_v2, sizeof(nonce_v2)); + } + DISSECTOR_ASSERT_HINT(err == 0, "set nonce"); + G_STATIC_ASSERT(sizeof(odcid->len) == 1); + err = gcry_cipher_authenticate(h, odcid, 1 + odcid->len); + DISSECTOR_ASSERT_HINT(err == 0, "aad1"); + err = gcry_cipher_authenticate(h, tvb_get_ptr(tvb, 0, pseudo_packet_tail_length), pseudo_packet_tail_length); + DISSECTOR_ASSERT_HINT(err == 0, "aad2"); + // Plaintext is empty, there is no need to call gcry_cipher_encrypt. + err = gcry_cipher_checktag(h, tvb_get_ptr(tvb, pseudo_packet_tail_length, 16), 16); + if (err) { + quic_packet->retry_integrity_failure = TRUE; + } else { + quic_packet->retry_integrity_success = TRUE; + } + gcry_cipher_close(h); +} + +void +quic_add_connection(packet_info *pinfo, quic_cid_t *cid) +{ + quic_datagram *dgram_info; + + dgram_info = (quic_datagram *)p_get_proto_data(wmem_file_scope(), pinfo, proto_quic, 0); + if (dgram_info && dgram_info->conn) { + quic_connection_add_cid(dgram_info->conn, cid, dgram_info->from_server); + } +} + +void +quic_add_loss_bits(packet_info *pinfo, guint64 value) +{ + quic_datagram *dgram_info; + quic_info_data_t *conn; + + dgram_info = (quic_datagram *)p_get_proto_data(wmem_file_scope(), pinfo, proto_quic, 0); + if (dgram_info && dgram_info->conn) { + conn = dgram_info->conn; + if (dgram_info->from_server) { + conn->server_loss_bits_recv = TRUE; + if (value == 1) { + conn->server_loss_bits_send = TRUE; + } + } else { + conn->client_loss_bits_recv = TRUE; + if (value == 1) { + conn->client_loss_bits_send = TRUE; + } + } + } +} + +/* Check if "multipath" feature has been negotiated */ +static gboolean +quic_multipath_negotiated(quic_info_data_t *conn) +{ + return conn->client_multipath && conn->server_multipath; +} + +void +quic_add_multipath(packet_info *pinfo) +{ + quic_datagram *dgram_info; + quic_info_data_t *conn; + + dgram_info = (quic_datagram *)p_get_proto_data(wmem_file_scope(), pinfo, proto_quic, 0); + if (dgram_info && dgram_info->conn) { + conn = dgram_info->conn; + if (dgram_info->from_server) { + conn->server_multipath = TRUE; + } else { + conn->client_multipath = TRUE; + } + } +} + +void +quic_add_grease_quic_bit(packet_info *pinfo) +{ + quic_datagram *dgram_info; + quic_info_data_t *conn; + + dgram_info = (quic_datagram *)p_get_proto_data(wmem_file_scope(), pinfo, proto_quic, 0); + if (dgram_info && dgram_info->conn) { + conn = dgram_info->conn; + if (dgram_info->from_server) { + conn->server_grease_quic_bit = TRUE; + } else { + conn->client_grease_quic_bit = TRUE; + } + } +} + +static quic_info_data_t * +quic_find_stateless_reset_token(packet_info *pinfo, tvbuff_t *tvb, gboolean *from_server) +{ + /* RFC 9000 10.3.1 Detecting a Stateless Reset + * "The endpoint identifies a received datagram as a Stateless + * Reset by comparing the last 16 bytes of the datagram with all + * stateless reset tokens associated with the remote address on + * which the datagram was received." That means we check all QUIC + * connections on the 5-tuple (as when a nonzero Connection ID is + * used there can be more than one.) + */ + quic_info_data_t* conn = quic_connection_from_conv(pinfo); + const quic_cid_item_t *cids; + + while (conn) { + gboolean conn_from_server; + conn_from_server = conn->server_port == pinfo->srcport && + addresses_equal(&conn->server_address, &pinfo->src); + cids = conn_from_server ? &conn->server_cids : &conn->client_cids; + while (cids) { + const quic_cid_t *cid = &cids->data; + /* XXX: Ibid., "An endpoint MUST NOT check for any stateless + * reset token associated with connection IDs it has not + * used or for connection IDs that have been retired," + * so we ideally should track when they are retired. + */ + if (cid->reset_token_set && + !tvb_memeql(tvb, -16, cid->reset_token, 16) ) { + *from_server = conn_from_server; + return conn; + } + cids = cids->next; + } + conn = conn->prev; + } + return NULL; +} + +void +quic_add_stateless_reset_token(packet_info *pinfo, tvbuff_t *tvb, gint offset, const quic_cid_t *cid) +{ + quic_datagram *dgram_info; + quic_info_data_t *conn; + quic_cid_item_t *cids; + + dgram_info = (quic_datagram *)p_get_proto_data(wmem_file_scope(), pinfo, proto_quic, 0); + if (dgram_info && dgram_info->conn) { + conn = dgram_info->conn; + if (dgram_info->from_server) { + cids = &conn->server_cids; + } else { + cids = &conn->client_cids; + } + + if (cid) { + while (cids) { + quic_cid_t *old_cid = &cids->data; + if (quic_connection_equal(old_cid, cid) ) { + tvb_memcpy(tvb, old_cid->reset_token, offset, 16); + old_cid->reset_token_set = TRUE; + return; + } + cids = cids->next; + } + } else { + /* If cid is NULL (this is a Handshake message), + * add it to the most recent cid. (There could + * have been a Retry.) + */ + while (cids->next != NULL) cids = cids->next; + quic_cid_t *old_cid = &cids->data; + tvb_memcpy(tvb, old_cid->reset_token, offset, 16); + old_cid->reset_token_set = TRUE; + return; + } + } + /* Failed to find cid. */ + return; +} + +static void +quic_add_connection_info(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, quic_info_data_t *conn) +{ + proto_tree *ctree; + proto_item *pi; + + ctree = proto_tree_add_subtree(tree, tvb, 0, 0, ett_quic_connection_info, NULL, "QUIC Connection information"); + if (!conn) { + expert_add_info(pinfo, ctree, &ei_quic_connection_unknown); + return; + } + + /* Set the conversation elements so that TLS and other subdissectors + * calling find_conversation_pinfo() find this QUIC connection and + * not all QUIC connections multiplexed on the same network 5-tuple. + */ + conversation_set_elements_by_id(pinfo, CONVERSATION_QUIC, conn->number); + pi = proto_tree_add_uint(ctree, hf_quic_connection_number, tvb, 0, 0, conn->number); + proto_item_set_generated(pi); +#if 0 + proto_tree_add_debug_text(ctree, "Client CID: %s", cid_to_string(pinfo->pool, &conn->client_cids.data)); + proto_tree_add_debug_text(ctree, "Server CID: %s", cid_to_string(pinfo->pool, &conn->server_cids.data)); + // Note: for Retry, this value has been cleared before. + proto_tree_add_debug_text(ctree, "InitialCID: %s", cid_to_string(pinfo->pool, &conn->client_dcid_initial)); +#endif +} + +/** + * Dissects the common part after the first byte for packets using the Long + * Header form. + */ +static int +dissect_quic_long_header_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, + guint offset, const quic_packet_info_t *quic_packet _U_, + quic_cid_t *dcid, quic_cid_t *scid) +{ + guint32 version; + guint32 dcil, scil; + proto_item *ti; + + version = tvb_get_ntohl(tvb, offset); + + ti = proto_tree_add_item(quic_tree, hf_quic_version, tvb, offset, 4, ENC_BIG_ENDIAN); + if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) { + proto_item_append_text(ti, " (Forcing Version Negotiation)"); + } + offset += 4; + + proto_tree_add_item_ret_uint(quic_tree, hf_quic_dcil, tvb, offset, 1, ENC_BIG_ENDIAN, &dcil); + offset++; + if (dcil) { + proto_tree_add_item(quic_tree, hf_quic_dcid, tvb, offset, dcil, ENC_NA); + // TODO expert info on CID mismatch with connection + if (dcil <= QUIC_MAX_CID_LENGTH) { + tvb_memcpy(tvb, dcid->cid, offset, dcil); + dcid->len = dcil; + } + offset += dcil; + } + + proto_tree_add_item_ret_uint(quic_tree, hf_quic_scil, tvb, offset, 1, ENC_BIG_ENDIAN, &scil); + offset++; + if (scil) { + proto_tree_add_item(quic_tree, hf_quic_scid, tvb, offset, scil, ENC_NA); + // TODO expert info on CID mismatch with connection + if (scil <= QUIC_MAX_CID_LENGTH) { + tvb_memcpy(tvb, scid->cid, offset, scil); + scid->len = scil; + } + offset += scil; + } + + if (dcid->len > 0) { + col_append_fstr(pinfo->cinfo, COL_INFO, ", DCID=%s", cid_to_string(pinfo->pool, dcid)); + } + if (scid->len > 0) { + col_append_fstr(pinfo->cinfo, COL_INFO, ", SCID=%s", cid_to_string(pinfo->pool, scid)); + } + return offset; +} + +/* Retry Packet dissection */ +static int +dissect_quic_retry_packet(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, + quic_datagram *dgram_info _U_, quic_packet_info_t *quic_packet, + const quic_cid_t *odcid, guint32 version) +{ + guint offset = 0; + quic_cid_t dcid = {.len=0}, scid = {.len=0}; + guint32 odcil = 0; + guint retry_token_len; + proto_item *ti; + + if (is_quic_v2(version)) { + proto_tree_add_item(quic_tree, hf_quic_long_packet_type_v2, tvb, offset, 1, ENC_NA); + } else { + proto_tree_add_item(quic_tree, hf_quic_long_packet_type, tvb, offset, 1, ENC_NA); + } + offset += 1; + col_set_str(pinfo->cinfo, COL_INFO, "Retry"); + + offset = dissect_quic_long_header_common(tvb, pinfo, quic_tree, offset, quic_packet, &dcid, &scid); + + if (is_quic_draft_max(version, 24)) { + proto_tree_add_item_ret_uint(quic_tree, hf_quic_odcil, tvb, offset, 1, ENC_NA, &odcil); + offset++; + proto_tree_add_item(quic_tree, hf_quic_odcid, tvb, offset, odcil, ENC_NA); + offset += odcil; + } + + retry_token_len = tvb_reported_length_remaining(tvb, offset); + // Remove length of Retry Integrity Tag + if (!is_quic_draft_max(version, 24) && retry_token_len >= 16) { + retry_token_len -= 16; + } + proto_tree_add_item(quic_tree, hf_quic_retry_token, tvb, offset, retry_token_len, ENC_NA); + offset += retry_token_len; + + if (!is_quic_draft_max(version, 24)) { + // Verify the Retry Integrity Tag according to + // https://tools.ietf.org/html/draft-ietf-quic-tls-25#section-5.8 + ti = proto_tree_add_item(quic_tree, hf_quic_retry_integrity_tag, tvb, offset, 16, ENC_NA); + if (!PINFO_FD_VISITED(pinfo) && odcid) { + // Skip validation if the Initial Packet is unknown, for example due + // to packet loss in the capture file. + quic_verify_retry_token(tvb, quic_packet, odcid, version); + } + if (quic_packet->retry_integrity_failure) { + expert_add_info(pinfo, ti, &ei_quic_bad_retry); + } else if (!quic_packet->retry_integrity_success) { + expert_add_info_format(pinfo, ti, &ei_quic_bad_retry, + "Cannot verify Retry Packet due to unknown ODCID"); + } else { + proto_item_append_text(ti, " [verified]"); + } + offset += 16; + } + + return offset; +} + +static int +dissect_quic_long_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, + quic_datagram *dgram_info, quic_packet_info_t *quic_packet) +{ + guint offset = 0; + guint8 long_packet_type; + guint32 version; + quic_cid_t dcid = {.len=0}, scid = {.len=0}; + gint32 len_token_length; + guint64 token_length; + gint32 len_payload_length; + guint64 payload_length; + guint8 first_byte = 0; + quic_info_data_t *conn = dgram_info->conn; + const gboolean from_server = dgram_info->from_server; + quic_ciphers *ciphers = NULL; + proto_item *ti; + + quic_extract_header(tvb, &long_packet_type, &version, &dcid, &scid); + if (!PINFO_FD_VISITED(pinfo)) { + quic_packet->packet_type = long_packet_type; + } + if (conn) { + if (long_packet_type == QUIC_LPT_INITIAL) { + ciphers = !from_server ? &conn->client_initial_ciphers : &conn->server_initial_ciphers; + } else if (long_packet_type == QUIC_LPT_0RTT && !from_server) { + ciphers = &conn->client_0rtt_ciphers; + } else if (long_packet_type == QUIC_LPT_HANDSHAKE) { + ciphers = !from_server ? &conn->client_handshake_ciphers : &conn->server_handshake_ciphers; + } + } + /* Prepare the Initial/Handshake cipher for header/payload decryption. */ + if (!PINFO_FD_VISITED(pinfo) && conn && ciphers) { +#define DIGEST_MIN_SIZE 32 /* SHA256 */ +#define DIGEST_MAX_SIZE 48 /* SHA384 */ + const gchar *error = NULL; + gchar early_data_secret[DIGEST_MAX_SIZE]; + guint early_data_secret_len = 0; + if (long_packet_type == QUIC_LPT_INITIAL && !from_server && + quic_connection_equal(&dcid, &conn->client_dcid_initial)) { + /* Create new decryption context based on the Client Connection + * ID from the *very first* Client Initial packet. */ + quic_create_initial_decoders(&dcid, &error, conn); + } else if (long_packet_type == QUIC_LPT_INITIAL && from_server && + version != conn->version) { + /* Compatibile Version Negotiation: the server (probably) updated the connection version. + We need to restart the ciphers since HP depends on version. + If/when updating the ciphers is a bit tricky during Compatible Version Negotiation. + TODO: do we really need to restart all the initial ciphers? + */ + conn->version = version; + quic_ciphers_reset(ciphers); + quic_create_initial_decoders(&conn->client_dcid_initial, &error, conn); + } else if (long_packet_type == QUIC_LPT_0RTT) { + early_data_secret_len = tls13_get_quic_secret(pinfo, FALSE, TLS_SECRET_0RTT_APP, DIGEST_MIN_SIZE, DIGEST_MAX_SIZE, early_data_secret); + if (early_data_secret_len == 0) { + error = "Secrets are not available"; + } + } else if (long_packet_type == QUIC_LPT_HANDSHAKE) { + if (!quic_are_ciphers_initialized(ciphers)) { + quic_create_decoders(pinfo, conn, ciphers, from_server, TLS_SECRET_HANDSHAKE, &error); + } + } + if (!error) { + guint32 pkn32 = 0; + int hp_cipher_algo = long_packet_type != QUIC_LPT_INITIAL && conn ? conn->cipher_algo : GCRY_CIPHER_AES128; + // PKN is after type(1) + version(4) + DCIL+DCID + SCIL+SCID + guint pn_offset = 1 + 4 + 1 + dcid.len + 1 + scid.len; + if (long_packet_type == QUIC_LPT_INITIAL) { + pn_offset += tvb_get_varint(tvb, pn_offset, 8, &token_length, ENC_VARINT_QUIC); + pn_offset += (guint)token_length; + } + pn_offset += tvb_get_varint(tvb, pn_offset, 8, &payload_length, ENC_VARINT_QUIC); + + // Assume failure unless proven otherwise. + error = "Header deprotection failed"; + if (long_packet_type != QUIC_LPT_0RTT) { + if (quic_decrypt_header(tvb, pn_offset, &ciphers->hp_cipher, hp_cipher_algo, &first_byte, &pkn32, FALSE)) { + error = NULL; + } + } else { + // Cipher is not stored with 0-RTT data or key, perform trial decryption. + for (guint i = 0; quic_create_0rtt_decoder(i, early_data_secret, early_data_secret_len, ciphers, &hp_cipher_algo, version); i++) { + if (quic_is_hp_cipher_initialized(&ciphers->hp_cipher) && quic_decrypt_header(tvb, pn_offset, &ciphers->hp_cipher, hp_cipher_algo, &first_byte, &pkn32, FALSE)) { + error = NULL; + break; + } + } + } + if (!error) { + quic_set_full_packet_number(conn, quic_packet, dgram_info->seq_num, from_server, first_byte, pkn32); + quic_packet->first_byte = first_byte; + } + } + if (error) { + quic_packet->decryption.error = wmem_strdup(wmem_file_scope(), error); + } + } else if (conn && quic_packet->pkn_len) { + first_byte = quic_packet->first_byte; + } + + proto_tree_add_item(quic_tree, hf_quic_fixed_bit, tvb, offset, 1, ENC_NA); + if (is_quic_v2(version)) { + proto_tree_add_item(quic_tree, hf_quic_long_packet_type_v2, tvb, offset, 1, ENC_NA); + } else { + proto_tree_add_item(quic_tree, hf_quic_long_packet_type, tvb, offset, 1, ENC_NA); + } + if (quic_packet->pkn_len) { + ti = proto_tree_add_uint(quic_tree, hf_quic_long_reserved, tvb, offset, 1, first_byte); + proto_item_set_generated(ti); + ti = proto_tree_add_uint(quic_tree, hf_quic_packet_number_length, tvb, offset, 1, first_byte); + proto_item_set_generated(ti); + } + offset += 1; + /* Trick: internal values in `long_packet_type` are always correctly mapped by V1 enum */ + col_set_str(pinfo->cinfo, COL_INFO, val_to_str_const(long_packet_type, quic_v1_long_packet_type_vals, "Long Header")); + + offset = dissect_quic_long_header_common(tvb, pinfo, quic_tree, offset, quic_packet, &dcid, &scid); + + if (long_packet_type == QUIC_LPT_INITIAL) { + ti = proto_tree_add_item_ret_varint(quic_tree, hf_quic_token_length, tvb, offset, -1, ENC_VARINT_QUIC, &token_length, &len_token_length); + offset += len_token_length; + + if (token_length) { + proto_tree_add_item(quic_tree, hf_quic_token, tvb, offset, (guint32)token_length, ENC_NA); + /* RFC 9287: "A client MAY also set the QUIC Bit to 0 in Initial, + * Handshake, or 0-RTT packets that are sent prior to receiving + * transport parameters from the server. However, a client MUST + * NOT set the QUIC Bit to 0 unless the Initial packets it sends + * include a token provided by the server in a NEW_TOKEN frame, + * received less than 604800 seconds (7 days) prior on a + * connection where the server also included the grease_quic_bit + * transport parameter." + */ + if (from_server) { + expert_add_info_format(pinfo, ti, &ei_quic_protocol_violation, + "Initial packets sent by the server must set the Token Length field to 0"); + } else if (conn) { + /* The client [may] know that the server supports greasing the + * QUIC bit, and perhaps will do so. (We can't really test if + * this token came less than 7 days ago from a server that + * supports it, so we'll assume it might be to be safe.) + */ + conn->server_grease_quic_bit = true; + } + offset += (guint)token_length; + } + } + + proto_tree_add_item_ret_varint(quic_tree, hf_quic_length, tvb, offset, -1, ENC_VARINT_QUIC, &payload_length, &len_payload_length); + offset += len_payload_length; + + if (quic_packet->decryption.error) { + expert_add_info_format(pinfo, quic_tree, &ei_quic_decryption_failed, + "Failed to create decryption context: %s", quic_packet->decryption.error); + return offset; + } + if (!conn || quic_packet->pkn_len == 0) { + // if not part of a connection, the full PKN cannot be reconstructed. + expert_add_info_format(pinfo, quic_tree, &ei_quic_decryption_failed, "Failed to decrypt packet number"); + return offset; + } + + ti = proto_tree_add_uint64(quic_tree, hf_quic_packet_number, tvb, offset, quic_packet->pkn_len, quic_packet->packet_number); + proto_item_set_generated(ti); + + offset += quic_packet->pkn_len; + col_append_fstr(pinfo->cinfo, COL_INFO, ", PKN: %" PRIu64, quic_packet->packet_number); + + /* Payload */ + ti = proto_tree_add_item(quic_tree, hf_quic_payload, tvb, offset, -1, ENC_NA); + + if (conn) { + quic_process_payload(tvb, pinfo, quic_tree, ti, offset, + conn, quic_packet, from_server, &ciphers->pp_cipher, first_byte, quic_packet->pkn_len); + } + if (!PINFO_FD_VISITED(pinfo) && !quic_packet->decryption.error) { + // Packet number is verified to be valid, remember it. + *quic_max_packet_number(conn, dgram_info->seq_num, from_server, first_byte) = quic_packet->packet_number; + } + offset += tvb_reported_length_remaining(tvb, offset); + + return offset; +} + +/* Check if "loss bits" feature has been negotiated */ +static gboolean +quic_loss_bits_negotiated(quic_info_data_t *conn, gboolean from_server) +{ + if (from_server) { + return conn->client_loss_bits_recv && conn->server_loss_bits_send; + } else { + return conn->server_loss_bits_recv && conn->client_loss_bits_send; + } +} + +static int +dissect_quic_short_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, + quic_datagram *dgram_info, quic_packet_info_t *quic_packet) +{ + guint offset = 0; + quic_cid_t dcid = {.len=0}; + guint8 first_byte = 0; + gboolean key_phase = FALSE; + proto_item *ti; + quic_pp_cipher *pp_cipher = NULL; + quic_info_data_t *conn = dgram_info->conn; + const gboolean from_server = dgram_info->from_server; + gboolean loss_bits_negotiated = FALSE; + + proto_item *pi = proto_tree_add_item(quic_tree, hf_quic_short, tvb, 0, -1, ENC_NA); + proto_tree *hdr_tree = proto_item_add_subtree(pi, ett_quic_short_header); + proto_tree_add_item(hdr_tree, hf_quic_header_form, tvb, 0, 1, ENC_NA); + + if (!PINFO_FD_VISITED(pinfo)) { + quic_packet->packet_type = QUIC_SHORT_PACKET; + } + if (conn) { + dcid.len = from_server ? conn->client_cids.data.len : conn->server_cids.data.len; + loss_bits_negotiated = quic_loss_bits_negotiated(conn, from_server); + } + if (!PINFO_FD_VISITED(pinfo) && conn) { + const gchar *error = NULL; + guint32 pkn32 = 0; + quic_hp_cipher *hp_cipher = quic_get_1rtt_hp_cipher(pinfo, conn, from_server, &error); + if (quic_is_hp_cipher_initialized(hp_cipher) && quic_decrypt_header(tvb, 1 + dcid.len, hp_cipher, conn->cipher_algo, &first_byte, &pkn32, loss_bits_negotiated)) { + quic_set_full_packet_number(conn, quic_packet, dgram_info->seq_num, from_server, first_byte, pkn32); + quic_packet->first_byte = first_byte; + } + if (error) { + quic_packet->decryption.error = wmem_strdup(wmem_file_scope(), error); + } + } else if (conn && quic_packet->pkn_len) { + first_byte = quic_packet->first_byte; + } + proto_tree_add_item(hdr_tree, hf_quic_fixed_bit, tvb, offset, 1, ENC_NA); + proto_tree_add_item(hdr_tree, hf_quic_spin_bit, tvb, offset, 1, ENC_NA); + /* Q and L bits are not protected by HP cipher */ + if (loss_bits_negotiated) { + proto_tree_add_item(hdr_tree, hf_quic_q_bit, tvb, offset, 1, ENC_NA); + proto_tree_add_item(hdr_tree, hf_quic_l_bit, tvb, offset, 1, ENC_NA); + } + if (quic_packet->pkn_len) { + key_phase = (first_byte & SH_KP) != 0; + /* No room for reserved bits with "loss bits" feature is enable */ + if (!loss_bits_negotiated) { + ti = proto_tree_add_uint(hdr_tree, hf_quic_short_reserved, tvb, offset, 1, first_byte); + proto_item_set_generated(ti); + } + ti = proto_tree_add_boolean(hdr_tree, hf_quic_key_phase, tvb, offset, 1, key_phase<<2); + proto_item_set_generated(ti); + ti = proto_tree_add_uint(hdr_tree, hf_quic_packet_number_length, tvb, offset, 1, first_byte); + proto_item_set_generated(ti); + } + offset += 1; + + col_clear(pinfo->cinfo, COL_INFO); + col_append_fstr(pinfo->cinfo, COL_INFO, "Protected Payload (KP%u)", key_phase); + + /* Connection ID */ + if (dcid.len > 0) { + proto_tree_add_item(hdr_tree, hf_quic_dcid, tvb, offset, dcid.len, ENC_NA); + tvb_memcpy(tvb, dcid.cid, offset, dcid.len); + offset += dcid.len; + const char *dcid_str = cid_to_string(pinfo->pool, &dcid); + col_append_fstr(pinfo->cinfo, COL_INFO, ", DCID=%s", dcid_str); + proto_item_append_text(pi, " DCID=%s", dcid_str); + } + + if (!PINFO_FD_VISITED(pinfo) && conn) { + pp_cipher = quic_get_pp_cipher(key_phase, conn, from_server); + } + + if (quic_packet->decryption.error) { + expert_add_info_format(pinfo, quic_tree, &ei_quic_decryption_failed, + "Failed to create decryption context: %s", quic_packet->decryption.error); + return offset; + } + if (!conn || conn->skip_decryption || quic_packet->pkn_len == 0) { + return offset; + } + + /* Packet Number */ + ti = proto_tree_add_uint64(hdr_tree, hf_quic_packet_number, tvb, offset, quic_packet->pkn_len, quic_packet->packet_number); + proto_item_set_generated(ti); + offset += quic_packet->pkn_len; + col_append_fstr(pinfo->cinfo, COL_INFO, ", PKN: %" PRIu64, quic_packet->packet_number); + proto_item_append_text(pi, " PKN=%" PRIu64, quic_packet->packet_number); + + /* Protected Payload */ + ti = proto_tree_add_item(hdr_tree, hf_quic_protected_payload, tvb, offset, -1, ENC_NA); + + if (conn) { + quic_process_payload(tvb, pinfo, quic_tree, ti, offset, + conn, quic_packet, from_server, pp_cipher, first_byte, quic_packet->pkn_len); + if (!PINFO_FD_VISITED(pinfo) && !quic_packet->decryption.error) { + // Packet number is verified to be valid, remember it. + *quic_max_packet_number(conn, dgram_info->seq_num, from_server, first_byte) = quic_packet->packet_number; + } + } + offset += tvb_reported_length_remaining(tvb, offset); + + return offset; +} + +void +quic_proto_tree_add_version(tvbuff_t *tvb, proto_tree *tree, int hfindex, guint offset) +{ + guint32 version; + proto_item *ti; + + ti = proto_tree_add_item_ret_uint(tree, hfindex, tvb, offset, 4, ENC_BIG_ENDIAN, &version); + if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) { + proto_item_append_text(ti, " (GREASE)"); + } +} + +static int +dissect_quic_version_negotiation(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, const quic_packet_info_t *quic_packet) +{ + guint offset = 0; + quic_cid_t dcid = {.len=0}, scid = {.len=0}; + + col_set_str(pinfo->cinfo, COL_INFO, "Version Negotiation"); + + proto_tree_add_item(quic_tree, hf_quic_vn_unused, tvb, offset, 1, ENC_NA); + offset += 1; + + offset = dissect_quic_long_header_common(tvb, pinfo, quic_tree, offset, quic_packet, &dcid, &scid); + + /* Supported Version */ + while(tvb_reported_length_remaining(tvb, offset) > 0){ + quic_proto_tree_add_version(tvb, quic_tree, hf_quic_supported_version, offset); + offset += 4; + } + + return offset; +} + +static int +dissect_quic_forcing_version_negotiation(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree, const quic_packet_info_t *quic_packet) +{ + guint offset = 0; + quic_cid_t dcid = {.len=0}, scid = {.len=0}; + + col_set_str(pinfo->cinfo, COL_INFO, "Forcing Version Negotiation"); + + proto_tree_add_item(quic_tree, hf_quic_vn_unused, tvb, offset, 1, ENC_NA); + offset += 1; + + offset = dissect_quic_long_header_common(tvb, pinfo, quic_tree, offset, quic_packet, &dcid, &scid); + + return offset; +} + +static tvbuff_t * +quic_get_message_tvb(tvbuff_t *tvb, const guint offset) +{ + guint64 token_length; + guint64 payload_length; + guint8 packet_type = tvb_get_guint8(tvb, offset); + // Retry and VN packets cannot be coalesced (clarified in draft -14). + if (packet_type & 0x80) { + guint version = tvb_get_ntohl(tvb, offset + 1); + guint8 long_packet_type = quic_get_long_packet_type(packet_type, version); + if (long_packet_type != QUIC_LPT_RETRY) { + // long header form, check version + // If this is not a VN packet but a valid long form, extract a subset. + // TODO check for valid QUIC versions as future versions might change the format. + if (version != 0) { + guint length = 5; // flag (1 byte) + version (4 bytes) + length += 1 + tvb_get_guint8(tvb, offset + length); // DCID + length += 1 + tvb_get_guint8(tvb, offset + length); // SCID + if (long_packet_type == QUIC_LPT_INITIAL) { + length += tvb_get_varint(tvb, offset + length, 8, &token_length, ENC_VARINT_QUIC); + length += (guint)token_length; + } + length += tvb_get_varint(tvb, offset + length, 8, &payload_length, ENC_VARINT_QUIC); + length += (guint)payload_length; + if (payload_length <= G_MAXINT32 && length < (guint)tvb_reported_length_remaining(tvb, offset)) { + return tvb_new_subset_length(tvb, offset, length); + } + } + } + } + + // short header form, VN or unknown message, return remaining data. + return tvb_new_subset_remaining(tvb, offset); +} + +static int +dissect_quic_stateless_reset(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *quic_tree, const quic_datagram *dgram_info _U_) +{ + proto_item *ti; + + col_set_str(pinfo->cinfo, COL_INFO, "Stateless Reset"); + + ti = proto_tree_add_uint(quic_tree, hf_quic_packet_length, tvb, 0, 0, tvb_reported_length(tvb)); + proto_item_set_generated(ti); + ti = proto_tree_add_item(quic_tree, hf_quic_header_form, tvb, 0, 1, ENC_NA); + if (tvb_get_guint8(tvb, 0) & 0x80) { + /* RFC 9000 says that endpoints MUST treat any packets ending in a valid + * stateless reset token as a Stateless Reset, even though they MUST + * send them formatted as packets with short headers. + */ + expert_add_info_format(pinfo, ti, &ei_quic_protocol_violation, + "Stateless Reset packets must be formatted as with short header"); + } + proto_tree_add_item(quic_tree, hf_quic_fixed_bit, tvb, 0, 1, ENC_NA); + proto_tree_add_bits_item(quic_tree, hf_quic_unpredictable_bits, tvb, 2, (tvb_reported_length(tvb) - 16)*8 - 2, ENC_NA); + proto_tree_add_item(quic_tree, hf_quic_stateless_reset_token, tvb, tvb_reported_length(tvb)-16, 16, ENC_NA); + + return tvb_reported_length(tvb); +} + +/** + * Extracts necessary information from header to find any existing connection. + * There are two special values for "long_packet_type": + * * QUIC_SHORT_PACKET for short header packets; + * * QUIC_LPT_VER_NEG for Version Negotiation packets. + * DCID and SCID are not modified unless available. For short header packets, + * DCID length is unknown, so the caller should truncate it as needed. + */ +static void +quic_extract_header(tvbuff_t *tvb, guint8 *long_packet_type, guint32 *version, + quic_cid_t *dcid, quic_cid_t *scid) +{ + guint offset = 0; + + guint8 packet_type = tvb_get_guint8(tvb, offset); + gboolean is_long_header = packet_type & 0x80; + + offset++; + + if (is_long_header) { + // long header form + *version = tvb_get_ntohl(tvb, offset); + *long_packet_type = quic_get_long_packet_type(packet_type, *version); + } else { + // short header form, store dummy value that is not a long packet type. + *long_packet_type = QUIC_SHORT_PACKET; + } + + + if (is_long_header) { + /* VN packets don't have any real packet type field, even if they have + a long header: use a dummy value */ + if (*version == 0x00000000) + *long_packet_type = QUIC_LPT_VER_NEG; + + // skip version + offset += 4; + + // read DCID and SCID (both are prefixed by a length byte). + guint8 dcil = tvb_get_guint8(tvb, offset); + offset++; + + if (dcil && dcil <= QUIC_MAX_CID_LENGTH) { + tvb_memcpy(tvb, dcid->cid, offset, dcil); + dcid->len = dcil; + } + offset += dcil; + + guint8 scil = tvb_get_guint8(tvb, offset); + offset++; + if (scil && scil <= QUIC_MAX_CID_LENGTH) { + tvb_memcpy(tvb, scid->cid, offset, scil); + scid->len = scil; + } + } else { + // Definitely not draft -10, set version to dummy value. + *version = 0; + // For short headers, the DCID length is unknown and could be 0 or + // anything from 1 to 20 bytes. Copy the maximum possible and let the + // consumer truncate it as necessary. + tvb_memcpy(tvb, dcid->cid, offset, QUIC_MAX_CID_LENGTH); + dcid->len = QUIC_MAX_CID_LENGTH; + } +} + +/** + * Sanity check on (coalasced) packet. + * https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-12.2 + * "Senders MUST NOT coalesce QUIC packets with different connection IDs + * into a single UDP datagram" + * For the first packet of the datagram, we simply save the DCID for later usage (no real check). + * For any subsequent packets, we control if DCID is valid. + * XXX: Generic Segmentation Offload (GSO) captures from Linux create headaches + * here, and even more so with short header packets. (#19109) + */ +static gboolean +check_dcid_on_coalesced_packet(tvbuff_t *tvb, const quic_datagram *dgram_info, + gboolean is_first_packet, quic_cid_t *first_packet_dcid) +{ + guint offset = 0; + guint8 first_byte, dcid_len; + quic_cid_t dcid = {.len=0}; + quic_info_data_t *conn = dgram_info->conn; + gboolean from_server = dgram_info->from_server; + bool grease_quic_bit; + + first_byte = tvb_get_guint8(tvb, offset); + offset++; + if (first_byte & 0x80) { + offset += 4; /* Skip version */ + dcid_len = tvb_get_guint8(tvb, offset); + offset++; + if (dcid_len && dcid_len <= QUIC_MAX_CID_LENGTH) { + dcid.len = dcid_len; + tvb_memcpy(tvb, dcid.cid, offset, dcid.len); + } + } else { + if (conn) { + dcid.len = from_server ? conn->client_cids.data.len : conn->server_cids.data.len; + if (dcid.len) { + tvb_memcpy(tvb, dcid.cid, offset, dcid.len); + } + } else { + /* If we don't have a valid quic_info_data_t structure for this flow, + we can't really validate the CID. */ + return TRUE; + } + } + + if (conn) { + grease_quic_bit = from_server ? conn->client_grease_quic_bit : conn->server_grease_quic_bit; + } else { + /* Assume we're allowed to grease the Fixed bit if no connection. */ + grease_quic_bit = true; + } + + if (is_first_packet) { + *first_packet_dcid = dcid; + return TRUE; /* Nothing to check */ + } + + if (!grease_quic_bit && (first_byte & 0x40) == 0) { + return false; + } + + /* If the first QUIC packet in the frame is an Initial or 0-RTT packet, + * then subsequent packets cannot be Short Header packets because the + * 1-RTT keys have not been negotiated yet on this connection. + * (Initial packets can be coalesced with with 0-RTT or Handshake + * long header packets, and it might be possible for Handshake long + * header packets to be coalesced with 1-RTT packets.) + */ + if (dgram_info->first_packet.packet_type == QUIC_LPT_INITIAL || + dgram_info->first_packet.packet_type == QUIC_LPT_0RTT) { + if ((first_byte & 0x80) == 0) { + return false; + } + } + + return quic_connection_equal(&dcid, first_packet_dcid); +} + +static int +dissect_quic(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + void *data _U_) +{ + proto_item *quic_ti, *ti; + proto_tree *quic_tree; + guint offset = 0; + quic_datagram *dgram_info = NULL; + quic_packet_info_t *quic_packet = NULL; + quic_cid_t real_retry_odcid = {.len=0}, *retry_odcid = NULL; + quic_cid_t first_packet_dcid = {.len=0}; /* DCID of the first packet of the datagram */ + + col_set_str(pinfo->cinfo, COL_PROTOCOL, "QUIC"); + + if (PINFO_FD_VISITED(pinfo)) { + dgram_info = (quic_datagram *)p_get_proto_data(wmem_file_scope(), pinfo, proto_quic, 0); + } + if (!dgram_info) { + dgram_info = wmem_new0(wmem_file_scope(), quic_datagram); + p_add_proto_data(wmem_file_scope(), pinfo, proto_quic, 0, dgram_info); + } + + quic_ti = proto_tree_add_item(tree, proto_quic, tvb, 0, -1, ENC_NA); + quic_tree = proto_item_add_subtree(quic_ti, ett_quic); + + if (!PINFO_FD_VISITED(pinfo)) { + guint8 long_packet_type; + guint32 version; + quic_cid_t dcid = {.len=0}, scid = {.len=0}; + gboolean from_server = FALSE; + quic_info_data_t *conn; + + quic_extract_header(tvb, &long_packet_type, &version, &dcid, &scid); + conn = quic_connection_find(pinfo, long_packet_type, &dcid, &from_server); + if (conn && long_packet_type == QUIC_LPT_RETRY && conn->client_dcid_set) { + // Save the original client DCID before erasure. + real_retry_odcid = conn->client_dcid_initial; + retry_odcid = &real_retry_odcid; + } + if (!conn && tvb_bytes_exist(tvb, -16, 16) && (conn = quic_find_stateless_reset_token(pinfo, tvb, &from_server))) { + dgram_info->stateless_reset = TRUE; + } else { + quic_connection_create_or_update(&conn, pinfo, long_packet_type, version, &scid, &dcid, from_server); + } + dgram_info->conn = conn; + dgram_info->from_server = from_server; + /* Senders MUST not coalesce packets with a different Connection ID + * into the same datagram, so we can store the connection ID sequence + * number here. + */ + dgram_info->seq_num = dcid.seq_num; +#if 0 + proto_tree_add_debug_text(quic_tree, "Connection: %d %p DCID=%s SCID=%s from_server:%d", pinfo->num, dgram_info->conn, cid_to_string(pinfo->pool, &dcid), cid_to_string(pinfo->pool, &scid), dgram_info->from_server); + } else { + proto_tree_add_debug_text(quic_tree, "Connection: %d %p from_server:%d", pinfo->num, dgram_info->conn, dgram_info->from_server); +#endif + } + + quic_add_connection_info(tvb, pinfo, quic_tree, dgram_info->conn); + + if (dgram_info->stateless_reset) { + return dissect_quic_stateless_reset(tvb, pinfo, quic_tree, dgram_info); + } + + do { + if (!quic_packet) { + quic_packet = &dgram_info->first_packet; + } else if (!PINFO_FD_VISITED(pinfo)) { + quic_packet->next = wmem_new0(wmem_file_scope(), quic_packet_info_t); + quic_packet = quic_packet->next; + } else { + quic_packet = quic_packet->next; + DISSECTOR_ASSERT(quic_packet); + } + + /* Ensure that coalesced QUIC packets end up separated. */ + if (offset > 0) { + quic_ti = proto_tree_add_item(tree, proto_quic, tvb, offset, -1, ENC_NA); + quic_tree = proto_item_add_subtree(quic_ti, ett_quic); + } + + tvbuff_t *next_tvb = quic_get_message_tvb(tvb, offset); + + if (!check_dcid_on_coalesced_packet(next_tvb, dgram_info, offset == 0, &first_packet_dcid)) { + /* Coalesced packet with unexpected CID; it probably is some kind + of unencrypted padding data added after the valid QUIC payload */ + expert_add_info_format(pinfo, quic_tree, &ei_quic_coalesced_padding_data, + "(Random) padding data appended to the datagram"); + break; + } + + proto_item_set_len(quic_ti, tvb_reported_length(next_tvb)); + ti = proto_tree_add_uint(quic_tree, hf_quic_packet_length, next_tvb, 0, 0, tvb_reported_length(next_tvb)); + proto_item_set_generated(ti); + guint new_offset = 0; + guint8 first_byte = tvb_get_guint8(next_tvb, 0); + if (first_byte & 0x80) { + proto_tree_add_item(quic_tree, hf_quic_header_form, next_tvb, 0, 1, ENC_NA); + guint32 version = tvb_get_ntohl(next_tvb, 1); + guint8 long_packet_type = quic_get_long_packet_type(first_byte, version); + if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) { + offset += dissect_quic_forcing_version_negotiation(next_tvb, pinfo, quic_tree, quic_packet); + if (tvb_reported_length_remaining(tvb, offset)) { + /* We can't decrypt any remaining data because we don't have a valid version */ + expert_add_info_format(pinfo, quic_tree, &ei_quic_data_after_forcing_vn, + "Data appended after a Forcing VN can't be decrypted"); + } + break; + } + if (version == 0) { + offset += dissect_quic_version_negotiation(next_tvb, pinfo, quic_tree, quic_packet); + break; + } + if (long_packet_type == QUIC_LPT_RETRY) { + new_offset = dissect_quic_retry_packet(next_tvb, pinfo, quic_tree, dgram_info, quic_packet, retry_odcid, version); + } else { + new_offset = dissect_quic_long_header(next_tvb, pinfo, quic_tree, dgram_info, quic_packet); + } + } else { /* Note that the "Fixed" bit might have been greased, + so 0x00 is a perfectly valid value as first_byte */ + new_offset = dissect_quic_short_header(next_tvb, pinfo, quic_tree, dgram_info, quic_packet); + } + if (tvb_reported_length_remaining(next_tvb, new_offset)) { + // should usually not be present unless decryption is not possible. + proto_tree_add_item(quic_tree, hf_quic_remaining_payload, next_tvb, new_offset, -1, ENC_NA); + } + offset += tvb_reported_length(next_tvb); + } while (tvb_reported_length_remaining(tvb, offset)); + + return offset; +} + +static gboolean +dissect_quic_short_header_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + // If this capture does not contain QUIC, skip the more expensive checks. + if (quic_cid_lengths == 0) { + return FALSE; + } + + // Is this a SH packet after connection migration? SH (since draft -22): + // Flag (1) + DCID (1-20) + PKN (1/2/4) + encrypted payload (>= 16). + if (tvb_captured_length(tvb) < 1 + 1 + 1 + 16) { + return FALSE; + } + + // DCID length is unknown, so extract the maximum and look for a match. + quic_cid_t dcid = {.len = MIN(QUIC_MAX_CID_LENGTH, tvb_captured_length(tvb) - 1 - 1 - 16)}; + tvb_memcpy(tvb, dcid.cid, 1, dcid.len); + gboolean from_server; + if (!quic_connection_find(pinfo, QUIC_SHORT_PACKET, &dcid, &from_server)) { + return FALSE; + } + + conversation_t *conversation = find_or_create_conversation(pinfo); + conversation_set_dissector(conversation, quic_handle); + dissect_quic(tvb, pinfo, tree, NULL); + return TRUE; +} + +static gboolean dissect_quic_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data) +{ + /* + * Since draft -22: + * Flag (1 byte) + Version (4 bytes) + + * Length (1 byte) + Destination Connection ID (0..255) + + * Length (1 byte) + Source Connection ID (0..255) + + * Payload length (1/2/4/8) + Packet number (1/2/4 bytes) + Payload. + * (absolute minimum: 9 + payload) + * (for Version Negotiation, payload len + PKN + payload is replaced by + * Supported Version (multiple of 4 bytes.) + */ + conversation_t *conversation = NULL; + int offset = 0; + guint8 flags, dcid, scid; + guint32 version; + gboolean is_quic = FALSE; + + /* Verify packet size (Flag (1 byte) + Connection ID (8 bytes) + Version (4 bytes)) */ + if (tvb_captured_length(tvb) < 13) + { + return FALSE; + } + + flags = tvb_get_guint8(tvb, offset); + /* Check if long Packet is set */ + if((flags & 0x80) == 0) { + // Perhaps this is a short header, check it. + return dissect_quic_short_header_heur(tvb, pinfo, tree); + } + offset += 1; + + // check for draft QUIC version (for draft -11 and newer) + version = tvb_get_ntohl(tvb, offset); + is_quic = (quic_draft_version(version) >= 11); + if (!is_quic) { + return FALSE; + } + + /* Check that CIDs lengths are valid */ + offset += 4; + dcid = tvb_get_guint8(tvb, offset); + if (dcid > QUIC_MAX_CID_LENGTH) { + return FALSE; + } + offset += 1 + dcid; + if (offset >= (int)tvb_captured_length(tvb)) { + return FALSE; + } + scid = tvb_get_guint8(tvb, offset); + if (scid > QUIC_MAX_CID_LENGTH) { + return FALSE; + } + + /* Ok! */ + conversation = find_or_create_conversation(pinfo); + conversation_set_dissector(conversation, quic_handle); + dissect_quic(tvb, pinfo, tree, data); + + return TRUE; +} + + +/** Initialize QUIC dissection state for a new capture file. */ +static void +quic_init(void) +{ + quic_connections = wmem_list_new(wmem_file_scope()); + quic_connections_count = 0; + quic_initial_connections = wmem_map_new(wmem_file_scope(), quic_connection_hash, quic_connection_equal); + quic_client_connections = wmem_map_new(wmem_file_scope(), quic_connection_hash, quic_connection_equal); + quic_server_connections = wmem_map_new(wmem_file_scope(), quic_connection_hash, quic_connection_equal); + quic_cid_lengths = 0; +} + +/** Release QUIC dissection state on closing a capture file. */ +static void +quic_cleanup(void) +{ + wmem_list_foreach(quic_connections, quic_connection_destroy, NULL); + quic_initial_connections = NULL; + quic_client_connections = NULL; + quic_server_connections = NULL; +} + +/* Follow QUIC Stream functionality {{{ */ +static void +quic_streams_add(packet_info *pinfo, quic_info_data_t *quic_info, guint64 stream_id) +{ + /* List: ordered list of Stream IDs in this connection */ + if (!quic_info->streams_list) { + quic_info->streams_list = wmem_list_new(wmem_file_scope()); + } + if (!wmem_list_find(quic_info->streams_list, GUINT_TO_POINTER(stream_id))) { + wmem_list_insert_sorted(quic_info->streams_list, GUINT_TO_POINTER(stream_id), + wmem_compare_uint); + } + + /* Map: first Stream ID for each UDP payload */ + quic_follow_stream *stream; + if (!quic_info->streams_map) { + quic_info->streams_map = wmem_map_new(wmem_file_scope(), g_direct_hash, g_direct_equal); + } + stream = wmem_map_lookup(quic_info->streams_map, GUINT_TO_POINTER(pinfo->num)); + if (!stream) { + stream = wmem_new0(wmem_file_scope(), quic_follow_stream); + stream->num = pinfo->num; + stream->stream_id = stream_id; + wmem_map_insert(quic_info->streams_map, GUINT_TO_POINTER(stream->num), stream); + } +} + +static quic_info_data_t * +get_conn_by_number(guint conn_number) +{ + quic_info_data_t *conn; + wmem_list_frame_t *elem; + + elem = wmem_list_head(quic_connections); + while (elem) { + conn = (quic_info_data_t *)wmem_list_frame_data(elem); + if (conn->number == conn_number) + return conn; + elem = wmem_list_frame_next(elem); + } + return NULL; +} + +gboolean +quic_get_stream_id_le(guint streamid, guint sub_stream_id, guint *sub_stream_id_out) +{ + quic_info_data_t *quic_info; + wmem_list_frame_t *curr_entry; + guint prev_stream_id; + + quic_info = get_conn_by_number(streamid); + if (!quic_info) { + return FALSE; + } + if (!quic_info->streams_list) { + return FALSE; + } + + prev_stream_id = G_MAXUINT32; + curr_entry = wmem_list_head(quic_info->streams_list); + while (curr_entry) { + if (GPOINTER_TO_UINT(wmem_list_frame_data(curr_entry)) > sub_stream_id && + prev_stream_id != G_MAXUINT32) { + *sub_stream_id_out = (guint)prev_stream_id; + return TRUE; + } + prev_stream_id = GPOINTER_TO_UINT(wmem_list_frame_data(curr_entry)); + curr_entry = wmem_list_frame_next(curr_entry); + } + + if (prev_stream_id != G_MAXUINT32) { + *sub_stream_id_out = prev_stream_id; + return TRUE; + } + + return FALSE; +} + +gboolean +quic_get_stream_id_ge(guint streamid, guint sub_stream_id, guint *sub_stream_id_out) +{ + quic_info_data_t *quic_info; + wmem_list_frame_t *curr_entry; + + quic_info = get_conn_by_number(streamid); + if (!quic_info) { + return FALSE; + } + if (!quic_info->streams_list) { + return FALSE; + } + + curr_entry = wmem_list_head(quic_info->streams_list); + while (curr_entry) { + if (GPOINTER_TO_UINT(wmem_list_frame_data(curr_entry)) >= sub_stream_id) { + /* StreamIDs are 64 bits long in QUIC, but "Follow Stream" generic code uses guint variables */ + *sub_stream_id_out = GPOINTER_TO_UINT(wmem_list_frame_data(curr_entry)); + return TRUE; + } + curr_entry = wmem_list_frame_next(curr_entry); + } + + return FALSE; +} + +static gboolean +quic_get_sub_stream_id(guint streamid, guint sub_stream_id, gboolean le, guint *sub_stream_id_out) +{ + if (le) { + return quic_get_stream_id_le(streamid, sub_stream_id, sub_stream_id_out); + } else { + return quic_get_stream_id_ge(streamid, sub_stream_id, sub_stream_id_out); + } +} + +static gchar * +quic_follow_conv_filter(epan_dissect_t *edt _U_, packet_info *pinfo, guint *stream, guint *sub_stream) +{ + quic_datagram *dgram_info = (quic_datagram *)p_get_proto_data(wmem_file_scope(), pinfo, proto_quic, 0); + + if (!dgram_info || !dgram_info->conn) { + return NULL; + } + + quic_info_data_t *conn = dgram_info->conn; + + /* First Stream ID in the selected packet */ + quic_follow_stream *s; + if (conn->streams_map) { + s = wmem_map_lookup(conn->streams_map, GUINT_TO_POINTER(pinfo->num)); + if (s) { + *stream = conn->number; + *sub_stream = (guint)s->stream_id; + return ws_strdup_printf("quic.connection.number eq %u and quic.stream.stream_id eq %u", conn->number, *sub_stream); + } + } + + return NULL; +} + +static gchar * +quic_follow_index_filter(guint stream, guint sub_stream) +{ + return ws_strdup_printf("quic.connection.number eq %u and quic.stream.stream_id eq %u", stream, sub_stream); +} + +static gchar * +quic_follow_address_filter(address *src_addr _U_, address *dst_addr _U_, int src_port _U_, int dst_port _U_) +{ + // This appears to be solely used for tshark. Let's not support matching by + // IP addresses and UDP ports for now since that fails after connection + // migration. If necessary, use udp_follow_address_filter. + return NULL; +} + +static tap_packet_status +follow_quic_tap_listener(void *tapdata, packet_info *pinfo, epan_dissect_t *edt _U_, const void *data, tap_flags_t flags _U_) +{ + follow_record_t *follow_record; + follow_info_t *follow_info = (follow_info_t *)tapdata; + const quic_follow_tap_data_t *follow_data = (const quic_follow_tap_data_t *)data; + + if (follow_info->substream_id != SUBSTREAM_UNUSED && + follow_info->substream_id != follow_data->stream_id) { + return TAP_PACKET_DONT_REDRAW; + } + + follow_record = g_new(follow_record_t, 1); + + // XXX: Ideally, we should also deal with stream retransmission + // and out of order packets in a similar manner to the TCP dissector, + // using the offset, plus ACKs and other information. + follow_record->data = g_byte_array_sized_new(tvb_captured_length(follow_data->tvb)); + follow_record->data = g_byte_array_append(follow_record->data, tvb_get_ptr(follow_data->tvb, 0, -1), tvb_captured_length(follow_data->tvb)); + follow_record->packet_num = pinfo->fd->num; + follow_record->abs_ts = pinfo->fd->abs_ts; + + /* This sets the address and port information the first time this + * stream is tapped. It will no longer be true after migration, but + * as it seems it's only used for display, using the initial values + * is the best we can do. + */ + + if (follow_data->from_server) { + follow_record->is_server = TRUE; + if (follow_info->client_port == 0) { + follow_info->server_port = pinfo->srcport; + copy_address(&follow_info->server_ip, &pinfo->src); + follow_info->client_port = pinfo->destport; + copy_address(&follow_info->client_ip, &pinfo->dst); + } + } else { + follow_record->is_server = FALSE; + if (follow_info->client_port == 0) { + follow_info->client_port = pinfo->srcport; + copy_address(&follow_info->client_ip, &pinfo->src); + follow_info->server_port = pinfo->destport; + copy_address(&follow_info->server_ip, &pinfo->dst); + } + } + + follow_info->bytes_written[follow_record->is_server] += follow_record->data->len; + + follow_info->payload = g_list_prepend(follow_info->payload, follow_record); + return TAP_PACKET_DONT_REDRAW; +} + +guint32 get_quic_connections_count(void) +{ + return quic_connections_count; +} +/* Follow QUIC Stream functionality }}} */ + +void +proto_register_quic(void) +{ + expert_module_t *expert_quic; + module_t *quic_module; + + static hf_register_info hf[] = { + { &hf_quic_connection_number, + { "Connection Number", "quic.connection.number", + FT_UINT32, BASE_DEC, NULL, 0x0, + "Connection identifier within this capture file", HFILL } + }, + + { &hf_quic_packet_length, + { "Packet Length", "quic.packet_length", + FT_UINT32, BASE_DEC, NULL, 0x0, + "Size of the QUIC packet", HFILL } + }, + + { &hf_quic_header_form, + { "Header Form", "quic.header_form", + FT_UINT8, BASE_DEC, VALS(quic_short_long_header_vals), 0x80, + "The most significant bit (0x80) of the first octet is set to 1 for long headers and 0 for short headers.", HFILL } + }, + + { &hf_quic_long_packet_type, + { "Packet Type", "quic.long.packet_type", + FT_UINT8, BASE_DEC, VALS(quic_v1_long_packet_type_vals), 0x30, + "Long Header Packet Type", HFILL } + }, + { &hf_quic_long_packet_type_v2, + { "Packet Type", "quic.long.packet_type_v2", + FT_UINT8, BASE_DEC, VALS(quic_v2_long_packet_type_vals), 0x30, + "Long Header Packet Type", HFILL } + }, + { &hf_quic_long_reserved, + { "Reserved", "quic.long.reserved", + FT_UINT8, BASE_DEC, NULL, 0x0c, + "Reserved bits (protected using header protection)", HFILL } + }, + { &hf_quic_packet_number_length, + { "Packet Number Length", "quic.packet_number_length", + FT_UINT8, BASE_DEC, VALS(quic_packet_number_lengths), 0x03, + "Packet Number field length (protected using header protection)", HFILL } + }, + { &hf_quic_dcid, + { "Destination Connection ID", "quic.dcid", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_scid, + { "Source Connection ID", "quic.scid", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_dcil, + { "Destination Connection ID Length", "quic.dcil", + FT_UINT8, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_scil, + { "Source Connection ID Length", "quic.scil", + FT_UINT8, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_token_length, + { "Token Length", "quic.token_length", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_token, + { "Token", "quic.token", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_length, + { "Length", "quic.length", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Length of Packet Number and Payload fields", HFILL } + }, + + { &hf_quic_packet_number, + { "Packet Number", "quic.packet_number", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Decoded packet number", HFILL } + }, + { &hf_quic_version, + { "Version", "quic.version", + FT_UINT32, BASE_RANGE_STRING | BASE_HEX, RVALS(quic_version_vals), 0x0, + NULL, HFILL } + }, + { &hf_quic_supported_version, + { "Supported Version", "quic.supported_version", + FT_UINT32, BASE_RANGE_STRING | BASE_HEX, RVALS(quic_version_vals), 0x0, + NULL, HFILL } + }, + { &hf_quic_vn_unused, + { "Unused", "quic.vn.unused", + FT_UINT8, BASE_HEX, NULL, 0x7F, + NULL, HFILL } + }, + { &hf_quic_short, + { "QUIC Short Header", "quic.short", + FT_NONE, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_fixed_bit, + { "Fixed Bit", "quic.fixed_bit", + FT_BOOLEAN, 8, NULL, 0x40, + "Must be 1", HFILL } + }, + { &hf_quic_spin_bit, + { "Spin Bit", "quic.spin_bit", + FT_BOOLEAN, 8, NULL, 0x20, + "Latency Spin Bit", HFILL } + }, + { &hf_quic_mp_add_address_first_byte, + { "Config", "quic.mp_first_byte", + FT_UINT8, BASE_HEX, NULL, 0, + NULL, HFILL } + }, + { &hf_quic_mp_add_address_reserved, + { "Reserved", "quic.mp_reserved_bit", + FT_UINT8, BASE_DEC, NULL, 0xE0, + NULL, HFILL } + }, + { &hf_quic_mp_add_address_port_present, + { "Port presence", "quic.port_presence_bit", + FT_BOOLEAN, 8, NULL, 0x10, + "Must be 1", HFILL } + }, + { &hf_quic_mp_add_address_ip_version, + { "IP Version", "quic.ip_version", + FT_UINT8, BASE_DEC, NULL, 0x0f, + NULL, HFILL } + }, + { &hf_quic_mp_add_address_id, + { "Address ID", "quic.mp_address_id", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_add_address_sq_number, + { "Sequence Number", "quic.mp_sequence_number", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_add_address_interface_type, + { "Interface Type", "quic.mp_interface_type", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_add_address_ip_address, + { "IP Address", "quic.mp_ip_address", + FT_IPv4, BASE_NONE, + NULL, 0x0, NULL, HFILL } + }, + { &hf_quic_mp_add_address_ip_address_v6, + { "IP Address", "quic.mp_ip_address_v6", + FT_IPv6, BASE_NONE, + NULL, 0x0, NULL, HFILL } + }, + { &hf_quic_mp_add_address_port, + { "Port", "quic.mp_port", + FT_UINT32, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_uniflow_id, + { "Uniflow ID", "quic.mp_uniflow_id", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_receiving_uniflows, + { "Receiving uniflows", "quic.mp_receiving_uniflows", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_active_sending_uniflows, + { "Active sending uniflows", "quic.mp_act_send_uf", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_receiving_uniflow_info_section, + { "Receiving uniflows", "quic.mp_receiving_uniflows_section", + FT_NONE, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_active_sending_uniflows_info_section, + { "Active sending uniflows", "quic.mp_act_send_uf_section", + FT_NONE, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_uniflow_info_section, + { "Uniflow Info Section", "quic.mp_uniflow_info_section", + FT_NONE, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_add_local_address_id , + { "Local address id", "quic.mp_add_local_address_id", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + + /* multipath */ + { &hf_quic_mp_ack_dcid_sequence_number, + { "DCID Sequence Number", "quic.mp_ack_dcid_sequence_number", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Destination Connection ID Sequence Number", HFILL } + }, + { &hf_quic_mp_pa_dcid_sequence_number, + { "DCID Sequence Number", "quic.mp_pa_dcid_sequence_number", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Destination Connection ID Sequence Number", HFILL } + }, + { &hf_quic_mp_ps_dcid_sequence_number, + { "DCID Sequence Number", "quic.mp_ps_dcid_sequence_number", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Destination Connection ID Sequence Number", HFILL } + }, + { &hf_quic_mp_ps_path_status_sequence_number, + { "Path Status Sequence Number", "quic.mp_ps_path_status_sequence_number", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_mp_ps_path_status, + { "Path Status", "quic.mp_ps_path_status", + FT_UINT64, BASE_DEC | BASE_VAL64_STRING, VALS64(quic_mp_path_status), 0x0, + NULL, HFILL } + }, + + + { &hf_quic_short_reserved, + { "Reserved", "quic.short.reserved", + FT_UINT8, BASE_DEC, NULL, 0x18, + "Reserved bits (protected using header protection)", HFILL } + }, + { &hf_quic_q_bit, + { "Square Signal Bit (Q)", "quic.q_bit", + FT_BOOLEAN, 8, NULL, 0x10, + "Square Signal Bit (used to measure and locate the source of packet loss)", HFILL } + }, + { &hf_quic_l_bit, + { "Loss Event Bit (L)", "quic.l_bit", + FT_BOOLEAN, 8, NULL, 0x08, + "Loss Event Bit (used to measure and locate the source of packet loss)", HFILL } + }, + { &hf_quic_key_phase, + { "Key Phase Bit", "quic.key_phase", + FT_BOOLEAN, 8, NULL, SH_KP, + "Selects the packet protection keys to use (protected using header protection)", HFILL } + }, + + { &hf_quic_payload, + { "Payload", "quic.payload", + FT_BYTES, BASE_NONE, NULL, 0x0, + "(Encrypted) payload of a packet", HFILL } + }, + { &hf_quic_protected_payload, + { "Protected Payload", "quic.protected_payload", + FT_BYTES, BASE_NONE, NULL, 0x0, + "1-RTT protected payload", HFILL } + }, + { &hf_quic_remaining_payload, + { "Remaining Payload", "quic.remaining_payload", + FT_BYTES, BASE_NONE, NULL, 0x0, + "Remaining payload in a packet (possibly PKN followed by encrypted payload)", HFILL } + }, + + { &hf_quic_odcil, + { "Original Destination Connection ID Length", "quic.odcil", + FT_UINT8, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_odcid, + { "Original Destination Connection ID", "quic.odcid", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_retry_token, + { "Retry Token", "quic.retry_token", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_retry_integrity_tag, + { "Retry Integrity Tag", "quic.retry_integrity_tag", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + + { &hf_quic_frame, + { "Frame", "quic.frame", + FT_NONE, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_frame_type, + { "Frame Type", "quic.frame_type", + FT_UINT64, BASE_RANGE_STRING | BASE_HEX, RVALS(quic_frame_type_vals), 0x0, + NULL, HFILL } + }, + + /* PADDING */ + { &hf_quic_padding_length, + { "Padding Length", "quic.padding_length", + FT_UINT32, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + /* ACK */ + { &hf_quic_ack_largest_acknowledged, + { "Largest Acknowledged", "quic.ack.largest_acknowledged", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Largest packet number the peer is acknowledging in this packet", HFILL } + }, + { &hf_quic_ack_ack_delay, + { "ACK Delay", "quic.ack.ack_delay", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Time from when the largest acknowledged packet, as indicated in the Largest Acknowledged field, was received by this peer to when this ACK was sent", HFILL } + }, + { &hf_quic_ack_ack_range_count, + { "ACK Range Count", "quic.ack.ack_range_count", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Number of Gap and ACK Range fields in the frame", HFILL } + }, + { &hf_quic_ack_first_ack_range, + { "First ACK Range", "quic.ack.first_ack_range", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Number of contiguous packets preceding the Largest Acknowledged that are being acknowledged", HFILL } + }, + { &hf_quic_ack_gap, + { "Gap", "quic.ack.gap", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Number of contiguous unacknowledged packets preceding the packet number one lower than the smallest in the preceding ACK Range", HFILL } + }, + { &hf_quic_ack_ack_range, + { "ACK Range", "quic.ack.ack_range", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Number of contiguous acknowledged packets preceding the largest packet number, as determined by the preceding Gap", HFILL } + }, + { &hf_quic_ack_ect0_count, + { "ECT(0) Count", "quic.ack.ect0_count", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Total number of packets received with the ECT(0) codepoint", HFILL } + }, + { &hf_quic_ack_ect1_count, + { "ECT(1) Count", "quic.ack.ect1_count", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Total number of packets received with the ECT(1) codepoint", HFILL } + }, + { &hf_quic_ack_ecn_ce_count, + { "ECN-CE Count", "quic.ack.ecn_ce_count", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Total number of packets received with the CE codepoint", HFILL } + }, + /* RESET_STREAM */ + { &hf_quic_rsts_stream_id, + { "Stream ID", "quic.rsts.stream_id", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Stream ID of the stream being terminated", HFILL } + }, + { &hf_quic_rsts_application_error_code, + { "Application Error code", "quic.rsts.application_error_code", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Indicates why the stream is being closed", HFILL } + }, + { &hf_quic_rsts_final_size, + { "Final Size", "quic.rsts.final_size", + FT_UINT64, BASE_DEC, NULL, 0x0, + "The final size of the stream by the RESET_STREAM sender (in bytes)", HFILL } + }, + /* STOP_SENDING */ + { &hf_quic_ss_stream_id, + { "Stream ID", "quic.ss.stream_id", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Stream ID of the stream being ignored", HFILL } + }, + { &hf_quic_ss_application_error_code, + { "Application Error code", "quic.ss.application_error_code", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Indicates why the sender is ignoring the stream", HFILL } + }, + /* CRYPTO */ + { &hf_quic_crypto_offset, + { "Offset", "quic.crypto.offset", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Byte offset into the stream", HFILL } + }, + { &hf_quic_crypto_length, + { "Length", "quic.crypto.length", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Length of the Crypto Data field", HFILL } + }, + { &hf_quic_crypto_crypto_data, + { "Crypto Data", "quic.crypto.crypto_data", + FT_NONE, BASE_NONE, NULL, 0x0, + "The cryptographic message data", HFILL } + }, + /* NEW_TOKEN */ + { &hf_quic_nt_length, + { "(Token) Length", "quic.nt.length", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Specifying the length of the token", HFILL } + }, + { &hf_quic_nt_token, + { "Token", "quic.nt.token", + FT_BYTES, BASE_NONE, NULL, 0x0, + "An opaque blob that the client may use with a future Initial packet", HFILL } + }, + /* STREAM */ + { &hf_quic_stream_fin, + { "Fin", "quic.stream.fin", + FT_BOOLEAN, 8, NULL, FTFLAGS_STREAM_FIN, + NULL, HFILL } + }, + { &hf_quic_stream_len, + { "Len(gth)", "quic.stream.len", + FT_BOOLEAN, 8, NULL, FTFLAGS_STREAM_LEN, + NULL, HFILL } + }, + { &hf_quic_stream_off, + { "Off(set)", "quic.stream.off", + FT_BOOLEAN, 8, NULL, FTFLAGS_STREAM_OFF, + NULL, HFILL } + }, + { &hf_quic_stream_stream_id, + { "Stream ID", "quic.stream.stream_id", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_stream_initiator, + { "Stream initiator", "quic.stream.initiator", + FT_UINT64, BASE_DEC | BASE_VAL64_STRING, VALS64(quic_frame_id_initiator), FTFLAGS_STREAM_INITIATOR, + NULL, HFILL } + }, + { &hf_quic_stream_direction, + { "Stream direction", "quic.stream.direction", + FT_UINT64, BASE_DEC | BASE_VAL64_STRING, VALS64(quic_frame_id_direction), FTFLAGS_STREAM_DIRECTION, + NULL, HFILL } + }, + { &hf_quic_stream_offset, + { "Offset", "quic.stream.offset", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_stream_length, + { "Length", "quic.stream.length", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_stream_data, + { "Stream Data", "quic.stream_data", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + + /* MAX_DATA */ + { &hf_quic_md_maximum_data, + { "Maximum Data", "quic.md.maximum_data", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Indicating the maximum amount of data that can be sent on the entire connection, in units of 1024 octets", HFILL } + }, + /* MAX_STREAM_DATA */ + { &hf_quic_msd_stream_id, + { "Stream ID", "quic.msd.stream_id", + FT_UINT64, BASE_DEC, NULL, 0x0, + "The stream ID of the stream that is affected", HFILL } + }, + { &hf_quic_msd_maximum_stream_data, + { "Maximum Stream Data", "quic.msd.maximum_stream_data", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Indicating the maximum amount of data that can be sent on the identified stream, in units of octets", HFILL } + }, + /* MAX_STREAMS */ + { &hf_quic_ms_max_streams, + { "Max Streams", "quic.ms.max_streams", + FT_UINT64, BASE_DEC, NULL, 0x0, + "A count of the cumulative number of streams of the corresponding type that can be opened over the lifetime of the connection", HFILL } + }, + /* DATA_BLOCKED */ + { &hf_quic_db_stream_data_limit, + { "Stream Data Limit", "quic.sb.stream_data_limit", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Indicating the connection-level limit at which the blocking occurred", HFILL } + }, + /* STREAM_DATA_BLOCKED */ + { &hf_quic_sdb_stream_id, + { "Stream ID", "quic.sdb.stream_id", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Indicating the stream which is flow control blocked", HFILL } + }, + { &hf_quic_sdb_stream_data_limit, + { "Stream Data Limit", "quic.sb.stream_data_limit", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Indicating the offset of the stream at which the blocking occurred", HFILL } + }, + /* STREAMS_BLOCKED */ + { &hf_quic_sb_stream_limit, + { "Stream Limit", "quic.sib.stream_limit", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Indicating the stream limit at the time the frame was sent", HFILL } + }, + /* NEW_CONNECTION_ID */ + { &hf_quic_nci_retire_prior_to, + { "Retire Prior To", "quic.nci.retire_prior_to", + FT_UINT64, BASE_DEC, NULL, 0x0, + "A variable-length integer indicating which connection IDs should be retired", HFILL } + }, + { &hf_quic_nci_sequence, + { "Sequence", "quic.nci.sequence", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Increases by 1 for each connection ID that is provided by the server", HFILL } + }, + { &hf_quic_nci_connection_id_length, + { "Connection ID Length", "quic.nci.connection_id.length", + FT_UINT8, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_nci_connection_id, + { "Connection ID", "quic.nci.connection_id", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_nci_stateless_reset_token, + { "Stateless Reset Token", "quic.nci.stateless_reset_token", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + /* RETIRE_CONNECTION_ID */ + { &hf_quic_rci_sequence, + { "Sequence", "quic.rci.sequence", + FT_UINT64, BASE_DEC, NULL, 0x0, + "The sequence number of the connection ID being retired", HFILL } + }, + /* PATH_CHALLENGE */ + { &hf_quic_path_challenge_data, + { "Data", "quic.path_challenge.data", + FT_BYTES, BASE_NONE, NULL, 0x0, + "Arbitrary data that must be matched by a PATH_RESPONSE frame", HFILL } + }, + /* PATH_RESPONSE */ + { &hf_quic_path_response_data, + { "Data", "quic.path_response.data", + FT_BYTES, BASE_NONE, NULL, 0x0, + "Arbitrary data that must match a PATH_CHALLENGE frame", HFILL } + }, + /* CONNECTION_CLOSE */ + { &hf_quic_cc_error_code, + { "Error code", "quic.cc.error_code", + FT_UINT64, BASE_DEC|BASE_RANGE_STRING, RVALS(quic_transport_error_code_vals), 0x0, + "Indicates the reason for closing this connection", HFILL } + }, + { &hf_quic_cc_error_code_app, + { "Application Error code", "quic.cc.error_code.app", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Indicates the reason for closing this application", HFILL } + }, + { &hf_quic_cc_error_code_tls_alert, + { "TLS Alert Description", "quic.cc.error_code.tls_alert", + FT_UINT8, BASE_DEC, VALS(ssl_31_alert_description), 0x0, + NULL, HFILL } + }, + { &hf_quic_cc_frame_type, + { "Frame Type", "quic.cc.frame_type", + FT_UINT64, BASE_DEC, NULL, 0x0, + "The type of frame that triggered the error", HFILL } + }, + { &hf_quic_cc_reason_phrase_length, + { "Reason phrase Length", "quic.cc.reason_phrase.length", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Specifying the length of the reason phrase", HFILL } + }, + { &hf_quic_cc_reason_phrase, + { "Reason phrase", "quic.cc.reason_phrase", + FT_STRING, BASE_NONE, NULL, 0x0, + "A human-readable explanation for why the connection was closed", HFILL } + }, + /* DATAGRAM */ + { &hf_quic_dg_length, + { "Datagram Length", "quic.dg.length", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Specifies the length of the datagram data in bytes", HFILL } + }, + { &hf_quic_dg, + { "Datagram", "quic.dg", + FT_BYTES, BASE_NONE, NULL, 0x0, + "The bytes of the datagram to be delivered", HFILL } + }, + /* ACK-FREQUENCY */ + { &hf_quic_af_sequence_number, + { "Sequence Number", "quic.af.sequence_number", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Sequence number assigned to the ACK_FREQUENCY frame by the sender to allow receivers to ignore obsolete frames", HFILL } + }, + { &hf_quic_af_ack_eliciting_threshold, + { "Ack-Eliciting Threshold", "quic.af.ack_eliciting_threshold", + FT_UINT64, BASE_DEC, NULL, 0x0, + "The maximum number of ack-eliciting packets the recipient of this frame can receive without sending an acknowledgment", HFILL } + }, + { &hf_quic_af_request_max_ack_delay, + { "Request Max Ack Delay", "quic.af.request_max_ack_delay", + FT_UINT64, BASE_DEC, NULL, 0x0, + "The value to which the endpoint requests the peer update its max_ack_delay", HFILL } + }, + { &hf_quic_af_reordering_threshold, + { "Reordering Threshold", "quic.af.reordering_threshold", + FT_UINT64, BASE_DEC, NULL, 0x0, + "The value that indicates the maximum packet reordering before eliciting an immediate ACK", HFILL } + }, + //{ &hf_quic_af_ignore_order, + // { "Ignore Order", "quic.af.ignore_order", + // FT_BOOLEAN, 8, NULL, 0x02, + // "This field is set to true by an endpoint that does not wish to receive an immediate acknowledgement when the peer receives a packet out of order", HFILL } + //}, + //{ &hf_quic_af_ignore_ce, + // { "Ignore CE", "quic.af.ignore_ce", + // FT_BOOLEAN, 8, NULL, 0x01, + // "This field is set to true by an endpoint that does not wish to receive an immediate acknowledgement when the peer receives CE-marked packets", HFILL } + //}, + + /* TIME STAMP */ + { &hf_quic_ts, + { "Time Stamp", "quic.ts", + FT_UINT64, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + + /* STATELESS RESET */ + { &hf_quic_unpredictable_bits, + { "Unpredictable Bits", "quic.unpredictable_bits", + FT_BYTES, BASE_NONE, NULL, 0x0, + "Bytes indistinguishable from random", + HFILL } + }, + { &hf_quic_stateless_reset_token, + { "Stateless Reset Token", "quic.stateless_reset_token", + FT_BYTES, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + + /* Fields for QUIC Stream data reassembly. */ + { &hf_quic_fragment_overlap, + { "Fragment overlap", "quic.fragment.overlap", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "Fragment overlaps with other fragments", HFILL } + }, + { &hf_quic_fragment_overlap_conflict, + { "Conflicting data in fragment overlap", "quic.fragment.overlap.conflict", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "Overlapping fragments contained conflicting data", HFILL } + }, + { &hf_quic_fragment_multiple_tails, + { "Multiple tail fragments found", "quic.fragment.multipletails", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "Several tails were found when reassembling the pdu", HFILL } + }, + { &hf_quic_fragment_too_long_fragment, + { "Fragment too long", "quic.fragment.toolongfragment", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "Fragment contained data past end of the pdu", HFILL } + }, + { &hf_quic_fragment_error, + { "Reassembling error", "quic.fragment.error", + FT_FRAMENUM, BASE_NONE, NULL, 0x0, + "Reassembling error due to illegal fragments", HFILL } + }, + { &hf_quic_fragment_count, + { "Fragment count", "quic.fragment.count", + FT_UINT32, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_fragment, + { "QUIC STREAM Data Fragment", "quic.fragment", + FT_FRAMENUM, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_fragments, + { "Reassembled QUIC STREAM Data Fragments", "quic.fragments", + FT_NONE, BASE_NONE, NULL, 0x0, + "QUIC STREAM Data Fragments", HFILL } + }, + { &hf_quic_reassembled_in, + { "Reassembled PDU in frame", "quic.reassembled_in", + FT_FRAMENUM, BASE_NONE, NULL, 0x0, + "The PDU that doesn't end in this fragment is reassembled in this frame", HFILL } + }, + { &hf_quic_reassembled_length, + { "Reassembled QUIC STREAM Data length", "quic.reassembled.length", + FT_UINT32, BASE_DEC, NULL, 0x0, + "The total length of the reassembled payload", HFILL } + }, + { &hf_quic_reassembled_data, + { "Reassembled QUIC STREAM Data", "quic.reassembled.data", + FT_BYTES, BASE_NONE, NULL, 0x0, + "The reassembled payload", HFILL } + }, + { &hf_quic_crypto_fragment_count, + { "Fragment count", "quic.crypto.fragment.count", + FT_UINT32, BASE_DEC, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_crypto_fragment, + { "QUIC CRYPTO Data Fragment", "quic.crypto.fragment", + FT_FRAMENUM, BASE_NONE, NULL, 0x0, + NULL, HFILL } + }, + { &hf_quic_crypto_fragments, + { "Reassembled QUIC CRYPTO Data Fragments", "quic.crypto.fragments", + FT_NONE, BASE_NONE, NULL, 0x0, + "QUIC STREAM Data Fragments", HFILL } + }, + { &hf_quic_crypto_reassembled_in, + { "Reassembled PDU in frame", "quic.crypto.reassembled_in", + FT_FRAMENUM, BASE_NONE, NULL, 0x0, + "The PDU that doesn't end in this fragment is reassembled in this frame", HFILL } + }, + }; + + static gint *ett[] = { + &ett_quic, + &ett_quic_af, + &ett_quic_short_header, + &ett_quic_connection_info, + &ett_quic_ft, + &ett_quic_ftflags, + &ett_quic_ftid, + &ett_quic_fragments, + &ett_quic_fragment, + &ett_quic_crypto_fragments, + &ett_quic_crypto_fragment, + }; + + static ei_register_info ei[] = { + { &ei_quic_connection_unknown, + { "quic.connection.unknown", PI_PROTOCOL, PI_NOTE, + "Unknown QUIC connection. Missing Initial Packet or migrated connection?", EXPFILL } + }, + { &ei_quic_ft_unknown, + { "quic.ft.unknown", PI_UNDECODED, PI_NOTE, + "Unknown Frame Type", EXPFILL } + }, + { &ei_quic_decryption_failed, + { "quic.decryption_failed", PI_DECRYPTION, PI_WARN, + "Failed to decrypt handshake", EXPFILL } + }, + { &ei_quic_protocol_violation, + { "quic.protocol_violation", PI_PROTOCOL, PI_WARN, + "Invalid data according to the protocol", EXPFILL } + }, + { &ei_quic_bad_retry, + { "quic.bad_retry", PI_PROTOCOL, PI_WARN, + "Retry Integrity Tag verification failure", EXPFILL } + }, + { &ei_quic_coalesced_padding_data, + { "quic.coalesced_padding_data", PI_PROTOCOL, PI_NOTE, + "Coalesced Padding Data", EXPFILL } + }, + { &ei_quic_retransmission, + { "quic.retransmission", PI_SEQUENCE, PI_NOTE, + "This QUIC frame has a reused stream offset (retransmission?)", EXPFILL } + }, + { &ei_quic_overlap, + { "quic.overlap", PI_SEQUENCE, PI_NOTE, + "This QUIC frame overlaps a previous frame in the stream", EXPFILL } + }, + { &ei_quic_data_after_forcing_vn, + { "quic.data_after_forcing_vn", PI_PROTOCOL, PI_NOTE, + "Unexpected data on a Forcing Version Negotiation packet", EXPFILL } + }, + }; + + proto_quic = proto_register_protocol("QUIC IETF", "QUIC", "quic"); + + proto_register_field_array(proto_quic, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + + expert_quic = expert_register_protocol(proto_quic); + expert_register_field_array(expert_quic, ei, array_length(ei)); + + quic_module = prefs_register_protocol(proto_quic, NULL); + prefs_register_bool_preference(quic_module, "reassemble_crypto_out_of_order", + "Reassemble out-of-order CRYPTO frames", + "Whether out-of-order CRYPTO frames should be buffered and reordered before " + "passing them to the TLS handshake dissector.", + &quic_crypto_out_of_order); + + quic_handle = register_dissector("quic", dissect_quic, proto_quic); + + register_init_routine(quic_init); + register_cleanup_routine(quic_cleanup); + + register_follow_stream(proto_quic, "quic_follow", quic_follow_conv_filter, quic_follow_index_filter, quic_follow_address_filter, + udp_port_to_display, follow_quic_tap_listener, get_quic_connections_count, + quic_get_sub_stream_id); + + // TODO implement custom reassembly functions that uses the QUIC Connection + // ID instead of address and port numbers. + reassembly_table_register(&quic_reassembly_table, + &addresses_ports_reassembly_table_functions); + + reassembly_table_register(&quic_crypto_reassembly_table, + &tcp_reassembly_table_functions); + + /* + * Application protocol. QUIC with TLS uses ALPN. + * https://tools.ietf.org/html/draft-ietf-quic-transport-23#section-7 + * This could in theory be an arbitrary octet string with embedded NUL + * bytes, but in practice these do not exist yet. + */ + quic_proto_dissector_table = register_dissector_table("quic.proto", "QUIC Protocol", proto_quic, FT_STRING, STRING_CASE_SENSITIVE); +} + +void +proto_reg_handoff_quic(void) +{ + tls13_handshake_handle = find_dissector("tls13-handshake"); + dissector_add_uint_with_preference("udp.port", 0, quic_handle); + heur_dissector_add("udp", dissect_quic_heur, "QUIC", "quic", proto_quic, HEURISTIC_ENABLE); + quic_follow_tap = register_tap("quic_follow"); +} + +gboolean +quic_conn_data_get_conn_client_dcid_initial(struct _packet_info *pinfo, quic_cid_t *dcid) +{ + if (pinfo == NULL || dcid == NULL) { + return false; + } + + quic_info_data_t * conn = quic_connection_from_conv(pinfo); + if (conn == NULL) { + return false; + } + + dcid->len = conn->client_dcid_initial.len; + memset(dcid->cid, 0, QUIC_MAX_CID_LENGTH); + memcpy(dcid->cid, conn->client_dcid_initial.cid, dcid->len); + + return true; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ |