QUIC: Add support for stateless reset

Fix #17938
This commit is contained in:
John Thacker 2022-08-14 23:50:54 -04:00 committed by A Wireshark GitLab Utility
parent 034e48b500
commit 2007c929d2
3 changed files with 136 additions and 6 deletions

View File

@ -171,6 +171,8 @@ static int hf_quic_af_reserved = -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;
@ -461,6 +463,7 @@ typedef struct quic_datagram {
quic_info_data_t *conn;
quic_packet_info_t first_packet;
bool from_server : 1;
bool stateless_reset : 1;
} quic_datagram;
/**
@ -981,9 +984,10 @@ quic_connection_hash(gconstpointer key)
{
const quic_cid_t *cid = (const quic_cid_t *)key;
return wmem_strong_hash((const guint8 *)cid, sizeof(quic_cid_t) - sizeof(cid->cid) + cid->len);
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)
{
@ -1127,7 +1131,7 @@ quic_connection_find(packet_info *pinfo, guint8 long_packet_type,
}
}
if (long_packet_type == QUIC_LPT_INITIAL && conn && !*from_server && dcid->len > 0 &&
memcmp(dcid, &conn->client_dcid_initial, sizeof(quic_cid_t)) &&
!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
@ -2465,8 +2469,8 @@ dissect_quic_frame_type(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree
}
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) {
quic_cid_t cid = {.len=0};
tvb_memcpy(tvb, cid.cid, offset, nci_length);
cid.len = nci_length;
quic_connection_add_cid(quic_info, &cid, from_server);
@ -2474,6 +2478,9 @@ dissect_quic_frame_type(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tree
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;
@ -3484,6 +3491,75 @@ quic_add_loss_bits(packet_info *pinfo, guint64 value)
}
}
static quic_info_data_t *
quic_find_stateless_reset_token(packet_info *pinfo, tvbuff_t *tvb, gboolean *from_server)
{
/* This is used when we have not found a connection, so use
* the 5-tuple. (XXX: When we do handle multiple connections
* on the same 5-tuple properly (#17099), this needs to search
* all of them.)
*/
quic_info_data_t* conn = quic_connection_from_conv(pinfo);
const quic_cid_item_t *cids;
if (conn) {
*from_server = conn->server_port == pinfo->srcport &&
addresses_equal(&conn->server_address, &pinfo->src);
cids = from_server ? &conn->server_cids : &conn->client_cids;
while (cids) {
const quic_cid_t *cid = &cids->data;
if (cid->reset_token_set &&
!tvb_memeql(tvb, -16, cid->reset_token, 16) ) {
return conn;
}
cids = cids->next;
}
}
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)
{
@ -3659,7 +3735,7 @@ dissect_quic_long_header(tvbuff_t *tvb, packet_info *pinfo, proto_tree *quic_tre
gchar early_data_secret[DIGEST_MAX_SIZE];
guint early_data_secret_len = 0;
if (long_packet_type == QUIC_LPT_INITIAL && !from_server &&
!memcmp(&dcid, &conn->client_dcid_initial, sizeof(quic_cid_t))) {
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);
@ -3966,6 +4042,31 @@ quic_get_message_tvb(tvbuff_t *tvb, const guint offset)
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":
@ -4119,7 +4220,11 @@ dissect_quic(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
real_retry_odcid = conn->client_dcid_initial;
retry_odcid = &real_retry_odcid;
}
quic_connection_create_or_update(&conn, pinfo, long_packet_type, version, &scid, &dcid, from_server);
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;
#if 0
@ -4131,6 +4236,10 @@ dissect_quic(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree,
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;
@ -4997,7 +5106,7 @@ proto_register_quic(void)
NULL, HFILL }
},
{ &hf_quic_nci_stateless_reset_token,
{ "Stateless Reset Token", "quic.stateless_reset_token",
{ "Stateless Reset Token", "quic.nci.stateless_reset_token",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
@ -5105,6 +5214,19 @@ proto_register_quic(void)
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",

View File

@ -39,6 +39,8 @@ typedef struct _quic_stream_info {
typedef struct quic_cid {
guint8 len;
guint8 cid[QUIC_MAX_CID_LENGTH];
guint8 reset_token[16];
gboolean reset_token_set;
} quic_cid_t;
/**
@ -76,6 +78,8 @@ quic_add_connection(packet_info *pinfo, const quic_cid_t *cid);
void
quic_add_loss_bits(packet_info *pinfo, guint64 value);
void
quic_add_stateless_reset_token(packet_info *pinfo, tvbuff_t *tvb, gint offset, const quic_cid_t *cid);
void
quic_proto_tree_add_version(tvbuff_t *tvb, proto_tree *tree, int hfindex, guint offset);
/**

View File

@ -7865,6 +7865,7 @@ ssl_dissect_hnd_hello_ext_quic_transport_parameters(ssl_common_dissect_t *hf, tv
case SSL_HND_QUIC_TP_STATELESS_RESET_TOKEN:
proto_tree_add_item(parameter_tree, hf->hf.hs_ext_quictp_parameter_stateless_reset_token,
tvb, offset, 16, ENC_BIG_ENDIAN);
quic_add_stateless_reset_token(pinfo, tvb, offset, NULL);
offset += 16;
break;
case SSL_HND_QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
@ -7959,6 +7960,9 @@ ssl_dissect_hnd_hello_ext_quic_transport_parameters(ssl_common_dissect_t *hf, tv
proto_tree_add_item(parameter_tree, hf->hf.hs_ext_quictp_parameter_pa_statelessresettoken,
tvb, offset, 16, ENC_NA);
if (connectionid_length >= 1 && connectionid_length <= QUIC_MAX_CID_LENGTH) {
quic_add_stateless_reset_token(pinfo, tvb, offset, &cid);
}
offset += 16;
}
break;