Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QUIC token parsing and QUIC packet type support #36

Merged
merged 3 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Loading