Skip to content

Commit

Permalink
Merge pull request #36 from sippejw/main
Browse files Browse the repository at this point in the history
Add QUIC token parsing and QUIC packet type support
  • Loading branch information
thearossman authored Jul 12, 2024
2 parents 5ea5dae + 4f52f0a commit b7b2c9f
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 48 deletions.
36 changes: 31 additions & 5 deletions core/src/protocols/stream/quic/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>, // length of token in bytes, if packet is of type Init or Retry
pub token: Option<String>, // hex string, if packet is of type Init or Retry
pub retry_tag: Option<String>, // hex string, if packet is of type Retry
}

/// Quic Short Header
Expand All @@ -22,3 +27,24 @@ pub struct QuicShortHeader {
#[serde(skip)]
pub dcid_bytes: Vec<u8>,
}

// 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<LongHeaderPacketType, QuicError> {
match value {
0x00 => Ok(LongHeaderPacketType::Initial),
0x01 => Ok(LongHeaderPacketType::ZeroRTT),
0x02 => Ok(LongHeaderPacketType::Handshake),
0x03 => Ok(LongHeaderPacketType::Retry),
_ => Err(QuicError::UnknowLongHeaderPacketType),
}
}
}
18 changes: 12 additions & 6 deletions core/src/protocols/stream/quic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -36,7 +38,7 @@ pub struct QuicPacket {
pub long_header: Option<QuicLongHeader>,

/// The number of bytes contained in the estimated payload
pub payload_bytes_count: u16,
pub payload_bytes_count: Option<u64>,
}

impl QuicPacket {
Expand All @@ -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<LongHeaderPacketType, QuicError> {
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),
}
}

Expand Down Expand Up @@ -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
}
}
}
182 changes: 146 additions & 36 deletions core/src/protocols/stream/quic/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -123,6 +125,10 @@ pub enum QuicError {
PacketTooShort,
UnknownVersion,
ShortHeader,
UnknowLongHeaderPacketType,
NoLongHeader,
UnsupportedVarLen,
InvalidDataIndices,
}

impl QuicPacket {
Expand All @@ -134,50 +140,149 @@ impl QuicPacket {
.join("")
}

/// Parses Quic packet from bytes
pub fn parse_from(data: &[u8]) -> Result<QuicPacket, QuicError> {
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<usize, QuicError> {
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<u64, QuicError> {
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<QuicPacket, QuicError> {
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,
Expand All @@ -187,6 +292,9 @@ impl QuicPacket {
dcid,
scid_len,
scid,
token_len,
token,
retry_tag,
}),
})
} else {
Expand All @@ -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,
})
}
}
Expand Down
3 changes: 2 additions & 1 deletion traces/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Binary file added traces/quic_retry.pcapng
Binary file not shown.

0 comments on commit b7b2c9f

Please sign in to comment.