diff --git a/core/src/protocols/stream/quic/header.rs b/core/src/protocols/stream/quic/header.rs index 76fd011b..f9386c3a 100644 --- a/core/src/protocols/stream/quic/header.rs +++ b/core/src/protocols/stream/quic/header.rs @@ -2,16 +2,21 @@ use serde::Serialize; +use crate::protocols::stream::quic::parser::QuicError; + /// Quic Long Header #[derive(Debug, Serialize, Clone)] pub struct QuicLongHeader { - pub packet_type: u8, + pub packet_type: LongHeaderPacketType, pub type_specific: u8, pub version: u32, - pub dcid_len: u8, // length of dcid in bytes - pub dcid: String, // hex string - pub scid_len: u8, // length of scid in bytes - pub scid: String, // hex string + pub dcid_len: u8, // length of dcid in bytes + pub dcid: String, // hex string + pub scid_len: u8, // length of scid in bytes + pub scid: String, // hex string + pub token_len: Option, // length of token in bytes, if packet is of type Init or Retry + pub token: Option, // hex string, if packet is of type Init or Retry + pub retry_tag: Option, // hex string, if packet is of type Retry } /// Quic Short Header @@ -22,3 +27,24 @@ pub struct QuicShortHeader { #[serde(skip)] pub dcid_bytes: Vec, } + +// Long Header Packet Types from RFC 9000 Table 5 +#[derive(Debug, Clone, Serialize, Copy)] +pub enum LongHeaderPacketType { + Initial, + ZeroRTT, + Handshake, + Retry, +} + +impl LongHeaderPacketType { + pub fn from_u8(value: u8) -> Result { + match value { + 0x00 => Ok(LongHeaderPacketType::Initial), + 0x01 => Ok(LongHeaderPacketType::ZeroRTT), + 0x02 => Ok(LongHeaderPacketType::Handshake), + 0x03 => Ok(LongHeaderPacketType::Retry), + _ => Err(QuicError::UnknowLongHeaderPacketType), + } + } +} diff --git a/core/src/protocols/stream/quic/mod.rs b/core/src/protocols/stream/quic/mod.rs index 27c5e9ec..0bb2067a 100644 --- a/core/src/protocols/stream/quic/mod.rs +++ b/core/src/protocols/stream/quic/mod.rs @@ -23,6 +23,8 @@ TODO: support HTTP/3 pub(crate) mod parser; pub use self::header::{QuicLongHeader, QuicShortHeader}; +use header::LongHeaderPacketType; +use parser::QuicError; use serde::Serialize; pub(crate) mod header; @@ -36,7 +38,7 @@ pub struct QuicPacket { pub long_header: Option, /// The number of bytes contained in the estimated payload - pub payload_bytes_count: u16, + pub payload_bytes_count: Option, } impl QuicPacket { @@ -52,10 +54,10 @@ impl QuicPacket { } /// Returns the packet type of the Quic packet - pub fn packet_type(&self) -> u8 { + pub fn packet_type(&self) -> Result { match &self.long_header { - Some(long_header) => long_header.packet_type, - None => 0, + Some(long_header) => Ok(long_header.packet_type), + None => Err(QuicError::NoLongHeader), } } @@ -102,7 +104,11 @@ impl QuicPacket { } /// Returns the number of bytes in the payload of the Quic packet - pub fn payload_bytes_count(&self) -> u16 { - self.payload_bytes_count + pub fn payload_bytes_count(&self) -> u64 { + if let Some(count) = self.payload_bytes_count { + count + } else { + 0 + } } } diff --git a/core/src/protocols/stream/quic/parser.rs b/core/src/protocols/stream/quic/parser.rs index 063c7805..ab327cbe 100644 --- a/core/src/protocols/stream/quic/parser.rs +++ b/core/src/protocols/stream/quic/parser.rs @@ -2,7 +2,9 @@ //! Custom Quic Parser with many design choices borrowed from //! [Wireshark Quic Disector](https://gitlab.com/wireshark/wireshark/-/blob/master/epan/dissectors/packet-quic.c) //! -use crate::protocols::stream::quic::header::{QuicLongHeader, QuicShortHeader}; +use crate::protocols::stream::quic::header::{ + LongHeaderPacketType, QuicLongHeader, QuicShortHeader, +}; use crate::protocols::stream::quic::QuicPacket; use crate::protocols::stream::{ ConnParsable, ConnState, L4Pdu, ParseResult, ProbeResult, Session, SessionData, @@ -123,6 +125,10 @@ pub enum QuicError { PacketTooShort, UnknownVersion, ShortHeader, + UnknowLongHeaderPacketType, + NoLongHeader, + UnsupportedVarLen, + InvalidDataIndices, } impl QuicPacket { @@ -134,50 +140,149 @@ impl QuicPacket { .join("") } - /// Parses Quic packet from bytes - pub fn parse_from(data: &[u8]) -> Result { - if data.len() <= 2 { + // Calculate the length of a variable length encoding + // See RFC 9000 Section 16 for details + pub fn get_var_len(a: u8) -> Result { + let two_msb = a >> 6; + match two_msb { + 0b00 => Ok(1), + 0b01 => Ok(2), + 0b10 => Ok(4), + 0b11 => Ok(8), + _ => Err(QuicError::UnsupportedVarLen), + } + } + + // Masks variable length encoding and returns u64 value for remainder of field + fn slice_to_u64(data: &[u8]) -> Result { + if data.len() > 8 { + return Err(QuicError::UnsupportedVarLen); + } + + let mut result: u64 = 0; + for &byte in data { + result = (result << 8) | u64::from(byte); + } + result &= !(0b11 << ((data.len() * 8) - 2)); // Var length encoding mask + Ok(result) + } + + fn access_data(data: &[u8], start: usize, end: usize) -> Result<&[u8], QuicError> { + if end < start { + return Err(QuicError::InvalidDataIndices); + } + if data.len() < end { return Err(QuicError::PacketTooShort); } - if (data[0] & 0x40) == 0 { + Ok(&data[start..end]) + } + + /// Parses Quic packet from bytes + pub fn parse_from(data: &[u8]) -> Result { + let mut offset = 0; + let packet_header_byte = QuicPacket::access_data(data, offset, offset + 1)?[0]; + offset += 1; + // Check the fixed bit + if (packet_header_byte & 0x40) == 0 { return Err(QuicError::FixedBitNotSet); } - if (data[0] & 0x80) != 0 { + // Check the Header form + if (packet_header_byte & 0x80) != 0 { // Long Header - if data.len() < 7 { - return Err(QuicError::PacketTooShort); - } - let version = ((data[1] as u32) << 24) - | ((data[2] as u32) << 16) - | ((data[3] as u32) << 8) - | (data[4] as u32); + // Parse packet type + let packet_type = LongHeaderPacketType::from_u8((packet_header_byte & 0x30) >> 4)?; + let type_specific = packet_header_byte & 0x0F; // Remainder of information from header byte, Reserved and protected packet number length + // Parse version + let version_bytes = QuicPacket::access_data(data, offset, offset + 4)?; + let version = ((version_bytes[0] as u32) << 24) + | ((version_bytes[1] as u32) << 16) + | ((version_bytes[2] as u32) << 8) + | (version_bytes[3] as u32); if QuicVersion::from_u32(version) == QuicVersion::Unknown { return Err(QuicError::UnknownVersion); } - - let packet_type = (data[0] & 0x30) >> 4; - let type_specific = data[0] & 0x0F; - - let dcid_len = data[5]; - let dcid_start = 6; - // There's a +2 in this size check because we need enough space to check the SCID length - if data.len() < (dcid_start + dcid_len as usize + 2) { - return Err(QuicError::PacketTooShort); - } - let dcid_bytes = &data[dcid_start..dcid_start + dcid_len as usize]; + offset += 4; + // Parse DCID + let dcid_len = QuicPacket::access_data(data, offset, offset + 1)?[0]; + offset += 1; + let dcid_bytes = QuicPacket::access_data(data, offset, offset + dcid_len as usize)?; let dcid = QuicPacket::vec_u8_to_hex_string(dcid_bytes); - let scid_len = data[dcid_start + dcid_len as usize]; - let scid_start = dcid_start + dcid_len as usize + 1; - if data.len() < (scid_start + scid_len as usize + 1) { - return Err(QuicError::PacketTooShort); - } - let scid_bytes = &data[scid_start..scid_start + scid_len as usize]; + offset += dcid_len as usize; + // Parse SCID + let scid_len = QuicPacket::access_data(data, offset, offset + 1)?[0]; + offset += 1; + let scid_bytes = QuicPacket::access_data(data, offset, offset + scid_len as usize)?; let scid = QuicPacket::vec_u8_to_hex_string(scid_bytes); + offset += scid_len as usize; + + let token_len; + let token; + let packet_len; + let retry_tag; + // Parse packet type specific fields + match packet_type { + LongHeaderPacketType::Initial => { + retry_tag = None; + // Parse token + let token_len_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + token_len = Some(QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + token_len_len, + )?)?); + offset += token_len_len; + let token_bytes = QuicPacket::access_data( + data, + offset, + offset + token_len.unwrap() as usize, + )?; + token = Some(QuicPacket::vec_u8_to_hex_string(token_bytes)); + offset += token_len.unwrap() as usize; + // Parse payload length + let packet_len_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + packet_len = Some(QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + packet_len_len, + )?)?); + } + LongHeaderPacketType::ZeroRTT | LongHeaderPacketType::Handshake => { + token_len = None; + token = None; + retry_tag = None; + // Parse payload length + let packet_len_len = QuicPacket::get_var_len( + QuicPacket::access_data(data, offset, offset + 1)?[0], + )?; + packet_len = Some(QuicPacket::slice_to_u64(QuicPacket::access_data( + data, + offset, + offset + packet_len_len, + )?)?); + } + LongHeaderPacketType::Retry => { + packet_len = None; + token_len = Some((data.len() - offset - 16) as u64); + // Parse retry token + let token_bytes = QuicPacket::access_data( + data, + offset, + offset + token_len.unwrap() as usize, + )?; + token = Some(QuicPacket::vec_u8_to_hex_string(token_bytes)); + offset += token_len.unwrap() as usize; + // Parse retry tag + let retry_tag_bytes = QuicPacket::access_data(data, offset, offset + 16)?; + retry_tag = Some(QuicPacket::vec_u8_to_hex_string(retry_tag_bytes)); + } + } - // Counts all bytes remaining - let payload_bytes_count = data.len() - scid_start - scid_len as usize; Ok(QuicPacket { - payload_bytes_count: payload_bytes_count as u16, + payload_bytes_count: packet_len, short_header: None, long_header: Some(QuicLongHeader { packet_type, @@ -187,6 +292,9 @@ impl QuicPacket { dcid, scid_len, scid, + token_len, + token, + retry_tag, }), }) } else { @@ -195,16 +303,18 @@ impl QuicPacket { if data.len() < 1 + max_dcid_len { max_dcid_len = data.len() - 1; } - let dcid_bytes = data[1..1 + max_dcid_len].to_vec(); + // Parse DCID + let dcid_bytes = QuicPacket::access_data(data, offset, offset + max_dcid_len)?.to_vec(); + offset += max_dcid_len; // Counts all bytes remaining - let payload_bytes_count = data.len() - 1 - max_dcid_len; + let payload_bytes_count = Some((data.len() - offset) as u64); Ok(QuicPacket { short_header: Some(QuicShortHeader { dcid: None, dcid_bytes, }), long_header: None, - payload_bytes_count: payload_bytes_count as u16, + payload_bytes_count, }) } } diff --git a/traces/README.md b/traces/README.md index 109fcb91..e5c94e44 100644 --- a/traces/README.md +++ b/traces/README.md @@ -5,4 +5,5 @@ A collection of sample packet captures pulled from a variety of sources. | Trace | Source | Description | |--------------------|-------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| | `small_flows.pcap` | [Tcpreplay Sample Captures](https://tcpreplay.appneta.com/wiki/captures.html) | A synthetic combination of a few different applications and protocols at a relatively low network traffic rate. | -| `tls_ciphers.pcap` | [Wireshark Sample Captures](https://wiki.wireshark.org/SampleCaptures) | OpenSSL client/server GET requests over TLS 1.2 with 73 different cipher suites. | +| `tls_ciphers.pcap` | [Wireshark Sample Captures](https://wiki.wireshark.org/SampleCaptures) | OpenSSL client/server GET requests over TLS 1.2 with 73 different cipher suites. | +| `quic_retry.pcapng`| [Wireshark Issue](https://gitlab.com/wireshark/wireshark/-/issues/18757) | An example of a QUIC Retry Packet. Original Pcap modified to remove CookedLinux and add Ether | \ No newline at end of file diff --git a/traces/quic_retry.pcapng b/traces/quic_retry.pcapng new file mode 100644 index 00000000..d6398e96 Binary files /dev/null and b/traces/quic_retry.pcapng differ