wireshark/epan/dissectors/packet-http3.c

2835 lines
112 KiB
C

/* packet-http3.c
* Routines for HTTP/3 dissection
* Copyright 2019, Peter Wu <peter@lekensteyn.nl>
* Copyright 2023, Omer Shapira <oesh@github.com>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/*
* https://tools.ietf.org/html/draft-ietf-quic-http-29
* https://tools.ietf.org/html/draft-ietf-quic-qpack-16
*
* Depends on the QUIC dissector for providing a reassembled stream of data, see
* packet-quic.c for details about supported QUIC draft versions.
* Depends on nghttp3 for HTTP header dissection.
* Currently supported HTTP/3 versions: h3-23 up to and including h3-29.
*/
#include <config.h>
#define WS_LOG_DOMAIN "HTTP3"
#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/exceptions.h>
#include <epan/proto_data.h>
#include <epan/to_str.h>
#include <stdint.h>
#include <string.h>
#include <wsutil/pint.h>
#include <epan/conversation_table.h>
#include <epan/dissectors/packet-http.h> /* for getting status reason-phrase */
#include "packet-quic.h"
#include "packet-tls-utils.h"
#include "wsutil/wmem/wmem_user_cb.h"
#include <epan/decode_as.h>
#include <epan/reassemble.h>
#include <epan/uat.h>
#ifdef HAVE_NGHTTP3
#include <nghttp3/nghttp3.h>
#endif
void proto_reg_handoff_http3(void);
void proto_register_http3(void);
static dissector_handle_t http3_handle;
#define PROTO_DATA_KEY_HEADER 0
#define PROTO_DATA_KEY_QPACK 1
static int proto_http3;
static int hf_http3_stream_uni;
static int hf_http3_stream_uni_type;
static int hf_http3_stream_bidi;
static int hf_http3_push_id;
static int hf_http3_frame;
static int hf_http3_frame_type;
static int hf_http3_frame_length;
static int hf_http3_frame_payload;
static int hf_http3_data;
//static int hf_http3_headers;
static int hf_http3_headers_count;
static int hf_http3_header;
static int hf_http3_header_length;
static int hf_http3_header_name_length;
static int hf_http3_header_name;
static int hf_http3_header_value_length;
static int hf_http3_header_value;
static int hf_http3_header_request_full_uri;
static int hf_http3_header_qpack_blocked;
static int hf_http3_header_qpack_blocked_stream_rcint;
static int hf_http3_header_qpack_blocked_decoder_wicnt;
//static int hf_http3_header_qpack_fatal;
#ifdef HAVE_NGHTTP3
/* Static HTTP3 headers */
static int hf_http3_headers_status;
static int hf_http3_headers_path;
static int hf_http3_headers_method;
static int hf_http3_headers_scheme;
static int hf_http3_headers_accept;
static int hf_http3_headers_accept_charset;
static int hf_http3_headers_accept_encoding;
static int hf_http3_headers_accept_language;
static int hf_http3_headers_accept_ranges;
static int hf_http3_headers_access_control_allow_origin;
static int hf_http3_headers_age;
static int hf_http3_headers_allow;
static int hf_http3_headers_authorization;
static int hf_http3_headers_authority;
static int hf_http3_headers_cache_control;
static int hf_http3_headers_content_disposition;
static int hf_http3_headers_content_encoding;
static int hf_http3_headers_content_language;
static int hf_http3_headers_content_length;
static int hf_http3_headers_content_location;
static int hf_http3_headers_content_range;
static int hf_http3_headers_content_type;
static int hf_http3_headers_cookie;
static int hf_http3_headers_date;
static int hf_http3_headers_etag;
static int hf_http3_headers_expect;
static int hf_http3_headers_expires;
static int hf_http3_headers_from;
static int hf_http3_headers_if_match;
static int hf_http3_headers_if_modified_since;
static int hf_http3_headers_if_none_match;
static int hf_http3_headers_if_range;
static int hf_http3_headers_if_unmodified_since;
static int hf_http3_headers_last_modified;
static int hf_http3_headers_link;
static int hf_http3_headers_location;
static int hf_http3_headers_max_forwards;
static int hf_http3_headers_proxy_authenticate;
static int hf_http3_headers_proxy_authorization;
static int hf_http3_headers_range;
static int hf_http3_headers_referer;
static int hf_http3_headers_refresh;
static int hf_http3_headers_retry_after;
static int hf_http3_headers_server;
static int hf_http3_headers_set_cookie;
static int hf_http3_headers_strict_transport_security;
static int hf_http3_headers_user_agent;
static int hf_http3_headers_vary;
static int hf_http3_headers_via;
static int hf_http3_headers_www_authenticate;
#endif
//static int hf_http3_qpack;
static int hf_http3_qpack_encoder;
//static int hf_http3_qpack_encoder_length;
static int hf_http3_qpack_encoder_icnt;
static int hf_http3_qpack_encoder_icnt_inc;
//static int hf_http3_qpack_encoder_opcode;
static int hf_http3_qpack_encoder_opcode_insert_indexed;
static int hf_http3_qpack_encoder_opcode_insert_indexed_ref;
static int hf_http3_qpack_encoder_opcode_insert_indexed_val;
static int hf_http3_qpack_encoder_opcode_insert_indexed_hval;
static int hf_http3_qpack_encoder_opcode_insert;
static int hf_http3_qpack_encoder_opcode_insert_name;
static int hf_http3_qpack_encoder_opcode_insert_hname;
static int hf_http3_qpack_encoder_opcode_insert_val;
static int hf_http3_qpack_encoder_opcode_insert_hval;
static int hf_http3_qpack_encoder_opcode_duplicate;
//static int hf_http3_qpack_encoder_opcode_duplicate_val;
static int hf_http3_qpack_encoder_opcode_dtable_cap;
static int hf_http3_qpack_encoder_opcode_dtable_cap_val;
static int hf_http3_settings;
static int hf_http3_settings_identifier;
static int hf_http3_settings_value;
static int hf_http3_settings_qpack_max_table_capacity;
static int hf_http3_settings_max_field_section_size;
static int hf_http3_settings_qpack_blocked_streams;
static int hf_http3_settings_extended_connect;
static int hf_http3_settings_webtransport;
static int hf_http3_settings_h3_datagram;
static int hf_http3_settings_h3_datagram_draft04;
static int hf_http3_priority_update_element_id;
static int hf_http3_priority_update_field_value;
/* QPACK dissection EIs */
//static expert_field ei_http3_qpack_enc_update;
static expert_field ei_http3_qpack_failed;
/* HTTP3 dissection EIs */
static expert_field ei_http3_unknown_stream_type;
//static expert_field ei_http3_data_not_decoded;
/* Encoded data EIs */
static expert_field ei_http3_header_encoded_state;
/* HTTP3 header decoding EIs */
static expert_field ei_http3_header_decoding_failed;
static expert_field ei_http3_header_decoding_blocked;
static expert_field ei_http3_header_decoding_no_output;
/* Initialize the subtree pointers */
static gint ett_http3;
static gint ett_http3_stream_uni;
static gint ett_http3_stream_bidi;
static gint ett_http3_frame;
static gint ett_http3_settings;
static gint ett_http3_headers;
static gint ett_http3_headers_qpack_blocked;
static gint ett_http3_qpack_update;
static gint ett_http3_qpack_opcode;
/**
* HTTP3 header constants.
* The below constants are used for dissecting the
* code. This is not an exahustive list.
*/
#define HTTP3_HEADER_NAME_CONTENT_ENCODING "content-encoding"
#define HTTP3_HEADER_NAME_CONTENT_TYPE "content-type"
#define HTTP3_HEADER_NAME_TRANSFER_ENCODING "transfer-encoding"
#define HTTP3_HEADER_NAME_AUTHORITY ":authority"
#define HTTP3_HEADER_NAME_METHOD ":method"
#define HTTP3_HEADER_NAME_PATH ":path"
#define HTTP3_HEADER_NAME_SCHEME ":scheme"
#define HTTP3_HEADER_NAME_STATUS ":status"
#define HTTP3_HEADER_METHOD_CONNECT "CONNECT"
#define HTTP3_HEADER_STATUS_PARTIAL_CONTENT "206"
#define HTTP3_HEADER_UNKNOWN "<unknown>"
/**
* Unidirectional stream types.
* https://tools.ietf.org/html/draft-ietf-quic-http-29#section-6.2
* https://tools.ietf.org/html/draft-ietf-quic-qpack-16#section-4.2
*/
enum http3_stream_type {
HTTP3_STREAM_TYPE_CONTROL,
HTTP3_STREAM_TYPE_PUSH,
HTTP3_STREAM_TYPE_QPACK_ENCODER,
HTTP3_STREAM_TYPE_QPACK_DECODER,
HTTP3_STREAM_TYPE_WEBTRANSPORT = 0x54, // draft-ietf-webtrans-http3-03
};
/**
* Unidirectional stream types (62-bit code space).
* https://tools.ietf.org/html/draft-ietf-quic-http-29#section-11.2.4
*/
// clang-format off
static const val64_string http3_stream_types[] = {
/* 0x00 - 0x3f Assigned via Standards Action or IESG Approval. */
{ 0x00, "Control Stream" },
{ 0x01, "Push Stream" },
{ 0x02, "QPACK Encoder Stream" },
{ 0x03, "QPACK Decoder Stream" },
{ 0x54, "WebTransport Stream" },
/* 0x40 - 0x3FFFFFFFFFFFFFFF Assigned via Specification Required policy */
{ 0, NULL }
};
// clang-format on
/**
* Frame type codes (62-bit code space).
* https://tools.ietf.org/html/draft-ietf-quic-http-29#section-11.2.1
*/
#define HTTP3_DATA 0x0
#define HTTP3_HEADERS 0x1
#define HTTP3_CANCEL_PUSH 0x3
#define HTTP3_SETTINGS 0x4
#define HTTP3_PUSH_PROMISE 0x5
#define HTTP3_GOAWAY 0x7
#define HTTP3_MAX_PUSH_ID 0xD
#define HTTP3_WEBTRANSPORT_BISTREAM 0x41
#define HTTP3_PRIORITY_UPDATE_REQUEST_STREAM 0xF0700
#define HTTP3_PRIORITY_UPDATE_PUSH_STREAM 0xF0701
static const val64_string http3_frame_types[] = {
/* 0x00 - 0x3f Assigned via Standards Action or IESG Approval. */
{ HTTP3_DATA, "DATA" },
{ HTTP3_HEADERS, "HEADERS" },
{ 0x02, "Reserved" }, // "PRIORITY" in draft-22 and before
{ HTTP3_CANCEL_PUSH, "CANCEL_PUSH" },
{ HTTP3_SETTINGS, "SETTINGS" },
{ HTTP3_PUSH_PROMISE, "PUSH_PROMISE" },
{ 0x06, "Reserved" },
{ HTTP3_GOAWAY, "GOAWAY" },
{ 0x08, "Reserved" },
{ 0x09, "Reserved" },
{ HTTP3_MAX_PUSH_ID, "MAX_PUSH_ID" },
{ 0x0e, "Reserved" }, // "DUPLICATE_PUSH" in draft-26 and before
{ HTTP3_WEBTRANSPORT_BISTREAM, "WEBTRANSPORT_BISTREAM" }, // draft-ietf-webtrans-http3-03
{ HTTP3_PRIORITY_UPDATE_REQUEST_STREAM, "PRIORITY_UPDATE" }, // RFC 9218
{ HTTP3_PRIORITY_UPDATE_PUSH_STREAM, "PRIORITY_UPDATE" }, // RFC 9218
/* 0x40 - 0x3FFFFFFFFFFFFFFF Assigned via Specification Required policy */
{ 0, NULL }
};
/**
* Settings parameter type codes (62-bit code space).
* https://tools.ietf.org/html/draft-ietf-quic-http-29#name-http-2-settings-parameters
*/
#define HTTP3_QPACK_MAX_TABLE_CAPACITY 0x01
#define HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE 0x06
#define HTTP3_QPACK_BLOCKED_STREAMS 0x07
#define HTTP3_EXTENDED_CONNECT 0x08 /* https://datatracker.ietf.org/doc/draft-ietf-httpbis-h3-websockets */
#define HTTP3_H3_DATAGRAM 0x33 // rfc9297
#define HTTP3_H3_DATAGRAM_DRAFT04 0xffd277 // draft-ietf-masque-h3-datagram-04
#define HTTP3_WEBTRANSPORT 0x2b603742 // draft-ietf-webtrans-http3-03
static const val64_string http3_settings_vals[] = {
{ HTTP3_QPACK_MAX_TABLE_CAPACITY, "Max Table Capacity" },
{ HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE, "Max Field Section Size" },
{ HTTP3_QPACK_BLOCKED_STREAMS, "Blocked Streams" },
{ HTTP3_EXTENDED_CONNECT, "Extended CONNECT" },
{ HTTP3_WEBTRANSPORT, "Enable WebTransport" },
{ HTTP3_H3_DATAGRAM, "Enable Datagram" },
{ HTTP3_H3_DATAGRAM_DRAFT04, "Enable Datagram Draft04" },
{ 0, NULL }
};
/**
* QPACK encoder stream opcodes.
*/
#define QPACK_OPCODE_MASK 0xE0
#define QPACK_OPCODE_INSERT_INDEXED 0x80
#define QPACK_OPCODE_INSERT 0x40
#define QPACK_OPCODE_SET_DTABLE_CAP 0x20
#define QPACK_OPCODE_DUPLICATE 0x00
#define QPACK_HUFFMAN_5_STRING 0x20
#define QPACK_HUFFMAN_6_STRING 0x40
#define QPACK_HUFFMAN_7_STRING q0x80
typedef enum _http3_stream_dir {
FROM_CLIENT_TO_SERVER = 0,
FROM_SERVER_TO_CLIENT = 1,
} http3_stream_dir;
/**
* Essential data structures.
*/
/**
* HTTP3 stream info - contains information about HTTP3 stream.
* HTTP3 streams roughly correspond to QUIC streams, with the
* HTTP3 Server Push being an exception to the rule.
*/
typedef struct _http3_stream_info {
guint64 id; /**< HTTP3 stream id */
guint64 uni_stream_type; /**< Unidirectional stream type */
guint64 broken_from_offset; /**< Unrecognized stream starting at offset (if non-zero). */
http3_stream_dir direction;
} http3_stream_info_t;
/**
* HTTP3 session info - contains information about the HTTP3 session.
* HTTP3 sessions roughly correspond to QUIC connections, at least
* until the dissector will support connection migration and/or
* Multipath QUIC. When that happens, a single HTTP3 session would
* be mapped to multiple QUIC connections, or to multiple QUIC
* paths (in the MP-QUIC terminology).
*/
typedef void *qpack_decoder_t;
typedef void *qpack_decoder_ctx_t;
typedef struct _http3_session_info {
guint id;
qpack_decoder_t qpack_decoder[2]; /**< Decoders for outgoing/incoming QPACK streams. */
} http3_session_info_t;
/**
* Lookup or create new HTTP3 session object for the pinfo.
*/
static http3_session_info_t *http3_session_lookup_or_create(packet_info *pinfo);
/**
* HTTP3 Header dissection support.
*/
#define QPACK_MAX_DTABLE_SIZE 65536 /**< Max size of the QPACK dynamic table. */
#define QPACK_MAX_BLOCKED 512 /**< Upper limit on number of streams blocked on QPACK updates. */
/**
* Decompressed header field definition.
* Header field definitions are cached separately,
* to preserve memory.
*/
typedef struct _http3_header_field_def {
const guint8 *name;
guint name_len;
} http3_header_field_def_t;
/**
* HTTP3 header field.
*
* The header field contains two sections:
* - encoded points to the location of the encoded field in the *original* packet TVB.
* - decoded points to the formatted header string, which is allocated in a cache map,
* to conserve memory.
* The decoded fields are used to create an auxiliary TVB which will
* be used for dissection of decoded header values.
*/
typedef struct _http3_header_field {
struct {
guint len;
guint offset;
} encoded;
struct {
const guint8 *pstr;
guint pstr_len;
} decoded;
http3_header_field_def_t *def;
} http3_header_field_t;
/**
* HTTP3 encoded header data block.
*
* This helper structure is used to support header dissection.
*/
typedef struct _header_block_encoded_iter {
guint8 *bytes;
guint32 len;
guint32 pos;
} header_block_encoded_iter_t;
#define HEADER_BLOCK_ENC_ITER_PTR(hdata) \
((hdata)->encoded.bytes == NULL \
? NULL \
: ((hdata)->encoded.pos == (hdata)->encoded.len) ? NULL : (hdata)->encoded.bytes + (hdata)->encoded.pos)
#define HEADER_BLOCK_ENC_ITER_REMAINING(hdata) \
((hdata)->encoded.bytes == NULL ? 0 : ((hdata)->encoded.len - (hdata)->encoded.pos))
#define HEADER_BLOCK_ENC_ITER_INC(hdata, nread) \
do { \
if ((hdata)) { \
(hdata)->encoded.pos += (nread); \
DISSECTOR_ASSERT((hdata)->encoded.pos <= (hdata)->encoded.len); \
} \
} while (0)
/**
* HTTP3 header data block.
*
* The data block corresponds to contents of a single HTTP3 HEADERS frame.
* If a packet contains multiple HTTP3 HEADERS frames,
* the corresponding blocks will be chained using the `next'
* pointer. In this case, individual headers blocks
* will be identified by the `offset' field.
*/
typedef struct _http3_header_data {
guint len; /**< Length of the encoded headers block. */
guint offset; /**< Offset of the headers block in the pinfo TVB. */
unsigned ds_idx; /**< Index of the data source tvb in the pinfo. */
wmem_array_t * header_fields; /**< List of header fields contained in the header block. */
header_block_encoded_iter_t encoded; /**< Used for dissection, not allocated. */
struct _http3_header_data * next; /**< Next pointer in the chain. */
} http3_header_data_t;
/* HTTP3 QPACK encoder state
*
* Store information about how many entries a QPACK encoder stream
* has inserted into the decoder at a particular point in the capture
* file (both the number newly inserted in the portion of the stream
* contained in the current QUIC packet and the total up to that point.)
* If a capture frame contains multiple encoder stream segments, the
* corresponding blocks will be chained using the 'next' pointer. In this
* case, individual blocks will be identified by the data source index
* of the tvb within the capture frame and the offset in the ds_tvb.
* (Both are necessary for multiple QUIC packets coalesced in a single
* UDP datagram with multiple stream segments within a QUIC packet.)
*/
typedef struct _http3_qpack_encoder_state {
guint offset; /**< Offset of the headers block in the pinfo TVB. */
unsigned ds_idx; /**< Index of the data source tvb in the pinfo. */
uint32_t icnt_inc; /**< Number of insertions in this header segment. */
uint64_t icnt; /**< Total number of insertions up to this point. */
ptrdiff_t nread; /**< Number of bytes read; if negative, an error code. */
struct _http3_qpack_encoder_state * next; /**< Next pointer in the chain. */
} http3_qpack_encoder_state_t;
/**
* File-scoped context.
* This data structure is used to maintain file-scoped
* lookup tables. It is reset when the file-scoped
* allocator is exited.
*/
typedef struct _http3_file_local_ctx {
wmem_map_t *conn_info_map;
#ifdef HAVE_NGHTTP3
wmem_map_t *hdr_cache_map;
wmem_map_t *hdr_def_cache_map;
#endif
} http3_file_local_ctx;
/**
* @function http3_get_file_local_ctx
* @abstract Will create a new instance for the first time
* the file is visited.
* This function is not intended to be invked directly,
* but should be used via the `HTTP3_CONN_INFO_MAP` et. al. below.
* @returns file-local context.
*/
static http3_file_local_ctx *http3_get_file_local_ctx(void);
#define HTTP3_CONN_INFO_MAP http3_get_file_local_ctx()->conn_info_map
#ifdef HAVE_NGHTTP3
#define HTTP3_HEADER_CACHE http3_get_file_local_ctx()->hdr_cache_map
#define HTTP3_HEADER_NAME_CACHE http3_get_file_local_ctx()->hdr_def_cache_map
/* This global carries header name_length + name + value_length + value.
* It is allocated with file scoped memory, and then either placed in the
* cache map or, if it matches something already in the cache map, the
* memory is reallocated for the next header encountered. */
static char *http3_header_pstr = NULL;
#endif
/**
* Check whether the argument represents a reserved code point,
* for Stream Type, Frame Type, Error Code, etc.
*/
static inline gboolean
http3_is_reserved_code(guint64 stream_type)
{
return (stream_type - 0x21) % 0x1f == 0;
}
/**
* Attempt to parse QUIC-encoded variable integer.
*/
static gboolean
try_get_quic_varint(tvbuff_t *tvb, int offset, guint64 *value, int *lenvar)
{
if (tvb_reported_length_remaining(tvb, offset) == 0) {
return FALSE;
}
gint len = 1 << (tvb_get_guint8(tvb, offset) >> 6);
if (tvb_reported_length_remaining(tvb, offset) < len) {
return FALSE;
}
*lenvar = len;
if (value) {
gint n = (gint)tvb_get_varint(tvb, offset, -1, value, ENC_VARINT_QUIC);
DISSECTOR_ASSERT_CMPINT(n, ==, len);
}
return TRUE;
}
/**
* Return the size of entire HTTP/3 frame.
*/
static int
get_http3_frame_size(tvbuff_t *tvb, int offset)
{
int type_size, length_size;
guint64 frame_length;
if (!try_get_quic_varint(tvb, offset, NULL, &type_size)) {
return 0;
}
offset += type_size;
if (!try_get_quic_varint(tvb, offset, &frame_length, &length_size)) {
return 0;
}
guint64 frame_size = type_size + length_size + frame_length;
if (frame_size > G_MAXINT32) {
// We do not support such large frames.
return 0;
}
return (int)frame_size;
}
/**
* Check whether the pinfo contains at least one whole HTTP3 frame,
* and adjust the pinfo desegmentation settings for the lower
* layer (QUIC, generally) to continue the desegmentation process.
*/
static gboolean
http3_check_frame_size(tvbuff_t *tvb, packet_info *pinfo, int offset)
{
int frame_size = get_http3_frame_size(tvb, offset);
int remaining = tvb_reported_length_remaining(tvb, offset);
if (frame_size && frame_size <= remaining) {
return TRUE;
}
pinfo->desegment_offset = offset;
pinfo->desegment_len = frame_size ? (frame_size - remaining) : DESEGMENT_ONE_MORE_SEGMENT;
return FALSE;
}
/**
* Functions to support decompression of HTTP3 headers.
*/
#ifdef HAVE_NGHTTP3
/**
* File-scoped callback to release resources allocated for the QPACK
* decoder.
*/
static bool
qpack_decoder_del_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, void *user_data)
{
nghttp3_qpack_decoder_del((nghttp3_qpack_decoder *)user_data);
/* If we have a decoder, then we might have set http3_header_pstr to
* point to file scoped memory. Make sure we set it to NULL when leaving
* wmem_file_scope.
*/
http3_header_pstr = NULL;
return FALSE;
}
/**
* Memory allocation callbacks for nghttp3_qpack functionality.
*/
static void *
http3_nghttp3_malloc(size_t size, void *user_data _U_)
{
return wmem_alloc0(wmem_file_scope(), size);
}
static void
http3_nghttp3_free(void *ptr, void *user_data _U_)
{
wmem_free(wmem_file_scope(), ptr);
}
static void *
http3_nghttp3_calloc(size_t nmemb, size_t size, void *user_data _U_)
{
return wmem_alloc0(wmem_file_scope(), nmemb * size);
}
static void *
http3_nghttp3_realloc(void *ptr, size_t size, void *user_data _U_)
{
return wmem_realloc(wmem_file_scope(), ptr, size);
}
static nghttp3_mem g_qpack_mem_allocator = {
.malloc = http3_nghttp3_malloc,
.free = http3_nghttp3_free,
.calloc = http3_nghttp3_calloc,
.realloc = http3_nghttp3_realloc,
};
static nghttp3_mem *
qpack_mem_allocator(wmem_allocator_t *allocator _U_, int debug _U_)
{
nghttp3_mem *mem;
mem = &g_qpack_mem_allocator;
return mem;
}
/**
* Initialization routine for the http3_session object.
* Invoked during the creation of the new http3_session.
*/
static void
http3_initialize_qpack_decoders(http3_session_info_t *http3_session)
{
for (int dir = 0; dir < 2; dir++) {
nghttp3_qpack_decoder **pdecoder = (nghttp3_qpack_decoder **)&(http3_session->qpack_decoder[dir]);
nghttp3_qpack_decoder_new(pdecoder, QPACK_MAX_DTABLE_SIZE, QPACK_MAX_BLOCKED,
qpack_mem_allocator(wmem_file_scope(), 1));
nghttp3_qpack_decoder_set_max_dtable_capacity(*pdecoder, QPACK_MAX_DTABLE_SIZE);
wmem_register_callback(wmem_file_scope(), qpack_decoder_del_cb, *pdecoder);
}
}
static GHashTable *header_fields_hash = NULL;
static const char *
cid_to_string(const quic_cid_t *cid, wmem_allocator_t *scope)
{
if (cid->len == 0) {
return "(none)";
}
char *str = (char *)wmem_alloc0(scope, 2 * cid->len + 1);
bytes_to_hexstr(str, cid->cid, cid->len);
return str;
}
/* Given a packet_info and a tvbuff_t, returns the index of the
* data source tvb among the data sources in the packet.
*/
static uint32_t
get_tvb_ds_idx(packet_info *pinfo, tvbuff_t *tvb)
{
bool found = false;
tvbuff_t *ds_tvb = tvb_get_ds_tvb(tvb);
GSList *src_le;
struct data_source *src;
uint32_t ds_idx = 0;
for (src_le = pinfo->data_src; src_le != NULL; src_le = src_le->next) {
src = (struct data_source *)src_le->data;
if (ds_tvb == get_data_source_tvb(src)) {
found = true;
break;
}
ds_idx++;
}
/* If this gets made to a more general function, return a
* failure condition (-1?) that must be checked instead of asserting.
*/
DISSECTOR_ASSERT(found == true);
return ds_idx;
}
static http3_header_data_t *
http3_get_header_data(packet_info *pinfo, tvbuff_t *tvb, guint offset)
{
http3_header_data_t *data, *prev = NULL;
unsigned raw_offset = tvb_raw_offset(tvb) + offset;
/* The raw offset is relative to the original data source, which is
* the decrypted QUIC packet. There can be multiple decrypted QUIC
* packets in a single QUIC layer, so this guarantees the same raw
* offset from different decrypted data gives different keys.
*/
uint32_t ds_idx = get_tvb_ds_idx(pinfo, tvb);
data = (http3_header_data_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_http3, PROTO_DATA_KEY_HEADER);
/*
* Attempt to find existing header data block.
* In most cases, data will be `NULL'
* and this loop won't be visited.
*/
while (data != NULL) {
if (data->offset == raw_offset && data->ds_idx == ds_idx) {
/*
* We found the matching data. Return it.
*/
return data;
}
prev = data;
data = data->next;
}
/*
* We did not find header data matching the offset.
* Allocate a new header data block, and initialize
* the offset marker.
*/
data = wmem_new0(wmem_file_scope(), http3_header_data_t);
data->offset = raw_offset;
data->ds_idx = ds_idx;
/*
* Check whether the newly allocated data should be linked
* to the tail of existing header block chain, or whether
* it is the head of a new header block chain.
*/
if (prev != NULL) {
prev->next = data;
} else {
p_add_proto_data(wmem_file_scope(), pinfo, proto_http3, PROTO_DATA_KEY_HEADER, data);
}
return data;
}
static http3_qpack_encoder_state_t *
http3_get_qpack_encoder_state(packet_info *pinfo, tvbuff_t *tvb, guint offset)
{
http3_qpack_encoder_state_t *data, *prev = NULL;
unsigned raw_offset = tvb_raw_offset(tvb) + offset;
/* The raw offset is relative to the original data source, which is
* the decrypted QUIC packet. There can be multiple decrypted QUIC
* packets in a single QUIC layer, so this guarantees the same raw
* offset from different decrypted data gives different keys.
*/
uint32_t ds_idx = get_tvb_ds_idx(pinfo, tvb);
data = (http3_qpack_encoder_state_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_http3, PROTO_DATA_KEY_QPACK);
/*
* Attempt to find existing header data block.
* In most cases, data will be `NULL'
* and this loop won't be visited.
*/
while (data != NULL) {
if (data->offset == raw_offset && data->ds_idx == ds_idx) {
/*
* We found the matching data. Return it.
*/
return data;
}
prev = data;
data = data->next;
}
/*
* We did not find header data matching the offset.
* Allocate a new header data block, and initialize
* the offset marker.
*/
data = wmem_new0(wmem_file_scope(), http3_qpack_encoder_state_t);
data->offset = raw_offset;
data->ds_idx = ds_idx;
/*
* Check whether the newly allocated data should be linked
* to the tail of existing header block chain, or whether
* it is the head of a new header block chain.
*/
if (prev != NULL) {
prev->next = data;
} else {
p_add_proto_data(wmem_file_scope(), pinfo, proto_http3, PROTO_DATA_KEY_QPACK, data);
}
return data;
}
static inline http3_stream_dir
http3_packet_get_direction(quic_stream_info *stream_info)
{
return stream_info->from_server
? FROM_CLIENT_TO_SERVER
: FROM_SERVER_TO_CLIENT;
}
static void
try_append_method_path_info(packet_info *pinfo, proto_tree *tree, const gchar *method_header_value,
const gchar *path_header_value, const gchar *authority_header_value)
{
if (method_header_value != NULL) {
if ((strcmp(method_header_value, "CONNECT_UDP") == 0) || (strcmp(method_header_value, "CONNECT") == 0)) {
if (authority_header_value != NULL) {
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ": ", "%s %s", method_header_value, authority_header_value);
}
} else {
if (path_header_value != NULL) {
/* append request information to info column (for example, HEADERS: GET /demo/1.jpg) */
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ": ", "%s %s", method_header_value, path_header_value);
/* append request information to Stream node */
proto_item_append_text(tree, ", %s %s", method_header_value, path_header_value);
}
}
}
}
static proto_item *
try_add_named_header_field(proto_tree *tree, tvbuff_t *tvb, int offset, guint32 length, const char *header_name,
const char *header_value)
{
int hf_id = -1;
header_field_info *hfi;
proto_item *ti = NULL;
const gint *entry = (const gint *)g_hash_table_lookup(header_fields_hash, header_name);
if (entry == NULL) {
return NULL;
}
hf_id = *entry;
hfi = proto_registrar_get_nth(hf_id);
DISSECTOR_ASSERT(hfi != NULL);
if (FT_IS_UINT32(hfi->type)) {
guint32 value;
if (ws_strtou32(header_value, NULL, &value)) {
ti = proto_tree_add_uint(tree, hf_id, tvb, offset, length, value);
}
} else if (FT_IS_UINT(hfi->type)) {
guint64 value;
if (ws_strtou64(header_value, NULL, &value)) {
ti = proto_tree_add_uint64(tree, hf_id, tvb, offset, length, value);
}
} else {
ti = proto_tree_add_item(tree, hf_id, tvb, offset, length, ENC_BIG_ENDIAN);
}
return ti;
}
static int
dissect_http3_headers(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint tvb_offset, guint offset,
quic_stream_info *stream_info, http3_stream_info_t *http3_stream)
{
const gchar *authority_header_value = NULL;
const gchar *method_header_value = NULL;
const gchar *path_header_value = NULL;
const gchar *scheme_header_value = NULL;
const guint8 *header_name;
const guint8 *header_value;
gint length = 0;
guint32 header_name_length;
guint32 header_value_length;
http3_header_data_t *header_data;
http3_session_info_t *http3_session;
http3_stream_dir packet_direction;
int header_len = 0, hoffset = 0;
nghttp3_qpack_decoder *decoder;
proto_item *header, *ti, *ti_named_field;
proto_tree *header_tree, *blocked_rcint_tree;
tvbuff_t *header_tvb;
http3_session = http3_session_lookup_or_create(pinfo);
header_data = http3_get_header_data(pinfo, tvb, offset);
ws_noisy("pdinfo visited=%d", PINFO_FD_VISITED(pinfo));
if (!PINFO_FD_VISITED(pinfo)) {
/*
* This packet has not been processed yet, which means this is
* the first linear scan. We do header decompression only
* once in linear scan and cache the result. If we don't
* cache, already processed data will be fed into decompressor
* again and again since dissector will be called randomly.
* This makes context out-of-sync.
*/
length = tvb_reported_length_remaining(tvb, tvb_offset);
packet_direction = http3_packet_get_direction(stream_info);
decoder = http3_session->qpack_decoder[packet_direction];
DISSECTOR_ASSERT(decoder);
DISSECTOR_ASSERT(header_data);
DISSECTOR_ASSERT(header_data->encoded.bytes == NULL);
DISSECTOR_ASSERT(header_data->encoded.len == 0);
DISSECTOR_ASSERT(header_data->header_fields == NULL);
header_data->encoded.bytes = tvb_memdup(wmem_file_scope(), tvb, tvb_offset, length);
header_data->encoded.pos = 0;
header_data->encoded.len = length;
nghttp3_qpack_stream_context *sctx = NULL;
nghttp3_qpack_stream_context_new(&sctx, http3_stream->id, nghttp3_mem_default());
ws_debug("Header data: %p %d %d", header_data->encoded.bytes, header_data->encoded.pos,
header_data->encoded.len);
proto_tree_add_expert_format(tree, pinfo, &ei_http3_header_encoded_state, tvb, tvb_offset, 0,
"HTTP3 encoded headers - bytes %p pos %d len %d", header_data->encoded.bytes,
header_data->encoded.pos, header_data->encoded.len);
/*
* Attempt to decode headers.
*
* TODO: This may incorrectly put headers that were blocked
* for packet k in the past to this packet n. We will deal with this later
*/
while (HEADER_BLOCK_ENC_ITER_REMAINING(header_data)) {
nghttp3_qpack_nv nv;
guint8 flags;
ws_noisy("%p %p:%d decode decoder=%p sctx=%p", header_data->encoded.bytes,
HEADER_BLOCK_ENC_ITER_PTR(header_data),
HEADER_BLOCK_ENC_ITER_REMAINING(header_data), decoder, sctx);
gint32 nread = (gint32)nghttp3_qpack_decoder_read_request(decoder, sctx, &nv, &flags,
HEADER_BLOCK_ENC_ITER_PTR(header_data),
HEADER_BLOCK_ENC_ITER_REMAINING(header_data), 1);
if (nread < 0) {
/*
* This should be signaled up.
*/
ws_debug("Early return nread=%d err=%s", nread, nghttp3_strerror(nread));
proto_tree_add_expert_format(tree, pinfo, &ei_http3_header_decoding_failed, tvb, tvb_offset, 0,
"QPACK error decoder %p ctx %p flags %" PRIu8 " error %d (%s)", decoder,
sctx, flags, nread, nghttp3_strerror((int)nread));
break;
}
/*
* Check whether the QPACK decoder is blocked on QPACK encoder stream.
*/
if (flags & NGHTTP3_QPACK_DECODE_FLAG_BLOCKED) {
guint64 ricnt, wicnt;
ricnt = nghttp3_qpack_stream_context_get_ricnt(sctx);
wicnt = nghttp3_qpack_decoder_get_icnt(decoder);
ti = proto_tree_add_boolean(tree, hf_http3_header_qpack_blocked, tvb, tvb_offset, 0, true);
proto_item_set_generated(ti);
blocked_rcint_tree = proto_item_add_subtree(ti, ett_http3_headers_qpack_blocked);
ti = proto_tree_add_uint(blocked_rcint_tree, hf_http3_header_qpack_blocked_stream_rcint, tvb,
tvb_offset, 0, (guint32)ricnt);
proto_item_set_generated(ti);
proto_tree_add_uint(blocked_rcint_tree, hf_http3_header_qpack_blocked_decoder_wicnt, tvb, tvb_offset, 0,
(guint32)wicnt);
proto_tree_add_expert_format(tree, pinfo, &ei_http3_header_decoding_blocked, tvb, tvb_offset, 0,
"QPACK - blocked decoder %p ctx %p flags=%" PRIu8 " ricnt=%" PRIu64
" wicnt=%" PRIu64 " error %d (%s)",
decoder, sctx, flags, ricnt, wicnt, nread, nghttp3_strerror((int)nread));
ws_debug("Early return nread=%d blocked=%" PRIu8 " ricnt=%" PRIu64 " wicnt=%" PRIu64 "",
nread, flags, ricnt, wicnt);
break;
}
/*
* Check whether the decoder has emitted header data.
*/
if (flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT) {
http3_header_field_t *out;
http3_header_field_def_t *def;
char *cached_pstr;
nghttp3_vec name_vec;
nghttp3_vec value_vec;
guint32 name_len;
guint8 *name;
guint32 value_len;
guint8 *value;
guint32 pstr_len;
ws_noisy("Emit nread=%d flags=%" PRIu8 "", nread, flags);
if (header_data->header_fields == NULL) {
header_data->header_fields = wmem_array_new(wmem_file_scope(), sizeof(http3_header_field_t));
}
name_vec = nghttp3_rcbuf_get_buf(nv.name);
name_len = (guint32)name_vec.len;
name = name_vec.base;
value_vec = nghttp3_rcbuf_get_buf(nv.value);
value_len = (guint32)value_vec.len;
value = value_vec.base;
ws_debug("HTTP header: %.*s: %.*s", name_len, name, value_len, value);
pstr_len = (name_len + value_len + 4 + 4);
http3_header_pstr = (char *)wmem_realloc(wmem_file_scope(), http3_header_pstr, pstr_len);
phton32(&http3_header_pstr[0], name_len);
memcpy(&http3_header_pstr[4], name, name_len);
phton32(&http3_header_pstr[4 + name_len], value_len);
memcpy(&http3_header_pstr[4 + name_len + 4], value, value_len);
/* Lookup a field definition, or create one if needed */
def = (http3_header_field_def_t *)wmem_map_lookup(HTTP3_HEADER_NAME_CACHE, http3_header_pstr);
if (def == NULL) {
char *def_name = NULL;
def_name = (char *)wmem_realloc(wmem_file_scope(), def_name, name_len + 1);
memcpy(def_name, name, name_len);
def = wmem_new0(wmem_file_scope(), http3_header_field_def_t);
def->name_len = name_len;
def->name = (const char *)def_name;
wmem_map_insert(HTTP3_HEADER_NAME_CACHE, http3_header_pstr, def);
/* XXX: keys are not copied in wmem_maps, so once we use
* http3_header_pstr, we should set it to NULL so that
* the memory pointed to won't be realloc'ed. However,
* we'll do that in the other map below, as we only insert
* into these maps at the same time, we are guaranteed
* that we will be setting it to NULL below. This is
* fragile and should be replaced with a single map.
* I also don't see the point of this map considering
* that the name and name_len are contained within the
* pstr value and can (and are) parsed from it; this
* map doesn't seem to be used currently.
*/
}
/* Create an output field and add it to the headers array */
out = wmem_new0(wmem_file_scope(), http3_header_field_t);
out->def = def;
cached_pstr = (char *)wmem_map_lookup(HTTP3_HEADER_CACHE, http3_header_pstr);
if (cached_pstr) {
out->decoded.pstr = cached_pstr;
} else {
out->decoded.pstr = http3_header_pstr;
wmem_map_insert(HTTP3_HEADER_CACHE, http3_header_pstr, http3_header_pstr);
http3_header_pstr = NULL;
}
out->decoded.pstr_len = pstr_len;
wmem_array_append(header_data->header_fields, out, 1);
/*
* Decrease the reference counts on the NGHTTP3 nv structure to avoid
* memory leaks.
*/
nghttp3_rcbuf_decref(nv.name);
nghttp3_rcbuf_decref(nv.value);
} else {
proto_tree_add_expert_format(tree, pinfo, &ei_http3_header_decoding_no_output, tvb, tvb_offset, 0,
"QPACK - nothing emitted decoder %p ctx %p flags %" PRIu8 " error %d (%s)",
decoder, sctx, flags, nread, nghttp3_strerror((int)nread));
}
/*
* Check whether the QPACK decoder has finished.
*/
if (nread == 0 || (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL)) {
break;
}
HEADER_BLOCK_ENC_ITER_INC(header_data, nread);
}
nghttp3_qpack_stream_context_del(sctx);
}
if ((header_data->header_fields == NULL) || (wmem_array_get_count(header_data->header_fields) == 0)) {
return tvb_offset;
}
header_tvb = tvb_new_composite();
for (guint i = 0; i < wmem_array_get_count(header_data->header_fields); ++i) {
http3_header_field_t *in;
tvbuff_t *next_tvb;
in = (http3_header_field_t *)wmem_array_index(header_data->header_fields, i);
header_len += in->decoded.pstr_len;
/* Now setup the tvb buffer to have the new data */
next_tvb = tvb_new_child_real_data(tvb, in->decoded.pstr, in->decoded.pstr_len, in->decoded.pstr_len);
tvb_composite_append(header_tvb, next_tvb);
}
tvb_composite_finalize(header_tvb);
add_new_data_source(pinfo, header_tvb, "Decompressed Header");
ti = proto_tree_add_uint(tree, hf_http3_header_length, header_tvb, hoffset, 1, header_len);
proto_item_set_generated(ti);
ti = proto_tree_add_uint(tree, hf_http3_headers_count, header_tvb, hoffset, 1,
wmem_array_get_count(header_data->header_fields));
proto_item_set_generated(ti);
for (guint i = 0; i < wmem_array_get_count(header_data->header_fields); ++i) {
http3_header_field_t *in;
in = (http3_header_field_t *)wmem_array_index(header_data->header_fields, i);
/* Populate tree with header name/value details. */
/* Add 'Header' subtree with description. */
header = proto_tree_add_item(tree, hf_http3_header, tvb, tvb_offset, in->encoded.len, ENC_NA);
header_tree = proto_item_add_subtree(header, ett_http3_headers);
/* header value length */
proto_tree_add_item_ret_uint(header_tree, hf_http3_header_name_length, header_tvb, hoffset, 4,
ENC_BIG_ENDIAN, &header_name_length);
hoffset += 4;
/* Add header name. */
proto_tree_add_item_ret_string(header_tree, hf_http3_header_name, header_tvb, hoffset, header_name_length,
ENC_ASCII | ENC_NA, pinfo->pool, &header_name);
hoffset += header_name_length;
/* header value length */
proto_tree_add_item_ret_uint(header_tree, hf_http3_header_value_length, header_tvb, hoffset, 4,
ENC_BIG_ENDIAN, &header_value_length);
hoffset += 4;
/* Add header value. */
proto_tree_add_item_ret_string(header_tree, hf_http3_header_value, header_tvb, hoffset, header_value_length,
ENC_ASCII | ENC_NA, pinfo->pool, &header_value);
ti_named_field = try_add_named_header_field(header_tree, header_tvb, hoffset, header_value_length, header_name,
header_value);
hoffset += header_value_length;
proto_item_append_text(header, ": %s: %s", header_name, header_value);
/* Display :method, :path and :status in info column (just like http1.1 dissector does)*/
if (strcmp(header_name, HTTP3_HEADER_NAME_METHOD) == 0) {
method_header_value = header_value;
try_append_method_path_info(pinfo, tree, method_header_value, path_header_value, authority_header_value);
} else if (strcmp(header_name, HTTP3_HEADER_NAME_PATH) == 0) {
path_header_value = header_value;
try_append_method_path_info(pinfo, tree, method_header_value, path_header_value, authority_header_value);
http_add_path_components_to_tree(header_tvb, pinfo, ti_named_field, hoffset - header_value_length,
header_value_length);
} else if (strcmp(header_name, HTTP3_HEADER_NAME_AUTHORITY) == 0) {
authority_header_value = header_value;
try_append_method_path_info(pinfo, tree, method_header_value, path_header_value, authority_header_value);
} else if (strcmp(header_name, HTTP3_HEADER_NAME_STATUS) == 0) {
const gchar *reason_phase =
val_to_str_const((guint)strtoul(header_value, NULL, 10), vals_http_status_code, "Unknown");
/* append response status and reason phrase to info column (for example, HEADERS: 200 OK) */
col_append_sep_fstr(pinfo->cinfo, COL_INFO, ": ", "%s %s", header_value, reason_phase);
/* append response status and reason phrase to header_tree and Stream node */
proto_item_append_text(header_tree, " %s", reason_phase);
proto_item_append_text(tree, ", %s %s", header_value, reason_phase);
} else if (strcmp(header_name, HTTP3_HEADER_NAME_AUTHORITY) == 0) {
authority_header_value = header_value;
} else if (strcmp(header_name, HTTP3_HEADER_NAME_SCHEME) == 0) {
scheme_header_value = header_value;
}
tvb_offset += in->encoded.len;
}
/*
* Use the `:authority' Header as an indication that this packet is a request.
*/
if (authority_header_value) {
proto_item *e_ti;
gchar *uri;
/*
* https://www.ietf.org/rfc/rfc9114.html#name-request-pseudo-header-field
*
* All HTTP/3 requests MUST include exactly one value for the `:method',
* `:scheme', and `:path' pseudo-header fields, unless the request is
* a `CONNECT' request; see Section 4.4.
*/
if (method_header_value && strcmp(method_header_value, HTTP3_HEADER_METHOD_CONNECT) == 0) {
uri = wmem_strdup(pinfo->pool, authority_header_value);
} else {
uri = wmem_strdup_printf(pinfo->pool, "%s://%s%s", scheme_header_value, authority_header_value,
path_header_value);
}
e_ti = proto_tree_add_string(tree, hf_http3_header_request_full_uri, tvb, 0, 0, uri);
proto_item_set_url(e_ti);
proto_item_set_generated(e_ti);
}
return tvb_offset;
}
#endif /* HAVE_NGHTTP3 */
static http3_session_info_t *
http3_session_new(void)
{
http3_session_info_t *http3_session;
http3_session = wmem_new0(wmem_file_scope(), http3_session_info_t);
#ifdef HAVE_NGHTTP3
http3_initialize_qpack_decoders(http3_session);
#endif
return http3_session;
}
static http3_session_info_t *
http3_session_lookup_or_create(packet_info *pinfo)
{
http3_session_info_t *http3_session;
/* First, try to look up the session by initial QUIC DCID */
quic_cid_t initial_dcid = {0};
if (quic_conn_data_get_conn_client_dcid_initial(pinfo, &initial_dcid)) {
/* Look up the session data in the conn map */
http3_session = (http3_session_info_t *)wmem_map_lookup(HTTP3_CONN_INFO_MAP, &initial_dcid);
if (http3_session == NULL) {
quic_cid_t *dcid_p = wmem_memdup(wmem_file_scope(), &initial_dcid, sizeof(initial_dcid));
http3_session = http3_session_new();
wmem_map_insert(HTTP3_CONN_INFO_MAP, dcid_p, http3_session);
}
} else {
/* Initial DCID can not be found, use the 5-tuple for lookup */
conversation_t *conversation = find_or_create_conversation(pinfo);
http3_session = (http3_session_info_t *)conversation_get_proto_data(conversation, proto_http3);
if (http3_session == NULL) {
http3_session = http3_session_new();
conversation_add_proto_data(conversation, proto_http3, http3_session);
}
}
return http3_session;
}
static conversation_t *
http3_find_inner_conversation(packet_info *pinfo, quic_stream_info *stream_info, http3_stream_info_t *http3_stream, void **ctx)
{
conversation_t *inner_conv = NULL;
if (stream_info != NULL) {
if (ctx) {
*ctx = pinfo->conv_elements;
}
wmem_array_t *conversation_elements = wmem_array_new(pinfo->pool, sizeof(conversation_element_t));
conversation_element_t h3_stream_addr = {
.type = CE_ADDRESS,
.addr_val = (pinfo->srcport < pinfo->destport) ? pinfo->src : pinfo->dst,
};
wmem_array_append_one(conversation_elements, h3_stream_addr);
conversation_element_t h3_stream_port = {
.type = CE_PORT,
.port_val = (pinfo->srcport < pinfo->destport) ? pinfo->srcport : pinfo->destport,
};
wmem_array_append_one(conversation_elements, h3_stream_port);
conversation_element_t h3_stream_quic_stream = {
.type = CE_UINT64,
.uint64_val = http3_stream->id,
};
wmem_array_append_one(conversation_elements, h3_stream_quic_stream);
conversation_element_t h3_stream_last = {
.type = CE_CONVERSATION_TYPE,
.conversation_type_val = CONVERSATION_LOG,
};
wmem_array_append_one(conversation_elements, h3_stream_last);
pinfo->conv_elements = (conversation_element_t *)wmem_array_get_raw(conversation_elements);
inner_conv = find_conversation_pinfo(pinfo, 0);
if (!inner_conv) {
inner_conv = conversation_new_full(pinfo->fd->num, pinfo->conv_elements);
}
}
return inner_conv;
}
static void
http3_reset_inner_conversation(packet_info *pinfo, void *ctx)
{
if (ctx) {
struct conversation_element *conv_elements = (struct conversation_element *)ctx;
pinfo->conv_elements = conv_elements;
}
}
static int
dissect_http3_data(tvbuff_t *tvb, packet_info *pinfo, proto_tree *http3_tree, guint offset _U_,
quic_stream_info *stream_info, http3_stream_info_t *http3_stream)
{
void *saved_ctx = NULL;
gint remaining;
conversation_t *inner_conv _U_;
proto_item *ti_data _U_;
remaining = tvb_reported_length(tvb);
inner_conv = http3_find_inner_conversation(pinfo, stream_info, http3_stream, &saved_ctx);
ti_data = proto_tree_add_item(http3_tree, hf_http3_data, tvb, offset, remaining, ENC_NA);
http3_reset_inner_conversation(pinfo, saved_ctx);
return tvb_reported_length(tvb);
}
/* Settings */
static int
dissect_http3_settings(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http3_tree, guint offset)
{
guint64 settingsid, value;
int lenvar;
proto_item *ti_settings, *pi;
proto_tree *settings_tree;
while (tvb_reported_length_remaining(tvb, offset) > 0) {
ti_settings = proto_tree_add_item(http3_tree, hf_http3_settings, tvb, offset, 2, ENC_NA);
settings_tree = proto_item_add_subtree(ti_settings, ett_http3_settings);
pi = proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_identifier, tvb, offset, -1,
ENC_VARINT_QUIC, &settingsid, &lenvar);
/* Check if it is a GREASE Settings ID */
if (http3_is_reserved_code(settingsid)) {
proto_item_set_text(pi, "Settings Identifier: Reserved (%#" PRIx64 ")", settingsid);
proto_item_append_text(ti_settings, " - Reserved (GREASE)");
} else {
proto_item_append_text(ti_settings, " - %s",
val64_to_str(settingsid, http3_settings_vals, "Unknown (%#" PRIx64 ")"));
}
offset += lenvar;
proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_value, tvb, offset, -1, ENC_VARINT_QUIC, NULL,
&lenvar);
switch (settingsid) {
case HTTP3_QPACK_MAX_TABLE_CAPACITY:
proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_qpack_max_table_capacity, tvb, offset, -1,
ENC_VARINT_QUIC, &value, &lenvar);
proto_item_append_text(ti_settings, ": %" PRIu64, value);
break;
case HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE:
proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_max_field_section_size, tvb, offset, -1,
ENC_VARINT_QUIC, &value, &lenvar);
proto_item_append_text(ti_settings, ": %" PRIu64, value);
break;
case HTTP3_QPACK_BLOCKED_STREAMS:
proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_qpack_blocked_streams, tvb, offset, -1,
ENC_VARINT_QUIC, &value, &lenvar);
proto_item_append_text(ti_settings, ": %" PRIu64, value);
break;
case HTTP3_EXTENDED_CONNECT:
proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_extended_connect, tvb, offset, -1,
ENC_VARINT_QUIC, &value, &lenvar);
proto_item_append_text(ti_settings, ": %" PRIu64, value);
break;
case HTTP3_WEBTRANSPORT:
proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_webtransport, tvb, offset, -1,
ENC_VARINT_QUIC, &value, &lenvar);
proto_item_append_text(ti_settings, ": %" PRIu64, value);
break;
case HTTP3_H3_DATAGRAM:
proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_h3_datagram, tvb, offset, -1,
ENC_VARINT_QUIC, &value, &lenvar);
proto_item_append_text(ti_settings, ": %" PRIu64, value);
break;
case HTTP3_H3_DATAGRAM_DRAFT04:
proto_tree_add_item_ret_varint(settings_tree, hf_http3_settings_h3_datagram_draft04, tvb, offset, -1,
ENC_VARINT_QUIC, &value, &lenvar);
proto_item_append_text(ti_settings, ": %" PRIu64, value);
break;
default:
/* No Default */
break;
}
offset += lenvar;
}
return offset;
}
/**
* Priority Update
*/
static int
dissect_http3_priority_update(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *http3_tree, guint offset,
guint64 frame_length)
{
guint64 priority_field_value_len;
int lenvar;
proto_tree_add_item_ret_varint(http3_tree, hf_http3_priority_update_element_id, tvb, offset, -1, ENC_VARINT_QUIC,
NULL, &lenvar);
offset += lenvar;
priority_field_value_len = frame_length - lenvar;
proto_tree_add_item(http3_tree, hf_http3_priority_update_field_value, tvb, offset, (int)priority_field_value_len,
ENC_ASCII);
offset += (int)priority_field_value_len;
return offset;
}
static int
dissect_http3_frame(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, quic_stream_info *stream_info, http3_stream_info_t *http3_stream)
{
uint64_t frame_type, frame_length;
int type_length_size, lenvar, payload_length;
proto_item *ti_ft, *ti_ft_type;
proto_tree *ft_tree;
const gchar *ft_display_name;
ti_ft = proto_tree_add_item(tree, hf_http3_frame, tvb, offset, -1, ENC_NA);
ft_tree = proto_item_add_subtree(ti_ft, ett_http3_frame);
ti_ft_type = proto_tree_add_item_ret_varint(ft_tree, hf_http3_frame_type, tvb, offset, -1, ENC_VARINT_QUIC, &frame_type,
&lenvar);
offset += lenvar;
type_length_size = lenvar;
if (http3_is_reserved_code(frame_type)) {
proto_item_set_text(ti_ft_type, "Type: Reserved (%#" PRIx64 ")", frame_type);
ft_display_name = "Reserved (GREASE)";
} else {
ft_display_name = val64_to_str(frame_type, http3_frame_types, "Unknown (%#" PRIx64 ")");
col_append_sep_str(pinfo->cinfo, COL_INFO, ", ", ft_display_name);
}
proto_tree_add_item_ret_varint(ft_tree, hf_http3_frame_length, tvb, offset, -1, ENC_VARINT_QUIC, &frame_length,
&lenvar);
proto_item_set_text(ti_ft, "%s len=%" PRId64, ft_display_name, frame_length);
offset += lenvar;
type_length_size += lenvar;
if (frame_length >= (uint64_t)(INT32_MAX - type_length_size)) {
// There is no way for us to correctly handle these sizes. Most likely
// it is garbage.
return INT32_MAX;
}
payload_length = (int)frame_length;
proto_item_set_len(ti_ft, type_length_size + payload_length);
if (payload_length == 0) {
return offset;
}
proto_tree_add_item(ft_tree, hf_http3_frame_payload, tvb, offset, payload_length, ENC_NA);
switch (frame_type) {
case HTTP3_DATA: { /* TODO: dissect Data Frame */
tvbuff_t *next_tvb = tvb_new_subset_length(tvb, offset, payload_length);
dissect_http3_data(next_tvb, pinfo, ft_tree, 0, stream_info, http3_stream);
} break;
case HTTP3_HEADERS: {
#ifdef HAVE_NGHTTP3
tvbuff_t *next_tvb = tvb_new_subset_length(tvb, offset, payload_length);
dissect_http3_headers(next_tvb, pinfo, ft_tree, 0, offset, stream_info, http3_stream);
#endif /* HAVE_NGHTTP3 */
} break;
case HTTP3_CANCEL_PUSH: /* TODO: dissect Cancel_Push Frame */
break;
case HTTP3_SETTINGS: { /* Settings Frame */
tvbuff_t *next_tvb = tvb_new_subset_length(tvb, offset, payload_length);
dissect_http3_settings(next_tvb, pinfo, ft_tree, 0);
} break;
case HTTP3_PUSH_PROMISE: /* TODO: dissect Push_Promise_Frame */
break;
case HTTP3_GOAWAY: /* TODO: dissect Goaway Frame */
break;
case HTTP3_MAX_PUSH_ID: /* TODO: dissect Max_Push_ID Frame */
break;
case HTTP3_PRIORITY_UPDATE_REQUEST_STREAM:
case HTTP3_PRIORITY_UPDATE_PUSH_STREAM: { /* Priority_Update Frame */
tvbuff_t *next_tvb = tvb_new_subset_length(tvb, offset, payload_length);
dissect_http3_priority_update(next_tvb, pinfo, ft_tree, 0, frame_length);
} break;
default: /* TODO: add expert_advise */
break;
}
offset += payload_length;
return offset;
}
static void
report_unknown_stream_type(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
quic_stream_info *stream_info, http3_stream_info_t *http3_stream)
{
/*
* https://www.rfc-editor.org/rfc/rfc9114.html#name-unidirectional-streams
*
* "If the stream header indicates a stream type which is not supported by
* the recipient, the remainder of the stream cannot be consumed as the
* semantics are unknown."
*/
proto_tree_add_expert_format(tree, pinfo, &ei_http3_unknown_stream_type, tvb, offset, 0,
"Unknown stream type %#" PRIx64 " on Stream ID %#" PRIx64,
http3_stream->uni_stream_type, stream_info->stream_id);
}
/**
* https://www.rfc-editor.org/rfc/rfc7541#section-5.1
* via
* https://www.rfc-editor.org/rfc/rfc9204.html#name-prefixed-integers
*
* Read a QPACK varint value, return number of consumed bytes, including the prefix byte.
*
* Optionally return the value of the one-bit flag that precedes the QPACK prefixed integer.
*
* Such flag is is interpreted differently, depending on the context:
* - If the prefixed integer represents length of a string literal, the flag value
* indicates that the following string literal is encoded using Huffman code.
* See https://www.rfc-editor.org/rfc/rfc7541#section-5.2 for details.
* - If the prefixed integer represents a name index, the flag value indicates
* that the following name index belongs to the static/dynamic table.
* See https://www.rfc-editor.org/rfc/rfc9204.html#name-insert-with-name-reference
* for details.
*/
#define HTTP3_QPACK_MAX_SHIFT 62
#define HTTP3_QPACK_MAX_INT ((1ull << HTTP3_QPACK_MAX_SHIFT) - 1)
static gint
read_qpack_prefixed_integer(tvbuff_t *tvb, int offset, gint prefix,
guint64 *out_result, gboolean *out_fin, gboolean *out_flag)
{
/*
* This can throw a ReportedBoundError; in fact, we count on that
* currently in order to detect QPACK fields split across packets.
*/
const uint8_t *buf = tvb_get_ptr(tvb, offset, -1);
const uint8_t *end = buf + tvb_captured_length_remaining(tvb, offset);
guint64 k = (uint8_t)((1 << prefix) - 1);
guint64 n = 0;
guint64 add = 0;
guint64 shift = 0;
const guint8 *p = buf;
if (out_flag) {
*out_flag = *p & (1 << prefix);
}
if (((*p) & k) != k) {
*out_result = (*p) & k;
*out_fin = true;
return 1;
}
n = k;
if (++p == end) {
*out_result = n;
*out_fin = false;
return (gint)(p - buf);
}
for (; p != end; ++p, shift += 7) {
add = (*p) & 0x7f;
if (shift > HTTP3_QPACK_MAX_SHIFT) {
return -1;
}
if ((HTTP3_QPACK_MAX_INT >> shift) < add) {
return -1;
}
add <<= shift;
if (HTTP3_QPACK_MAX_INT - add < n) {
return -1;
}
n += add;
if (((*p) & (1 << 7)) == 0) {
break;
}
}
*out_result = n;
/* If we consumed all bytes, return the consumed bytes */
if (p == end) {
*out_fin = false;
return (gint)(p - buf);
}
/* Otherwise, consume extra byte and mark the fin output param */
if (out_fin) {
*out_fin = true;
}
return (gint)(p + 1 - buf);
}
static gint
dissect_http3_qpack_encoder_stream(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree,
int offset, http3_stream_info_t *http3_stream _U_)
{
guint remaining;
proto_item *opcode_ti;
proto_tree *opcode_tree;
tvbuff_t *decoded_tvb;
guint decoded = 0;
gint fin = 0, inc = 0;
volatile bool can_continue = true;
remaining = tvb_captured_length_remaining(tvb, offset);
while (decoded < remaining && can_continue) {
gint opcode_offset = offset + decoded;
gint opcode_len = 0;
guint8 opcode = 0;
fin = 0;
TRY {
opcode = tvb_get_guint8(tvb, opcode_offset) & QPACK_OPCODE_MASK;
ws_noisy("Decoding opcode=%" PRIu8 " decoded=%d remaining=%d", opcode, decoded, remaining);
if (opcode & QPACK_OPCODE_INSERT_INDEXED) {
gint table_entry_len = 0;
guint64 table_entry = 0;
gint value_offset = 0;
gint value_len = 0;
gint val_bytes_offset = 0;
guint64 val_bytes_len = 0;
gboolean value_huffman = FALSE;
/*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 1 | T | Name Index (6+) |
* +---+---+-----------------------+
* | H | Value Length (7+) |
* +---+---------------------------+
* | Value String (Length bytes) |
* +-------------------------------+
*
*/
decoded += read_qpack_prefixed_integer(tvb, opcode_offset, 6, &table_entry, &fin, NULL);
table_entry_len = offset + decoded - opcode_offset;
value_offset = offset + decoded;
decoded += read_qpack_prefixed_integer(tvb, value_offset, 7, &val_bytes_len, &fin, &value_huffman);
val_bytes_offset = offset + decoded;
decoded += (guint32)val_bytes_len;
value_len = offset + decoded - value_offset;
opcode_len = offset + decoded - opcode_offset;
opcode_ti = proto_tree_add_item(tree, hf_http3_qpack_encoder_opcode_insert_indexed, tvb, opcode_offset,
opcode_len, ENC_NA);
opcode_tree = proto_item_add_subtree(opcode_ti, ett_http3_qpack_opcode);
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_indexed_ref, tvb, opcode_offset,
table_entry_len, ENC_NA);
if (value_huffman) {
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_indexed_hval, tvb,
val_bytes_offset, (guint32)val_bytes_len, ENC_NA);
decoded_tvb = tvb_child_uncompress_hpack_huff(tvb, (int)val_bytes_offset, (int)val_bytes_len);
if (decoded_tvb) {
add_new_data_source(pinfo, decoded_tvb, "Decoded QPACK Value");
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_indexed_val, decoded_tvb,
0, tvb_captured_length(decoded_tvb), ENC_NA);
}
} else {
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_indexed_val, tvb,
val_bytes_offset, (guint32)val_bytes_len, ENC_NA);
}
proto_item_set_text(opcode_ti, "QPACK encoder INSERT_INDEXED ref_len=%d ref=%" PRIu64 " val_len=%d",
table_entry_len, table_entry, value_len);
} else if (opcode & QPACK_OPCODE_INSERT) {
guint name_len_offset = 0;
guint name_len_len = 0;
guint name_len = 0;
gboolean name_huffman = FALSE;
guint name_bytes_offset = 0;
guint64 name_bytes_len = 0;
guint val_len_offset = 0;
guint val_len_len = 0;
guint val_len = 0;
gboolean value_huffman = FALSE;
guint val_bytes_offset = 0;
guint64 val_bytes_len = 0;
/*
* Insert with literal name:
* See https://datatracker.ietf.org/doc/html/rfc9204#name-insert-with-literal-name
*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 1 | H | Name Length (5+) |
* +---+---+---+-------------------+
* | Name String (Length bytes) |
* +---+---------------------------+
* | H | Value Length (7+) |
* +---+---------------------------+
* | Value String (Length bytes) |
* +-------------------------------+
*
*/
/* Read the 5-encoded name length */
name_len_offset = offset + decoded;
decoded += read_qpack_prefixed_integer(tvb, name_len_offset, 5, &name_bytes_len, &fin, &name_huffman);
name_len_len = offset + decoded - name_len_offset;
name_len = name_len_len + (guint32)name_bytes_len;
name_bytes_offset = offset + decoded;
decoded += (guint32)name_bytes_len;
/* Read the 7-encoded value length */
val_len_offset = offset + decoded;
decoded += read_qpack_prefixed_integer(tvb, val_len_offset, 7, &val_bytes_len, &fin, &value_huffman);
val_len_len = offset + decoded - val_len_offset;
val_len = val_len_len + (guint32)val_bytes_len;
val_bytes_offset = offset + decoded;
decoded += (guint32)val_bytes_len;
opcode_len = offset + decoded - opcode_offset;
opcode_ti =
proto_tree_add_item(tree, hf_http3_qpack_encoder_opcode_insert, tvb, opcode_offset, opcode_len, ENC_NA);
opcode_tree = proto_item_add_subtree(opcode_ti, ett_http3_qpack_opcode);
if (name_huffman) {
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_hname, tvb, name_bytes_offset,
(guint32)name_bytes_len, ENC_NA);
decoded_tvb = tvb_child_uncompress_hpack_huff(tvb, (int)name_bytes_offset, (int)name_bytes_len);
if (decoded_tvb) {
add_new_data_source(pinfo, decoded_tvb, "Decoded QPACK Name");
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_name, decoded_tvb,
0, tvb_captured_length(decoded_tvb), ENC_NA);
}
} else {
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_name, tvb, name_bytes_offset,
(guint32)name_bytes_len, ENC_NA);
}
if (value_huffman) {
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_hval, tvb, val_bytes_offset,
(guint32)val_bytes_len, ENC_NA);
decoded_tvb = tvb_child_uncompress_hpack_huff(tvb, (int)val_bytes_offset, (int)val_bytes_len);
if (decoded_tvb) {
add_new_data_source(pinfo, decoded_tvb, "Decoded QPACK Value");
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_val, decoded_tvb,
0, tvb_captured_length(decoded_tvb), ENC_NA);
}
} else {
proto_tree_add_item(opcode_tree, hf_http3_qpack_encoder_opcode_insert_val, tvb, val_bytes_offset,
(guint32)val_bytes_len, ENC_NA);
}
proto_item_set_text(opcode_ti, "QPACK encoder opcode: INSERT name_len=%d val_len=%d", name_len, val_len);
} else if (opcode & QPACK_OPCODE_SET_DTABLE_CAP) {
guint64 dynamic_capacity = 0;
/*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 0 | 1 | Capacity (5+) |
* +---+---+---+-------------------+
*/
decoded += read_qpack_prefixed_integer(tvb, opcode_offset, 5, &dynamic_capacity, &fin, NULL);
opcode_len = offset + decoded - opcode_offset;
opcode_ti = proto_tree_add_item(tree, hf_http3_qpack_encoder_opcode_dtable_cap, tvb, opcode_offset,
opcode_len, ENC_NA);
opcode_tree = proto_item_add_subtree(opcode_ti, ett_http3_qpack_opcode);
proto_tree_add_uint64(opcode_tree, hf_http3_qpack_encoder_opcode_dtable_cap_val, tvb, opcode_offset, opcode_len,
dynamic_capacity);
proto_item_set_text(opcode_ti, "QPACK encoder opcode: Set DTable Cap=%" PRIu64 "", dynamic_capacity);
} else if (opcode == QPACK_OPCODE_DUPLICATE) {
guint64 duplicate_of = 0;
/*
* 0 1 2 3 4 5 6 7
* +---+---+---+---+---+---+---+---+
* | 0 | 0 | 0 | Index (5+) |
* +---+---+---+-------------------+
*/
inc = read_qpack_prefixed_integer(tvb, opcode_offset, 5, &duplicate_of, &fin, NULL);
DISSECTOR_ASSERT(0 < inc);
DISSECTOR_ASSERT(decoded + inc <= remaining);
decoded += inc;
opcode_len = offset + decoded - opcode_offset;
proto_tree_add_item(tree, hf_http3_qpack_encoder_opcode_duplicate, tvb, opcode_offset,
opcode_len, ENC_NA);
} else {
ws_debug("Opcode=%" PRIu8 ": UNKNOWN", opcode);
can_continue = false;
}
}
CATCH(ReportedBoundsError) {
decoded = opcode_offset - offset;
can_continue = false;
}
ENDTRY;
}
return decoded;
}
static gint
dissect_http3_qpack_enc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
quic_stream_info *stream_info, http3_stream_info_t *http3_stream)
{
gint remaining, remaining_captured, retval, decoded = 0;
proto_item * qpack_update;
proto_tree * qpack_update_tree;
http3_session_info_t *http3_session;
remaining_captured = tvb_captured_length_remaining(tvb, offset);
remaining = tvb_reported_length_remaining(tvb, offset);
DISSECTOR_ASSERT(remaining_captured == remaining);
retval = remaining;
http3_session = http3_session_lookup_or_create(pinfo);
DISSECTOR_ASSERT(http3_session);
/*
* Add a QPACK encoder tree item.
*/
qpack_update = proto_tree_add_item(tree, hf_http3_qpack_encoder, tvb, offset, remaining, ENC_NA);
qpack_update_tree = proto_item_add_subtree(qpack_update, ett_http3_qpack_update);
decoded = dissect_http3_qpack_encoder_stream(tvb, pinfo, qpack_update_tree, offset,
http3_stream);
if (!PINFO_FD_VISITED(pinfo)) {
ws_debug("decode encoder stream: Wireshark decoded=%u of %u", decoded, remaining);
}
if (decoded < remaining) {
pinfo->desegment_offset = offset + decoded;
pinfo->desegment_len = DESEGMENT_ONE_MORE_SEGMENT;
}
#ifdef HAVE_NGHTTP3
if (remaining > 0) {
proto_item * ti;
http3_stream_dir packet_direction = http3_packet_get_direction(stream_info);
nghttp3_qpack_decoder *decoder = http3_session->qpack_decoder[packet_direction];
http3_qpack_encoder_state_t *encoder_state = http3_get_qpack_encoder_state(pinfo, tvb, offset);
if (!PINFO_FD_VISITED(pinfo)) {
/*
* Since we are now defragmenting, pass only the number of bytes
* decoded to the nghttp3_qpack_decoder. Otherwise, we'll end up
* sending the same bytes to the decoder again when the packet
* is defragmented.
*/
uint8_t *qpack_buf = (uint8_t *)tvb_memdup(pinfo->pool, tvb, offset, decoded);
gint qpack_buf_len = decoded;
/*
* Get the instr count prior to processing the data.
*/
uint64_t icnt_before = nghttp3_qpack_decoder_get_icnt(decoder);
encoder_state->nread = nghttp3_qpack_decoder_read_encoder(decoder, qpack_buf, qpack_buf_len);
encoder_state->icnt = nghttp3_qpack_decoder_get_icnt(decoder);
encoder_state->icnt_inc = (uint32_t)(encoder_state->icnt - icnt_before);
ws_debug("decode encoder stream: decoder=%p nread=%td new insertions=%u total insertions=%" PRIu64, decoder, encoder_state->nread, encoder_state->icnt_inc, encoder_state->icnt);
}
/* nghttp3_qpack_decoder_read_encoder() returns a nghttp3_ssize
* (ptrdiff_t), negative in the case of errors, but nghttp3_strerror()
* accepts int instead.
*/
if (encoder_state->nread < 0) {
quic_cid_t quic_cid = {.len = 0};
gboolean initial_cid_found = quic_conn_data_get_conn_client_dcid_initial(pinfo, &quic_cid);
proto_tree_add_expert_format(
tree, pinfo, &ei_http3_qpack_failed, tvb, offset, 0, "QPACK decoder %p DCID %s [found=%d] error %d (%s)",
decoder, cid_to_string(&quic_cid, pinfo->pool), initial_cid_found, (int)encoder_state->nread, nghttp3_strerror((int)encoder_state->nread));
}
proto_item_set_text(qpack_update, "QPACK encoder stream; %d opcodes (%" PRIu64 " total)", encoder_state->icnt_inc,
encoder_state->icnt);
ti = proto_tree_add_uint(qpack_update_tree, hf_http3_qpack_encoder_icnt_inc, tvb, offset, 0,
encoder_state->icnt_inc);
proto_item_set_generated(ti);
ti = proto_tree_add_uint64(qpack_update_tree, hf_http3_qpack_encoder_icnt, tvb, offset, 0,
encoder_state->icnt);
proto_item_set_generated(ti);
}
#else
(void)stream_info;
(void)qpack_update;
(void)decoded;
#endif /* HAVE_NGHTTP3 */
return retval;
}
static int
dissect_http3_client_bidi_stream(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset,
quic_stream_info *stream_info, http3_stream_info_t *http3_stream)
{
proto_item *ti_stream;
proto_tree *stream_tree;
ti_stream = proto_tree_add_item(tree, hf_http3_stream_bidi, tvb, offset, 1, ENC_NA);
stream_tree = proto_item_add_subtree(ti_stream, ett_http3_stream_bidi);
while (tvb_reported_length_remaining(tvb, offset)) {
if (!http3_check_frame_size(tvb, pinfo, offset)) {
return tvb_captured_length(tvb);
}
offset = dissect_http3_frame(tvb, pinfo, stream_tree, offset, stream_info, http3_stream);
}
return offset;
}
static int
dissect_http3_uni_stream(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, quic_stream_info *stream_info,
http3_stream_info_t *http3_stream)
{
guint64 stream_type;
int lenvar;
proto_item *ti_stream, *ti_stream_type;
proto_tree *stream_tree;
const gchar *stream_display_name;
ti_stream = proto_tree_add_item(tree, hf_http3_stream_uni, tvb, offset, -1, ENC_NA);
stream_tree = proto_item_add_subtree(ti_stream, ett_http3_stream_uni);
if (stream_info->offset == 0) {
ti_stream_type = proto_tree_add_item_ret_varint(stream_tree, hf_http3_stream_uni_type, tvb, offset, -1, ENC_VARINT_QUIC, &stream_type,
&lenvar);
offset += lenvar;
http3_stream->uni_stream_type = stream_type;
if (http3_is_reserved_code(stream_type)) {
// Reserved to exercise requirement that unknown types are ignored.
proto_item_set_text(ti_stream_type, "Stream Type: Reserved (%#" PRIx64 ")", stream_type);
stream_display_name = "Reserved (GREASE)";
}
else {
stream_display_name = val64_to_str(stream_type, http3_stream_types, "Unknown (%#" PRIx64 ")");
}
proto_item_set_text(ti_stream, "UNI STREAM: %s off=%" PRIu64 "", stream_display_name, stream_info->stream_offset);
} else {
stream_type = http3_stream->uni_stream_type;
/*ti_stream_type = proto_tree_add_item(stream_tree, hf_http3_stream_uni_type, tvb, offset, -1, ENC_NA);*/
}
switch (stream_type) {
case HTTP3_STREAM_TYPE_CONTROL:
while (tvb_reported_length_remaining(tvb, offset)) {
if (!http3_check_frame_size(tvb, pinfo, offset)) {
return tvb_captured_length(tvb);
}
offset = dissect_http3_frame(tvb, pinfo, stream_tree, offset, stream_info, http3_stream);
}
break;
case HTTP3_STREAM_TYPE_PUSH:
// The remaining data of this stream consists of HTTP/3 frames.
if (stream_info->offset == 0) {
proto_tree_add_item_ret_varint(stream_tree, hf_http3_push_id, tvb, offset, -1, ENC_VARINT_QUIC, NULL, &lenvar);
offset += lenvar;
}
break;
case HTTP3_STREAM_TYPE_QPACK_ENCODER:
offset = dissect_http3_qpack_enc(tvb, pinfo, stream_tree, offset, stream_info, http3_stream);
break;
case HTTP3_STREAM_TYPE_QPACK_DECODER:
// TODO
offset = tvb_captured_length(tvb);
break;
case HTTP3_STREAM_TYPE_WEBTRANSPORT:
// TODO
offset = tvb_captured_length(tvb);
break;
default:
// Unknown or reserved stream type, consume everything.
if (!http3_is_reserved_code(stream_type)) {
if (!PINFO_FD_VISITED(pinfo)) {
http3_stream->broken_from_offset = stream_info->offset + offset;
}
report_unknown_stream_type(tvb, pinfo, stream_tree, offset, stream_info, http3_stream);
}
offset = tvb_captured_length(tvb);
break;
}
return offset;
}
static int
dissect_http3(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data)
{
quic_stream_info * stream_info = (quic_stream_info *)data;
proto_item * ti;
proto_tree * http3_tree;
int offset = 0;
http3_stream_info_t *http3_stream;
if (!stream_info) {
return 0;
}
switch (QUIC_STREAM_TYPE(stream_info->stream_id)) {
case QUIC_STREAM_CLIENT_BIDI:
/* Used for HTTP requests and responses. */
if (!http3_check_frame_size(tvb, pinfo, offset)) {
return tvb_captured_length(tvb);
}
break;
case QUIC_STREAM_SERVER_BIDI:
/* "HTTP/3 does not use server-initiated bidirectional streams,
* though an extension could define a use for these streams." */
break;
case QUIC_STREAM_CLIENT_UNI:
case QUIC_STREAM_SERVER_UNI:
break;
}
col_set_str(pinfo->cinfo, COL_PROTOCOL, "HTTP3");
// Only clear the columns if this is the first HTTP/3 STREAM in the packet.
if (!proto_is_frame_protocol(pinfo->layers, "http3")) {
col_clear(pinfo->cinfo, COL_INFO);
}
ti = proto_tree_add_item(tree, proto_http3, tvb, 0, -1, ENC_NA);
http3_tree = proto_item_add_subtree(ti, ett_http3);
http3_stream = (http3_stream_info_t *)quic_stream_get_proto_data(pinfo, stream_info);
if (!http3_stream) {
http3_stream = wmem_new0(wmem_file_scope(), http3_stream_info_t);
quic_stream_add_proto_data(pinfo, stream_info, http3_stream);
http3_stream->id = stream_info->stream_id;
}
// If a STREAM has unknown data, everything afterwards cannot be dissected.
if (http3_stream->broken_from_offset && http3_stream->broken_from_offset <= stream_info->offset + offset) {
report_unknown_stream_type(tvb, pinfo, tree, offset, stream_info, http3_stream);
return tvb_captured_length(tvb);
}
switch (QUIC_STREAM_TYPE(stream_info->stream_id)) {
case QUIC_STREAM_CLIENT_BIDI:
/* Used for HTTP requests and responses. */
dissect_http3_client_bidi_stream(tvb, pinfo, http3_tree, offset, stream_info, http3_stream);
break;
case QUIC_STREAM_SERVER_BIDI:
/* "HTTP/3 does not use server-initiated bidirectional streams,
* though an extension could define a use for these streams." */
// XXX expert info?
return tvb_captured_length(tvb);
case QUIC_STREAM_CLIENT_UNI:
case QUIC_STREAM_SERVER_UNI:
dissect_http3_uni_stream(tvb, pinfo, http3_tree, offset, stream_info, http3_stream);
break;
}
return tvb_captured_length(tvb);
}
#ifdef HAVE_NGHTTP3
static void
register_static_headers(void)
{
header_fields_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
/*
* Here hf[x].hfinfo.name is a header method which is used as key
* for matching ids while processing http3 packets.
*/
static hf_register_info hf[] = {
{ &hf_http3_headers_authority,
{ ":authority", "http3.headers.authority",
FT_STRING, BASE_NONE, NULL, 0x0,
"Authority portion of the target URI", HFILL }
},
{ &hf_http3_headers_status,
{ ":status", "http3.headers.status",
FT_UINT16, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_headers_path,
{ ":path", "http3.headers.path",
FT_STRING, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_headers_method,
{ ":method", "http3.headers.method",
FT_STRING, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_headers_scheme,
{ ":scheme", "http3.headers.scheme",
FT_STRING, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_headers_accept,
{ "accept", "http3.headers.accept",
FT_STRING, BASE_NONE, NULL, 0x0,
"Media types that are acceptable to the user agent", HFILL }
},
{ &hf_http3_headers_accept_charset,
{ "accept-charset", "http3.headers.accept_charset",
FT_STRING, BASE_NONE, NULL, 0x0,
"Acceptable charsets in textual responses for the user agent", HFILL }
},
{ &hf_http3_headers_accept_encoding,
{ "accept-encoding", "http3.headers.accept_encoding",
FT_STRING, BASE_NONE, NULL, 0x0,
"Acceptable content codings (like compression) in responses for the user agent", HFILL }
},
{ &hf_http3_headers_accept_language,
{ "accept-language", "http3.headers.accept_language",
FT_STRING, BASE_NONE, NULL, 0x0,
"Preferred natural languages for the user agent", HFILL }
},
{ &hf_http3_headers_accept_ranges,
{ "accept-ranges", "http3.headers.accept_ranges",
FT_STRING, BASE_NONE, NULL, 0x0,
"Bytes range which server may use for partial data transfer", HFILL }
},
{ &hf_http3_headers_access_control_allow_origin,
{ "access-control-allow-origin", "http3.headers.access_control_allow_origin",
FT_STRING, BASE_NONE, NULL, 0x0,
"Origin control for cross-origin resource sharing", HFILL }
},
{ &hf_http3_headers_age,
{ "age", "http3.headers.age",
FT_UINT64, BASE_DEC, NULL, 0x0,
"Time in seconds which was spent for transferring data through proxy", HFILL }
},
{ &hf_http3_headers_allow,
{ "allow", "http3.headers.allow",
FT_STRING, BASE_NONE, NULL, 0x0,
"List of allowed methods for request", HFILL }
},
{ &hf_http3_headers_authorization,
{ "authorization", "http3.headers.authorization",
FT_STRING, BASE_NONE, NULL, 0x0,
"Credentials for a server-side authorization", HFILL }
},
{ &hf_http3_headers_cache_control,
{ "cache-control", "http3.headers.cache_control",
FT_STRING, BASE_NONE, NULL, 0x0,
"Request or response directives for a cache control", HFILL }
},
{ &hf_http3_headers_content_disposition,
{ "content-disposition", "http3.headers.content_disposition",
FT_STRING, BASE_NONE, NULL, 0x0,
"Indicates that response will be displayed as page or downloaded with dialog box", HFILL }
},
{ &hf_http3_headers_content_encoding,
{ "content-encoding", "http3.headers.content_encoding",
FT_STRING, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_headers_content_language,
{ "content-language", "http3.headers.content_language",
FT_STRING, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_headers_content_length,
{ "content-length", "http3.headers.content_length",
FT_UINT64, BASE_DEC, NULL, 0x0,
"Size of body in bytes", HFILL }
},
{ &hf_http3_headers_content_location,
{ "content-location", "http3.headers.content_location",
FT_STRING, BASE_NONE, NULL, 0x0,
"Alternative URL for a response data", HFILL }
},
{ &hf_http3_headers_content_range,
{ "content-range", "http3.headers.content_range",
FT_STRING, BASE_NONE, NULL, 0x0,
"Range of bytes which was sent by server for partial data transfer", HFILL }
},
{ &hf_http3_headers_content_type,
{ "content-type", "http3.headers.content_type",
FT_STRING, BASE_NONE, NULL, 0x0,
"MIME type of response", HFILL }
},
{ &hf_http3_headers_cookie,
{ "cookie", "http3.headers.cookie",
FT_STRING, BASE_NONE, NULL, 0x0,
"Stored cookies", HFILL }
},
{ &hf_http3_headers_date,
{ "date", "http3.headers.date",
FT_STRING, BASE_NONE, NULL, 0x0,
"Date and time at which the data was originated", HFILL }
},
{ &hf_http3_headers_etag,
{ "etag", "http3.headers.etag",
FT_STRING, BASE_NONE, NULL, 0x0,
"Directive for version indication of resource", HFILL }
},
{ &hf_http3_headers_expect,
{ "expect", "http3.headers.expect",
FT_STRING, BASE_NONE, NULL, 0x0,
"Expectations that need to be fulfilled for correct request", HFILL }
},
{ &hf_http3_headers_expires,
{ "expires", "http3.headers.expires",
FT_STRING, BASE_NONE, NULL, 0x0,
"Data after which resource will be stale", HFILL }
},
{ &hf_http3_headers_from,
{ "from", "http3.headers.from",
FT_STRING, BASE_NONE, NULL, 0x0,
"Email of a person who responsible for a requesting data", HFILL }
},
{ &hf_http3_headers_if_match,
{ "if-match", "http3.headers.if_match",
FT_STRING, BASE_NONE, NULL, 0x0,
"Mechanism for requesting data matched by a list of ETags", HFILL }
},
{ &hf_http3_headers_if_modified_since,
{ "if-modified-since", "http3.headers.if_modified_since",
FT_STRING, BASE_NONE, NULL, 0x0,
"Resource will be sent with status code 200 if it was modified otherwise with status code 304", HFILL }
},
{ &hf_http3_headers_if_none_match,
{ "if-none-match", "http3.headers.if_none_match",
FT_STRING, BASE_NONE, NULL, 0x0,
"Mechanism for requesting data not matched by a list of ETags", HFILL }
},
{ &hf_http3_headers_if_range,
{ "if-range", "http3.headers.if_range",
FT_STRING, BASE_NONE, NULL, 0x0,
"Mechanism for a range request which is used to check if a resource was modified", HFILL }
},
{ &hf_http3_headers_if_unmodified_since,
{ "if-unmodified-since", "http3.headers.if_unmodified_since",
FT_STRING, BASE_NONE, NULL, 0x0,
"Resource will be processed if it was not modified otherwise 412 error will be returned", HFILL }
},
{ &hf_http3_headers_last_modified,
{ "last-modified", "http3.headers.last_modified",
FT_STRING, BASE_NONE, NULL, 0x0,
"Date and time at which the origin server believes the resource was last modified", HFILL }
},
{ &hf_http3_headers_link,
{ "link", "http3.headers.link",
FT_STRING, BASE_NONE, NULL, 0x0,
"Mechanism for indicating that resource will be preloaded", HFILL }
},
{ &hf_http3_headers_location,
{ "location", "http3.headers.location",
FT_STRING, BASE_NONE, NULL, 0x0,
"Mechanism for indicating that client will be redirected", HFILL }
},
{ &hf_http3_headers_max_forwards,
{ "max-forwards", "http3.headers.max_forwards",
FT_UINT64, BASE_DEC, NULL, 0x0,
"Mechanism for limiting the number of proxies", HFILL }
},
{ &hf_http3_headers_proxy_authenticate,
{ "proxy-authenticate", "http3.headers.proxy_authenticate",
FT_STRING, BASE_NONE, NULL, 0x0,
"Authentication method that should be used to gain access to a resource behind a proxy server", HFILL }
},
{ &hf_http3_headers_proxy_authorization,
{ "proxy-authorization", "http3.headers.proxy_authorization",
FT_STRING, BASE_NONE, NULL, 0x0,
"Credentials for a proxy-side authorization", HFILL }
},
{ &hf_http3_headers_range,
{ "range", "http3.headers.range",
FT_STRING, BASE_NONE, NULL, 0x0,
"Range of resource bytes that server should return", HFILL }
},
{ &hf_http3_headers_referer,
{ "referer", "http3.headers.referer",
FT_STRING, BASE_NONE, NULL, 0x0,
"Address of the previous web page", HFILL }
},
{ &hf_http3_headers_refresh,
{ "refresh", "http3.headers.refresh",
FT_STRING, BASE_NONE, NULL, 0x0,
"Time in seconds after which client will be redirected by given url", HFILL }
},
{ &hf_http3_headers_retry_after,
{ "retry-after", "http3.headers.retry_after",
FT_STRING, BASE_NONE, NULL, 0x0,
"Mechanism to indicate when resource expected to be available", HFILL }
},
{ &hf_http3_headers_server,
{ "server", "http3.headers.server",
FT_STRING, BASE_NONE, NULL, 0x0,
"Information about server software", HFILL }
},
{ &hf_http3_headers_set_cookie,
{ "set-cookie", "http3.headers.set_cookie",
FT_STRING, BASE_NONE, NULL, 0x0,
"Send a cookie to the client", HFILL }
},
{ &hf_http3_headers_strict_transport_security,
{ "strict-transport-security", "http3.headers.strict_transport_security",
FT_STRING, BASE_NONE, NULL, 0x0,
"HSTS indicates that resource should be accessed only using HTTPS", HFILL }
},
{ &hf_http3_headers_user_agent,
{ "user-agent", "http3.headers.user_agent",
FT_STRING, BASE_NONE, NULL, 0x0,
"Information about client software", HFILL }
},
{ &hf_http3_headers_vary,
{ "vary", "http3.headers.vary",
FT_STRING, BASE_NONE, NULL, 0x0,
"Mechanism for selecting which header will be used for content negotiation algorithm", HFILL }
},
{ &hf_http3_headers_via,
{ "via", "http3.headers.via",
FT_STRING, BASE_NONE, NULL, 0x0,
"Additional information for loop detection and protocol capabilities in proxy requests", HFILL }
},
{ &hf_http3_headers_www_authenticate,
{ "www-authenticate", "http3.headers.www_authenticate",
FT_STRING, BASE_NONE, NULL, 0x0,
"Authentication method that should be used to gain access to a resource", HFILL }
}
};
gchar *header_name;
for (guint i = 0; i < G_N_ELEMENTS(hf); ++i) {
header_name = g_strdup(hf[i].hfinfo.name);
g_hash_table_insert(header_fields_hash, header_name, &hf[i].hfinfo.id);
}
proto_register_field_array(proto_http3, hf, G_N_ELEMENTS(hf));
}
#endif /* HAVE_NGHTTP3 */
void
proto_register_http3(void)
{
expert_module_t * expert_http3;
module_t *module_http3 _U_;
static hf_register_info hf[] = {
{ &hf_http3_stream_uni,
{ "Uni Stream", "http3.stream.uni",
FT_NONE, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_stream_uni_type,
{ "Uni Stream Type", "http3.stream_uni_type",
FT_UINT64, BASE_HEX|BASE_VAL64_STRING, VALS64(http3_stream_types), 0x0,
NULL, HFILL }
},
{ &hf_http3_stream_bidi,
{ "Request Stream", "http3.stream",
FT_NONE, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_push_id,
{ "Push ID", "http3.push_id",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_frame,
{ "Frame", "http3.frame",
FT_NONE, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_frame_type,
{ "Type", "http3.frame_type",
FT_UINT64, BASE_HEX|BASE_VAL64_STRING, VALS64(http3_frame_types), 0x0,
"Frame Type", HFILL }
},
{ &hf_http3_frame_length,
{ "Length", "http3.frame_length",
FT_UINT64, BASE_DEC, NULL, 0x0,
"Length of the Frame Payload", HFILL }
},
{ &hf_http3_frame_payload,
{ "Frame Payload", "http3.frame_payload",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
/* Data */
{ &hf_http3_data,
{ "Data", "http3.data",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
/* Headers */
//{ &hf_http3_headers,
// { "Header", "http3.headers",
// FT_UINT32, BASE_DEC, NULL, 0x0,
// NULL, HFILL }
//},
{ &hf_http3_headers_count,
{ "Headers Count", "http3.headers.count",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_header,
{ "Header", "http3.headers.header",
FT_NONE, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_header_length,
{ "Header Length", "http3.headers.header.length",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_header_name_length,
{ "Name Length", "http3.headers.header.name.length",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_header_name,
{ "Name", "http3.header.header.name",
FT_STRING, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_header_value_length,
{ "Value Length", "http3.headers.header.value.length",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_header_value,
{ "Value", "http3.headers.header.value",
FT_STRING, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_header_request_full_uri,
{ "Full request URI", "http3.request.full_uri",
FT_STRING, BASE_NONE, NULL, 0x0,
"The full requested URI (including host name)", HFILL }
},
{ &hf_http3_header_qpack_blocked,
{ "HEADERS head-of-line-blocked on QPACK encoder stream", "http3.header.qpack.blocked",
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_header_qpack_blocked_stream_rcint,
{ "Required instruction count", "http3.header.qpack.blocked.rcint",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_header_qpack_blocked_decoder_wicnt,
{ "Available instruction count", "http3.header.qpack.blocked.wcint",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
//{ &hf_http3_header_qpack_fatal,
// { "QPACK decoding error", "http3.header.qpack.fatal",
// FT_BOOLEAN, BASE_NONE, NULL, 0x0,
// NULL, HFILL }
//},
//{ &hf_http3_qpack,
// { "QPACK", "http3.qpack",
// FT_BYTES, BASE_NONE, NULL, 0x0,
// NULL, HFILL }
//},
{ &hf_http3_qpack_encoder,
{ "QPACK encoder", "http3.qpack.encoder",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
//{ &hf_http3_qpack_encoder_length,
// { "QPACK encoder update length", "http3.qpack.encoder.length",
// FT_UINT32, BASE_DEC, NULL, 0x0,
// NULL, HFILL }
//},
{ &hf_http3_qpack_encoder_icnt,
{ "QPACK encoder instruction count", "http3.qpack.encoder.icnt",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_icnt_inc,
{ "QPACK encoder instruction count increment", "http3.qpack.encoder.icnt.inc",
FT_UINT32, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
//{ &hf_http3_qpack_encoder_opcode,
// { "QPACK encoder opcode", "http3.qpack.encoder.opcode",
// FT_BYTES, BASE_NONE, NULL, 0x0,
// NULL, HFILL }
// },
{ &hf_http3_qpack_encoder_opcode_insert_indexed,
{ "Insert with Name Reference", "http3.qpack.encoder.opcode.insert_indexed",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_insert_indexed_ref,
{ "Name Reference", "http3.qpack.encoder.opcode.insert_indexed.ref",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_insert_indexed_val,
{ "Value", "http3.qpack.encoder.opcode.insert_indexed.val",
FT_BYTES, BASE_SHOW_ASCII_PRINTABLE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_insert_indexed_hval,
{ "Value (Huffman)", "http3.qpack.encoder.opcode.insert_indexed.hval",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_insert,
{ "Insert with Literal Name", "http3.qpack.encoder.opcode.insert",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_insert_name,
{ "Literal Name", "http3.qpack.encoder.opcode.insert.name",
FT_BYTES, BASE_SHOW_ASCII_PRINTABLE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_insert_hname,
{ "Literal Name (Huffman)", "http3.qpack.encoder.opcode.insert.hname",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_insert_val,
{ "Value", "http3.qpack.encoder.opcode.insert.val",
FT_BYTES, BASE_SHOW_ASCII_PRINTABLE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_insert_hval,
{ "Value (Huffman)", "http3.qpack.encoder.opcode.insert.hval",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_duplicate,
{ "Duplicate", "http3.qpack.encoder.opcode.duplicate",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
//{ &hf_http3_qpack_encoder_opcode_duplicate_val,
// { "Duplicate Index", "http3.qpack.encoder.opcode.duplicate.val",
// FT_BYTES, BASE_NONE, NULL, 0x0,
// NULL, HFILL }
//},
{ &hf_http3_qpack_encoder_opcode_dtable_cap,
{ "Set Dynamic Table Capacity", "http3.qpack.encoder.opcode.dtable_cap",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_qpack_encoder_opcode_dtable_cap_val,
{ "Capacity", "http3.qpack.encoder.opcode.dtable_cap.val",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
/* Settings */
{ &hf_http3_settings,
{ "Settings", "http3.settings",
FT_NONE, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_settings_identifier,
{ "Settings Identifier", "http3.settings.id",
FT_UINT64, BASE_HEX|BASE_VAL64_STRING, VALS64(http3_settings_vals), 0x0,
NULL, HFILL }
},
{ &hf_http3_settings_value,
{ "Settings Value", "http3.settings.value",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_settings_qpack_max_table_capacity,
{ "Max Table Capacity", "http3.settings.qpack.max_table_capacity",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_settings_max_field_section_size,
{ "Max header list size", "http3.settings.max_field_section_size",
FT_UINT64, BASE_DEC, NULL, 0x0,
"The default value is unlimited.", HFILL }
},
{ &hf_http3_settings_qpack_blocked_streams,
{ "Blocked Streams", "http3.settings.qpack.blocked_streams",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_settings_extended_connect,
{ "Extended CONNECT", "http3.settings.extended_connect",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_settings_webtransport,
{ "WebTransport", "http3.settings.webtransport",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_settings_h3_datagram,
{ "H3 DATAGRAM", "http3.settings.h3_datagram",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_settings_h3_datagram_draft04,
{ "H3 DATAGRAM Draft04", "http3.settings.h3_datagram_draft04",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
/* Priority Update */
{ &hf_http3_priority_update_element_id,
{ "Priority Update Element ID", "http3.priority_update_element_id",
FT_UINT64, BASE_DEC, NULL, 0x0,
NULL, HFILL }
},
{ &hf_http3_priority_update_field_value,
{ "Priority Update Field Value", "http3.priority_update_field_value",
FT_STRING, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
};
static gint *ett[] = {&ett_http3,
&ett_http3_stream_uni,
&ett_http3_stream_bidi,
&ett_http3_frame,
&ett_http3_settings,
&ett_http3_headers,
&ett_http3_headers_qpack_blocked,
&ett_http3_qpack_update,
&ett_http3_qpack_opcode};
static ei_register_info ei[] = {
{ &ei_http3_unknown_stream_type,
{ "http3.unknown_stream_type", PI_UNDECODED, PI_WARN,
"An unknown stream type was encountered", EXPFILL }
},
//{ &ei_http3_data_not_decoded,
// { "http3.data_not_decoded", PI_UNDECODED, PI_WARN,
// "Data not decoded", EXPFILL }
// },
// { &ei_http3_qpack_enc_update,
// { "http3.qpack_enc_update", PI_UNDECODED, PI_WARN,
// "Success decoding QPACK buffer", EXPFILL }
// },
{ &ei_http3_qpack_failed,
{ "http3.qpack_enc_failed", PI_UNDECODED, PI_NOTE,
"Error decoding QPACK buffer", EXPFILL }
},
{ &ei_http3_header_encoded_state ,
{ "http3.expert.header.encoded_state", PI_DEBUG, PI_NOTE,
"HTTP3 header encoded block", EXPFILL }
},
{ &ei_http3_header_decoding_failed ,
{ "http3.expert.header_decoding.failed", PI_UNDECODED, PI_NOTE,
"Failed to decode HTTP3 header name/value", EXPFILL }
},
{ &ei_http3_header_decoding_blocked,
{ "http3.expert.header_decoding.blocked", PI_UNDECODED, PI_NOTE,
"Failed to decode HTTP3 header name/value (blocked on QPACK)", EXPFILL}
},
{ &ei_http3_header_decoding_no_output,
{ "http3.expert.header_decoding.no_output", PI_UNDECODED, PI_NOTE,
"Failed to decode HTTP3 header name/value (QPACK decoder no emission)", EXPFILL}
},
};
proto_http3 = proto_register_protocol("Hypertext Transfer Protocol Version 3", "HTTP3", "http3");
proto_register_field_array(proto_http3, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
module_http3 = prefs_register_protocol(proto_http3, NULL);
expert_http3 = expert_register_protocol(proto_http3);
expert_register_field_array(expert_http3, ei, array_length(ei));
http3_handle = register_dissector("http3", dissect_http3, proto_http3);
#ifdef HAVE_NGHTTP3
/* Fill hash table with static headers */
register_static_headers();
#endif
}
void
proto_reg_handoff_http3(void)
{
dissector_add_string("quic.proto", "h3", http3_handle);
}
/**
* Implementation of helper functions.
*/
static http3_file_local_ctx *g_http3_file_local_ctx = NULL;
static guint
http3_conn_info_hash(gconstpointer key)
{
guint8 bkey[QUIC_MAX_CID_LENGTH];
const quic_cid_t *v;
guint h = 0;
if (key) {
v = (const quic_cid_t *)key;
memset(&bkey[0], 0, QUIC_MAX_CID_LENGTH);
memcpy(&bkey[0], &v->cid[0], MIN(v->len, QUIC_MAX_CID_LENGTH));
h = wmem_strong_hash(&bkey[0], QUIC_MAX_CID_LENGTH);
}
return h;
}
static gboolean
http3_conn_info_equal(gconstpointer lhs, gconstpointer rhs)
{
const quic_cid_t *a = (const quic_cid_t *)lhs;
const quic_cid_t *b = (const quic_cid_t *)rhs;
size_t alen = a->len;
size_t blen = b->len;
return alen == blen && memcmp(&a->cid[0], &b->cid[0], alen) == 0;
}
#ifdef HAVE_NGHTTP3
/* Due to QPACK compression, we may get lots of relatively large
header decoded_header_fields (e.g., 4KiB). Allocating each of them requires lots
of memory. The maximum compression is achieved in QPACK by
referencing header field stored in dynamic table by one or two
bytes. We reduce memory usage by caching header field in this
wmem_map_t to reuse its memory region when we see the same header
field next time. */
static size_t
http3_hdrcache_length(gconstpointer vv)
{
const guint8 *v = (const guint8 *)vv;
guint32 namelen, valuelen;
namelen = pntoh32(v);
valuelen = pntoh32(v + sizeof(namelen) + namelen);
return namelen + sizeof(namelen) + valuelen + sizeof(valuelen);
}
static guint
http3_hdrcache_hash(gconstpointer key)
{
return wmem_strong_hash((const guint8 *)key, http3_hdrcache_length(key));
}
static gboolean
http3_hdrcache_equal(gconstpointer lhs, gconstpointer rhs)
{
const guint8 *a = (const guint8 *)lhs;
const guint8 *b = (const guint8 *)rhs;
size_t alen = http3_hdrcache_length(a);
size_t blen = http3_hdrcache_length(b);
return alen == blen && memcmp(a, b, alen) == 0;
}
static size_t
http3_hdrdefcache_length(gconstpointer vv)
{
const guint8 *v = (const guint8 *)vv;
guint32 namelen;
namelen = pntoh32(v);
return namelen + sizeof(namelen);
}
static guint
http3_hdrdefcache_hash(gconstpointer key)
{
return wmem_strong_hash((const guint8 *)key, http3_hdrdefcache_length(key));
}
static gboolean
http3_hdrdefcache_equal(gconstpointer lhs, gconstpointer rhs)
{
const guint8 *a = (const guint8 *)lhs;
const guint8 *b = (const guint8 *)rhs;
size_t alen = http3_hdrdefcache_length(a);
size_t blen = http3_hdrdefcache_length(b);
return alen == blen && memcmp(a, b, alen) == 0;
}
#endif
/* Deallocation callback */
static bool
http3_file_local_ctx_del_cb(wmem_allocator_t *allocator _U_, wmem_cb_event_t event _U_, void *user_data _U_)
{
g_http3_file_local_ctx = NULL;
return FALSE;
}
static http3_file_local_ctx *
http3_get_file_local_ctx(void)
{
if (g_http3_file_local_ctx == NULL) {
/*
* The file-local context hasn't been initialized yet
* for the current file.
*/
g_http3_file_local_ctx = wmem_new(wmem_file_scope(), http3_file_local_ctx);
g_http3_file_local_ctx->conn_info_map =
wmem_map_new(wmem_file_scope(), http3_conn_info_hash, http3_conn_info_equal);
#ifdef HAVE_NGHTTP3
g_http3_file_local_ctx->hdr_cache_map =
wmem_map_new(wmem_file_scope(), http3_hdrcache_hash, http3_hdrcache_equal);
g_http3_file_local_ctx->hdr_def_cache_map =
wmem_map_new(wmem_file_scope(), http3_hdrdefcache_hash, http3_hdrdefcache_equal);
#endif
wmem_register_callback(wmem_file_scope(), http3_file_local_ctx_del_cb, NULL);
}
return g_http3_file_local_ctx;
}
/*
* 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:
*/